diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index ed01d5bb..4e9c45dc 100644 --- a/d2core/d2map/d2mapengine/engine.go +++ b/d2core/d2map/d2mapengine/engine.go @@ -250,7 +250,7 @@ func (m *MapEngine) RemoveEntity(entity d2interface.MapEntity) { // GetTiles returns a slice of all tiles matching the given style, // sequence and tileType. -func (m *MapEngine) GetTiles(style, sequence, tileType int) []d2dt1.Tile { +func (m *MapEngine) GetTiles(style, sequence int, tileType d2enum.TileType) []d2dt1.Tile { tiles := make([]d2dt1.Tile, 0, len(m.dt1TileData)) for idx := range m.dt1TileData { diff --git a/d2core/d2map/d2mapengine/map_tile.go b/d2core/d2map/d2mapengine/map_tile.go index f43f66db..4ed17333 100644 --- a/d2core/d2map/d2mapengine/map_tile.go +++ b/d2core/d2map/d2mapengine/map_tile.go @@ -30,7 +30,7 @@ func (t *MapTile) GetSubTileFlags(x, y int) *d2dt1.SubTileFlags { func (t *MapTile) PrepareTile(x, y int, me *MapEngine) { for wIdx := range t.Components.Walls { wall := &t.Components.Walls[wIdx] - options := me.GetTiles(int(wall.Style), int(wall.Sequence), int(wall.Type)) + options := me.GetTiles(int(wall.Style), int(wall.Sequence), wall.Type) if options == nil { break diff --git a/d2core/d2map/d2mapentity/factory.go b/d2core/d2map/d2mapentity/factory.go index a723be97..dc0bb91a 100644 --- a/d2core/d2map/d2mapentity/factory.go +++ b/d2core/d2map/d2mapentity/factory.go @@ -18,7 +18,9 @@ import ( ) const ( - subtilesPerTile = 5 + subtilesPerTile = 5 + retailFps = 25.0 + millisecondsPerSecond = 1000.0 ) // NewMapEntityFactory creates a MapEntityFactory instance with the given asset manager @@ -233,15 +235,15 @@ func (f *MapEntityFactory) NewCastOverlay(x, y int, overlayRecord *d2records.Ove d2enum.DrawEffectModulate, ) - // TODO: Frame index and played count seem to be shared across the cloned animation objects when we retrieve the animation from the asset manager cache. - animation.Rewind() - animation.ResetPlayedCount() - if err != nil { return nil, err } - animationSpeed := float64(overlayRecord.AnimRate*25.0) / 1000.0 + // TODO: Frame index and played count seem to be shared across the cloned animation objects when we retrieve the animation from the asset manager cache. + animation.Rewind() + animation.ResetPlayedCount() + + animationSpeed := float64(overlayRecord.AnimRate*retailFps) / millisecondsPerSecond playLoop := false // TODO: should be based on the overlay record, some overlays can repeat(e.g. Bone Shield, Frozen Armor) animation.SetPlayLength(animationSpeed) diff --git a/d2core/d2map/d2mapentity/object.go b/d2core/d2map/d2mapentity/object.go index 97870fb2..e3cd04ab 100644 --- a/d2core/d2map/d2mapentity/object.go +++ b/d2core/d2map/d2mapentity/object.go @@ -84,7 +84,7 @@ func (ob *Object) Render(target d2interface.Surface) { ) if ob.highlight { - target.PushBrightness(2) + target.PushBrightness(highlightBrightness) defer target.Pop() } diff --git a/d2core/d2map/d2mapentity/player.go b/d2core/d2map/d2mapentity/player.go index 5c50133e..a79c487b 100644 --- a/d2core/d2map/d2mapentity/player.go +++ b/d2core/d2map/d2mapentity/player.go @@ -78,6 +78,10 @@ func (p *Player) IsInTown() bool { return p.isInTown } +const ( + half = 0.5 +) + // Advance is called once per frame and processes a // single game tick. func (p *Player) Advance(tickTime float64) { @@ -92,7 +96,9 @@ func (p *Player) Advance(tickTime float64) { } // skills are casted after the first half of the casting animation is played - isHalfDoneCasting := float64(p.composite.GetCurrentFrame())/float64(p.composite.GetFrameCount()) >= 0.5 + percentDone := float64(p.composite.GetCurrentFrame()) / float64(p.composite.GetFrameCount()) + isHalfDoneCasting := percentDone >= half + if isHalfDoneCasting && p.onFinishedCasting != nil { p.onFinishedCasting() p.onFinishedCasting = nil diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index e673e358..bb69a3ef 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -557,16 +557,20 @@ func (mr *MapRenderer) renderTileDebug(ax, ay, debugVisLevel int, target d2inter } } -// Advance is called once per frame and maintains the MapRenderer's record previous render timestamp and current frame. -func (mr *MapRenderer) Advance(elapsed float64) { - frameLength := 0.1 +const ( + frameOverflow = 10 + frameLength = 1.0 / frameOverflow +) +// Advance is called once per frame and maintains the MapRenderer's previous +// render timestamp and current frame. +func (mr *MapRenderer) Advance(elapsed float64) { mr.lastFrameTime += elapsed framesAdvanced := int(mr.lastFrameTime / frameLength) mr.lastFrameTime -= float64(framesAdvanced) * frameLength mr.currentFrame += framesAdvanced - if mr.currentFrame > 9 { + if mr.currentFrame >= frameOverflow { mr.currentFrame = 0 } diff --git a/d2core/d2map/d2maprenderer/tile_cache.go b/d2core/d2map/d2maprenderer/tile_cache.go index cdff19fb..3b8b767c 100644 --- a/d2core/d2map/d2maprenderer/tile_cache.go +++ b/d2core/d2map/d2maprenderer/tile_cache.go @@ -17,6 +17,12 @@ const ( defaultFloorTileHeight = 10 ) +const ( + blockOffsetY = 32 + tileSurfaceWidth = 160 + tileSurfaceHeight = 80 +) + func (mr *MapRenderer) generateTileCache() { var err error mr.palette, err = mr.loadPaletteForAct(d2enum.RegionIdType(mr.mapEngine.LevelType().ID)) @@ -113,10 +119,8 @@ func (mr *MapRenderer) generateFloorCache(tile *d2ds1.FloorShadowRecord) { } } -const shadowTileType = 13 - func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord) { - tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), shadowTileType) + tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), d2enum.TileShadow) var tileData *d2dt1.Tile @@ -165,7 +169,7 @@ func (mr *MapRenderer) generateShadowCache(tile *d2ds1.FloorShadowRecord) { } func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord) { - tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), int(tile.Type)) + tileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), tile.Type) var tileData *d2dt1.Tile @@ -177,8 +181,11 @@ func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord) { var newTileData *d2dt1.Tile = nil - if tile.Type == 3 { - newTileOptions := mr.mapEngine.GetTiles(int(tile.Style), int(tile.Sequence), int(4)) + if tile.Type == d2enum.TileRightPartOfNorthCornerWall { + newTileOptions := mr.mapEngine.GetTiles( + int(tile.Style), int(tile.Sequence), + d2enum.TileLeftPartOfNorthCornerWall, + ) newTileData = &newTileOptions[tile.RandomIndex] } @@ -193,16 +200,16 @@ func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord) { for _, block := range target.Blocks { tileMinY = d2math.MinInt32(tileMinY, int32(block.Y)) - tileMaxY = d2math.MaxInt32(tileMaxY, int32(block.Y+32)) + tileMaxY = d2math.MaxInt32(tileMaxY, int32(block.Y+blockOffsetY)) } realHeight := d2math.MaxInt32(d2math.AbsInt32(tileData.Height), tileMaxY-tileMinY) tileYOffset := -tileMinY - if tile.Type == 15 { + if tile.Type == d2enum.TileRoof { tile.YAdjust = -int(tileData.RoofHeight) } else { - tile.YAdjust = int(tileMinY) + 80 + tile.YAdjust = int(tileMinY) + tileSurfaceHeight } cachedImage := mr.getImageCacheRecord(tile.Style, tile.Sequence, tile.Type, tile.RandomIndex) @@ -215,17 +222,17 @@ func (mr *MapRenderer) generateWallCache(tile *d2ds1.WallRecord) { return } - image, err := mr.renderer.NewSurface(160, int(realHeight), d2enum.FilterNearest) + image, err := mr.renderer.NewSurface(tileSurfaceWidth, int(realHeight), d2enum.FilterNearest) if err != nil { log.Print(err) } - indexData := make([]byte, 160*realHeight) + indexData := make([]byte, tileSurfaceWidth*realHeight) - d2dt1.DecodeTileGfxData(tileData.Blocks, &indexData, tileYOffset, 160) + d2dt1.DecodeTileGfxData(tileData.Blocks, &indexData, tileYOffset, tileSurfaceWidth) if newTileData != nil { - d2dt1.DecodeTileGfxData(newTileData.Blocks, &indexData, tileYOffset, 160) + d2dt1.DecodeTileGfxData(newTileData.Blocks, &indexData, tileYOffset, tileSurfaceWidth) } pixels := d2util.ImgIndexToRGBA(indexData, mr.palette) diff --git a/d2core/d2map/d2maprenderer/viewport.go b/d2core/d2map/d2maprenderer/viewport.go index 4bc429da..17b11979 100644 --- a/d2core/d2map/d2maprenderer/viewport.go +++ b/d2core/d2map/d2maprenderer/viewport.go @@ -20,6 +20,10 @@ const ( half = 2 ) +const ( + worldToOrthoOffsetX = 3 +) + // Viewport is used for converting vectors between screen (pixel), orthogonal (Camera) and world (isometric) space. type Viewport struct { defaultScreenRect d2geom.Rectangle @@ -113,8 +117,8 @@ func (v *Viewport) OrthoToScreenF(x, y float64) (screenX, screenY float64) { // IsTileVisible returns false if no part of the tile is within the game screen. func (v *Viewport) IsTileVisible(x, y float64) bool { - orthoX1, orthoY1 := v.WorldToOrtho(x-3, y) - orthoX2, orthoY2 := v.WorldToOrtho(x+3, y) + orthoX1, orthoY1 := v.WorldToOrtho(x-worldToOrthoOffsetX, y) + orthoX2, orthoY2 := v.WorldToOrtho(x+worldToOrthoOffsetX, y) return v.IsOrthoRectVisible(orthoX1, orthoY1, orthoX2, orthoY2) } diff --git a/d2game/d2gamescreen/game.go b/d2game/d2gamescreen/game.go index 5a3bdfd6..6c896363 100644 --- a/d2game/d2gamescreen/game.go +++ b/d2game/d2gamescreen/game.go @@ -31,6 +31,10 @@ const ( spawnItemErrStr = "failed to send SpawnItem packet to the server: (%d, %d) %+v" ) +const ( + black50alpha = 0x0000007f // rgba +) + // Game represents the Gameplay screen type Game struct { *d2mapentity.MapEntityFactory @@ -205,8 +209,7 @@ func (v *Game) Render(screen d2interface.Surface) error { if v.gameControls != nil { if v.gameControls.HelpOverlay != nil && v.gameControls.HelpOverlay.IsOpen() { - // When help overlay is open, put transparent black screen. Magic noumber is hex for RGBA. - screen.DrawRect(800, 600, d2util.Color(0x0000007f)) + screen.DrawRect(screenWidth, screenHeight, d2util.Color(black50alpha)) } if err := v.gameControls.Render(screen); err != nil { @@ -232,7 +235,7 @@ func (v *Game) Advance(elapsed float64) error { } v.ticksSinceLevelCheck += elapsed - if v.ticksSinceLevelCheck > 1.0 { + if v.ticksSinceLevelCheck > 1 { v.ticksSinceLevelCheck = 0 if v.localPlayer != nil { tilePosition := v.localPlayer.Position.Tile() diff --git a/d2game/d2gamescreen/map_engine_testing.go b/d2game/d2gamescreen/map_engine_testing.go index 940932bd..46bfa05e 100644 --- a/d2game/d2gamescreen/map_engine_testing.go +++ b/d2game/d2gamescreen/map_engine_testing.go @@ -20,6 +20,10 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen" ) +const ( + subtilesPerTile = 5 +) + type regionSpec struct { regionType d2enum.RegionIdType startPresetIndex int @@ -226,108 +230,137 @@ func (met *MapEngineTest) OnUnload() error { return nil } +const ( + lineSmallOffsetY = 12 + lineNormalOffsetY = 16 + lineSmallIndentX = 10 + lineNormalIndentX = 15 + lineBigIndentX = 170 // distance between text columns +) + // Render renders the Map Engine Test screen func (met *MapEngineTest) Render(screen d2interface.Surface) error { met.mapRenderer.Render(screen) - screen.PushTranslation(0, 16) - screen.DrawTextf("N - next region, P - previous region") - screen.PushTranslation(0, 16) - screen.DrawTextf("Shift+N - next preset, Shift+P - previous preset") - screen.PushTranslation(0, 16) - screen.DrawTextf("Ctrl+N - next file, Ctrl+P - previous file") - screen.PushTranslation(0, 16) - screen.DrawTextf("Left click selects tile, right click unselects") - screen.PushTranslation(0, 16) + screen.PushTranslation(0, lineNormalOffsetY) + defer screen.Pop() - popN := 5 + screen.DrawTextf("N - next region, P - previous region") + + screen.PushTranslation(0, lineNormalOffsetY) + defer screen.Pop() + + screen.DrawTextf("Shift+N - next preset, Shift+P - previous preset") + + screen.PushTranslation(0, lineNormalOffsetY) + defer screen.Pop() + + screen.DrawTextf("Ctrl+N - next file, Ctrl+P - previous file") + + screen.PushTranslation(0, lineNormalOffsetY) + defer screen.Pop() + + screen.DrawTextf("Left click selects tile, right click unselects") + + screen.PushTranslation(0, lineNormalOffsetY) + defer screen.Pop() if met.selectedTile == nil { - screen.PushTranslation(15, 16) - popN++ + screen.PushTranslation(lineNormalIndentX, lineNormalOffsetY) + defer screen.Pop() screen.DrawTextf("No tile selected") } else { - screen.PushTranslation(10, 32) + screen.PushTranslation(lineSmallIndentX, lineNormalOffsetY) + defer screen.Pop() + screen.PushTranslation(0, lineNormalOffsetY) // extra vspace + defer screen.Pop() + screen.DrawTextf("Tile %v,%v", met.selX, met.selY) - screen.PushTranslation(15, 16) + screen.PushTranslation(lineNormalIndentX, lineNormalOffsetY) + defer screen.Pop() + screen.DrawTextf("Walls") + tpop := 0 for _, wall := range met.selectedTile.Components.Walls { - screen.PushTranslation(0, 12) + screen.PushTranslation(0, lineSmallOffsetY) tpop++ tmpString := fmt.Sprintf("%#v", wall) stringSlice := strings.Split(tmpString, " ") tmp2 := strings.Split(stringSlice[0], "{") stringSlice[0] = tmp2[1] for _, str := range stringSlice { - screen.PushTranslation(0, 12) + screen.PushTranslation(0, lineSmallOffsetY) tpop++ screen.DrawTextf(str) } } + screen.PopN(tpop) - screen.PushTranslation(170, 0) + screen.PushTranslation(lineBigIndentX, 0) + defer screen.Pop() screen.DrawTextf("Floors") + tpop = 0 for _, floor := range met.selectedTile.Components.Floors { - screen.PushTranslation(0, 12) + screen.PushTranslation(0, lineSmallOffsetY) tpop++ tmpString := fmt.Sprintf("%#v", floor) stringSlice := strings.Split(tmpString, " ") tmp2 := strings.Split(stringSlice[0], "{") stringSlice[0] = tmp2[1] for _, str := range stringSlice { - screen.PushTranslation(0, 12) + screen.PushTranslation(0, lineSmallOffsetY) tpop++ screen.DrawTextf(str) } } screen.PopN(tpop) - tpop = 0 - screen.PushTranslation(170, 0) + screen.PushTranslation(lineBigIndentX, 0) + defer screen.Pop() screen.DrawTextf("Shadows") + + tpop = 0 for _, shadow := range met.selectedTile.Components.Shadows { - screen.PushTranslation(0, 12) + screen.PushTranslation(0, lineSmallOffsetY) tpop++ tmpString := fmt.Sprintf("%#v", shadow) stringSlice := strings.Split(tmpString, " ") tmp2 := strings.Split(stringSlice[0], "{") stringSlice[0] = tmp2[1] for _, str := range stringSlice { - screen.PushTranslation(0, 12) + screen.PushTranslation(0, lineSmallOffsetY) tpop++ screen.DrawTextf(str) } } screen.PopN(tpop) - tpop = 0 - screen.PushTranslation(170, 0) + screen.PushTranslation(lineBigIndentX, 0) + defer screen.Pop() screen.DrawTextf("Substitutions") + + tpop = 0 for _, subst := range met.selectedTile.Components.Substitutions { - screen.PushTranslation(0, 12) + screen.PushTranslation(0, lineSmallOffsetY) tpop++ tmpString := fmt.Sprintf("%#v", subst) stringSlice := strings.Split(tmpString, " ") tmp2 := strings.Split(stringSlice[0], "{") stringSlice[0] = tmp2[1] for _, str := range stringSlice { - screen.PushTranslation(0, 12) + screen.PushTranslation(0, lineSmallOffsetY) tpop++ screen.DrawTextf(str) } } screen.PopN(tpop) - - popN += 5 } - screen.PopN(popN) - return nil } @@ -376,7 +409,11 @@ func (met *MapEngineTest) handleLeftClick() { camVect := met.mapRenderer.Camera.GetPosition().Vector - x, y := float64(met.lastMouseX-400)/5, float64(met.lastMouseY-300)/5 + halfScreenWidth, halfScreenHeight := screenWidth>>1, screenHeight>>1 + + x := float64(met.lastMouseX-halfScreenWidth) / subtilesPerTile + y := float64(met.lastMouseY-halfScreenHeight) / subtilesPerTile + targetPosition := d2vector.NewPositionTile(x, y) targetPosition.Add(&camVect) diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index 888e6191..0274a4c1 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -242,7 +242,7 @@ type GameControls struct { escapeMenu *EscapeMenu ui *d2ui.UIManager inventory *Inventory - skilltree *SkillTree + skilltree *skillTree heroStatsPanel *HeroStatsPanel HelpOverlay *help.Overlay miniPanel *miniPanel @@ -362,7 +362,7 @@ func NewGameControls( mapRenderer: mapRenderer, inventory: NewInventory(asset, ui, inventoryRecord), skillSelectMenu: NewSkillSelectMenu(asset, ui, hero), - skilltree: NewSkillTree(hero.Skills, hero.Class, asset, renderer, ui, guiManager), + skilltree: newSkillTree(hero.Skills, hero.Class, asset, renderer, ui, guiManager), heroStatsPanel: NewHeroStatsPanel(asset, ui, hero.Name(), hero.Class, hero.Stats), HelpOverlay: help.NewHelpOverlay(asset, renderer, ui, guiManager), miniPanel: newMiniPanel(asset, ui, isSinglePlayer), @@ -817,7 +817,7 @@ func (g *GameControls) Load() { g.loadUIButtons() g.inventory.Load() - g.skilltree.Load() + g.skilltree.load() g.heroStatsPanel.Load() g.HelpOverlay.Load() } diff --git a/d2game/d2player/inventory.go b/d2game/d2player/inventory.go index 0db1b6a8..5558bdb2 100644 --- a/d2game/d2player/inventory.go +++ b/d2game/d2player/inventory.go @@ -2,8 +2,8 @@ package d2player import ( "fmt" - "image/color" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" @@ -15,6 +15,17 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) +const ( + frameInventoryTopLeft = 4 + frameInventoryTopRight = 5 + frameInventoryBottomLeft = 6 + frameInventoryBottomRight = 7 +) + +const ( + blackAlpha70 = 0x000000C8 +) + // Inventory represents the inventory type Inventory struct { asset *d2asset.AssetManager @@ -135,14 +146,16 @@ func (g *Inventory) Render(target d2interface.Surface) error { return nil } - g.frame.Render(target) + if err := g.frame.Render(target); err != nil { + return err + } x, y := g.originX+1, g.originY y += 64 // Panel // Top left - if err := g.panel.SetCurrentFrame(4); err != nil { + if err := g.panel.SetCurrentFrame(frameInventoryTopLeft); err != nil { return err } @@ -157,7 +170,7 @@ func (g *Inventory) Render(target d2interface.Surface) error { x += w // Top right - if err := g.panel.SetCurrentFrame(5); err != nil { + if err := g.panel.SetCurrentFrame(frameInventoryTopRight); err != nil { return err } @@ -172,7 +185,7 @@ func (g *Inventory) Render(target d2interface.Surface) error { y += h // Bottom right - if err := g.panel.SetCurrentFrame(7); err != nil { + if err := g.panel.SetCurrentFrame(frameInventoryBottomRight); err != nil { return err } @@ -184,7 +197,7 @@ func (g *Inventory) Render(target d2interface.Surface) error { } // Bottom left - if err := g.panel.SetCurrentFrame(6); err != nil { + if err := g.panel.SetCurrentFrame(frameInventoryBottomLeft); err != nil { return err } @@ -241,21 +254,27 @@ func (g *Inventory) renderItemDescription(target d2interface.Surface, i Inventor maxH += h } - halfW, halfH := maxW/2, maxH/2 + halfW, halfH := maxW>>1, maxH>>1 centerX, centerY := g.hoverX, iy-halfH - if (centerX + halfW) > 800 { - centerX = 800 - halfW + if (centerX + halfW) > screenWidth { + centerX = screenWidth - halfW } - if (centerY + halfH) > 600 { - centerY = 600 - halfH + if (centerY + halfH) > screenHeight { + centerY = screenHeight - halfH } target.PushTranslation(centerX, centerY) + defer target.Pop() + target.PushTranslation(-halfW, -halfH) - target.DrawRect(maxW, maxH, color.RGBA{0, 0, 0, uint8(200)}) + defer target.Pop() + + target.DrawRect(maxW, maxH, d2util.Color(blackAlpha70)) + target.PushTranslation(halfW, 0) + defer target.Pop() for idx := range lines { g.hoverLabel.SetText(lines[idx]) @@ -265,5 +284,4 @@ func (g *Inventory) renderItemDescription(target d2interface.Surface, i Inventor } target.PopN(len(lines)) - target.PopN(3) } diff --git a/d2game/d2player/mini_panel.go b/d2game/d2player/mini_panel.go index d6305cca..139ce335 100644 --- a/d2game/d2player/mini_panel.go +++ b/d2game/d2player/mini_panel.go @@ -10,6 +10,21 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) +const ( + miniPanelX = 325 + miniPanelY = 526 + miniPanelWidth = 156 + miniPanelHeight = 26 +) + +const ( + containerOffsetX = -75 + containerOffsetY = -48 + + buttonOffsetX = -72 + buttonOffsetY = -51 +) + type miniPanel struct { asset *d2asset.AssetManager container *d2ui.Sprite @@ -37,7 +52,12 @@ func newMiniPanel(asset *d2asset.AssetManager, uiManager *d2ui.UIManager, isSing return nil } - rectangle := d2geom.Rectangle{Left: 325, Top: 526, Width: 156, Height: 26} + rectangle := d2geom.Rectangle{ + Left: miniPanelX, + Top: miniPanelY, + Width: miniPanelWidth, + Height: miniPanelHeight, + } if !isSinglePlayer { rectangle.Width = 182 @@ -79,8 +99,10 @@ func (m *miniPanel) Render(target d2interface.Surface) error { } width, height := target.GetSize() + halfW, halfH := width>>1, height>>1 + x, y := halfW+containerOffsetX, halfH+containerOffsetY - m.container.SetPosition((width/2)-75, height-48) + m.container.SetPosition(x, y) if err := m.container.Render(target); err != nil { return err @@ -98,7 +120,10 @@ func (m *miniPanel) Render(target d2interface.Surface) error { return err } - m.button.SetPosition((width/2)-72+(buttonWidth*i), height-51) + offsetX := buttonOffsetX + (buttonWidth * i) + x, y := halfW+offsetX, height+buttonOffsetY + + m.button.SetPosition(x, y) if err := m.button.Render(target); err != nil { return err diff --git a/d2game/d2player/skill_select_panel.go b/d2game/d2player/skill_select_panel.go index 4d2cdf19..55e4722a 100644 --- a/d2game/d2player/skill_select_panel.go +++ b/d2game/d2player/skill_select_panel.go @@ -2,10 +2,11 @@ package d2player import ( "fmt" - "image/color" "log" "sort" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom" @@ -36,16 +37,16 @@ const ( type SkillPanel struct { asset *d2asset.AssetManager activeSkill *d2hero.HeroSkill - isOpen bool - regenerateImageCache bool hero *d2mapentity.Player ListRows []*SkillListRow - isLeftPanel bool renderer d2interface.Renderer ui *d2ui.UIManager hoveredSkill *d2hero.HeroSkill hoverTooltipRect *d2geom.Rectangle hoverTooltipText *d2ui.Label + isOpen bool + regenerateImageCache bool + isLeftPanel bool } // NewHeroSkillsPanel creates a new hero status panel @@ -85,11 +86,10 @@ func (s *SkillPanel) Close() { } // IsInRect returns whether the X Y coordinates are in some of the list rows of the panel. -func (s *SkillPanel) IsInRect(X int, Y int) bool { +func (s *SkillPanel) IsInRect(x, y int) bool { for _, listRow := range s.ListRows { - // TODO: investigate why listRow can be nil - if listRow != nil && listRow.IsInRect(X, Y) { + if listRow != nil && listRow.IsInRect(x, y) { return true } } @@ -98,9 +98,9 @@ func (s *SkillPanel) IsInRect(X int, Y int) bool { } // GetListRowByPos returns the skill list row for a given X and Y, based on the width and height of the skills list. -func (s *SkillPanel) GetListRowByPos(X int, Y int) *SkillListRow { +func (s *SkillPanel) GetListRowByPos(x, y int) *SkillListRow { for _, listRow := range s.ListRows { - if listRow.IsInRect(X, Y) { + if listRow.IsInRect(x, y) { return listRow } } @@ -115,11 +115,15 @@ func (s *SkillPanel) Render(target d2interface.Surface) error { } if s.regenerateImageCache { - s.generateSkillRowImageCache(target) + if err := s.generateSkillRowImageCache(); err != nil { + return err + } + s.regenerateImageCache = false } renderedRows := 0 + for _, skillListRow := range s.ListRows { if len(skillListRow.Skills) == 0 { continue @@ -129,7 +133,11 @@ func (s *SkillPanel) Render(target d2interface.Surface) error { rowOffsetY := skillPanelOffsetY - (renderedRows * skillIconHeight) target.PushTranslation(startX, rowOffsetY) - target.Render(skillListRow.cachedImage) + + if err := target.Render(skillListRow.cachedImage); err != nil { + return err + } + target.Pop() renderedRows++ @@ -137,20 +145,25 @@ func (s *SkillPanel) Render(target d2interface.Surface) error { if s.hoveredSkill != nil { target.PushTranslation(s.hoverTooltipRect.Left, s.hoverTooltipRect.Top) - target.DrawRect(s.hoverTooltipRect.Width, s.hoverTooltipRect.Height, color.RGBA{0, 0, 0, uint8(200)}) + + black70 := d2util.Color(blackAlpha70) + target.DrawRect(s.hoverTooltipRect.Width, s.hoverTooltipRect.Height, black70) // the text should be centered horizontally in the tooltip rect - target.PushTranslation(s.hoverTooltipRect.Width/2, 0) + centerX := s.hoverTooltipRect.Width >> 1 + target.PushTranslation(centerX, 0) s.hoverTooltipText.Render(target) - target.PopN(2) + target.Pop() + target.Pop() } return nil } // RegenerateImageCache will force re-generating the cached menu image on next Render. -// Somewhat expensive operation, should not be called often. Currently called every time the panel is opened or when the player learns a new skill. +// Somewhat expensive operation, should not be called often. +// Currently called every time the panel is opened or when the player learns a new skill. func (s *SkillPanel) RegenerateImageCache() { s.regenerateImageCache = true } @@ -169,7 +182,7 @@ func (s *SkillPanel) Toggle() { } } -func (s *SkillPanel) generateSkillRowImageCache(target d2interface.Surface) error { +func (s *SkillPanel) generateSkillRowImageCache() error { for idx := range s.ListRows { s.ListRows[idx] = &SkillListRow{Skills: make([]*d2hero.HeroSkill, 0), Rectangle: d2geom.Rectangle{Height: 0, Width: 0}} } @@ -189,6 +202,7 @@ func (s *SkillPanel) generateSkillRowImageCache(target d2interface.Surface) erro } visibleRows := 0 + for idx, skillListRow := range s.ListRows { // row won't be considered as visible if len(skillListRow.Skills) == 0 { @@ -202,13 +216,15 @@ func (s *SkillPanel) generateSkillRowImageCache(target d2interface.Surface) erro Top: skillPanelOffsetY - (visibleRows * skillIconHeight), } + skillRow := skillListRow + sort.SliceStable(skillListRow.Skills, func(a, b int) bool { // left panel skills are aligned by ID (low to high), right panel is the opposite if s.isLeftPanel { - return skillListRow.Skills[a].ID < skillListRow.Skills[b].ID + return skillRow.Skills[a].ID < skillRow.Skills[b].ID } - return skillListRow.Skills[a].ID > skillListRow.Skills[b].ID + return skillRow.Skills[a].ID > skillRow.Skills[b].ID }) cachedImage, err := s.createSkillListImage(skillListRow) @@ -234,6 +250,7 @@ func (s *SkillPanel) createSkillListImage(skillsListRow *SkillListRow) (d2interf lastSkillResourcePath := d2resource.GenericSkills skillSprite, _ := s.ui.NewSprite(s.getSkillResourceByClass(""), d2resource.PaletteSky) + for idx, skill := range skillsListRow.Skills { currentResourcePath := s.getSkillResourceByClass(skill.Charclass) // only load a new sprite if the DCC file path changed @@ -257,6 +274,7 @@ func (s *SkillPanel) createSkillListImage(skillsListRow *SkillListRow) (d2interf if err := skillSprite.Render(surface); err != nil { return nil, err } + surface.Pop() } @@ -272,38 +290,39 @@ func (s *SkillPanel) getRowStartX(skillRow *SkillListRow) int { return rightPanelEndX - skillRow.GetWidth() } -func (s *SkillPanel) getSkillAtPos(X int, Y int) *d2hero.HeroSkill { - listRow := s.GetListRowByPos(X, Y) +func (s *SkillPanel) getSkillAtPos(x, y int) *d2hero.HeroSkill { + listRow := s.GetListRowByPos(x, y) if listRow == nil { return nil } - skillIndex := (X - s.getRowStartX(listRow)) / skillIconWidth + skillIndex := (x - s.getRowStartX(listRow)) / skillIconWidth skill := listRow.Skills[skillIndex] return skill } -func (s *SkillPanel) getSkillIdxAtPos(X int, Y int) int { - listRow := s.GetListRowByPos(X, Y) +func (s *SkillPanel) getSkillIdxAtPos(x, y int) int { + listRow := s.GetListRowByPos(x, y) if listRow == nil { return -1 } - skillIndex := (X - s.getRowStartX(listRow)) / skillIconWidth + skillIndex := (x - s.getRowStartX(listRow)) / skillIconWidth return skillIndex } -// HandleClick will change the hero's active(left or right) skill and return true. Returns false if the given X, Y is out of panel boundaries. -func (s *SkillPanel) HandleClick(X int, Y int) bool { - if !s.isOpen || !s.IsInRect(X, Y) { +// HandleClick will change the hero's active(left or right) skill and return true. +// Returns false if the given X, Y is out of panel boundaries. +func (s *SkillPanel) HandleClick(x, y int) bool { + if !s.isOpen || !s.IsInRect(x, y) { return false } - clickedSkill := s.getSkillAtPos(X, Y) + clickedSkill := s.getSkillAtPos(x, y) if clickedSkill == nil { return false @@ -319,28 +338,28 @@ func (s *SkillPanel) HandleClick(X int, Y int) bool { } // HandleMouseMove will process a mouse move event, if inside the panel. -func (s *SkillPanel) HandleMouseMove(X int, Y int) bool { +func (s *SkillPanel) HandleMouseMove(x, y int) bool { if !s.isOpen { return false } - if !s.IsInRect(X, Y) { + if !s.IsInRect(x, y) { // panel still open but player hovered outside panel - hide the previously hovered skill(if any) s.hoveredSkill = nil return false } previousHovered := s.hoveredSkill - s.hoveredSkill = s.getSkillAtPos(X, Y) + s.hoveredSkill = s.getSkillAtPos(x, y) if previousHovered != s.hoveredSkill && s.hoveredSkill != nil { skillDescription := d2tbl.TranslateString(s.hoveredSkill.ShortKey) s.hoverTooltipText.SetText(fmt.Sprintf("%s\n%s", s.hoveredSkill.Skill, skillDescription)) - listRow := s.GetListRowByPos(X, Y) + listRow := s.GetListRowByPos(x, y) textWidth, textHeight := s.hoverTooltipText.GetSize() - tooltipX := (s.getSkillIdxAtPos(X, Y) * skillIconWidth) + s.getRowStartX(listRow) + tooltipX := (s.getSkillIdxAtPos(x, y) * skillIconWidth) + s.getRowStartX(listRow) tooltipWidth := textWidth + tooltipPadLeft + tooltipPadRight if tooltipX+tooltipWidth >= screenWidth { diff --git a/d2game/d2player/skilltree.go b/d2game/d2player/skilltree.go index ea404b81..f19b1919 100644 --- a/d2game/d2player/skilltree.go +++ b/d2game/d2player/skilltree.go @@ -15,40 +15,72 @@ import ( ) const ( - TabButtonX = 628 - TabButton0Y = 385 - TabButton1Y = 277 - TabButton2Y = 170 + tabButtonX = 628 + tabButton0Y = 385 + tabButton1Y = 277 + tabButton2Y = 170 - AvailSPLabelX = 677 - AvailSPLabelY = 72 + availSPLabelX = 677 + availSPLabelY = 72 - SkillIconXOff = 346 - SkillIconYOff = 59 - SkillIconDistX = 69 - SkillIconDistY = 68 + skillIconXOff = 346 + skillIconYOff = 59 + skillIconDistX = 69 + skillIconDistY = 68 ) -type SkillTreeTab struct { - buttonText string - button *d2ui.Button +const ( + firstTab = iota + secondTab + thirdTab +) + +const ( + tabIndexOffset = 4 + + frameOffsetTop = 4 + frameOffsetBottom = 6 +) + +const ( + frameCommonTabTopLeft = iota + frameCommonTabTopRight + frameCommonTabBottomLeft + frameCommonTabBottomRight +) + +const ( + frameSelectedTab1Full = 7 + frameSelectedTab2Top = 9 // tab2 top and bottom portions are in 2 frames :( + frameSelectedTab2Bottom = 11 + frameSelectedTab3Full = 13 +) + +const ( + skillTreePanelX = 401 + skillTreePanelY = 64 +) + +type skillTreeTab struct { + buttonText string + button *d2ui.Button } -func (st *SkillTreeTab) CreateButton(uiManager *d2ui.UIManager, x int, y int) { +func (st *skillTreeTab) createButton(uiManager *d2ui.UIManager, x, y int) { st.button = uiManager.NewButton(d2ui.ButtonTypeSkillTreeTab, st.buttonText) st.button.SetVisible(false) st.button.SetPosition(x, y) } -type SkillTreeHeroTypeResources struct { - skillIcon *d2ui.Sprite - skillIconPath string - skillPanel *d2ui.Sprite +type skillTreeHeroTypeResources struct { + skillIcon *d2ui.Sprite + skillIconPath string + skillPanel *d2ui.Sprite skillPanelPath string } -type SkillTree struct { - resources *SkillTreeHeroTypeResources +type skillTree struct { + resources *skillTreeHeroTypeResources asset *d2asset.AssetManager renderer d2interface.Renderer guiManager *d2gui.GuiManager @@ -58,187 +90,192 @@ type SkillTree struct { heroClass d2enum.Hero frame *d2ui.UIFrame availSPLabel *d2ui.Label - tab [3]*SkillTreeTab + tab [3]*skillTreeTab isOpen bool originX int originY int selectedTab int } -func NewSkillTree( +func newSkillTree( skills map[int]*d2hero.HeroSkill, - heroClass d2enum.Hero, + heroClass d2enum.Hero, asset *d2asset.AssetManager, renderer d2interface.Renderer, ui *d2ui.UIManager, guiManager *d2gui.GuiManager, -) *SkillTree { - st := &SkillTree { - skills: skills, - heroClass: heroClass, - asset: asset, - renderer: renderer, - uiManager: ui, +) *skillTree { + st := &skillTree{ + skills: skills, + heroClass: heroClass, + asset: asset, + renderer: renderer, + uiManager: ui, guiManager: guiManager, - originX: 401, - originY: 64, - tab: [3]*SkillTreeTab{ + originX: skillTreePanelX, + originY: skillTreePanelY, + tab: [3]*skillTreeTab{ {}, {}, {}, }, } + return st } -func (s *SkillTree) Load() { - +func (s *skillTree) load() { s.frame = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameRight) s.setHeroTypeResourcePath() - s.LoadForHeroType() + s.loadForHeroType() s.setTab(0) } -func (s *SkillTree) LoadForHeroType() { +func (s *skillTree) loadForHeroType() { sp, err := s.uiManager.NewSprite(s.resources.skillPanelPath, d2resource.PaletteSky) if err != nil { log.Print(err) } + s.resources.skillPanel = sp si, err := s.uiManager.NewSprite(s.resources.skillIconPath, d2resource.PaletteSky) if err != nil { log.Print(err) } + s.resources.skillIcon = si - s.tab[0].CreateButton(s.uiManager, TabButtonX, TabButton0Y) - s.tab[0].button.OnActivated(func() { s.setTab(0) }) - s.tab[1].CreateButton(s.uiManager, TabButtonX, TabButton1Y) - s.tab[1].button.OnActivated(func() { s.setTab(1) }) - s.tab[2].CreateButton(s.uiManager, TabButtonX, TabButton2Y) - s.tab[2].button.OnActivated(func() { s.setTab(2) }) + s.tab[firstTab].createButton(s.uiManager, tabButtonX, tabButton0Y) + s.tab[firstTab].button.OnActivated(func() { s.setTab(firstTab) }) + + s.tab[secondTab].createButton(s.uiManager, tabButtonX, tabButton1Y) + s.tab[secondTab].button.OnActivated(func() { s.setTab(secondTab) }) + + s.tab[thirdTab].createButton(s.uiManager, tabButtonX, tabButton2Y) + s.tab[thirdTab].button.OnActivated(func() { s.setTab(thirdTab) }) s.availSPLabel = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) - s.availSPLabel.SetPosition(AvailSPLabelX, AvailSPLabelY) + s.availSPLabel.SetPosition(availSPLabelX, availSPLabelY) s.availSPLabel.Alignment = d2gui.HorizontalAlignCenter - s.availSPLabel.SetText(fmt.Sprintf("%s\n%s\n%s", - d2tbl.TranslateString("StrSklTree1"), - d2tbl.TranslateString("StrSklTree2"), - d2tbl.TranslateString("StrSklTree3"), - )) + s.availSPLabel.SetText(makeTabString("StrSklTree1", "StrSklTree2", "StrSklTree3")) } -func (s *SkillTree) setHeroTypeResourcePath() { - var res *SkillTreeHeroTypeResources +type heroTabData struct { + resources *skillTreeHeroTypeResources + str1, str2, str3 string +} - switch s.heroClass { - case d2enum.HeroBarbarian: - res = &SkillTreeHeroTypeResources { - skillPanelPath: d2resource.SkillsPanelBarbarian, - skillIconPath: d2resource.BarbarianSkills, +func makeTabString(keys ...interface{}) string { + translations := make([]interface{}, len(keys)) + + token := "%s" + format := token + + for idx, key := range keys { + if idx > 0 { + format += "\n" + token } - s.tab[0].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree21"), - d2tbl.TranslateString("StrSklTree4")) - s.tab[1].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree21"), - d2tbl.TranslateString("StrSklTree22")) - s.tab[2].buttonText = d2tbl.TranslateString("StrSklTree20") - - case d2enum.HeroNecromancer: - res = &SkillTreeHeroTypeResources { - skillPanelPath: d2resource.SkillsPanelNecromancer, - skillIconPath: d2resource.NecromancerSkills, - } - - s.tab[0].buttonText = d2tbl.TranslateString("StrSklTree19") - s.tab[1].buttonText = fmt.Sprintf("%s\n%s\n%s", - d2tbl.TranslateString("StrSklTree17"), - d2tbl.TranslateString("StrSklTree18"), - d2tbl.TranslateString("StrSklTree5")) - s.tab[2].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree16"), - d2tbl.TranslateString("StrSklTree5")) - case d2enum.HeroPaladin: - res = &SkillTreeHeroTypeResources { - skillPanelPath: d2resource.SkillsPanelPaladin, - skillIconPath: d2resource.PaladinSkills, - } - - s.tab[0].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree15"), - d2tbl.TranslateString("StrSklTree4")) - s.tab[1].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree14"), - d2tbl.TranslateString("StrSklTree13")) - s.tab[2].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree12"), - d2tbl.TranslateString("StrSklTree13")) - case d2enum.HeroAssassin: - res = &SkillTreeHeroTypeResources { - skillPanelPath: d2resource.SkillsPanelAssassin, - skillIconPath: d2resource.AssassinSkills, - } - - s.tab[0].buttonText = d2tbl.TranslateString("StrSklTree30") - s.tab[1].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree31"), - d2tbl.TranslateString("StrSklTree32")) - s.tab[2].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree33"), - d2tbl.TranslateString("StrSklTree34")) - case d2enum.HeroSorceress: - res = &SkillTreeHeroTypeResources { - skillPanelPath: d2resource.SkillsPanelSorcerer, - skillIconPath: d2resource.SorcererSkills, - } - s.tab[0].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree25"), - d2tbl.TranslateString("StrSklTree5")) - s.tab[1].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree24"), - d2tbl.TranslateString("StrSklTree5")) - s.tab[2].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree23"), - d2tbl.TranslateString("StrSklTree5")) - case d2enum.HeroAmazon: - res = &SkillTreeHeroTypeResources { - skillPanelPath: d2resource.SkillsPanelAmazon, - skillIconPath: d2resource.AmazonSkills, - } - s.tab[0].buttonText = fmt.Sprintf("%s\n%s\n%s", - d2tbl.TranslateString("StrSklTree10"), - d2tbl.TranslateString("StrSklTree11"), - d2tbl.TranslateString("StrSklTree4")) - s.tab[1].buttonText = fmt.Sprintf("%s\n%s\n%s", - d2tbl.TranslateString("StrSklTree8"), - d2tbl.TranslateString("StrSklTree9"), - d2tbl.TranslateString("StrSklTree4")) - s.tab[2].buttonText = fmt.Sprintf("%s\n%s\n%s", - d2tbl.TranslateString("StrSklTree6"), - d2tbl.TranslateString("StrSklTree7"), - d2tbl.TranslateString("StrSklTree4")) - case d2enum.HeroDruid: - res = &SkillTreeHeroTypeResources { - skillPanelPath: d2resource.SkillsPanelDruid, - skillIconPath: d2resource.DruidSkills, - } - s.tab[0].buttonText = d2tbl.TranslateString("StrSklTree26") - s.tab[1].buttonText = fmt.Sprintf("%s\n%s", - d2tbl.TranslateString("StrSklTree27"), - d2tbl.TranslateString("StrSklTree28")) - s.tab[2].buttonText = d2tbl.TranslateString("StrSklTree29") - default: - log.Fatal("Unknown Hero Type") + translations[idx] = d2tbl.TranslateString(key.(string)) } - s.resources = res + + return fmt.Sprintf(format, translations...) } -func (s *SkillTree) Toggle() { +func (s *skillTree) getTab(class d2enum.Hero) (heroTabData, bool) { + tabMap := map[d2enum.Hero]heroTabData{ + d2enum.HeroBarbarian: { + &skillTreeHeroTypeResources{ + skillPanelPath: d2resource.SkillsPanelBarbarian, + skillIconPath: d2resource.BarbarianSkills, + }, + makeTabString("StrSklTree21", "StrSklTree4"), + makeTabString("StrSklTree21", "StrSklTree22"), + makeTabString("StrSklTree20"), + }, + d2enum.HeroNecromancer: { + &skillTreeHeroTypeResources{ + skillPanelPath: d2resource.SkillsPanelNecromancer, + skillIconPath: d2resource.NecromancerSkills, + }, + makeTabString("StrSklTree19"), + makeTabString("StrSklTree17", "StrSklTree18", "StrSklTree5"), + makeTabString("StrSklTree16", "StrSklTree5"), + }, + d2enum.HeroPaladin: { + &skillTreeHeroTypeResources{ + skillPanelPath: d2resource.SkillsPanelPaladin, + skillIconPath: d2resource.PaladinSkills, + }, + makeTabString("StrSklTree15", "StrSklTree4"), + makeTabString("StrSklTree14", "StrSklTree13"), + makeTabString("StrSklTree12", "StrSklTree13"), + }, + + d2enum.HeroAssassin: { + &skillTreeHeroTypeResources{ + skillPanelPath: d2resource.SkillsPanelAssassin, + skillIconPath: d2resource.AssassinSkills, + }, + + makeTabString("StrSklTree30"), + makeTabString("StrSklTree31", "StrSklTree32"), + makeTabString("StrSklTree33", "StrSklTree34"), + }, + d2enum.HeroSorceress: { + &skillTreeHeroTypeResources{ + skillPanelPath: d2resource.SkillsPanelSorcerer, + skillIconPath: d2resource.SorcererSkills, + }, + makeTabString("StrSklTree25", "StrSklTree5"), + makeTabString("StrSklTree24", "StrSklTree5"), + makeTabString("StrSklTree23", "StrSklTree5"), + }, + + d2enum.HeroAmazon: { + &skillTreeHeroTypeResources{ + skillPanelPath: d2resource.SkillsPanelAmazon, + skillIconPath: d2resource.AmazonSkills, + }, + makeTabString("StrSklTree10", "StrSklTree11", "StrSklTree4"), + makeTabString("StrSklTree8", "StrSklTree9", "StrSklTree4"), + makeTabString("StrSklTree6", "StrSklTree7", "StrSklTree4"), + }, + + d2enum.HeroDruid: { + &skillTreeHeroTypeResources{ + skillPanelPath: d2resource.SkillsPanelDruid, + skillIconPath: d2resource.DruidSkills, + }, + makeTabString("StrSklTree26"), + makeTabString("StrSklTree27", "StrSklTree28"), + makeTabString("StrSklTree29"), + }, + } + + entry, found := tabMap[class] + + return entry, found +} + +func (s *skillTree) setHeroTypeResourcePath() { + entry, found := s.getTab(s.heroClass) + if !found { + log.Fatal("Unknown Hero Type") + } + + s.resources = entry.resources + s.tab[firstTab].buttonText = entry.str1 + s.tab[secondTab].buttonText = entry.str1 + s.tab[thirdTab].buttonText = entry.str1 +} + +// Toggle the skill tree visibility +func (s *skillTree) Toggle() { fmt.Println("SkillTree toggled") + if s.isOpen { s.Close() } else { @@ -246,170 +283,219 @@ func (s *SkillTree) Toggle() { } } -func (s *SkillTree) Close() { +// Close the skill tree +func (s *skillTree) Close() { s.isOpen = false s.guiManager.SetLayout(nil) - for i:=0; i < 3; i++ { + + for i := 0; i < 3; i++ { s.tab[i].button.SetVisible(false) } } -func (s *SkillTree) Open() { +// Open the skill tree +func (s *skillTree) Open() { s.isOpen = true if s.layout == nil { s.layout = d2gui.CreateLayout(s.renderer, d2gui.PositionTypeHorizontal, s.asset) } - for i:=0; i < 3; i++ { + for i := 0; i < 3; i++ { s.tab[i].button.SetVisible(true) } - s.guiManager.SetLayout(s.layout) + s.guiManager.SetLayout(s.layout) } -func (s *SkillTree) IsOpen() bool { +func (s *skillTree) IsOpen() bool { return s.isOpen } -func (s *SkillTree) setTab(tab int) { +func (s *skillTree) setTab(tab int) { s.selectedTab = tab } -func (s *SkillTree) renderPanelSegment( +func (s *skillTree) renderPanelSegment( target d2interface.Surface, frame int) error { if err := s.resources.skillPanel.SetCurrentFrame(frame); err != nil { return err } + if err := s.resources.skillPanel.Render(target); err != nil { return err } + return nil } -func (s *SkillTree) renderTabCommon (target d2interface.Surface) error { +func (s *skillTree) renderTabCommon(target d2interface.Surface) error { skillPanel := s.resources.skillPanel x, y := s.originX, s.originY // top - w, h, err := skillPanel.GetFrameSize(0) + w, h, err := skillPanel.GetFrameSize(frameCommonTabTopLeft) if err != nil { return err } + y += h + skillPanel.SetPosition(x, y) - if err:= s.renderPanelSegment(target, 0); err != nil { + + err = s.renderPanelSegment(target, frameCommonTabTopLeft) + if err != nil { return err } skillPanel.SetPosition(x+w, y) - if err:= s.renderPanelSegment(target, 1); err != nil { + + err = s.renderPanelSegment(target, frameCommonTabTopRight) + if err != nil { return err } // bottom - _, h, err = skillPanel.GetFrameSize(2) + _, h, err = skillPanel.GetFrameSize(frameCommonTabBottomLeft) if err != nil { return err } + y += h + skillPanel.SetPosition(x, y) - if err:= s.renderPanelSegment(target, 2); err != nil { + + err = s.renderPanelSegment(target, frameCommonTabBottomLeft) + if err != nil { return err } skillPanel.SetPosition(x+w, y) - if err:= s.renderPanelSegment(target, 3); err != nil { + + err = s.renderPanelSegment(target, frameCommonTabBottomRight) + if err != nil { return err } // available skill points label s.availSPLabel.Render(target) + return nil } -func (s *SkillTree) renderTab (target d2interface.Surface, tab int) error { - var frameID [2]int - - frameID[0] = 4 + (4*tab) - frameID[1] = 6 + (4*tab) +func (s *skillTree) renderTab(target d2interface.Surface, tab int) error { + topFrame := frameOffsetTop + (tabIndexOffset * tab) + bottomFrame := frameOffsetBottom + (tabIndexOffset * tab) skillPanel := s.resources.skillPanel x, y := s.originX, s.originY // top - _, h0, err := skillPanel.GetFrameSize(frameID[0]) + _, h0, err := skillPanel.GetFrameSize(topFrame) if err != nil { return err } + y += h0 + skillPanel.SetPosition(x, y) - if err:= s.renderPanelSegment(target, frameID[0]); err != nil { + + err = s.renderPanelSegment(target, topFrame) + if err != nil { return err } // bottom - w, h1, err := skillPanel.GetFrameSize(frameID[1]) + w, h1, err := skillPanel.GetFrameSize(bottomFrame) if err != nil { return err } + skillPanel.SetPosition(x, y+h1) - if err:= s.renderPanelSegment(target, frameID[1]); err != nil { + + if err := s.renderPanelSegment(target, bottomFrame); err != nil { return err } // tab button highlighted switch tab { - case 0: + case firstTab: skillPanel.SetPosition(x+w, y+h1) - if err:= s.renderPanelSegment(target, 7); err != nil { + + if err := s.renderPanelSegment(target, frameSelectedTab1Full); err != nil { return err } - case 1: + case secondTab: x += w - skillPanel.SetPosition(x, s.originY + h0) - if err:= s.renderPanelSegment(target, 9); err != nil { + skillPanel.SetPosition(x, s.originY+h0) + + if err := s.renderPanelSegment(target, frameSelectedTab2Top); err != nil { return err } - skillPanel.SetPosition(x, y + h1) - if err:= s.renderPanelSegment(target, 11); err != nil { + + skillPanel.SetPosition(x, y+h1) + + if err := s.renderPanelSegment(target, frameSelectedTab2Bottom); err != nil { return err } - case 2: + case thirdTab: skillPanel.SetPosition(x+w, y) - if err:= s.renderPanelSegment(target, 13); err != nil { + + if err := s.renderPanelSegment(target, frameSelectedTab3Full); err != nil { return err } } + return nil } -func (s *SkillTree) renderSkillIcons(target d2interface.Surface, tab int) error { - +func (s *skillTree) renderSkillIcons(target d2interface.Surface, tab int) error { skillIcon := s.resources.skillIcon - for idx:=range s.skills { + + for idx := range s.skills { skill := s.skills[idx] - if skill.SkillPage != tab + 1 { + if skill.SkillPage != tab+1 { continue } + if err := skillIcon.SetCurrentFrame(skill.IconCel); err != nil { return err } - skillIcon.SetPosition(SkillIconXOff + skill.SkillColumn * SkillIconDistX, SkillIconYOff + skill.SkillRow * SkillIconDistY) + + x := skillIconXOff + skill.SkillColumn*skillIconDistX + y := skillIconYOff + skill.SkillRow*skillIconDistY + + skillIcon.SetPosition(x, y) + if err := skillIcon.Render(target); err != nil { return err } } - return nil + return nil } -func (s *SkillTree) Render (target d2interface.Surface) error { + +// Render the skill tree panel +func (s *skillTree) Render(target d2interface.Surface) error { if !s.isOpen { return nil } - s.frame.Render(target) - s.renderTabCommon(target) - s.renderTab(target, s.selectedTab) - s.renderSkillIcons(target, s.selectedTab) + + if err := s.frame.Render(target); err != nil { + return err + } + + if err := s.renderTabCommon(target); err != nil { + return err + } + + if err := s.renderTab(target, s.selectedTab); err != nil { + return err + } + + if err := s.renderSkillIcons(target, s.selectedTab); err != nil { + return err + } + return nil }