1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2025-02-03 15:17:04 -05:00
OpenDiablo2/d2game/d2gamescreen/select_hero_class.go
2021-06-13 17:10:38 +02:00

801 lines
29 KiB
Go

package d2gamescreen
import (
"image"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2screen"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
)
const (
millisecondsPerSecond = 1000.0
)
type heroRenderConfig struct {
selectSfx string
idleSelectedAnimationPath string
forwardWalkAnimationPath string
forwardWalkOverlayAnimationPath string
deselectSfx string
selectedAnimationPath string
selectedOverlayAnimationPath string
backWalkAnimationPath string
backWalkOverlayAnimationPath string
idleAnimationPath string
selectionBounds image.Rectangle
position image.Point
idlePlayLengthMs int
forwardWalkPlayLengthMs int
backWalkPlayLengthMs int
forwardWalkOverlayBlend bool
}
func point(x, y int) image.Point {
return image.Point{X: x, Y: y}
}
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{
idleAnimationPath: d2resource.CharacterSelectBarbarianUnselected,
idleSelectedAnimationPath: d2resource.CharacterSelectBarbarianUnselectedH,
forwardWalkAnimationPath: d2resource.CharacterSelectBarbarianForwardWalk,
forwardWalkOverlayAnimationPath: d2resource.CharacterSelectBarbarianForwardWalkOverlay,
forwardWalkOverlayBlend: false,
selectedAnimationPath: d2resource.CharacterSelectBarbarianSelected,
selectedOverlayAnimationPath: "",
backWalkAnimationPath: d2resource.CharacterSelectBarbarianBackWalk,
backWalkOverlayAnimationPath: "",
selectionBounds: rect(barbRectMinX, barbRectMinY, barbRectMaxX, barbRectMaxY),
selectSfx: d2resource.SFXBarbarianSelect,
deselectSfx: d2resource.SFXBarbarianDeselect,
position: point(barbPosX, barbPosY),
idlePlayLengthMs: barbIdleLength,
forwardWalkPlayLengthMs: barbForwardLength,
backWalkPlayLengthMs: barbBackLength,
}
configs[d2enum.HeroSorceress] = &heroRenderConfig{
idleAnimationPath: d2resource.CharacterSelectSorceressUnselected,
idleSelectedAnimationPath: d2resource.CharacterSelectSorceressUnselectedH,
forwardWalkAnimationPath: d2resource.CharacterSelectSorceressForwardWalk,
forwardWalkOverlayAnimationPath: d2resource.CharacterSelectSorceressForwardWalkOverlay,
forwardWalkOverlayBlend: true,
selectedAnimationPath: d2resource.CharacterSelectSorceressSelected,
selectedOverlayAnimationPath: d2resource.CharacterSelectSorceressSelectedOverlay,
backWalkAnimationPath: d2resource.CharacterSelectSorceressBackWalk,
backWalkOverlayAnimationPath: d2resource.CharacterSelectSorceressBackWalkOverlay,
selectionBounds: rect(sorcRectMinX, sorcRectMinY, sorcRectMaxX, sorcRectMaxY),
selectSfx: d2resource.SFXSorceressSelect,
deselectSfx: d2resource.SFXSorceressDeselect,
position: point(sorcPosX, sorcPosY),
idlePlayLengthMs: sorcIdleLength,
forwardWalkPlayLengthMs: sorcForwardLength,
backWalkPlayLengthMs: sorcBackLength,
}
configs[d2enum.HeroNecromancer] = &heroRenderConfig{
idleAnimationPath: d2resource.CharacterSelectNecromancerUnselected,
idleSelectedAnimationPath: d2resource.CharacterSelectNecromancerUnselectedH,
forwardWalkAnimationPath: d2resource.CharacterSelectNecromancerForwardWalk,
forwardWalkOverlayAnimationPath: d2resource.CharacterSelectNecromancerForwardWalkOverlay,
forwardWalkOverlayBlend: true,
selectedAnimationPath: d2resource.CharacterSelectNecromancerSelected,
selectedOverlayAnimationPath: d2resource.CharacterSelectNecromancerSelectedOverlay,
backWalkAnimationPath: d2resource.CharacterSelectNecromancerBackWalk,
backWalkOverlayAnimationPath: d2resource.CharacterSelectNecromancerBackWalkOverlay,
selectionBounds: rect(necRectMinX, necRectMinY, necRectMaxX, necRectMaxY),
selectSfx: d2resource.SFXNecromancerSelect,
deselectSfx: d2resource.SFXNecromancerDeselect,
position: point(necPosX, necPosY),
idlePlayLengthMs: necIdleLength,
forwardWalkPlayLengthMs: necForwardLength,
backWalkPlayLengthMs: necBackLength,
}
configs[d2enum.HeroPaladin] = &heroRenderConfig{
idleAnimationPath: d2resource.CharacterSelectPaladinUnselected,
idleSelectedAnimationPath: d2resource.CharacterSelectPaladinUnselectedH,
forwardWalkAnimationPath: d2resource.CharacterSelectPaladinForwardWalk,
forwardWalkOverlayAnimationPath: d2resource.CharacterSelectPaladinForwardWalkOverlay,
forwardWalkOverlayBlend: false,
selectedAnimationPath: d2resource.CharacterSelectPaladinSelected,
selectedOverlayAnimationPath: "",
backWalkAnimationPath: d2resource.CharacterSelectPaladinBackWalk,
backWalkOverlayAnimationPath: "",
selectionBounds: rect(palRectMinX, palRectMinY, palRectMaxX, palRectMaxY),
selectSfx: d2resource.SFXPaladinSelect,
deselectSfx: d2resource.SFXPaladinDeselect,
position: point(palPosX, palPosY),
idlePlayLengthMs: palIdleLength,
forwardWalkPlayLengthMs: palForwardLength,
backWalkPlayLengthMs: palBackLength,
}
configs[d2enum.HeroAmazon] = &heroRenderConfig{
idleAnimationPath: d2resource.CharacterSelectAmazonUnselected,
idleSelectedAnimationPath: d2resource.CharacterSelectAmazonUnselectedH,
forwardWalkAnimationPath: d2resource.CharacterSelectAmazonForwardWalk,
forwardWalkOverlayAnimationPath: "",
forwardWalkOverlayBlend: false,
selectedAnimationPath: d2resource.CharacterSelectAmazonSelected,
selectedOverlayAnimationPath: "",
backWalkAnimationPath: d2resource.CharacterSelectAmazonBackWalk,
backWalkOverlayAnimationPath: "",
selectionBounds: rect(amaRectMinX, amaRectMinY, amaRectMaxX, amaRectMaxY),
selectSfx: d2resource.SFXAmazonSelect,
deselectSfx: d2resource.SFXAmazonDeselect,
position: point(amaPosX, amaPosY),
idlePlayLengthMs: amaIdleLength,
forwardWalkPlayLengthMs: amaForwardLength,
backWalkPlayLengthMs: amaBackLength,
}
configs[d2enum.HeroAssassin] = &heroRenderConfig{
idleAnimationPath: d2resource.CharacterSelectAssassinUnselected,
idleSelectedAnimationPath: d2resource.CharacterSelectAssassinUnselectedH,
forwardWalkAnimationPath: d2resource.CharacterSelectAssassinForwardWalk,
forwardWalkOverlayAnimationPath: "",
forwardWalkOverlayBlend: false,
selectedAnimationPath: d2resource.CharacterSelectAssassinSelected,
selectedOverlayAnimationPath: "",
backWalkAnimationPath: d2resource.CharacterSelectAssassinBackWalk,
backWalkOverlayAnimationPath: "",
selectionBounds: rect(assRectMinX, assRectMinY, assRectMaxX, assRectMaxY),
selectSfx: d2resource.SFXAssassinSelect,
deselectSfx: d2resource.SFXAssassinDeselect,
position: point(assPosX, assPosY),
idlePlayLengthMs: assIdleLength,
forwardWalkPlayLengthMs: assForwardLength,
backWalkPlayLengthMs: assBackLength,
}
configs[d2enum.HeroDruid] = &heroRenderConfig{
idleAnimationPath: d2resource.CharacterSelectDruidUnselected,
idleSelectedAnimationPath: d2resource.CharacterSelectDruidUnselectedH,
forwardWalkAnimationPath: d2resource.CharacterSelectDruidForwardWalk,
forwardWalkOverlayAnimationPath: "",
forwardWalkOverlayBlend: false,
selectedAnimationPath: d2resource.CharacterSelectDruidSelected,
selectedOverlayAnimationPath: "",
backWalkAnimationPath: d2resource.CharacterSelectDruidBackWalk,
backWalkOverlayAnimationPath: "",
selectionBounds: rect(druRectMinX, druRectMinY, druRectMaxX, druRectMaxY),
selectSfx: d2resource.SFXDruidSelect,
deselectSfx: d2resource.SFXDruidDeselect,
position: point(druPosX, druPosY),
idlePlayLengthMs: druIdleLength,
forwardWalkPlayLengthMs: druForwardLength,
backWalkPlayLengthMs: druBackLength,
}
return configs
}
// HeroRenderInfo stores the rendering information of a hero for the Select Hero Class screen
type HeroRenderInfo struct {
DeselectSfx d2interface.SoundEffect
SelectSfx d2interface.SoundEffect
shc *SelectHeroClass
ForwardWalkSprite *d2ui.Sprite
ForwardWalkSpriteOverlay *d2ui.Sprite
SelectedSprite *d2ui.Sprite
SelectedSpriteOverlay *d2ui.Sprite
BackWalkSprite *d2ui.Sprite
BackWalkSpriteOverlay *d2ui.Sprite
IdleSelectedSprite *d2ui.Sprite
IdleSprite *d2ui.Sprite
SelectionBounds image.Rectangle
Stance d2enum.HeroStance
}
func (hri *HeroRenderInfo) advance(elapsed float64) {
advanceSprite(hri.shc, hri.IdleSprite, elapsed)
advanceSprite(hri.shc, hri.IdleSelectedSprite, elapsed)
advanceSprite(hri.shc, hri.ForwardWalkSprite, elapsed)
advanceSprite(hri.shc, hri.ForwardWalkSpriteOverlay, elapsed)
advanceSprite(hri.shc, hri.SelectedSprite, elapsed)
advanceSprite(hri.shc, hri.SelectedSpriteOverlay, elapsed)
advanceSprite(hri.shc, hri.BackWalkSprite, elapsed)
advanceSprite(hri.shc, hri.BackWalkSpriteOverlay, elapsed)
}
// CreateSelectHeroClass creates an instance of a SelectHeroClass
func CreateSelectHeroClass(
navigator d2interface.Navigator,
asset *d2asset.AssetManager,
renderer d2interface.Renderer,
audioProvider d2interface.AudioProvider,
ui *d2ui.UIManager,
connectionType d2clientconnectiontype.ClientConnectionType,
l d2util.LogLevel,
connectionHost string,
) (*SelectHeroClass, error) {
playerStateFactory, err := d2hero.NewHeroStateFactory(asset)
if err != nil {
return nil, err
}
inventoryItemFactory, err := d2inventory.NewInventoryItemFactory(asset)
if err != nil {
return nil, err
}
selectHeroClass := &SelectHeroClass{
asset: asset,
heroRenderInfo: make(map[d2enum.Hero]*HeroRenderInfo),
selectedHero: d2enum.HeroNone,
connectionType: connectionType,
connectionHost: connectionHost,
audioProvider: audioProvider,
renderer: renderer,
navigator: navigator,
uiManager: ui,
HeroStateFactory: playerStateFactory,
InventoryItemFactory: inventoryItemFactory,
}
selectHeroClass.Logger = d2util.NewLogger()
selectHeroClass.Logger.SetLevel(l)
selectHeroClass.Logger.SetPrefix(logPrefix)
return selectHeroClass, nil
}
// SelectHeroClass represents the Select Hero Class screen
type SelectHeroClass struct {
navigator d2interface.Navigator
audioProvider d2interface.AudioProvider
renderer d2interface.Renderer
asset *d2asset.AssetManager
headingLabel *d2ui.Label
heroClassLabel *d2ui.Label
heroDesc1Label *d2ui.Label
heroDesc2Label *d2ui.Label
heroDesc3Label *d2ui.Label
heroNameTextbox *d2ui.TextBox
heroNameLabel *d2ui.Label
heroRenderInfo map[d2enum.Hero]*HeroRenderInfo
*d2inventory.InventoryItemFactory
*d2hero.HeroStateFactory
campfire *d2ui.Sprite
exitButton *d2ui.Button
okButton *d2ui.Button
expansionCheckbox *d2ui.Checkbox
expansionCharLabel *d2ui.Label
hardcoreCheckbox *d2ui.Checkbox
hardcoreCharLabel *d2ui.Label
bgImage *d2ui.Sprite
uiManager *d2ui.UIManager
*d2util.Logger
connectionHost string
connectionType d2clientconnectiontype.ClientConnectionType
selectedHero d2enum.Hero
}
// OnLoad loads the resources for the Select Hero Class screen
func (v *SelectHeroClass) OnLoad(loading d2screen.LoadingState) {
v.audioProvider.PlayBGM(d2resource.BGMTitle)
loading.Progress(tenPercent)
v.bgImage = v.loadSprite(
d2resource.CharacterSelectBackground,
point(0, 0),
0,
true,
false,
)
loading.Progress(thirtyPercent)
v.createLabels()
loading.Progress(fourtyPercent)
v.createButtons()
v.campfire = v.loadSprite(
d2resource.CharacterSelectCampfire,
point(campfirePosX, campfirePosY),
0,
true,
true,
)
v.createCheckboxes()
loading.Progress(fiftyPercent)
for hero, config := range getHeroRenderConfiguration() {
position := config.position
forwardWalkOverlaySprite := v.loadSprite(
config.forwardWalkOverlayAnimationPath,
position,
config.forwardWalkPlayLengthMs,
false,
config.forwardWalkOverlayBlend,
)
v.heroRenderInfo[hero] = &HeroRenderInfo{
Stance: d2enum.HeroStanceIdle,
IdleSprite: v.loadSprite(config.idleAnimationPath, position,
config.idlePlayLengthMs, true, false),
IdleSelectedSprite: v.loadSprite(config.idleSelectedAnimationPath,
position,
config.idlePlayLengthMs, true, false),
ForwardWalkSprite: v.loadSprite(config.forwardWalkAnimationPath, position,
config.forwardWalkPlayLengthMs, false, false),
ForwardWalkSpriteOverlay: forwardWalkOverlaySprite,
SelectedSprite: v.loadSprite(config.selectedAnimationPath, position,
config.idlePlayLengthMs, true, false),
SelectedSpriteOverlay: v.loadSprite(config.selectedOverlayAnimationPath, position,
config.idlePlayLengthMs, true, true),
BackWalkSprite: v.loadSprite(config.backWalkAnimationPath, position,
config.backWalkPlayLengthMs, false, false),
BackWalkSpriteOverlay: v.loadSprite(config.backWalkOverlayAnimationPath, position,
config.backWalkPlayLengthMs, false, true),
SelectionBounds: config.selectionBounds,
SelectSfx: v.loadSoundEffect(config.selectSfx),
DeselectSfx: v.loadSoundEffect(config.deselectSfx),
}
}
}
func (v *SelectHeroClass) createLabels() {
v.headingLabel = v.uiManager.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
fontWidth, _ := v.headingLabel.GetSize()
half := 2
halfFontWidth := fontWidth / half
v.headingLabel.SetPosition(headingX-halfFontWidth, headingY)
v.headingLabel.SetText(v.asset.TranslateString(d2enum.SelectHeroClassLabel))
v.headingLabel.Alignment = d2ui.HorizontalAlignCenter
v.heroClassLabel = v.uiManager.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
v.heroClassLabel.Alignment = d2ui.HorizontalAlignCenter
v.heroClassLabel.SetPosition(heroClassLabelX, heroClassLabelY)
v.heroDesc1Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc1Label.Alignment = d2ui.HorizontalAlignCenter
v.heroDesc1Label.SetPosition(heroDescLine1X, heroDescLine1Y)
v.heroDesc2Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc2Label.Alignment = d2ui.HorizontalAlignCenter
v.heroDesc2Label.SetPosition(heroDescLine2X, heroDescLine2Y)
v.heroDesc3Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc3Label.Alignment = d2ui.HorizontalAlignCenter
v.heroDesc3Label.SetPosition(heroDescLine3X, heroDescLine3Y)
v.heroNameLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroNameLabel.Alignment = d2ui.HorizontalAlignLeft
v.heroNameLabel.SetText(d2ui.ColorTokenize(v.asset.TranslateString(d2enum.CharNameLabel), d2ui.ColorTokenGold))
v.heroNameLabel.SetPosition(heroNameLabelX, heroNameLabelY)
v.expansionCharLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.expansionCharLabel.Alignment = d2ui.HorizontalAlignLeft
v.expansionCharLabel.SetText(d2ui.ColorTokenize(v.asset.TranslateString("#803"), d2ui.ColorTokenGold))
v.expansionCharLabel.SetPosition(expansionLabelX, expansionLabelY)
v.hardcoreCharLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.hardcoreCharLabel.Alignment = d2ui.HorizontalAlignLeft
v.hardcoreCharLabel.SetText(d2ui.ColorTokenize(v.asset.TranslateString(d2enum.HardCoreLabel), d2ui.ColorTokenGold))
v.hardcoreCharLabel.SetPosition(hardcoreLabelX, hardcoreLabelY)
}
func (v *SelectHeroClass) createButtons() {
v.exitButton = v.uiManager.NewButton(d2ui.ButtonTypeMedium, v.asset.TranslateString(d2enum.ExitLabel))
v.exitButton.SetPosition(selHeroExitBtnX, selHeroExitBtnY)
v.exitButton.OnActivated(func() { v.onExitButtonClicked() })
v.okButton = v.uiManager.NewButton(d2ui.ButtonTypeMedium, v.asset.TranslateString(d2enum.OKLabel))
v.okButton.SetPosition(selHeroOkBtnX, selHeroOkBtnY)
v.okButton.OnActivated(func() { v.onOkButtonClicked() })
v.okButton.SetVisible(false)
v.okButton.SetEnabled(false)
}
func (v *SelectHeroClass) createCheckboxes() {
v.heroNameTextbox = v.uiManager.NewTextbox()
v.heroNameTextbox.SetPosition(heroNameTextBoxX, heoNameTextBoxY)
v.heroNameTextbox.SetVisible(false)
v.expansionCheckbox = v.uiManager.NewCheckbox(true)
v.expansionCheckbox.SetPosition(expandsionCheckboxX, expansionCheckboxY)
v.expansionCheckbox.SetVisible(false)
v.hardcoreCheckbox = v.uiManager.NewCheckbox(false)
v.hardcoreCheckbox.SetPosition(hardcoreCheckoxX, hardcoreCheckboxY)
v.hardcoreCheckbox.SetVisible(false)
}
// OnUnload releases the resources of the Select Hero Class screen
func (v *SelectHeroClass) OnUnload() error {
for i := range v.heroRenderInfo {
v.heroRenderInfo[i].SelectSfx.Stop()
v.heroRenderInfo[i].DeselectSfx.Stop()
}
v.heroRenderInfo = nil
return nil
}
func (v *SelectHeroClass) onExitButtonClicked() {
v.navigator.ToCharacterSelect(v.connectionType, v.connectionHost)
}
func (v *SelectHeroClass) onOkButtonClicked() {
heroName := v.heroNameTextbox.GetText()
defaultStats := v.asset.Records.Character.Stats[v.selectedHero]
statsState := v.CreateHeroStatsState(v.selectedHero, defaultStats)
playerState, err := v.CreateHeroState(heroName, v.selectedHero, statsState)
if err != nil {
v.Errorf("failed to create hero state!, err: %v", err.Error())
return
}
err = v.Save(playerState)
if err != nil {
v.Errorf("failed to save game state!, err: %v", err.Error())
return
}
playerState.Equipment = v.InventoryItemFactory.DefaultHeroItems[v.selectedHero]
v.navigator.ToCreateGame(playerState.FilePath, v.connectionType, v.connectionHost)
}
// Render renders the Select Hero Class screen
func (v *SelectHeroClass) Render(screen d2interface.Surface) {
v.bgImage.RenderSegmented(screen, 4, 3, 0)
v.headingLabel.Render(screen)
for heroClass, heroInfo := range v.heroRenderInfo {
if heroInfo.Stance == d2enum.HeroStanceIdle || heroInfo.Stance == d2enum.HeroStanceIdleSelected {
v.renderHero(screen, heroClass)
}
}
for heroClass, heroInfo := range v.heroRenderInfo {
if heroInfo.Stance != d2enum.HeroStanceIdle && heroInfo.Stance != d2enum.HeroStanceIdleSelected {
v.renderHero(screen, heroClass)
}
}
if v.selectedHero != d2enum.HeroNone {
v.heroClassLabel.Render(screen)
v.heroDesc1Label.Render(screen)
v.heroDesc2Label.Render(screen)
v.heroDesc3Label.Render(screen)
}
v.campfire.Render(screen)
if v.heroNameTextbox.GetVisible() {
v.heroNameLabel.Render(screen)
v.expansionCharLabel.Render(screen)
v.hardcoreCharLabel.Render(screen)
}
}
// Advance runs the update logic on the Select Hero Class screen
func (v *SelectHeroClass) Advance(tickTime float64) error {
canSelect := true
if err := v.campfire.Advance(tickTime); err != nil {
return err
}
for infoIdx := range v.heroRenderInfo {
v.heroRenderInfo[infoIdx].advance(tickTime)
if v.heroRenderInfo[infoIdx].Stance != d2enum.HeroStanceIdle &&
v.heroRenderInfo[infoIdx].Stance != d2enum.HeroStanceIdleSelected &&
v.heroRenderInfo[infoIdx].Stance != d2enum.HeroStanceSelected {
canSelect = false
}
}
for heroType := range v.heroRenderInfo {
v.updateHeroSelectionHover(heroType, canSelect)
}
v.okButton.SetEnabled(len(v.heroNameTextbox.GetText()) >= 2 && v.selectedHero != d2enum.HeroNone)
return nil
}
func (v *SelectHeroClass) updateHeroSelectionHover(hero d2enum.Hero, canSelect bool) {
renderInfo := v.heroRenderInfo[hero]
switch renderInfo.Stance {
case d2enum.HeroStanceApproaching:
if renderInfo.ForwardWalkSprite.IsOnLastFrame() {
renderInfo.Stance = d2enum.HeroStanceSelected
setSpriteToFirstFrame(renderInfo.SelectedSprite)
setSpriteToFirstFrame(renderInfo.SelectedSpriteOverlay)
}
return
case d2enum.HeroStanceRetreating:
if renderInfo.BackWalkSprite.IsOnLastFrame() {
renderInfo.Stance = d2enum.HeroStanceIdle
setSpriteToFirstFrame(renderInfo.IdleSprite)
}
return
}
if !canSelect || renderInfo.Stance == d2enum.HeroStanceSelected {
return
}
mouseX, mouseY := v.uiManager.CursorPosition()
b := renderInfo.SelectionBounds
mouseHover := (mouseX >= b.Min.X) && (mouseX <= b.Min.X+b.Max.X) && (mouseY >= b.Min.Y) && (mouseY <= b.Min.Y+b.Max.Y)
if mouseHover && v.uiManager.CursorButtonPressed(d2ui.CursorButtonLeft) {
v.handleCursorButtonPress(hero, renderInfo)
return
}
v.setCurrentFrame(mouseHover, renderInfo)
if v.selectedHero == d2enum.HeroNone && mouseHover {
v.selectedHero = hero
v.updateHeroText()
}
}
func (v *SelectHeroClass) handleCursorButtonPress(hero d2enum.Hero, renderInfo *HeroRenderInfo) {
v.heroNameTextbox.SetVisible(true)
v.heroNameTextbox.Activate()
v.okButton.SetVisible(true)
v.expansionCheckbox.SetVisible(true)
v.hardcoreCheckbox.SetVisible(true)
renderInfo.Stance = d2enum.HeroStanceApproaching
setSpriteToFirstFrame(renderInfo.ForwardWalkSprite)
setSpriteToFirstFrame(renderInfo.ForwardWalkSpriteOverlay)
for _, heroInfo := range v.heroRenderInfo {
if heroInfo.Stance != d2enum.HeroStanceSelected {
continue
}
heroInfo.SelectSfx.Stop()
heroInfo.DeselectSfx.Play()
heroInfo.Stance = d2enum.HeroStanceRetreating
setSpriteToFirstFrame(heroInfo.BackWalkSprite)
setSpriteToFirstFrame(heroInfo.BackWalkSpriteOverlay)
}
v.selectedHero = hero
v.updateHeroText()
renderInfo.SelectSfx.Play()
}
func (v *SelectHeroClass) setCurrentFrame(mouseHover bool, renderInfo *HeroRenderInfo) {
if mouseHover && renderInfo.Stance != d2enum.HeroStanceIdleSelected {
if err := renderInfo.IdleSelectedSprite.SetCurrentFrame(renderInfo.IdleSprite.GetCurrentFrame()); err != nil {
v.Errorf("could not set current frame to: %d\n", renderInfo.IdleSprite.GetCurrentFrame())
}
renderInfo.Stance = d2enum.HeroStanceIdleSelected
} else if !mouseHover && renderInfo.Stance != d2enum.HeroStanceIdle {
if err := renderInfo.IdleSprite.SetCurrentFrame(renderInfo.IdleSelectedSprite.GetCurrentFrame()); err != nil {
v.Errorf("could not set current frame to: %d\n", renderInfo.IdleSelectedSprite.GetCurrentFrame())
}
renderInfo.Stance = d2enum.HeroStanceIdle
}
}
func (v *SelectHeroClass) renderHero(screen d2interface.Surface, hero d2enum.Hero) {
renderInfo := v.heroRenderInfo[hero]
switch renderInfo.Stance {
case d2enum.HeroStanceIdle:
drawSprite(renderInfo.IdleSprite, screen)
case d2enum.HeroStanceIdleSelected:
drawSprite(renderInfo.IdleSelectedSprite, screen)
case d2enum.HeroStanceApproaching:
drawSprite(renderInfo.ForwardWalkSprite, screen)
drawSprite(renderInfo.ForwardWalkSpriteOverlay, screen)
case d2enum.HeroStanceSelected:
drawSprite(renderInfo.SelectedSprite, screen)
drawSprite(renderInfo.SelectedSpriteOverlay, screen)
case d2enum.HeroStanceRetreating:
drawSprite(renderInfo.BackWalkSprite, screen)
drawSprite(renderInfo.BackWalkSpriteOverlay, screen)
}
}
func (v *SelectHeroClass) updateHeroText() {
// v.setDescLabels("") really takes a string translation key, but temporarily disabled.
switch v.selectedHero {
case d2enum.HeroNone:
return
case d2enum.HeroBarbarian:
v.heroClassLabel.SetText(v.asset.TranslateString("partycharbar"))
v.setDescLabels(d2enum.BarbarianDescr, "")
case d2enum.HeroNecromancer:
v.heroClassLabel.SetText(v.asset.TranslateString("partycharnec"))
v.setDescLabels(d2enum.NecromancerDescr, "")
case d2enum.HeroPaladin:
v.heroClassLabel.SetText(v.asset.TranslateString("partycharpal"))
v.setDescLabels(d2enum.PaladinDescr, "")
case d2enum.HeroAssassin:
v.heroClassLabel.SetText(v.asset.TranslateString("partycharass"))
v.setDescLabels(0, "#305")
case d2enum.HeroSorceress:
v.heroClassLabel.SetText(v.asset.TranslateString("partycharsor"))
v.setDescLabels(d2enum.SorceressDescr, "")
case d2enum.HeroAmazon:
v.heroClassLabel.SetText(v.asset.TranslateString("partycharama"))
v.setDescLabels(d2enum.AmazonDescr, "")
case d2enum.HeroDruid:
v.heroClassLabel.SetText(v.asset.TranslateString("partychardru"))
// here is a problem with polish language: in polish string table, there are two items with key "#304"
v.setDescLabels(0, "#304")
}
}
const (
oneLine = 1
twoLine = 2
)
func (v *SelectHeroClass) setDescLabels(descKey int, key string) {
var heroDesc string
if key != "" {
heroDesc = v.asset.TranslateString(key)
} else {
heroDesc = v.asset.TranslateString(descKey)
}
parts := d2util.SplitIntoLinesWithMaxWidth(heroDesc, heroDescCharWidth)
numLines := len(parts)
if numLines > oneLine {
v.heroDesc1Label.SetText(parts[0])
v.heroDesc2Label.SetText(parts[1])
} else {
v.heroDesc1Label.SetText("")
v.heroDesc2Label.SetText("")
}
if numLines > twoLine {
v.heroDesc3Label.SetText(parts[2])
} else {
v.heroDesc3Label.SetText("")
}
}
func setSpriteToFirstFrame(sprite *d2ui.Sprite) {
if sprite != nil {
sprite.Rewind()
}
}
func drawSprite(sprite *d2ui.Sprite, target d2interface.Surface) {
if sprite != nil {
sprite.Render(target)
}
}
func advanceSprite(v *SelectHeroClass, sprite *d2ui.Sprite, elapsed float64) {
if sprite != nil {
if err := sprite.Advance(elapsed); err != nil {
v.Error("could not advance the sprite:" + err.Error())
}
}
}
func (v *SelectHeroClass) loadSprite(animationPath string, position image.Point,
playLength int,
playLoop,
blend bool) *d2ui.Sprite {
if animationPath == "" {
return nil
}
sprite, err := v.uiManager.NewSprite(animationPath, d2resource.PaletteFechar)
if err != nil {
v.Error("could not load sprite for the animation: %s\n" + animationPath + "with error: " + err.Error())
return nil
}
sprite.PlayForward()
sprite.SetPlayLoop(playLoop)
if blend {
sprite.SetEffect(d2enum.DrawEffectModulate)
}
if playLength != 0 {
sprite.SetPlayLength(float64(playLength) / millisecondsPerSecond)
}
sprite.SetPosition(position.X, position.Y)
return sprite
}
func (v *SelectHeroClass) loadSoundEffect(sfx string) d2interface.SoundEffect {
result, err := v.audioProvider.LoadSound(sfx, false, false)
if err != nil {
v.Error(err.Error())
return nil
}
return result
}