OpenDiablo2/d2game/d2gamescreen/character_select.go

383 lines
12 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"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
"github.com/OpenDiablo2/OpenDiablo2/d2script"
)
// 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
terminal d2interface.Terminal
scriptEngine *d2script.ScriptEngine
renderer d2interface.Renderer
}
// CreateCharacterSelect creates the character select screen and returns a pointer to it
func CreateCharacterSelect(
renderer d2interface.Renderer,
inputManager d2interface.InputManager,
audioProvider d2interface.AudioProvider,
connectionType d2clientconnectiontype.ClientConnectionType,
connectionHost string, term d2interface.Terminal, scriptEngine *d2script.ScriptEngine,
) *CharacterSelect {
return &CharacterSelect{
selectedCharacter: -1,
renderer: renderer,
connectionType: connectionType,
connectionHost: connectionHost,
inputManager: inputManager,
audioProvider: audioProvider,
terminal: term,
scriptEngine: scriptEngine,
}
}
// 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() {
d2screen.SetNextScreen(CreateSelectHeroClass(v.renderer, v.inputManager, v.audioProvider, v.connectionType, v.connectionHost,
v.terminal, v.scriptEngine))
}
func (v *CharacterSelect) onExitButtonClicked() {
mainMenu := CreateMainMenu(v.renderer, v.inputManager, v.audioProvider, v.terminal, v.scriptEngine)
mainMenu.setScreenMode(screenModeMainMenu)
d2screen.SetNextScreen(mainMenu)
}
// 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() {
gameClient, _ := d2client.Create(v.connectionType, v.scriptEngine)
host := ""
if v.connectionType == d2clientconnectiontype.LANClient {
host = v.connectionHost
}
if err := gameClient.Open(host, v.gameStates[v.selectedCharacter].FilePath); err != nil {
// TODO an error screen should be shown in this case
fmt.Printf("can not connect to the host: %s", host)
}
d2screen.SetNextScreen(CreateGame(v.renderer, v.inputManager, v.audioProvider, gameClient, v.terminal, v.scriptEngine))
}