1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-04 23:10:42 +00:00
OpenDiablo2/d2game/d2gamescreen/select_hero_class.go
lord 854fce3b14
remove d2asset singleton (#726)
* export d2asset singleton

* add *d2asset.AssetManager to d2app

- d2app now has a reference to an asset manager which it will use for loading
- added asset loader methods to the asset manager
- functions in d2asset are now wrappers for asset manager methods

* add asset manager reference to audio provider

- d2app asset manager reference is now passed to audio provider
- asset manager is created in main.go for now to pass into audio provider
- CreateSoundEffect is now a method, no longer exported, uses the asset manager reference

* d2app passes asset manager refence to map engine test

* in d2asset, all calls to LoadFile replaced with call to Singleton.Loadfile

* blizzard intro and credits screen

- d2app passes reference to the asset manager to these screens

* asset manager for d2map

- adding MapStampFactory, takes an asset manager reference
- embedded MapStampFactory into the MapEngine
- LoadStamp is now a method of the MapStampFactory

* d2asset: removed LoadFileStream, LoadFile, and FileExists

* d2gui changes

- singleton now has an asset manager reference
- calls to d2asset loader functions removed
- createButton is now a method of LayoutManager
- moved LayoutEntry to its own file

* map entity factory

- Map engine has an embedded map entity factory
- Map stamp factory gets a reference to the map engine's entity factory
- Stamps are given a reference to the map engine entity factory when created
- Character select gets a map entity factory
- Embedded the stamp factory into the MapEngine

* asset manager for d2ui

- d2ui is passed an asset manager reference when created
- all calls to d2asset loader functions in d2ui now refer to the asset manager
- d2gamescreen gets a ui manager when created
- help overlay is now passed a ui manager when created

* d2gamescreen + d2player: asset manager references

added an asset manager reference to
- inventory panel + inventory grid
- mini panel
- game controls
- help overlay
- character select
- main menu
- select hero class
- hero stats panel

* Removed d2asset.LoadAnimation

all references to this function have been replaced with calls to the asset manager method

* adding asset to help overlay, bugfix for 4d59c91

* Removed d2asset.LoadFont and d2asset.LoadAnimationWithEffect

all references to these have been replaced with calls to the asset manager methods

* MapRenderer now gets an asset manager reference

* removed d2asset.LoadPalette

all references have been replaced with calls to an asset manager instance

* merged d2object with d2mapentity

d2object was only being used to create objects in the map, so the provider
function is now a method of the map entity factory. calls to d2asset have
been removed.

* removed d2asset.LoadComposite

all calls are now made to the asset manager method

* removed d2asset singleton

all singleton references have been removed, a single instance of the
asset manager is passed around the entire app

* rename Initialize to NewAssetManager
2020-09-12 16:51:30 -04:00

768 lines
25 KiB
Go

package d2gamescreen
import (
"fmt"
"image"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
"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/d2gui"
"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"
)
type heroRenderConfig struct {
idleAnimationPath string
idleSelectedAnimationPath string
forwardWalkAnimationPath string
forwardWalkOverlayAnimationPath string
forwardWalkOverlayBlend bool
selectedAnimationPath string
selectedOverlayAnimationPath string
backWalkAnimationPath string
backWalkOverlayAnimationPath string
selectionBounds image.Rectangle
selectSfx string
deselectSfx string
position image.Point
idlePlayLengthMs int
forwardWalkPlayLengthMs int
backWalkPlayLengthMs int
}
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{
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
type HeroRenderInfo struct {
Stance d2enum.HeroStance
IdleSprite *d2ui.Sprite
IdleSelectedSprite *d2ui.Sprite
ForwardWalkSprite *d2ui.Sprite
ForwardWalkSpriteOverlay *d2ui.Sprite
SelectedSprite *d2ui.Sprite
SelectedSpriteOverlay *d2ui.Sprite
BackWalkSprite *d2ui.Sprite
BackWalkSpriteOverlay *d2ui.Sprite
SelectionBounds image.Rectangle
SelectSfx d2interface.SoundEffect
DeselectSfx d2interface.SoundEffect
}
func (hri *HeroRenderInfo) advance(elapsed float64) {
advanceSprite(hri.IdleSprite, elapsed)
advanceSprite(hri.IdleSelectedSprite, elapsed)
advanceSprite(hri.ForwardWalkSprite, elapsed)
advanceSprite(hri.ForwardWalkSpriteOverlay, elapsed)
advanceSprite(hri.SelectedSprite, elapsed)
advanceSprite(hri.SelectedSpriteOverlay, elapsed)
advanceSprite(hri.BackWalkSprite, elapsed)
advanceSprite(hri.BackWalkSpriteOverlay, elapsed)
}
// SelectHeroClass represents the Select Hero Class screen
type SelectHeroClass struct {
asset *d2asset.AssetManager
uiManager *d2ui.UIManager
bgImage *d2ui.Sprite
campfire *d2ui.Sprite
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
selectedHero d2enum.Hero
exitButton *d2ui.Button
okButton *d2ui.Button
expansionCheckbox *d2ui.Checkbox
expansionCharLabel *d2ui.Label
hardcoreCheckbox *d2ui.Checkbox
hardcoreCharLabel *d2ui.Label
connectionType d2clientconnectiontype.ClientConnectionType
connectionHost string
audioProvider d2interface.AudioProvider
renderer d2interface.Renderer
navigator Navigator
}
// CreateSelectHeroClass creates an instance of a SelectHeroClass
func CreateSelectHeroClass(
navigator Navigator,
manager *d2asset.AssetManager,
renderer d2interface.Renderer,
audioProvider d2interface.AudioProvider,
ui *d2ui.UIManager,
connectionType d2clientconnectiontype.ClientConnectionType,
connectionHost string,
) *SelectHeroClass {
result := &SelectHeroClass{
heroRenderInfo: make(map[d2enum.Hero]*HeroRenderInfo),
selectedHero: d2enum.HeroNone,
connectionType: connectionType,
connectionHost: connectionHost,
audioProvider: audioProvider,
renderer: renderer,
navigator: navigator,
uiManager: ui,
}
return result
}
// 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("Select Hero Class")
v.headingLabel.Alignment = d2gui.HorizontalAlignCenter
v.heroClassLabel = v.uiManager.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
v.heroClassLabel.Alignment = d2gui.HorizontalAlignCenter
v.heroClassLabel.SetPosition(heroClassLabelX, heroClassLabelY)
v.heroDesc1Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc1Label.Alignment = d2gui.HorizontalAlignCenter
v.heroDesc1Label.SetPosition(heroDescLine1X, heroDescLine1Y)
v.heroDesc2Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc2Label.Alignment = d2gui.HorizontalAlignCenter
v.heroDesc2Label.SetPosition(heroDescLine2X, heroDescLine2Y)
v.heroDesc3Label = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroDesc3Label.Alignment = d2gui.HorizontalAlignCenter
v.heroDesc3Label.SetPosition(heroDescLine3X, heroDescLine3Y)
v.heroNameLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.heroNameLabel.Alignment = d2gui.HorizontalAlignLeft
v.heroNameLabel.SetText(d2ui.ColorTokenize("Character Name", d2ui.ColorTokenGold))
v.heroNameLabel.SetPosition(heroNameLabelX, heroNameLabelY)
v.expansionCharLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.expansionCharLabel.Alignment = d2gui.HorizontalAlignLeft
v.expansionCharLabel.SetText(d2ui.ColorTokenize("EXPANSION CHARACTER", d2ui.ColorTokenGold))
v.expansionCharLabel.SetPosition(expansionLabelX, expansionLabelY)
v.hardcoreCharLabel = v.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteUnits)
v.hardcoreCharLabel.Alignment = d2gui.HorizontalAlignLeft
v.hardcoreCharLabel.SetText(d2ui.ColorTokenize("Hardcore", d2ui.ColorTokenGold))
v.hardcoreCharLabel.SetPosition(hardcoreLabelX, hardcoreLabelY)
}
func (v *SelectHeroClass) createButtons() {
v.exitButton = v.uiManager.NewButton(d2ui.ButtonTypeMedium, "EXIT")
v.exitButton.SetPosition(selHeroExitBtnX, selHeroExitBtnY)
v.exitButton.OnActivated(func() { v.onExitButtonClicked() })
v.okButton = v.uiManager.NewButton(d2ui.ButtonTypeMedium, "OK")
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() {
gameState := d2player.CreatePlayerState(
v.heroNameTextbox.GetText(),
v.selectedHero,
d2datadict.CharStats[v.selectedHero],
)
v.navigator.ToCreateGame(gameState.FilePath, d2clientconnectiontype.Local, v.connectionHost)
}
// Render renders the Select Hero Class screen
func (v *SelectHeroClass) Render(screen d2interface.Surface) error {
if err := v.bgImage.RenderSegmented(screen, 4, 3, 0); err != nil {
return err
}
v.headingLabel.Render(screen)
if v.selectedHero != d2enum.HeroNone {
v.heroClassLabel.Render(screen)
v.heroDesc1Label.Render(screen)
v.heroDesc2Label.Render(screen)
v.heroDesc3Label.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 err := v.campfire.Render(screen); err != nil {
return err
}
if v.heroNameTextbox.GetVisible() {
v.heroNameLabel.Render(screen)
v.expansionCharLabel.Render(screen)
v.hardcoreCharLabel.Render(screen)
}
return nil
}
// 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 {
fmt.Printf("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 {
fmt.Printf("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(d2tbl.TranslateString("partycharbar"))
v.setDescLabels("He is unequaled in close-quarters combat and mastery of weapons.")
case d2enum.HeroNecromancer:
v.heroClassLabel.SetText(d2tbl.TranslateString("partycharnec"))
v.setDescLabels("Summoning undead minions and cursing his enemies are his specialties.")
case d2enum.HeroPaladin:
v.heroClassLabel.SetText(d2tbl.TranslateString("partycharpal"))
v.setDescLabels("He is a natural party leader, holy man, and blessed warrior.")
case d2enum.HeroAssassin:
v.heroClassLabel.SetText(d2tbl.TranslateString("partycharass"))
v.setDescLabels("Schooled in the Martial Arts, her mind and body are deadly weapons.")
case d2enum.HeroSorceress:
v.heroClassLabel.SetText(d2tbl.TranslateString("partycharsor"))
v.setDescLabels("She has mastered the elemental magicks -- fire, lightning, and ice.")
case d2enum.HeroAmazon:
v.heroClassLabel.SetText(d2tbl.TranslateString("partycharama"))
v.setDescLabels("Skilled with the spear and the bow, she is a very versatile fighter.")
case d2enum.HeroDruid:
v.heroClassLabel.SetText(d2tbl.TranslateString("partychardru"))
v.setDescLabels("Commanding the forces of nature, he summons wild beasts and raging storms to his side.")
}
}
const (
oneLine = 1
twoLine = 2
)
func (v *SelectHeroClass) setDescLabels(descKey string) {
heroDesc := d2tbl.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 {
if err := sprite.Render(target); err != nil {
x, y := sprite.GetPosition()
fmt.Printf("could not render the sprite to the position(x: %d, y: %d)\n", x, y)
}
}
}
func advanceSprite(sprite *d2ui.Sprite, elapsed float64) {
if sprite != nil {
if err := sprite.Advance(elapsed); err != nil {
fmt.Printf("could not advance the sprite\n")
}
}
}
func (v *SelectHeroClass) loadSprite(animationPath string, position image.Point,
playLength int,
playLoop,
blend bool) *d2ui.Sprite {
if animationPath == "" {
return nil
}
animation, err := v.asset.LoadAnimation(animationPath, d2resource.PaletteFechar)
if err != nil {
fmt.Printf("could not load animation: %s\n", animationPath)
return nil
}
animation.PlayForward()
animation.SetPlayLoop(playLoop)
if blend {
animation.SetEffect(d2enum.DrawEffectModulate)
}
if playLength != 0 {
animation.SetPlayLengthMs(playLength)
}
sprite, err := v.uiManager.NewSprite(animation)
if err != nil {
fmt.Printf("could not load sprite for the animation: %s\n", animationPath)
return nil
}
sprite.SetPosition(position.X, position.Y)
return sprite
}
func (v *SelectHeroClass) loadSoundEffect(sfx string) d2interface.SoundEffect {
result, _ := v.audioProvider.LoadSound(sfx, false, false)
return result
}