1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-20 06:05:23 +00:00
OpenDiablo2/d2game/d2gamescreen/character_select.go
David Carrell 3bdbd5c358
rely on App to know how to navigate between screens using a callback interface with explicit methods (#592)
remove unused struct vars that were only stored in order to pass to the next screens

consolidate create game code to single method

export ScreenMode consts and SetScreenMode method to allow app to create the correct screens

Co-authored-by: carrelda <carrelda@git>
2020-07-14 13:11:23 -04:00

366 lines
11 KiB
Go

package d2gamescreen
import (
"fmt"
"image/color"
"math"
"os"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
)
// CharacterSelect represents the character select screen
type CharacterSelect struct {
background *d2ui.Sprite
newCharButton d2ui.Button
convertCharButton d2ui.Button
deleteCharButton d2ui.Button
exitButton d2ui.Button
okButton d2ui.Button
deleteCharCancelButton d2ui.Button
deleteCharOkButton d2ui.Button
selectionBox *d2ui.Sprite
okCancelBox *d2ui.Sprite
d2HeroTitle d2ui.Label
deleteCharConfirmLabel d2ui.Label
charScrollbar d2ui.Scrollbar
characterNameLabel [8]d2ui.Label
characterStatsLabel [8]d2ui.Label
characterExpLabel [8]d2ui.Label
characterImage [8]*d2mapentity.Player
gameStates []*d2player.PlayerState
selectedCharacter int
showDeleteConfirmation bool
connectionType d2clientconnectiontype.ClientConnectionType
connectionHost string
inputManager d2interface.InputManager
audioProvider d2interface.AudioProvider
renderer d2interface.Renderer
navigator Navigator
}
// CreateCharacterSelect creates the character select screen and returns a pointer to it
func CreateCharacterSelect(
navigator Navigator,
renderer d2interface.Renderer,
inputManager d2interface.InputManager,
audioProvider d2interface.AudioProvider,
connectionType d2clientconnectiontype.ClientConnectionType,
connectionHost string,
) *CharacterSelect {
return &CharacterSelect{
selectedCharacter: -1,
renderer: renderer,
connectionType: connectionType,
connectionHost: connectionHost,
inputManager: inputManager,
audioProvider: audioProvider,
navigator: navigator,
}
}
// OnLoad loads the resources for the Character Select screen
func (v *CharacterSelect) OnLoad(loading d2screen.LoadingState) {
v.audioProvider.PlayBGM(d2resource.BGMTitle)
if err := v.inputManager.BindHandler(v); err != nil {
fmt.Println("failed to add Character Select screen as event handler")
}
loading.Progress(0.1)
animation, _ := d2asset.LoadAnimation(d2resource.CharacterSelectionBackground, d2resource.PaletteSky)
v.background, _ = d2ui.LoadSprite(animation)
v.background.SetPosition(0, 0)
v.createButtons(loading)
v.d2HeroTitle = d2ui.CreateLabel(d2resource.Font42, d2resource.PaletteUnits)
v.d2HeroTitle.SetPosition(320, 23)
v.d2HeroTitle.Alignment = d2gui.HorizontalAlignCenter
loading.Progress(0.3)
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)
animation, _ = d2asset.LoadAnimation(d2resource.CharacterSelectionSelectBox, d2resource.PaletteSky)
v.selectionBox, _ = d2ui.LoadSprite(animation)
v.selectionBox.SetPosition(37, 86)
animation, _ = d2asset.LoadAnimation(d2resource.PopUpOkCancel, d2resource.PaletteFechar)
v.okCancelBox, _ = d2ui.LoadSprite(animation)
v.okCancelBox.SetPosition(270, 175)
v.charScrollbar = d2ui.CreateScrollbar(586, 87, 369)
v.charScrollbar.OnActivated(func() { v.onScrollUpdate() })
d2ui.AddWidget(&v.charScrollbar)
loading.Progress(0.5)
for i := 0; i < 8; i++ {
xOffset := 115
if i&1 > 0 {
xOffset = 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.characterStatsLabel[i] = d2ui.CreateLabel(d2resource.Font16, d2resource.PaletteUnits)
v.characterStatsLabel[i].SetPosition(xOffset, 115+((i/2)*95))
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.refreshGameStates()
}
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.OnActivated(func() { v.onNewCharButtonClicked() })
d2ui.AddWidget(&v.newCharButton)
v.convertCharButton = d2ui.CreateButton(
v.renderer,
d2ui.ButtonTypeTall,
"CONVERT TO\nEXPANSION",
)
v.convertCharButton.SetPosition(233, 468)
v.convertCharButton.SetEnabled(false)
d2ui.AddWidget(&v.convertCharButton)
v.deleteCharButton = d2ui.CreateButton(
v.renderer,
d2ui.ButtonTypeTall,
"DELETE\nCHARACTER",
)
v.deleteCharButton.OnActivated(func() { v.onDeleteCharButtonClicked() })
v.deleteCharButton.SetPosition(433, 468)
d2ui.AddWidget(&v.deleteCharButton)
v.exitButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeMedium, "EXIT")
v.exitButton.SetPosition(33, 537)
v.exitButton.OnActivated(func() { v.onExitButtonClicked() })
d2ui.AddWidget(&v.exitButton)
loading.Progress(0.2)
v.deleteCharCancelButton = d2ui.CreateButton(v.renderer, d2ui.ButtonTypeOkCancel, "NO")
v.deleteCharCancelButton.SetPosition(282, 308)
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.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.OnActivated(func() { v.onOkButtonClicked() })
d2ui.AddWidget(&v.okButton)
}
func (v *CharacterSelect) onScrollUpdate() {
v.moveSelectionBox()
v.updateCharacterBoxes()
}
func (v *CharacterSelect) updateCharacterBoxes() {
expText := "EXPANSION CHARACTER"
for i := 0; i < 8; i++ {
idx := i + (v.charScrollbar.GetCurrentOffset() * 2)
if idx >= len(v.gameStates) {
v.characterNameLabel[i].SetText("")
v.characterStatsLabel[i].SetText("")
v.characterExpLabel[i].SetText("")
v.characterImage[i] = nil
continue
}
v.characterNameLabel[i].SetText(v.gameStates[idx].HeroName)
v.characterStatsLabel[i].SetText("Level 1 " + v.gameStates[idx].HeroType.String())
v.characterExpLabel[i].SetText(expText)
// TODO: Generate or load the object from the actual player data...
v.characterImage[i] = d2mapentity.CreatePlayer("", "", 0, 0, 0,
v.gameStates[idx].HeroType,
*v.gameStates[idx].Stats,
d2inventory.HeroObjects[v.gameStates[idx].HeroType],
)
}
}
func (v *CharacterSelect) onNewCharButtonClicked() {
v.navigator.ToSelectHero(v.connectionType, v.connectionHost)
}
func (v *CharacterSelect) onExitButtonClicked() {
v.navigator.ToMainMenu()
}
// Render renders the Character Select screen
func (v *CharacterSelect) Render(screen d2interface.Surface) error {
if err := v.background.RenderSegmented(screen, 4, 3, 0); err != nil {
return err
}
v.d2HeroTitle.Render(screen)
actualSelectionIndex := v.selectedCharacter - (v.charScrollbar.GetCurrentOffset() * 2)
if v.selectedCharacter > -1 && actualSelectionIndex >= 0 && actualSelectionIndex < 8 {
if err := v.selectionBox.RenderSegmented(screen, 2, 1, 0); err != nil {
return err
}
}
for i := 0; i < 8; i++ {
idx := i + (v.charScrollbar.GetCurrentOffset() * 2)
if idx >= len(v.gameStates) {
continue
}
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)
v.characterImage[i].Render(screen)
screen.Pop()
}
if v.showDeleteConfirmation {
screen.DrawRect(800, 600, color.RGBA{A: 128})
if err := v.okCancelBox.RenderSegmented(screen, 2, 1, 0); err != nil {
return err
}
v.deleteCharConfirmLabel.Render(screen)
}
return nil
}
func (v *CharacterSelect) moveSelectionBox() {
if v.selectedCharacter == -1 {
v.d2HeroTitle.SetText("")
return
}
bw := 272
bh := 92
selectedIndex := v.selectedCharacter - (v.charScrollbar.GetCurrentOffset() * 2)
v.selectionBox.SetPosition(37+((selectedIndex&1)*bw), 86+(bh*(selectedIndex/2)))
v.d2HeroTitle.SetText(v.gameStates[v.selectedCharacter].HeroName)
}
// OnMouseButtonDown is called when a mouse button is clicked
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
if localMouseX > 0 && localMouseX < bw*2 && localMouseY >= 0 && localMouseY < bh*4 {
adjustY := localMouseY / bh
selectedIndex := adjustY * 2
if localMouseX > bw {
selectedIndex++
}
if (v.charScrollbar.GetCurrentOffset()*2)+selectedIndex < len(v.gameStates) {
v.selectedCharacter = (v.charScrollbar.GetCurrentOffset() * 2) + selectedIndex
v.moveSelectionBox()
}
}
return true
}
}
return false
}
// Advance runs the update logic on the Character Select screen
func (v *CharacterSelect) Advance(tickTime float64) error {
for _, hero := range v.characterImage {
if hero != nil {
hero.Advance(tickTime)
}
}
return nil
}
func (v *CharacterSelect) onDeleteCharButtonClicked() {
v.toggleDeleteCharacterDialog(true)
}
func (v *CharacterSelect) onDeleteCharacterConfirmClicked() {
_ = os.Remove(v.gameStates[v.selectedCharacter].FilePath)
v.charScrollbar.SetCurrentOffset(0)
v.refreshGameStates()
v.toggleDeleteCharacterDialog(false)
v.deleteCharButton.SetEnabled(len(v.gameStates) > 0)
v.okButton.SetEnabled(len(v.gameStates) > 0)
}
func (v *CharacterSelect) onDeleteCharacterCancelClicked() {
v.toggleDeleteCharacterDialog(false)
}
func (v *CharacterSelect) toggleDeleteCharacterDialog(showDialog bool) {
v.showDeleteConfirmation = showDialog
v.okButton.SetEnabled(!showDialog)
v.deleteCharButton.SetEnabled(!showDialog)
v.exitButton.SetEnabled(!showDialog)
v.newCharButton.SetEnabled(!showDialog)
v.deleteCharOkButton.SetVisible(showDialog)
v.deleteCharCancelButton.SetVisible(showDialog)
}
func (v *CharacterSelect) refreshGameStates() {
v.gameStates = d2player.GetAllPlayerStates()
v.updateCharacterBoxes()
if len(v.gameStates) > 0 {
v.selectedCharacter = 0
v.d2HeroTitle.SetText(v.gameStates[0].HeroName)
v.charScrollbar.SetMaxOffset(int(math.Ceil(float64(len(v.gameStates)-8) / float64(2))))
} else {
v.selectedCharacter = -1
v.charScrollbar.SetMaxOffset(0)
}
v.moveSelectionBox()
}
func (v *CharacterSelect) onOkButtonClicked() {
v.navigator.ToCreateGame(v.gameStates[v.selectedCharacter].FilePath, v.connectionType, v.connectionHost)
}