1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-06-29 10:35:23 +00:00
OpenDiablo2/d2game/d2player/hero_stats_panel.go

379 lines
11 KiB
Go
Raw Normal View History

package d2player
import (
"log"
"strconv"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
2020-07-26 18:52:54 +00:00
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
2020-07-26 18:52:54 +00:00
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
const ( // for the dc6 frames
statsPanelTopLeft = iota
statsPanelTopRight
statsPanelBottomLeft
statsPanelBottomRight
)
const (
statsPanelOffsetX, statsPanelOffsetY = 80, 64
)
const (
labelLevelX, labelLevelY = 110, 100
labelHeroNameX, labelHeroNameY = 165, 72
labelHeroClassX, labelHeroClassY = 330, 72
labelExperienceX, labelExperienceY = 200, 100
labelNextLevelX, labelNextLevelY = 330, 100
labelStrengthX, labelStrengthY = 100, 150
labelDexterityX, labelDexterityY = 100, 213
labelVitalityX, labelVitalityY = 100, 300
labelEnergyX, labelEnergyY = 100, 360
labelDefenseX, labelDefenseY = 280, 260
labelStaminaX, labelStaminaY = 280, 300
labelLifeX, labelLifeY = 280, 322
labelManaX, labelManaY = 280, 360
labelResFireLine1X, labelResFireLine1Y = 310, 395
labelResFireLine2X, labelResFireLine2Y = 310, 402
labelResColdLine1X, labelResColdLine1Y = 310, 445
labelResColdLine2X, labelResColdLine2Y = 310, 452
labelResLightLine1X, labelResLightLine1Y = 310, 420
labelResLightLine2X, labelResLightLine2Y = 310, 427
labelResPoisLine1X, labelResPoisLine1Y = 310, 468
labelResPoisLine2X, labelResPoisLine2Y = 310, 477
)
const (
heroStatsCloseButtonX, heroStatsCloseButtonY = 208, 453
)
2020-07-26 18:52:54 +00:00
// PanelText represents text on the panel
type PanelText struct {
X int
Y int
Text string
Font string
AlignCenter bool
}
2020-07-26 18:52:54 +00:00
// StatsPanelLabels represents the labels in the status panel
type StatsPanelLabels struct {
Level *d2ui.Label
Experience *d2ui.Label
NextLevelExp *d2ui.Label
Strength *d2ui.Label
Dexterity *d2ui.Label
Vitality *d2ui.Label
Energy *d2ui.Label
Health *d2ui.Label
MaxHealth *d2ui.Label
Mana *d2ui.Label
MaxMana *d2ui.Label
MaxStamina *d2ui.Label
Stamina *d2ui.Label
}
2020-07-26 18:52:54 +00:00
// HeroStatsPanel represents the hero status panel
type HeroStatsPanel struct {
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 20:51:30 +00:00
asset *d2asset.AssetManager
uiManager *d2ui.UIManager
frame *d2ui.UIFrame
panel *d2ui.Sprite
heroState *d2hero.HeroStatsState
heroName string
heroClass d2enum.Hero
renderer d2interface.Renderer
staticMenuImageCache *d2interface.Surface
labels *StatsPanelLabels
closeButton *d2ui.Button
onCloseCb func()
originX int
originY int
isOpen bool
}
2020-07-26 18:52:54 +00:00
// NewHeroStatsPanel creates a new hero status panel
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 20:51:30 +00:00
func NewHeroStatsPanel(asset *d2asset.AssetManager, ui *d2ui.UIManager, heroName string, heroClass d2enum.Hero,
heroState *d2hero.HeroStatsState) *HeroStatsPanel {
originX := 0
originY := 0
return &HeroStatsPanel{
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 20:51:30 +00:00
asset: asset,
uiManager: ui,
renderer: ui.Renderer(),
originX: originX,
originY: originY,
heroState: heroState,
heroName: heroName,
heroClass: heroClass,
labels: &StatsPanelLabels{},
}
}
// Load the data for the hero status panel
func (s *HeroStatsPanel) Load() {
var err error
s.frame = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft)
s.closeButton = s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "")
s.closeButton.SetVisible(false)
s.closeButton.SetPosition(heroStatsCloseButtonX, heroStatsCloseButtonY)
s.closeButton.OnActivated(func() { s.Close() })
s.panel, err = s.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
if err != nil {
log.Print(err)
}
s.initStatValueLabels()
}
2020-07-26 18:52:54 +00:00
// IsOpen returns true if the hero status panel is open
func (s *HeroStatsPanel) IsOpen() bool {
return s.isOpen
}
2020-07-26 18:52:54 +00:00
// Toggle toggles the visibility of the hero status panel
func (s *HeroStatsPanel) Toggle() {
if s.isOpen {
s.Close()
} else {
s.Open()
}
}
2020-07-26 18:52:54 +00:00
// Open opens the hero status panel
func (s *HeroStatsPanel) Open() {
s.isOpen = true
s.closeButton.SetVisible(true)
}
2020-07-26 18:52:54 +00:00
// Close closed the hero status panel
func (s *HeroStatsPanel) Close() {
s.isOpen = false
s.closeButton.SetVisible(false)
s.onCloseCb()
}
// SetOnCloseCb the callback run on closing the HeroStatsPanel
func (s *HeroStatsPanel) SetOnCloseCb(cb func()) {
s.onCloseCb = cb
}
2020-07-26 18:52:54 +00:00
// Render renders the hero status panel
func (s *HeroStatsPanel) Render(target d2interface.Surface) error {
if !s.isOpen {
2020-07-26 18:52:54 +00:00
return nil
}
if s.staticMenuImageCache == nil {
frameWidth, frameHeight := s.frame.GetFrameBounds()
framesCount := s.frame.GetFrameCount()
surface, err := s.renderer.NewSurface(frameWidth*framesCount, frameHeight*framesCount, d2enum.FilterNearest)
if err != nil {
2020-07-26 18:52:54 +00:00
return err
}
2020-07-26 18:52:54 +00:00
s.staticMenuImageCache = &surface
2020-07-26 18:52:54 +00:00
if err := s.renderStaticMenu(*s.staticMenuImageCache); err != nil {
return err
}
}
if err := target.Render(*s.staticMenuImageCache); err != nil {
return err
}
2020-07-26 18:52:54 +00:00
s.renderStatValues(target)
2020-07-26 18:52:54 +00:00
return nil
}
2020-07-26 18:52:54 +00:00
func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) error {
if err := s.renderStaticPanelFrames(target); err != nil {
2020-07-26 18:52:54 +00:00
return err
}
s.renderStaticLabels(target)
2020-07-26 18:52:54 +00:00
return nil
}
2020-07-26 18:52:54 +00:00
func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) error {
if err := s.frame.Render(target); err != nil {
2020-07-26 18:52:54 +00:00
return err
}
frames := []int{
statsPanelTopLeft,
statsPanelTopRight,
statsPanelBottomRight,
statsPanelBottomLeft,
2020-07-26 18:52:54 +00:00
}
currentX := s.originX + statsPanelOffsetX
currentY := s.originY + statsPanelOffsetY
2020-07-26 18:52:54 +00:00
for _, frameIndex := range frames {
if err := s.panel.SetCurrentFrame(frameIndex); err != nil {
return err
}
2020-07-26 18:52:54 +00:00
w, h := s.panel.GetCurrentFrameSize()
switch frameIndex {
case statsPanelTopLeft:
s.panel.SetPosition(currentX, currentY+h)
currentX += w
case statsPanelTopRight:
s.panel.SetPosition(currentX, currentY+h)
currentY += h
case statsPanelBottomRight:
s.panel.SetPosition(currentX, currentY+h)
case statsPanelBottomLeft:
s.panel.SetPosition(currentX-w, currentY+h)
}
if err := s.panel.Render(target); err != nil {
return err
}
2020-07-26 18:52:54 +00:00
}
return nil
}
func (s *HeroStatsPanel) renderStaticLabels(target d2interface.Surface) {
var label *d2ui.Label
2020-07-26 18:52:54 +00:00
// all static labels are not stored since we use them only once to generate the image cache
var staticLabelConfigs = []struct {
x, y int
txt string
font string
centerAlign bool
}{
{labelHeroNameX, labelHeroNameY, s.heroName, d2resource.Font16, true},
{labelHeroClassX, labelHeroClassY, s.heroClass.String(), d2resource.Font16, true},
{labelLevelX, labelLevelY, "Level", d2resource.Font6, true},
{labelExperienceX, labelExperienceY, "Experience", d2resource.Font6, true},
{labelNextLevelX, labelNextLevelY, "Next Level", d2resource.Font6, true},
{labelStrengthX, labelStrengthY, "Strength", d2resource.Font6, false},
{labelDexterityX, labelDexterityY, "Dexterity", d2resource.Font6, false},
{labelVitalityX, labelVitalityY, "Vitality", d2resource.Font6, false},
{labelEnergyX, labelEnergyY, "Energy", d2resource.Font6, false},
{labelDefenseX, labelDefenseY, "Defense", d2resource.Font6, false},
{labelStaminaX, labelStaminaY, "Stamina", d2resource.Font6, true},
{labelLifeX, labelLifeY, "Life", d2resource.Font6, true},
{labelManaX, labelManaY, "Mana", d2resource.Font6, true},
2020-07-26 18:52:54 +00:00
// can't use "Fire\nResistance" because line spacing is too big and breaks the layout
{labelResFireLine1X, labelResFireLine1Y, "Fire", d2resource.Font6, true},
{labelResFireLine2X, labelResFireLine2Y, "Resistance", d2resource.Font6, true},
2020-07-26 18:52:54 +00:00
{labelResColdLine1X, labelResColdLine1Y, "Cold", d2resource.Font6, true},
{labelResColdLine2X, labelResColdLine2Y, "Resistance", d2resource.Font6, true},
2020-07-26 18:52:54 +00:00
{labelResLightLine1X, labelResLightLine1Y, "Lightning", d2resource.Font6, true},
{labelResLightLine2X, labelResLightLine2Y, "Resistance", d2resource.Font6, true},
2020-07-26 18:52:54 +00:00
{labelResPoisLine1X, labelResPoisLine1Y, "Poison", d2resource.Font6, true},
{labelResPoisLine2X, labelResPoisLine2Y, "Resistance", d2resource.Font6, true},
2020-07-26 18:52:54 +00:00
}
2020-08-11 22:01:33 +00:00
for _, cfg := range staticLabelConfigs {
label = s.createTextLabel(PanelText{
cfg.x, cfg.y,
cfg.txt,
cfg.font,
cfg.centerAlign,
})
label.Render(target)
}
}
func (s *HeroStatsPanel) initStatValueLabels() {
valueLabelConfigs := []struct {
assignTo **d2ui.Label
value int
x, y int
}{
{&s.labels.Level, s.heroState.Level, 112, 110},
{&s.labels.Experience, s.heroState.Experience, 200, 110},
{&s.labels.NextLevelExp, s.heroState.NextLevelExp, 330, 110},
{&s.labels.Strength, s.heroState.Strength, 175, 147},
{&s.labels.Dexterity, s.heroState.Dexterity, 175, 207},
{&s.labels.Vitality, s.heroState.Vitality, 175, 295},
{&s.labels.Energy, s.heroState.Energy, 175, 355},
{&s.labels.MaxStamina, s.heroState.MaxStamina, 330, 295},
{&s.labels.Stamina, int(s.heroState.Stamina), 370, 295},
{&s.labels.MaxHealth, s.heroState.MaxHealth, 330, 320},
{&s.labels.Health, s.heroState.Health, 370, 320},
{&s.labels.MaxMana, s.heroState.MaxMana, 330, 355},
{&s.labels.Mana, s.heroState.Mana, 370, 355},
}
for _, cfg := range valueLabelConfigs {
*cfg.assignTo = s.createStatValueLabel(cfg.value, cfg.x, cfg.y)
}
}
func (s *HeroStatsPanel) renderStatValues(target d2interface.Surface) {
s.renderStatValueNum(s.labels.Level, s.heroState.Level, target)
s.renderStatValueNum(s.labels.Experience, s.heroState.Experience, target)
s.renderStatValueNum(s.labels.NextLevelExp, s.heroState.NextLevelExp, target)
s.renderStatValueNum(s.labels.Strength, s.heroState.Strength, target)
s.renderStatValueNum(s.labels.Dexterity, s.heroState.Dexterity, target)
s.renderStatValueNum(s.labels.Vitality, s.heroState.Vitality, target)
s.renderStatValueNum(s.labels.Energy, s.heroState.Energy, target)
s.renderStatValueNum(s.labels.MaxHealth, s.heroState.MaxHealth, target)
s.renderStatValueNum(s.labels.Health, s.heroState.Health, target)
s.renderStatValueNum(s.labels.MaxStamina, s.heroState.MaxStamina, target)
s.renderStatValueNum(s.labels.Stamina, int(s.heroState.Stamina), target)
s.renderStatValueNum(s.labels.MaxMana, s.heroState.MaxMana, target)
s.renderStatValueNum(s.labels.Mana, s.heroState.Mana, target)
}
func (s *HeroStatsPanel) renderStatValueNum(label *d2ui.Label, value int,
target d2interface.Surface) {
label.SetText(strconv.Itoa(value))
label.Render(target)
}
2020-08-11 22:01:33 +00:00
func (s *HeroStatsPanel) createStatValueLabel(stat, x, y int) *d2ui.Label {
text := strconv.Itoa(stat)
return s.createTextLabel(PanelText{X: x, Y: y, Text: text, Font: d2resource.Font16, AlignCenter: true})
}
func (s *HeroStatsPanel) createTextLabel(element PanelText) *d2ui.Label {
label := s.uiManager.NewLabel(element.Font, d2resource.PaletteStatic)
if element.AlignCenter {
label.Alignment = d2gui.HorizontalAlignCenter
}
label.SetText(element.Text)
label.SetPosition(element.X, element.Y)
return label
}