diff --git a/d2game/d2gamescreen/blizzard_intro.go b/d2game/d2gamescreen/blizzard_intro.go index e49a0f55..8bc3782e 100644 --- a/d2game/d2gamescreen/blizzard_intro.go +++ b/d2game/d2gamescreen/blizzard_intro.go @@ -23,8 +23,8 @@ func (v *BlizzardIntro) OnLoad(loading d2screen.LoadingState) { loading.Error(err) return } - - loading.Progress(0.5) + + loading.Progress(fiftyPercent) v.videoDecoder = d2video.CreateBinkDecoder(videoBytes) } diff --git a/d2game/d2gamescreen/character_select.go b/d2game/d2gamescreen/character_select.go index 3c43a73a..6b1091b7 100644 --- a/d2game/d2gamescreen/character_select.go +++ b/d2game/d2gamescreen/character_select.go @@ -70,6 +70,56 @@ func CreateCharacterSelect( } } +const ( + tenPercent = 0.1 * iota + twentyPercent + thirtyPercent + fourtyPercent + fiftyPercent + sixtyPercent + seventyPercent + eightyPercent + ninetyPercent +) + +const ( + rootLabelOffsetX = 115 + rootLabelOffsetY = 100 + labelHeight = 15 +) + +const ( + selectionBoxNumColumns = 2 + selectionBoxNumRows = 4 + selectionBoxWidth = 272 + selectionBoxHeight = 92 + selectionBoxOffsetX = 37 + selectionBoxOffsetY = 86 + selectionBoxImageOffsetX = 40 + selectionBoxImageOffsetY = 50 +) + +const ( + blackHalfOpacity = 0x0000007f + lightBrown = 0xbca88cff + lightGreen = 0x18ff00ff +) + +const ( + screenWidth = 800 + screenHeight = 600 +) + +const ( + newCharBtnX, newCharBtnY = 33, 468 + convertCharBtnX, convertCharBtnY = 233, 468 + deleteCharBtnX, deleteCharBtnY = 433, 468 + deleteCancelX, deleteCancelY = 282, 308 + deleteOkX, deleteOkY = 422, 308 + exitBtnX, exitBtnY = 33, 537 + okBtnX, okBtnY = 625, 537 +) + // OnLoad loads the resources for the Character Select screen func (v *CharacterSelect) OnLoad(loading d2screen.LoadingState) { v.audioProvider.PlayBGM(d2resource.BGMTitle) @@ -78,64 +128,101 @@ func (v *CharacterSelect) OnLoad(loading d2screen.LoadingState) { fmt.Println("failed to add Character Select screen as event handler") } - loading.Progress(0.1) + loading.Progress(tenPercent) animation, _ := d2asset.LoadAnimation(d2resource.CharacterSelectionBackground, d2resource.PaletteSky) + bgX, bgY := 0, 0 v.background, _ = d2ui.LoadSprite(animation) - v.background.SetPosition(0, 0) + v.background.SetPosition(bgX, bgY) v.createButtons(loading) + heroTitleX, heroTitleY := 320, 23 v.d2HeroTitle = d2ui.CreateLabel(d2resource.Font42, d2resource.PaletteUnits) - v.d2HeroTitle.SetPosition(320, 23) + v.d2HeroTitle.SetPosition(heroTitleX, heroTitleY) v.d2HeroTitle.Alignment = d2gui.HorizontalAlignCenter - loading.Progress(0.3) + loading.Progress(thirtyPercent) v.deleteCharConfirmLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) lines := "Are you sure that you want\nto delete this character?\nTake note: this will delete all\nversions of this Character." v.deleteCharConfirmLabel.SetText(lines) v.deleteCharConfirmLabel.Alignment = d2gui.HorizontalAlignCenter - v.deleteCharConfirmLabel.SetPosition(400, 185) + deleteConfirmX, deleteConfirmY := 400, 185 + v.deleteCharConfirmLabel.SetPosition(deleteConfirmX, deleteConfirmY) animation, _ = d2asset.LoadAnimation(d2resource.CharacterSelectionSelectBox, d2resource.PaletteSky) v.selectionBox, _ = d2ui.LoadSprite(animation) - v.selectionBox.SetPosition(37, 86) + selBoxX, selBoxY := 37, 86 + v.selectionBox.SetPosition(selBoxX, selBoxY) animation, _ = d2asset.LoadAnimation(d2resource.PopUpOkCancel, d2resource.PaletteFechar) v.okCancelBox, _ = d2ui.LoadSprite(animation) - v.okCancelBox.SetPosition(270, 175) + okCancelX, okCancelY := 270, 175 + v.okCancelBox.SetPosition(okCancelX, okCancelY) - v.charScrollbar = d2ui.CreateScrollbar(586, 87, 369) + scrollBarX, scrollBarY, scrollBarHeight := 586, 87, 369 + v.charScrollbar = d2ui.CreateScrollbar(scrollBarX, scrollBarY, scrollBarHeight) v.charScrollbar.OnActivated(func() { v.onScrollUpdate() }) d2ui.AddWidget(&v.charScrollbar) - loading.Progress(0.5) + + loading.Progress(fiftyPercent) for i := 0; i < 8; i++ { - xOffset := 115 + offsetX, offsetY := rootLabelOffsetX, rootLabelOffsetY+((i/2)*95) if i&1 > 0 { - xOffset = 385 + offsetX = 385 } v.characterNameLabel[i] = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) - v.characterNameLabel[i].Color = color.RGBA{R: 188, G: 168, B: 140, A: 255} - v.characterNameLabel[i].SetPosition(xOffset, 100+((i/2)*95)) + v.characterNameLabel[i].Color = rgbaColor(lightBrown) + v.characterNameLabel[i].SetPosition(offsetX, offsetY) + + offsetY += labelHeight v.characterStatsLabel[i] = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) - v.characterStatsLabel[i].SetPosition(xOffset, 115+((i/2)*95)) + v.characterStatsLabel[i].SetPosition(offsetX, offsetY) + + offsetY += labelHeight v.characterExpLabel[i] = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteStatic) - v.characterExpLabel[i].Color = color.RGBA{R: 24, G: 255, A: 255} - v.characterExpLabel[i].SetPosition(xOffset, 130+((i/2)*95)) + v.characterExpLabel[i].Color = rgbaColor(lightGreen) + v.characterExpLabel[i].SetPosition(offsetX, offsetY) } v.refreshGameStates() } +func rgbaColor(rgba uint32) color.RGBA { + result := color.RGBA{} + a, b, g, r := 0, 1, 2, 3 + byteWidth := 8 + byteMask := 0xff + + for idx := 0; idx < 4; idx++ { + shift := idx * byteWidth + component := uint8(rgba>>shift) & uint8(byteMask) + + switch idx { + case a: + result.A = component + case b: + result.B = component + case g: + result.G = component + case r: + result.R = component + } + } + + return result +} + func (v *CharacterSelect) createButtons(loading d2screen.LoadingState) { v.newCharButton = d2ui.CreateButton( v.renderer, d2ui.ButtonTypeTall, "CREATE NEW\nCHARACTER", ) - v.newCharButton.SetPosition(33, 468) + + v.newCharButton.SetPosition(newCharBtnX, newCharBtnY) v.newCharButton.OnActivated(func() { v.onNewCharButtonClicked() }) d2ui.AddWidget(&v.newCharButton) @@ -144,7 +231,8 @@ func (v *CharacterSelect) createButtons(loading d2screen.LoadingState) { d2ui.ButtonTypeTall, "CONVERT TO\nEXPANSION", ) - v.convertCharButton.SetPosition(233, 468) + + v.convertCharButton.SetPosition(convertCharBtnX, convertCharBtnY) v.convertCharButton.SetEnabled(false) d2ui.AddWidget(&v.convertCharButton) @@ -154,29 +242,30 @@ func (v *CharacterSelect) createButtons(loading d2screen.LoadingState) { "DELETE\nCHARACTER", ) v.deleteCharButton.OnActivated(func() { v.onDeleteCharButtonClicked() }) - v.deleteCharButton.SetPosition(433, 468) + v.deleteCharButton.SetPosition(deleteCharBtnX, deleteCharBtnY) d2ui.AddWidget(&v.deleteCharButton) v.exitButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, "EXIT") - v.exitButton.SetPosition(33, 537) + v.exitButton.SetPosition(exitBtnX, exitBtnY) v.exitButton.OnActivated(func() { v.onExitButtonClicked() }) d2ui.AddWidget(&v.exitButton) - loading.Progress(0.2) + + loading.Progress(twentyPercent) v.deleteCharCancelButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeOkCancel, "NO") - v.deleteCharCancelButton.SetPosition(282, 308) + v.deleteCharCancelButton.SetPosition(deleteCancelX, deleteCancelY) v.deleteCharCancelButton.SetVisible(false) v.deleteCharCancelButton.OnActivated(func() { v.onDeleteCharacterCancelClicked() }) d2ui.AddWidget(&v.deleteCharCancelButton) v.deleteCharOkButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeOkCancel, "YES") - v.deleteCharOkButton.SetPosition(422, 308) + v.deleteCharOkButton.SetPosition(deleteOkX, deleteOkY) v.deleteCharOkButton.SetVisible(false) v.deleteCharOkButton.OnActivated(func() { v.onDeleteCharacterConfirmClicked() }) d2ui.AddWidget(&v.deleteCharOkButton) v.okButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, "OK") - v.okButton.SetPosition(625, 537) + v.okButton.SetPosition(okBtnX, okBtnY) v.okButton.OnActivated(func() { v.onOkButtonClicked() }) d2ui.AddWidget(&v.okButton) } @@ -248,13 +337,16 @@ func (v *CharacterSelect) Render(screen d2interface.Surface) error { v.characterNameLabel[i].Render(screen) v.characterStatsLabel[i].Render(screen) v.characterExpLabel[i].Render(screen) - screen.PushTranslation(v.characterNameLabel[i].X-40, v.characterNameLabel[i].Y+50) + + charImgX := v.characterNameLabel[i].X - selectionBoxImageOffsetX + charImgY := v.characterNameLabel[i].Y + selectionBoxImageOffsetY + screen.PushTranslation(charImgX, charImgY) v.characterImage[i].Render(screen) screen.Pop() } if v.showDeleteConfirmation { - screen.DrawRect(800, 600, color.RGBA{A: 128}) + screen.DrawRect(screenWidth, screenHeight, rgbaColor(blackHalfOpacity)) if err := v.okCancelBox.RenderSegmented(screen, 2, 1, 0); err != nil { return err @@ -275,7 +367,10 @@ func (v *CharacterSelect) moveSelectionBox() { bw := 272 bh := 92 selectedIndex := v.selectedCharacter - (v.charScrollbar.GetCurrentOffset() * 2) - v.selectionBox.SetPosition(37+((selectedIndex&1)*bw), 86+(bh*(selectedIndex/2))) + + selBoxX := selectionBoxOffsetX + ((selectedIndex & 1) * bw) + selBoxY := selectionBoxOffsetY + (bh * (selectedIndex / 2)) + v.selectionBox.SetPosition(selBoxX, selBoxY) v.d2HeroTitle.SetText(v.gameStates[v.selectedCharacter].HeroName) } @@ -284,14 +379,15 @@ func (v *CharacterSelect) OnMouseButtonDown(event d2interface.MouseEvent) bool { if !v.showDeleteConfirmation { if event.Button() == d2enum.MouseButtonLeft { mx, my := event.X(), event.Y() - bw := 272 - bh := 92 - localMouseX := mx - 37 - localMouseY := my - 86 + + bw := selectionBoxWidth + bh := selectionBoxHeight + localMouseX := mx - selectionBoxOffsetX + localMouseY := my - selectionBoxOffsetY if localMouseX > 0 && localMouseX < bw*2 && localMouseY >= 0 && localMouseY < bh*4 { adjustY := localMouseY / bh - selectedIndex := adjustY * 2 + selectedIndex := adjustY * selectionBoxNumColumns if localMouseX > bw { selectedIndex++ @@ -354,8 +450,11 @@ func (v *CharacterSelect) refreshGameStates() { if len(v.gameStates) > 0 { v.selectedCharacter = 0 + numStates := selectionBoxNumColumns * selectionBoxNumRows + byHalf := 2.0 + v.d2HeroTitle.SetText(v.gameStates[0].HeroName) - v.charScrollbar.SetMaxOffset(int(math.Ceil(float64(len(v.gameStates)-8) / float64(2)))) + v.charScrollbar.SetMaxOffset(int(math.Ceil(float64(len(v.gameStates)-numStates) / byHalf))) } else { v.selectedCharacter = -1 v.charScrollbar.SetMaxOffset(0) diff --git a/d2game/d2gamescreen/credits.go b/d2game/d2gamescreen/credits.go index 47626ec6..3cd6d842 100644 --- a/d2game/d2gamescreen/credits.go +++ b/d2game/d2gamescreen/credits.go @@ -3,7 +3,6 @@ package d2gamescreen import ( "bufio" "fmt" - "image/color" "log" "os" "path" @@ -17,6 +16,13 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) +const ( + creditsX, creditsY = 0, 0 + charSelExitBtnX, charSelExitBtnY = 33, 543 +) + +const secondsPerCycle float64 = 0.02 + type labelItem struct { Label d2ui.Label IsHeading bool @@ -80,14 +86,14 @@ func (v *Credits) LoadContributors() []string { func (v *Credits) OnLoad(loading d2screen.LoadingState) { animation, _ := d2asset.LoadAnimation(d2resource.CreditsBackground, d2resource.PaletteSky) v.creditsBackground, _ = d2ui.LoadSprite(animation) - v.creditsBackground.SetPosition(0, 0) - loading.Progress(0.2) + v.creditsBackground.SetPosition(creditsX, creditsY) + loading.Progress(twentyPercent) v.exitButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, "EXIT") - v.exitButton.SetPosition(33, 543) + v.exitButton.SetPosition(charSelExitBtnX, charSelExitBtnY) v.exitButton.OnActivated(func() { v.onExitButtonClicked() }) d2ui.AddWidget(&v.exitButton) - loading.Progress(0.4) + loading.Progress(fourtyPercent) fileData, err := d2asset.LoadFile(d2resource.CreditsText) if err != nil { @@ -95,7 +101,7 @@ func (v *Credits) OnLoad(loading d2screen.LoadingState) { return } - loading.Progress(0.6) + loading.Progress(sixtyPercent) creditData, _ := d2common.Utf16BytesToString(fileData[2:]) v.creditsText = strings.Split(creditData, "\r\n") @@ -104,7 +110,7 @@ func (v *Credits) OnLoad(loading d2screen.LoadingState) { v.creditsText[i] = strings.Trim(v.creditsText[i], " ") } - loading.Progress(0.8) + loading.Progress(eightyPercent) v.creditsText = append(v.LoadContributors(), v.creditsText...) } @@ -127,8 +133,6 @@ func (v *Credits) Render(screen d2interface.Surface) error { return nil } -const secondsPerCycle = float64(0.02) - // Advance runs the update logic on the credits screen func (v *Credits) Advance(tickTime float64) error { v.cycleTime += tickTime @@ -210,13 +214,22 @@ func (v *Credits) addNextItem() { } } +const( + itemLabelY = 605 + itemLabelX = 400 + itemLabel2offsetX = 10 + halfItemLabel2offsetX = itemLabel2offsetX/2 +) + func (v *Credits) setItemLabelPosition(label *d2ui.Label, isHeading, isNextHeading, isNextSpace bool) (isDoubled, nextHeading bool) { width, _ := label.GetSize() + half := 2 + halfWidth := width/half if !isHeading && !isNextHeading && !isNextSpace { isDoubled = true // Gotta go side by side - label.SetPosition(400-width, 605) + label.SetPosition(itemLabelX-width, itemLabelY) text2 := v.creditsText[0] v.creditsText = v.creditsText[1:] @@ -225,24 +238,29 @@ func (v *Credits) setItemLabelPosition(label *d2ui.Label, isHeading, isNextHeadi label2 := v.getNewFontLabel(isHeading) label2.SetText(text2) - label2.SetPosition(410, 605) + label2.SetPosition(itemLabelX+itemLabel2offsetX, itemLabelY) return isDoubled, nextHeading } - label.SetPosition(405-width/2, 605) + label.SetPosition(itemLabelX+halfItemLabel2offsetX-halfWidth, itemLabelY) return isDoubled, isNextHeading } +const ( + lightRed = 0xff5852ff + beige = 0xc6b296ff +) + func (v *Credits) getNewFontLabel(isHeading bool) *d2ui.Label { for _, label := range v.labels { if label.Available { label.Available = false if isHeading { - label.Label.Color = color.RGBA{R: 255, G: 88, B: 82, A: 255} + label.Label.Color = rgbaColor(lightRed) } else { - label.Label.Color = color.RGBA{R: 198, G: 178, B: 150, A: 255} + label.Label.Color = rgbaColor(beige) } return &label.Label @@ -256,9 +274,9 @@ func (v *Credits) getNewFontLabel(isHeading bool) *d2ui.Label { } if isHeading { - newLabelItem.Label.Color = color.RGBA{R: 255, G: 88, B: 82, A: 255} + newLabelItem.Label.Color = rgbaColor(lightRed) } else { - newLabelItem.Label.Color = color.RGBA{R: 198, G: 178, B: 150, A: 255} + newLabelItem.Label.Color = rgbaColor(beige) } v.labels = append(v.labels, newLabelItem) diff --git a/d2game/d2gamescreen/escape_menu.go b/d2game/d2gamescreen/escape_menu.go index f48a7388..65c2f75e 100644 --- a/d2game/d2gamescreen/escape_menu.go +++ b/d2game/d2gamescreen/escape_menu.go @@ -22,6 +22,7 @@ const ( sidePanelsSize = 80 pentSize = 52 menuSize = 500 + spacerWidth = 10 // layouts noLayoutID layoutID = iota - 2 @@ -247,7 +248,7 @@ func (m *EscapeMenu) addTitle(l *layout, text string) { fmt.Printf("could not add label: %s to the escape menu\n", text) } - l.AddSpacerStatic(10, labelGutter) + l.AddSpacerStatic(spacerWidth, labelGutter) } func (m *EscapeMenu) addBigSelectionLabel(l *layout, text string, targetLayout layoutID) { @@ -262,12 +263,12 @@ func (m *EscapeMenu) addBigSelectionLabel(l *layout, text string, targetLayout l label.SetMouseEnterHandler(func(_ d2interface.MouseMoveEvent) { m.onHoverElement(elID) }) - l.AddSpacerStatic(10, labelGutter) + l.AddSpacerStatic(spacerWidth, labelGutter) l.actionableElements = append(l.actionableElements, label) } func (m *EscapeMenu) addPreviousMenuLabel(l *layout) { - l.AddSpacerStatic(10, labelGutter) + l.AddSpacerStatic(spacerWidth, labelGutter) guiLabel, _ := l.AddLabel("PREVIOUS MENU", d2gui.FontStyle30Units) label := &showLayoutLabel{Label: guiLabel, target: optionsLayoutID, showLayout: m.showLayout} label.SetMouseClickHandler(func(_ d2interface.MouseEvent) { @@ -313,7 +314,7 @@ func (m *EscapeMenu) addEnumLabel(l *layout, optID optionID, text string, values layout.SetMouseClickHandler(func(_ d2interface.MouseEvent) { label.Trigger() }) - l.AddSpacerStatic(10, labelGutter) + l.AddSpacerStatic(spacerWidth, labelGutter) l.actionableElements = append(l.actionableElements, label) } @@ -378,9 +379,9 @@ func (m *EscapeMenu) onHoverElement(id int) { m.layouts[m.currentLayout].currentEl = id x, _ := m.leftPent.GetPosition() - m.leftPent.SetPosition(x, y+10) + m.leftPent.SetPosition(x, y+spacerWidth) x, _ = m.rightPent.GetPosition() - m.rightPent.SetPosition(x, y+10) + m.rightPent.SetPosition(x, y+spacerWidth) } func (m *EscapeMenu) onUpdateValue(optID optionID, value string) { diff --git a/d2game/d2gamescreen/game.go b/d2game/d2gamescreen/game.go index 0682b0fe..bb39f434 100644 --- a/d2game/d2gamescreen/game.go +++ b/d2game/d2gamescreen/game.go @@ -18,6 +18,12 @@ import ( const hideZoneTextAfterSeconds = 2.0 +const ( + moveErrStr = "failed to send MovePlayer packet to the server, playerId: %s, x: %g, x: %g\n" + bindControlsErrStr = "failed to add gameControls as input handler for player: %s\n" + castErrStr = "failed to send CastSkill packet to the server, playerId: %s, missileId: %d, x: %g, x: %g\n" +) + // Game represents the Gameplay screen type Game struct { gameClient *d2client.GameClient @@ -35,7 +41,14 @@ type Game struct { } // CreateGame creates the Gameplay screen and returns a pointer to it -func CreateGame(navigator Navigator, renderer d2interface.Renderer, inputManager d2interface.InputManager, audioProvider d2interface.AudioProvider, gameClient *d2client.GameClient, term d2interface.Terminal) *Game { +func CreateGame( + navigator Navigator, + renderer d2interface.Renderer, + inputManager d2interface.InputManager, + audioProvider d2interface.AudioProvider, + gameClient *d2client.GameClient, + term d2interface.Terminal, +) *Game { result := &Game{ gameClient: gameClient, gameControls: nil, @@ -126,7 +139,9 @@ func (v *Game) Advance(tickTime float64) error { // skip showing zone change text the first time we enter the world if v.lastRegionType != d2enum.RegionNone && v.lastRegionType != tile.RegionType { //TODO: Should not be using RegionType as an index - this will return incorrect LevelDetails record for most of the zones. - v.gameControls.SetZoneChangeText(fmt.Sprintf("Entering The %s", d2datadict.LevelDetails[int(tile.RegionType)].LevelDisplayName)) + areaName := d2datadict.LevelDetails[int(tile.RegionType)].LevelDisplayName + areaChgStr := fmt.Sprintf("Entering The %s", areaName) + v.gameControls.SetZoneChangeText(areaChgStr) v.gameControls.ShowZoneChangeText() v.gameControls.HideZoneChangeTextAfter(hideZoneTextAfterSeconds) } @@ -162,7 +177,7 @@ func (v *Game) bindGameControls() { v.gameControls.Load() if err := v.inputManager.BindHandler(v.gameControls); err != nil { - fmt.Printf("failed to add gameControls as input handler for player: %s\n", player.Id) + fmt.Printf(bindControlsErrStr, player.Id) } break @@ -170,12 +185,15 @@ func (v *Game) bindGameControls() { } // OnPlayerMove sends the player move action to the server -func (v *Game) OnPlayerMove(x, y float64) { +func (v *Game) OnPlayerMove(targetX, targetY float64) { worldPosition := v.localPlayer.Position.World() - err := v.gameClient.SendPacketToServer(d2netpacket.CreateMovePlayerPacket(v.gameClient.PlayerID, worldPosition.X(), worldPosition.Y(), x, y)) + playerID, worldX, worldY := v.gameClient.PlayerID, worldPosition.X(), worldPosition.Y() + createPlayerPacket := d2netpacket.CreateMovePlayerPacket(playerID, worldX, worldY, targetX, targetY) + err := v.gameClient.SendPacketToServer(createPlayerPacket) + if err != nil { - fmt.Printf("failed to send MovePlayer packet to the server, playerId: %s, x: %g, x: %g\n", v.gameClient.PlayerID, x, y) + fmt.Printf(moveErrStr, v.gameClient.PlayerID, targetX, targetY) } } @@ -183,9 +201,6 @@ func (v *Game) OnPlayerMove(x, y float64) { func (v *Game) OnPlayerCast(missileID int, targetX, targetY float64) { err := v.gameClient.SendPacketToServer(d2netpacket.CreateCastPacket(v.gameClient.PlayerID, missileID, targetX, targetY)) if err != nil { - fmt.Printf( - "failed to send CastSkill packet to the server, playerId: %s, missileId: %d, x: %g, x: %g\n", - v.gameClient.PlayerID, missileID, targetX, targetY, - ) + fmt.Printf(castErrStr, v.gameClient.PlayerID, missileID, targetX, targetY) } } diff --git a/d2game/d2gamescreen/gui_testing.go b/d2game/d2gamescreen/gui_testing.go index 3e6f0703..0211f912 100644 --- a/d2game/d2gamescreen/gui_testing.go +++ b/d2game/d2gamescreen/gui_testing.go @@ -24,7 +24,7 @@ func CreateGuiTestMain(renderer d2interface.Renderer) *GuiTestMain { func (g *GuiTestMain) OnLoad(loading d2screen.LoadingState) { layout := d2gui.CreateLayout(g.renderer, d2gui.PositionTypeHorizontal) - loading.Progress(0.3) + loading.Progress(thirtyPercent) // layoutLeft := layout.AddLayout(d2gui.PositionTypeVertical) layoutLeft.SetHorizontalAlign(d2gui.HorizontalAlignCenter) @@ -55,7 +55,7 @@ func (g *GuiTestMain) OnLoad(loading d2screen.LoadingState) { fmt.Printf("could not add label: %s to the GuiTestMain screen\n", "FontStyleFormal12Static") } - loading.Progress(0.6) + loading.Progress(sixtyPercent) layout.AddSpacerDynamic() @@ -82,7 +82,7 @@ func (g *GuiTestMain) OnLoad(loading d2screen.LoadingState) { fmt.Printf("could not add button: %s to the GuiTestMain screen\n", "Wide") } - loading.Progress(0.9) + loading.Progress(ninetyPercent) layout.SetVerticalAlign(d2gui.VerticalAlignMiddle) d2gui.SetLayout(layout) diff --git a/d2game/d2gamescreen/main_menu.go b/d2game/d2gamescreen/main_menu.go index b0c1726d..de1aaf25 100644 --- a/d2game/d2gamescreen/main_menu.go +++ b/d2game/d2gamescreen/main_menu.go @@ -3,7 +3,6 @@ package d2gamescreen import ( "fmt" - "image/color" "log" "os" "os/exec" @@ -22,10 +21,11 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2script" ) -type MainMenuScreenMode int +type mainMenuScreenMode int +// mainMenuScreenMode types const ( - ScreenModeUnknown MainMenuScreenMode = iota + ScreenModeUnknown mainMenuScreenMode = iota ScreenModeTrademark ScreenModeMainMenu ScreenModeMultiplayer @@ -33,6 +33,44 @@ const ( ScreenModeServerIP ) +const ( + joinGameDialogX, joinGameDialogY = 318, 245 + serverIPbackgroundX, serverIPbackgroundY = 270, 175 + backgroundX, backgroundY = 0, 0 + versionLabelX, versionLabelY = 795, -10 + commitLabelX, commitLabelY = 2, 2 + copyrightX, copyrightY = 400, 500 + copyright2X, copyright2Y = 400, 525 + od2LabelX, od2LabelY = 400, 580 + tcpOptionsX, tcpOptionsY = 400, 23 + joinGameX, joinGameY = 400, 190 + diabloLogoX, diabloLogoY = 400, 120 + exitDiabloBtnX, exitDiabloBtnY = 264, 535 + creditBtnX, creditBtnY = 264, 505 + cineBtnX, cineBtnY = 401, 505 + singlePlayerBtnX, singlePlayerBtnY = 264, 290 + githubBtnX, githubBtnY = 264, 400 + mapTestBtnX, mapTestBtnY = 264, 440 + tcpBtnX, tcpBtnY = 33, 543 + srvCancelBtnX, srvCancelBtnY = 285, 305 + srvOkBtnX, srvOkBtnY = 420, 305 + multiplayerBtnX, multiplayerBtnY = 264, 330 + tcpNetBtnX, tcpNetBtnY = 264, 280 + networkCancelBtnX, networkCancelBtnY = 264, 540 + tcpHostBtnX, tcpHostBtnY = 264, 280 + tcpJoinBtnX, tcpJoinBtnY = 264, 320 +) + +const ( + white = 0xffffffff + lightYellow = 0xffff8cff + gold = 0xd8c480ff +) + +const ( + joinGameCharacterFilter = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890._:" +) + // MainMenu represents the main menu type MainMenu struct { tcpIPBackground *d2ui.Sprite @@ -65,7 +103,7 @@ type MainMenu struct { tcpIPOptionsLabel d2ui.Label tcpJoinGameLabel d2ui.Label tcpJoinGameEntry d2ui.TextBox - screenMode MainMenuScreenMode + screenMode mainMenuScreenMode leftButtonHeld bool inputManager d2interface.InputManager @@ -76,7 +114,12 @@ type MainMenu struct { } // CreateMainMenu creates an instance of MainMenu -func CreateMainMenu(navigator Navigator, renderer d2interface.Renderer, inputManager d2interface.InputManager, audioProvider d2interface.AudioProvider) *MainMenu { +func CreateMainMenu( + navigator Navigator, + renderer d2interface.Renderer, + inputManager d2interface.InputManager, + audioProvider d2interface.AudioProvider, +) *MainMenu { return &MainMenu{ screenMode: ScreenModeUnknown, leftButtonHeld: true, @@ -90,7 +133,7 @@ func CreateMainMenu(navigator Navigator, renderer d2interface.Renderer, inputMan // OnLoad is called to load the resources for the main menu func (v *MainMenu) OnLoad(loading d2screen.LoadingState) { v.audioProvider.PlayBGM(d2resource.BGMTitle) - loading.Progress(0.2) + loading.Progress(twentyPercent) v.createLabels(loading) v.loadBackgroundSprites() @@ -98,10 +141,10 @@ func (v *MainMenu) OnLoad(loading d2screen.LoadingState) { v.createButtons(loading) v.tcpJoinGameEntry = d2ui.CreateTextbox(v.renderer) - v.tcpJoinGameEntry.SetPosition(318, 245) - v.tcpJoinGameEntry.SetFilter("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890._:") + v.tcpJoinGameEntry.SetPosition(joinGameDialogX, joinGameDialogY) + v.tcpJoinGameEntry.SetFilter(joinGameCharacterFilter) d2ui.AddWidget(&v.tcpJoinGameEntry) - loading.Progress(0.9) + loading.Progress(ninetyPercent) if v.screenMode == ScreenModeUnknown { v.SetScreenMode(ScreenModeTrademark) @@ -117,56 +160,56 @@ func (v *MainMenu) OnLoad(loading d2screen.LoadingState) { func (v *MainMenu) loadBackgroundSprites() { animation, _ := d2asset.LoadAnimation(d2resource.GameSelectScreen, d2resource.PaletteSky) v.background, _ = d2ui.LoadSprite(animation) - v.background.SetPosition(0, 0) + v.background.SetPosition(backgroundX, backgroundY) animation, _ = d2asset.LoadAnimation(d2resource.TrademarkScreen, d2resource.PaletteSky) v.trademarkBackground, _ = d2ui.LoadSprite(animation) - v.trademarkBackground.SetPosition(0, 0) + v.trademarkBackground.SetPosition(backgroundX, backgroundY) animation, _ = d2asset.LoadAnimation(d2resource.TCPIPBackground, d2resource.PaletteSky) v.tcpIPBackground, _ = d2ui.LoadSprite(animation) - v.tcpIPBackground.SetPosition(0, 0) + v.tcpIPBackground.SetPosition(backgroundX, backgroundY) animation, _ = d2asset.LoadAnimation(d2resource.PopUpOkCancel, d2resource.PaletteFechar) v.serverIPBackground, _ = d2ui.LoadSprite(animation) - v.serverIPBackground.SetPosition(270, 175) + v.serverIPBackground.SetPosition(serverIPbackgroundX, serverIPbackgroundY) } func (v *MainMenu) createLabels(loading d2screen.LoadingState) { v.versionLabel = d2ui.CreateLabel(d2resource.FontFormal12, d2resource.PaletteStatic) v.versionLabel.Alignment = d2gui.HorizontalAlignRight v.versionLabel.SetText("OpenDiablo2 - " + d2common.BuildInfo.Branch) - v.versionLabel.Color = color.RGBA{R: 255, G: 255, B: 255, A: 255} - v.versionLabel.SetPosition(795, -10) + v.versionLabel.Color = rgbaColor(white) + v.versionLabel.SetPosition(versionLabelX, versionLabelY) v.commitLabel = d2ui.CreateLabel(d2resource.FontFormal10, d2resource.PaletteStatic) v.commitLabel.Alignment = d2gui.HorizontalAlignLeft v.commitLabel.SetText(d2common.BuildInfo.Commit) - v.commitLabel.Color = color.RGBA{R: 255, G: 255, B: 255, A: 255} - v.commitLabel.SetPosition(2, 2) + v.commitLabel.Color = rgbaColor(white) + v.commitLabel.SetPosition(commitLabelX, commitLabelY) v.copyrightLabel = d2ui.CreateLabel(d2resource.FontFormal12, d2resource.PaletteStatic) v.copyrightLabel.Alignment = d2gui.HorizontalAlignCenter v.copyrightLabel.SetText("Diablo 2 is © Copyright 2000-2016 Blizzard Entertainment") - v.copyrightLabel.Color = color.RGBA{R: 188, G: 168, B: 140, A: 255} - v.copyrightLabel.SetPosition(400, 500) - loading.Progress(0.3) + v.copyrightLabel.Color = rgbaColor(lightBrown) + v.copyrightLabel.SetPosition(copyrightX, copyrightY) + loading.Progress(thirtyPercent) v.copyrightLabel2 = d2ui.CreateLabel(d2resource.FontFormal12, d2resource.PaletteStatic) v.copyrightLabel2.Alignment = d2gui.HorizontalAlignCenter v.copyrightLabel2.SetText("All Rights Reserved.") - v.copyrightLabel2.Color = color.RGBA{R: 188, G: 168, B: 140, A: 255} - v.copyrightLabel2.SetPosition(400, 525) + v.copyrightLabel2.Color = rgbaColor(lightBrown) + v.copyrightLabel2.SetPosition(copyright2X, copyright2Y) v.openDiabloLabel = d2ui.CreateLabel(d2resource.FontFormal10, d2resource.PaletteStatic) v.openDiabloLabel.Alignment = d2gui.HorizontalAlignCenter v.openDiabloLabel.SetText("OpenDiablo2 is neither developed by, nor endorsed by Blizzard or its parent company Activision") - v.openDiabloLabel.Color = color.RGBA{R: 255, G: 255, B: 140, A: 255} - v.openDiabloLabel.SetPosition(400, 580) - loading.Progress(0.5) + v.openDiabloLabel.Color = rgbaColor(lightYellow) + v.openDiabloLabel.SetPosition(od2LabelX, od2LabelY) + loading.Progress(fiftyPercent) v.tcpIPOptionsLabel = d2ui.CreateLabel(d2resource.Font42, d2resource.PaletteUnits) - v.tcpIPOptionsLabel.SetPosition(400, 23) + v.tcpIPOptionsLabel.SetPosition(tcpOptionsX, tcpOptionsY) v.tcpIPOptionsLabel.Alignment = d2gui.HorizontalAlignCenter v.tcpIPOptionsLabel.SetText("TCP/IP Options") @@ -174,8 +217,8 @@ func (v *MainMenu) createLabels(loading d2screen.LoadingState) { v.tcpJoinGameLabel.Alignment = d2gui.HorizontalAlignCenter v.tcpJoinGameLabel.SetText("Enter Host IP Address\nto Join Game") - v.tcpJoinGameLabel.Color = color.RGBA{R: 216, G: 196, B: 128, A: 255} - v.tcpJoinGameLabel.SetPosition(400, 190) + v.tcpJoinGameLabel.Color = rgbaColor(gold) + v.tcpJoinGameLabel.SetPosition(joinGameX, joinGameY) } func (v *MainMenu) createLogos(loading d2screen.LoadingState) { @@ -183,97 +226,97 @@ func (v *MainMenu) createLogos(loading d2screen.LoadingState) { v.diabloLogoLeft, _ = d2ui.LoadSprite(animation) v.diabloLogoLeft.SetEffect(d2enum.DrawEffectModulate) v.diabloLogoLeft.PlayForward() - v.diabloLogoLeft.SetPosition(400, 120) - loading.Progress(0.6) + v.diabloLogoLeft.SetPosition(diabloLogoX, diabloLogoY) + loading.Progress(sixtyPercent) animation, _ = d2asset.LoadAnimation(d2resource.Diablo2LogoFireRight, d2resource.PaletteUnits) v.diabloLogoRight, _ = d2ui.LoadSprite(animation) v.diabloLogoRight.SetEffect(d2enum.DrawEffectModulate) v.diabloLogoRight.PlayForward() - v.diabloLogoRight.SetPosition(400, 120) + v.diabloLogoRight.SetPosition(diabloLogoX, diabloLogoY) animation, _ = d2asset.LoadAnimation(d2resource.Diablo2LogoBlackLeft, d2resource.PaletteUnits) v.diabloLogoLeftBack, _ = d2ui.LoadSprite(animation) - v.diabloLogoLeftBack.SetPosition(400, 120) + v.diabloLogoLeftBack.SetPosition(diabloLogoX, diabloLogoY) animation, _ = d2asset.LoadAnimation(d2resource.Diablo2LogoBlackRight, d2resource.PaletteUnits) v.diabloLogoRightBack, _ = d2ui.LoadSprite(animation) - v.diabloLogoRightBack.SetPosition(400, 120) + v.diabloLogoRightBack.SetPosition(diabloLogoX, diabloLogoY) } func (v *MainMenu) createButtons(loading d2screen.LoadingState) { v.exitDiabloButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "EXIT DIABLO II") - v.exitDiabloButton.SetPosition(264, 535) + v.exitDiabloButton.SetPosition(exitDiabloBtnX, exitDiabloBtnY) v.exitDiabloButton.OnActivated(func() { v.onExitButtonClicked() }) d2ui.AddWidget(&v.exitDiabloButton) v.creditsButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeShort, "CREDITS") - v.creditsButton.SetPosition(264, 505) + v.creditsButton.SetPosition(creditBtnX, creditBtnY) v.creditsButton.OnActivated(func() { v.onCreditsButtonClicked() }) d2ui.AddWidget(&v.creditsButton) v.cinematicsButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeShort, "CINEMATICS") - v.cinematicsButton.SetPosition(401, 505) + v.cinematicsButton.SetPosition(cineBtnX, cineBtnY) d2ui.AddWidget(&v.cinematicsButton) - loading.Progress(0.7) + loading.Progress(seventyPercent) v.singlePlayerButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "SINGLE PLAYER") - v.singlePlayerButton.SetPosition(264, 290) + v.singlePlayerButton.SetPosition(singlePlayerBtnX, singlePlayerBtnY) v.singlePlayerButton.OnActivated(func() { v.onSinglePlayerClicked() }) d2ui.AddWidget(&v.singlePlayerButton) v.githubButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "PROJECT WEBSITE") - v.githubButton.SetPosition(264, 400) + v.githubButton.SetPosition(githubBtnX, githubBtnY) v.githubButton.OnActivated(func() { v.onGithubButtonClicked() }) d2ui.AddWidget(&v.githubButton) v.mapTestButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "MAP ENGINE TEST") - v.mapTestButton.SetPosition(264, 440) + v.mapTestButton.SetPosition(mapTestBtnX, mapTestBtnY) v.mapTestButton.OnActivated(func() { v.onMapTestClicked() }) d2ui.AddWidget(&v.mapTestButton) v.btnTCPIPCancel = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, d2common.TranslateString("cancel")) - v.btnTCPIPCancel.SetPosition(33, 543) + v.btnTCPIPCancel.SetPosition(tcpBtnX, tcpBtnY) v.btnTCPIPCancel.OnActivated(func() { v.onTCPIPCancelClicked() }) d2ui.AddWidget(&v.btnTCPIPCancel) v.btnServerIPCancel = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeOkCancel, "CANCEL") - v.btnServerIPCancel.SetPosition(285, 305) + v.btnServerIPCancel.SetPosition(srvCancelBtnX, srvCancelBtnY) v.btnServerIPCancel.OnActivated(func() { v.onBtnTCPIPCancelClicked() }) d2ui.AddWidget(&v.btnServerIPCancel) v.btnServerIPOk = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeOkCancel, "OK") - v.btnServerIPOk.SetPosition(420, 305) + v.btnServerIPOk.SetPosition(srvOkBtnX, srvOkBtnY) v.btnServerIPOk.OnActivated(func() { v.onBtnTCPIPOkClicked() }) d2ui.AddWidget(&v.btnServerIPOk) v.createMultiplayerMenuButtons() - loading.Progress(0.8) + loading.Progress(eightyPercent) } func (v *MainMenu) createMultiplayerMenuButtons() { v.multiplayerButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "MULTIPLAYER") - v.multiplayerButton.SetPosition(264, 330) + v.multiplayerButton.SetPosition(multiplayerBtnX, multiplayerBtnY) v.multiplayerButton.OnActivated(func() { v.onMultiplayerClicked() }) d2ui.AddWidget(&v.multiplayerButton) v.networkTCPIPButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "TCP/IP GAME") - v.networkTCPIPButton.SetPosition(264, 280) + v.networkTCPIPButton.SetPosition(tcpNetBtnX, tcpNetBtnY) v.networkTCPIPButton.OnActivated(func() { v.onNetworkTCPIPClicked() }) d2ui.AddWidget(&v.networkTCPIPButton) v.networkCancelButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, d2common.TranslateString("cancel")) - v.networkCancelButton.SetPosition(264, 540) + v.networkCancelButton.SetPosition(networkCancelBtnX, networkCancelBtnY) v.networkCancelButton.OnActivated(func() { v.onNetworkCancelClicked() }) d2ui.AddWidget(&v.networkCancelButton) v.btnTCPIPHostGame = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "HOST GAME") - v.btnTCPIPHostGame.SetPosition(264, 280) + v.btnTCPIPHostGame.SetPosition(tcpHostBtnX, tcpHostBtnY) v.btnTCPIPHostGame.OnActivated(func() { v.onTCPIPHostGameClicked() }) d2ui.AddWidget(&v.btnTCPIPHostGame) v.btnTCPIPJoinGame = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeWide, "JOIN GAME") - v.btnTCPIPJoinGame.SetPosition(264, 320) + v.btnTCPIPJoinGame.SetPosition(tcpJoinBtnX, tcpJoinBtnY) v.btnTCPIPJoinGame.OnActivated(func() { v.onTCPIPJoinGameClicked() }) d2ui.AddWidget(&v.btnTCPIPJoinGame) } @@ -436,7 +479,8 @@ func (v *MainMenu) OnMouseButtonDown(event d2interface.MouseEvent) bool { return false } -func (v *MainMenu) SetScreenMode(screenMode MainMenuScreenMode) { +// SetScreenMode sets the screen mode (which sub-menu the screen is on) +func (v *MainMenu) SetScreenMode(screenMode mainMenuScreenMode) { v.screenMode = screenMode isMainMenu := screenMode == ScreenModeMainMenu isMultiplayer := screenMode == ScreenModeMultiplayer diff --git a/d2game/d2gamescreen/map_engine_testing.go b/d2game/d2gamescreen/map_engine_testing.go index 1c55799e..35823abd 100644 --- a/d2game/d2gamescreen/map_engine_testing.go +++ b/d2game/d2gamescreen/map_engine_testing.go @@ -103,7 +103,12 @@ type MapEngineTest struct { } // CreateMapEngineTest creates the Map Engine Test screen and returns a pointer to it -func CreateMapEngineTest(currentRegion, levelPreset int, term d2interface.Terminal, renderer d2interface.Renderer, inputManager d2interface.InputManager) *MapEngineTest { +func CreateMapEngineTest(currentRegion, + levelPreset int, + term d2interface.Terminal, + renderer d2interface.Renderer, + inputManager d2interface.InputManager, +) *MapEngineTest { result := &MapEngineTest{ currentRegion: currentRegion, levelPreset: levelPreset, @@ -171,15 +176,15 @@ func (met *MapEngineTest) OnLoad(loading d2screen.LoadingState) { fmt.Printf("could not add MapEngineTest as event handler") } - loading.Progress(0.2) + loading.Progress(twentyPercent) met.mapEngine = d2mapengine.CreateMapEngine() - loading.Progress(0.5) + loading.Progress(fiftyPercent) met.mapRenderer = d2maprenderer.CreateMapRenderer(met.renderer, met.mapEngine, met.terminal) - loading.Progress(0.7) + loading.Progress(seventyPercent) met.loadRegionByIndex(met.currentRegion, met.levelPreset, met.fileIndex) } diff --git a/d2game/d2gamescreen/navigate.go b/d2game/d2gamescreen/navigate.go index 0507f2d6..422c1376 100644 --- a/d2game/d2gamescreen/navigate.go +++ b/d2game/d2gamescreen/navigate.go @@ -4,6 +4,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" ) +// Navigator is used for transitioning between game screens type Navigator interface { ToMainMenu() ToSelectHero(connType d2clientconnectiontype.ClientConnectionType, connHost string) diff --git a/d2game/d2gamescreen/select_hero_class.go b/d2game/d2gamescreen/select_hero_class.go index c63976a5..c0f3cd07 100644 --- a/d2game/d2gamescreen/select_hero_class.go +++ b/d2game/d2gamescreen/select_hero_class.go @@ -3,7 +3,6 @@ package d2gamescreen import ( "fmt" "image" - "image/color" "github.com/OpenDiablo2/OpenDiablo2/d2common" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict" @@ -37,98 +36,206 @@ type heroRenderConfig struct { backWalkPlayLengthMs int } -func getHeroRenderConfiguration() map[d2enum.Hero]*heroRenderConfig { - return map[d2enum.Hero]*heroRenderConfig{ - d2enum.HeroBarbarian: createHeroRenderConfig( - d2resource.CharacterSelectBarbarianUnselected, d2resource.CharacterSelectBarbarianUnselectedH, - d2resource.CharacterSelectBarbarianForwardWalk, d2resource.CharacterSelectBarbarianForwardWalkOverlay, - false, d2resource.CharacterSelectBarbarianSelected, "", - d2resource.CharacterSelectBarbarianBackWalk, "", - image.Rectangle{Min: image.Point{X: 364, Y: 201}, Max: image.Point{X: 90, Y: 170}}, - d2resource.SFXBarbarianSelect, d2resource.SFXBarbarianDeselect, image.Point{X: 400, Y: 330}, - 0, 2500, 1000, - ), - d2enum.HeroSorceress: createHeroRenderConfig( - d2resource.CharacterSelectSorceressUnselected, d2resource.CharacterSelectSorceressUnselectedH, - d2resource.CharacterSelectSorceressForwardWalk, d2resource.CharacterSelectSorceressForwardWalkOverlay, - true, d2resource.CharacterSelectSorceressSelected, d2resource.CharacterSelectSorceressSelectedOverlay, - d2resource.CharacterSelectSorceressBackWalk, d2resource.CharacterSelectSorceressBackWalkOverlay, - image.Rectangle{Min: image.Point{X: 580, Y: 240}, Max: image.Point{X: 65, Y: 160}}, - d2resource.SFXSorceressSelect, d2resource.SFXSorceressDeselect, image.Point{X: 626, Y: 352}, - 2500, 2300, 1200, - ), - d2enum.HeroNecromancer: createHeroRenderConfig( - d2resource.CharacterSelectNecromancerUnselected, d2resource.CharacterSelectNecromancerUnselectedH, - d2resource.CharacterSelectNecromancerForwardWalk, d2resource.CharacterSelectNecromancerForwardWalkOverlay, - true, d2resource.CharacterSelectNecromancerSelected, d2resource.CharacterSelectNecromancerSelectedOverlay, - d2resource.CharacterSelectNecromancerBackWalk, d2resource.CharacterSelectNecromancerBackWalkOverlay, - image.Rectangle{Min: image.Point{X: 265, Y: 220}, Max: image.Point{X: 55, Y: 175}}, - d2resource.SFXNecromancerSelect, d2resource.SFXNecromancerDeselect, image.Point{X: 300, Y: 335}, - 1200, 2000, 1500, - ), - d2enum.HeroPaladin: createHeroRenderConfig( - d2resource.CharacterSelectPaladinUnselected, d2resource.CharacterSelectPaladinUnselectedH, - d2resource.CharacterSelectPaladinForwardWalk, d2resource.CharacterSelectPaladinForwardWalkOverlay, - false, d2resource.CharacterSelectPaladinSelected, "", - d2resource.CharacterSelectPaladinBackWalk, "", - image.Rectangle{Min: image.Point{X: 490, Y: 210}, Max: image.Point{X: 65, Y: 180}}, - d2resource.SFXPaladinSelect, d2resource.SFXPaladinDeselect, image.Point{X: 521, Y: 338}, - 2500, 3400, 1300, - ), - d2enum.HeroAmazon: createHeroRenderConfig( - d2resource.CharacterSelectAmazonUnselected, d2resource.CharacterSelectAmazonUnselectedH, - d2resource.CharacterSelectAmazonForwardWalk, "", - false, d2resource.CharacterSelectAmazonSelected, "", - d2resource.CharacterSelectAmazonBackWalk, "", - image.Rectangle{Min: image.Point{X: 70, Y: 220}, Max: image.Point{X: 55, Y: 200}}, - d2resource.SFXAmazonSelect, d2resource.SFXAmazonDeselect, image.Point{X: 100, Y: 339}, - 2500, 2200, 1500, - ), - d2enum.HeroAssassin: createHeroRenderConfig( - d2resource.CharacterSelectAssassinUnselected, d2resource.CharacterSelectAssassinUnselectedH, - d2resource.CharacterSelectAssassinForwardWalk, "", - false, d2resource.CharacterSelectAssassinSelected, "", - d2resource.CharacterSelectAssassinBackWalk, "", - image.Rectangle{Min: image.Point{X: 175, Y: 235}, Max: image.Point{X: 50, Y: 180}}, - d2resource.SFXAssassinSelect, d2resource.SFXAssassinDeselect, image.Point{X: 231, Y: 365}, - 2500, 3800, 1500, - ), - d2enum.HeroDruid: createHeroRenderConfig( - d2resource.CharacterSelectDruidUnselected, d2resource.CharacterSelectDruidUnselectedH, - d2resource.CharacterSelectDruidForwardWalk, "", - false, d2resource.CharacterSelectDruidSelected, "", - d2resource.CharacterSelectDruidBackWalk, "", - image.Rectangle{Min: image.Point{X: 680, Y: 220}, Max: image.Point{X: 70, Y: 195}}, - d2resource.SFXDruidSelect, d2resource.SFXDruidDeselect, image.Point{X: 720, Y: 370}, - 1500, 4800, 1500, - ), - } +func point(x, y int) image.Point { + return image.Point{X: x, Y: y} } -func createHeroRenderConfig(idleAnimationPath, idleSelectedAnimationPath, forwardWalkAnimationPath, - forwardWalkOverlayAnimationPath string, forwardWalkOverlayBlend bool, selectedAnimationPath, - selectedOverlayAnimationPath, backWalkAnimationPath, backWalkOverlayAnimationPath string, - selectionBounds image.Rectangle, selectSfx, deselectSfx string, position image.Point, - idlePlayLengthMs, forwardWalkPlayLengthMs, backWalkPlayLengthMs int, -) *heroRenderConfig { - return &heroRenderConfig{ - idleAnimationPath: idleAnimationPath, - idleSelectedAnimationPath: idleSelectedAnimationPath, - forwardWalkAnimationPath: forwardWalkAnimationPath, - forwardWalkOverlayAnimationPath: forwardWalkOverlayAnimationPath, - forwardWalkOverlayBlend: forwardWalkOverlayBlend, - selectedAnimationPath: selectedAnimationPath, - selectedOverlayAnimationPath: selectedOverlayAnimationPath, - backWalkAnimationPath: backWalkAnimationPath, - backWalkOverlayAnimationPath: backWalkOverlayAnimationPath, - selectionBounds: selectionBounds, - selectSfx: selectSfx, - deselectSfx: deselectSfx, - position: position, - idlePlayLengthMs: idlePlayLengthMs, - forwardWalkPlayLengthMs: forwardWalkPlayLengthMs, - backWalkPlayLengthMs: backWalkPlayLengthMs, +func rect(x1, y1, x2, y2 int) image.Rectangle { + return image.Rectangle{Min: point(x1, y1), Max: point(x2, y2)} +} + +// animation position, selection box bound, animation play lengths in ms +const ( + barbPosX, barbPosY = 400, 330 + barbRectMinX, barbRectMinY, barbRectMaxX, barbRectMaxY = 364, 201, 90, 170 + barbIdleLength, barbForwardLength, barbBackLength = 0, 2500, 1000 + + sorcPosX, sorcPosY = 626, 352 + sorcRectMinX, sorcRectMinY, sorcRectMaxX, sorcRectMaxY = 580, 240, 65, 160 + sorcIdleLength, sorcForwardLength, sorcBackLength = 2500, 2300, 1200 + + necPosX, necPosY = 300, 335 + necRectMinX, necRectMinY, necRectMaxX, necRectMaxY = 265, 220, 55, 175 + necIdleLength, necForwardLength, necBackLength = 1200, 2000, 1500 + + palPosX, palPosY = 521, 338 + palRectMinX, palRectMinY, palRectMaxX, palRectMaxY = 490, 210, 65, 180 + palIdleLength, palForwardLength, palBackLength = 2500, 3400, 1300 + + amaPosX, amaPosY = 100, 339 + amaRectMinX, amaRectMinY, amaRectMaxX, amaRectMaxY = 70, 220, 55, 200 + amaIdleLength, amaForwardLength, amaBackLength = 2500, 2200, 1500 + + assPosX, assPosY = 231, 365 + assRectMinX, assRectMinY, assRectMaxX, assRectMaxY = 175, 235, 50, 180 + assIdleLength, assForwardLength, assBackLength = 2500, 3800, 1500 + + druPosX, druPosY = 720, 370 + druRectMinX, druRectMinY, druRectMaxX, druRectMaxY = 680, 220, 70, 195 + druIdleLength, druForwardLength, druBackLength = 1500, 4800, 1500 + + campfirePosX, campfirePosY = 380, 335 +) + +// label and button positions +const ( + headingX, headingY = 400, 17 + heroClassLabelX, heroClassLabelY = 400, 65 + heroDescLine1X, heroDescLine1Y = 400, 100 + heroDescLine2X, heroDescLine2Y = 400, 115 + heroDescLine3X, heroDescLine3Y = 400, 130 + heroNameLabelX, heroNameLabelY = 321, 475 + expansionLabelX, expansionLabelY = 339, 526 + hardcoreLabelX, hardcoreLabelY = 339, 548 + + selHeroExitBtnX, selHeroExitBtnY = 33, 537 + selHeroOkBtnX, selHeroOkBtnY = 630, 537 + + heroNameTextBoxX, heoNameTextBoxY = 318, 493 + expandsionCheckboxX, expansionCheckboxY = 318, 526 + hardcoreCheckoxX, hardcoreCheckboxY = 318, 548 +) + +const heroDescCharWidth = 37 + +//nolint:funlen // this func returns a map of structs and the structs are big, deal with it +func getHeroRenderConfiguration() map[d2enum.Hero]*heroRenderConfig { + configs := make(map[d2enum.Hero]*heroRenderConfig) + + configs[d2enum.HeroBarbarian] = &heroRenderConfig{ + d2resource.CharacterSelectBarbarianUnselected, + d2resource.CharacterSelectBarbarianUnselectedH, + d2resource.CharacterSelectBarbarianForwardWalk, + d2resource.CharacterSelectBarbarianForwardWalkOverlay, + false, + d2resource.CharacterSelectBarbarianSelected, + "", + d2resource.CharacterSelectBarbarianBackWalk, + "", + rect(barbRectMinX, barbRectMinY, barbRectMaxX, barbRectMaxY), + d2resource.SFXBarbarianSelect, + d2resource.SFXBarbarianDeselect, + point(barbPosX, barbPosY), + barbIdleLength, + barbForwardLength, + barbBackLength, } + + configs[d2enum.HeroSorceress] = &heroRenderConfig{ + d2resource.CharacterSelectSorceressUnselected, + d2resource.CharacterSelectSorceressUnselectedH, + d2resource.CharacterSelectSorceressForwardWalk, + d2resource.CharacterSelectSorceressForwardWalkOverlay, + true, + d2resource.CharacterSelectSorceressSelected, + d2resource.CharacterSelectSorceressSelectedOverlay, + d2resource.CharacterSelectSorceressBackWalk, + d2resource.CharacterSelectSorceressBackWalkOverlay, + rect(sorcRectMinX, sorcRectMinY, sorcRectMaxX, sorcRectMaxY), + d2resource.SFXSorceressSelect, + d2resource.SFXSorceressDeselect, + point(sorcPosX, sorcPosY), + sorcIdleLength, + sorcForwardLength, + sorcBackLength, + } + + configs[d2enum.HeroNecromancer] = &heroRenderConfig{ + d2resource.CharacterSelectNecromancerUnselected, + d2resource.CharacterSelectNecromancerUnselectedH, + d2resource.CharacterSelectNecromancerForwardWalk, + d2resource.CharacterSelectNecromancerForwardWalkOverlay, + true, + d2resource.CharacterSelectNecromancerSelected, + d2resource.CharacterSelectNecromancerSelectedOverlay, + d2resource.CharacterSelectNecromancerBackWalk, + d2resource.CharacterSelectNecromancerBackWalkOverlay, + rect(necRectMinX, necRectMinY, necRectMaxX, necRectMaxY), + d2resource.SFXNecromancerSelect, + d2resource.SFXNecromancerDeselect, + point(necPosX, necPosY), + necIdleLength, + necForwardLength, + necBackLength, + } + + configs[d2enum.HeroPaladin] = &heroRenderConfig{ + d2resource.CharacterSelectPaladinUnselected, + d2resource.CharacterSelectPaladinUnselectedH, + d2resource.CharacterSelectPaladinForwardWalk, + d2resource.CharacterSelectPaladinForwardWalkOverlay, + false, + d2resource.CharacterSelectPaladinSelected, + "", + d2resource.CharacterSelectPaladinBackWalk, + "", + rect(palRectMinX, palRectMinY, palRectMaxX, palRectMaxY), + d2resource.SFXPaladinSelect, + d2resource.SFXPaladinDeselect, + point(palPosX, palPosY), + palIdleLength, + palForwardLength, + palBackLength, + } + + configs[d2enum.HeroAmazon] = &heroRenderConfig{ + d2resource.CharacterSelectAmazonUnselected, + d2resource.CharacterSelectAmazonUnselectedH, + d2resource.CharacterSelectAmazonForwardWalk, + "", + false, + d2resource.CharacterSelectAmazonSelected, + "", + d2resource.CharacterSelectAmazonBackWalk, + "", + rect(amaRectMinX, amaRectMinY, amaRectMaxX, amaRectMaxY), + d2resource.SFXAmazonSelect, + d2resource.SFXAmazonDeselect, + point(amaPosX, amaPosY), + amaIdleLength, + amaForwardLength, + amaBackLength, + } + + configs[d2enum.HeroAssassin] = &heroRenderConfig{ + d2resource.CharacterSelectAssassinUnselected, + d2resource.CharacterSelectAssassinUnselectedH, + d2resource.CharacterSelectAssassinForwardWalk, + "", + false, + d2resource.CharacterSelectAssassinSelected, + "", + d2resource.CharacterSelectAssassinBackWalk, + "", + rect(assRectMinX, assRectMinY, assRectMaxX, assRectMaxY), + d2resource.SFXAssassinSelect, + d2resource.SFXAssassinDeselect, + point(assPosX, assPosY), + assIdleLength, + assForwardLength, + assBackLength, + } + + configs[d2enum.HeroDruid] = &heroRenderConfig{ + d2resource.CharacterSelectDruidUnselected, + d2resource.CharacterSelectDruidUnselectedH, + d2resource.CharacterSelectDruidForwardWalk, + "", + false, + d2resource.CharacterSelectDruidSelected, + "", + d2resource.CharacterSelectDruidBackWalk, + "", + rect(druRectMinX, druRectMinY, druRectMaxX, druRectMaxY), + d2resource.SFXDruidSelect, + d2resource.SFXDruidDeselect, + point(druPosX, druPosY), + druIdleLength, + druForwardLength, + druBackLength, + } + + return configs } // HeroRenderInfo stores the rendering information of a hero for the Select Hero Class screen @@ -186,7 +293,13 @@ type SelectHeroClass struct { } // CreateSelectHeroClass creates an instance of a SelectHeroClass -func CreateSelectHeroClass(navigator Navigator, renderer d2interface.Renderer, audioProvider d2interface.AudioProvider, connectionType d2clientconnectiontype.ClientConnectionType, connectionHost string) *SelectHeroClass { +func CreateSelectHeroClass( + navigator Navigator, + renderer d2interface.Renderer, + audioProvider d2interface.AudioProvider, + connectionType d2clientconnectiontype.ClientConnectionType, + connectionHost string, +) *SelectHeroClass { result := &SelectHeroClass{ heroRenderInfo: make(map[d2enum.Hero]*HeroRenderInfo), selectedHero: d2enum.HeroNone, @@ -203,20 +316,32 @@ func CreateSelectHeroClass(navigator Navigator, renderer d2interface.Renderer, a // OnLoad loads the resources for the Select Hero Class screen func (v *SelectHeroClass) OnLoad(loading d2screen.LoadingState) { v.audioProvider.PlayBGM(d2resource.BGMTitle) - loading.Progress(0.1) + loading.Progress(tenPercent) - v.bgImage = loadSprite(d2resource.CharacterSelectBackground, image.Point{X: 0, Y: 0}, 0, true, false) + v.bgImage = loadSprite( + d2resource.CharacterSelectBackground, + point(0, 0), + 0, + true, + false, + ) - loading.Progress(0.3) + loading.Progress(thirtyPercent) v.createLabels() - loading.Progress(0.4) + loading.Progress(fourtyPercent) v.createButtons() - v.campfire = loadSprite(d2resource.CharacterSelectCampfire, image.Point{X: 380, Y: 335}, 0, true, true) + v.campfire = loadSprite( + d2resource.CharacterSelectCampfire, + point(campfirePosX, campfirePosY), + 0, + true, + true, + ) v.createCheckboxes(v.renderer) - loading.Progress(0.5) + loading.Progress(fiftyPercent) for hero, config := range getHeroRenderConfiguration() { position := config.position @@ -247,53 +372,56 @@ func (v *SelectHeroClass) OnLoad(loading d2screen.LoadingState) { func (v *SelectHeroClass) createLabels() { v.headingLabel = d2ui.CreateLabel(d2resource.Font30, d2resource.PaletteUnits) fontWidth, _ := v.headingLabel.GetSize() - v.headingLabel.SetPosition(400-fontWidth/2, 17) + half := 2 + halfFontWidth := fontWidth / half + + v.headingLabel.SetPosition(headingX-halfFontWidth, headingY) v.headingLabel.SetText("Select Hero Class") v.headingLabel.Alignment = d2gui.HorizontalAlignCenter v.heroClassLabel = d2ui.CreateLabel(d2resource.Font30, d2resource.PaletteUnits) v.heroClassLabel.Alignment = d2gui.HorizontalAlignCenter - v.heroClassLabel.SetPosition(400, 65) + v.heroClassLabel.SetPosition(heroClassLabelX, heroClassLabelY) v.heroDesc1Label = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) v.heroDesc1Label.Alignment = d2gui.HorizontalAlignCenter - v.heroDesc1Label.SetPosition(400, 100) + v.heroDesc1Label.SetPosition(heroDescLine1X, heroDescLine1Y) v.heroDesc2Label = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) v.heroDesc2Label.Alignment = d2gui.HorizontalAlignCenter - v.heroDesc2Label.SetPosition(400, 115) + v.heroDesc2Label.SetPosition(heroDescLine2X, heroDescLine2Y) v.heroDesc3Label = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) v.heroDesc3Label.Alignment = d2gui.HorizontalAlignCenter - v.heroDesc3Label.SetPosition(400, 130) + v.heroDesc3Label.SetPosition(heroDescLine3X, heroDescLine3Y) v.heroNameLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) v.heroNameLabel.Alignment = d2gui.HorizontalAlignLeft - v.heroNameLabel.Color = color.RGBA{R: 216, G: 196, B: 128, A: 255} + v.heroNameLabel.Color = rgbaColor(gold) v.heroNameLabel.SetText("Character Name") - v.heroNameLabel.SetPosition(321, 475) + v.heroNameLabel.SetPosition(heroNameLabelX, heroNameLabelY) v.expansionCharLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) v.expansionCharLabel.Alignment = d2gui.HorizontalAlignLeft - v.expansionCharLabel.Color = color.RGBA{R: 216, G: 196, B: 128, A: 255} + v.expansionCharLabel.Color = rgbaColor(gold) v.expansionCharLabel.SetText("EXPANSION CHARACTER") - v.expansionCharLabel.SetPosition(339, 526) + v.expansionCharLabel.SetPosition(expansionLabelX, expansionLabelY) v.hardcoreCharLabel = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits) v.hardcoreCharLabel.Alignment = d2gui.HorizontalAlignLeft - v.hardcoreCharLabel.Color = color.RGBA{R: 216, G: 196, B: 128, A: 255} + v.hardcoreCharLabel.Color = rgbaColor(gold) v.hardcoreCharLabel.SetText("Hardcore") - v.hardcoreCharLabel.SetPosition(339, 548) + v.hardcoreCharLabel.SetPosition(hardcoreLabelX, hardcoreLabelY) } func (v *SelectHeroClass) createButtons() { v.exitButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, "EXIT") - v.exitButton.SetPosition(33, 537) + v.exitButton.SetPosition(selHeroExitBtnX, selHeroExitBtnY) v.exitButton.OnActivated(func() { v.onExitButtonClicked() }) d2ui.AddWidget(&v.exitButton) v.okButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, "OK") - v.okButton.SetPosition(630, 537) + v.okButton.SetPosition(selHeroOkBtnX, selHeroOkBtnY) v.okButton.OnActivated(func() { v.onOkButtonClicked() }) v.okButton.SetVisible(false) v.okButton.SetEnabled(false) @@ -302,17 +430,17 @@ func (v *SelectHeroClass) createButtons() { func (v *SelectHeroClass) createCheckboxes(renderer d2interface.Renderer) { v.heroNameTextbox = d2ui.CreateTextbox(renderer) - v.heroNameTextbox.SetPosition(318, 493) + v.heroNameTextbox.SetPosition(heroNameTextBoxX, heoNameTextBoxY) v.heroNameTextbox.SetVisible(false) d2ui.AddWidget(&v.heroNameTextbox) v.expansionCheckbox = d2ui.CreateCheckbox(v.renderer, true) - v.expansionCheckbox.SetPosition(318, 526) + v.expansionCheckbox.SetPosition(expandsionCheckboxX, expansionCheckboxY) v.expansionCheckbox.SetVisible(false) d2ui.AddWidget(&v.expansionCheckbox) v.hardcoreCheckbox = d2ui.CreateCheckbox(renderer, false) - v.hardcoreCheckbox.SetPosition(318, 548) + v.hardcoreCheckbox.SetPosition(hardcoreCheckoxX, hardcoreCheckboxY) v.hardcoreCheckbox.SetVisible(false) d2ui.AddWidget(&v.hardcoreCheckbox) } @@ -543,11 +671,18 @@ func (v *SelectHeroClass) updateHeroText() { } } +const ( + oneLine = 1 + twoLine = 2 +) + func (v *SelectHeroClass) setDescLabels(descKey string) { heroDesc := d2common.TranslateString(descKey) - parts := d2common.SplitIntoLinesWithMaxWidth(heroDesc, 37) + parts := d2common.SplitIntoLinesWithMaxWidth(heroDesc, heroDescCharWidth) - if len(parts) > 1 { + numLines := len(parts) + + if numLines > oneLine { v.heroDesc1Label.SetText(parts[0]) v.heroDesc2Label.SetText(parts[1]) } else { @@ -555,7 +690,7 @@ func (v *SelectHeroClass) setDescLabels(descKey string) { v.heroDesc2Label.SetText("") } - if len(parts) > 2 { + if numLines > twoLine { v.heroDesc3Label.SetText(parts[2]) } else { v.heroDesc3Label.SetText("")