OpenDiablo2/d2game/d2player/hero_stats_panel.go

396 lines
10 KiB
Go

package d2player
import (
"log"
"strconv"
"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/d2hero"
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
)
// PanelText represents text on the panel
type PanelText struct {
X int
Y int
Height int
Text string
Font string
AlignCenter bool
}
// 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
}
// HeroStatsPanel represents the hero status panel
type HeroStatsPanel struct {
asset *d2asset.AssetManager
uiManager *d2ui.UIManager
frame *d2ui.Sprite
panel *d2ui.Sprite
heroState *d2hero.HeroStatsState
heroName string
heroClass d2enum.Hero
renderer d2interface.Renderer
staticMenuImageCache *d2interface.Surface
labels *StatsPanelLabels
originX int
originY int
isOpen bool
}
// NewHeroStatsPanel creates a new hero status panel
func NewHeroStatsPanel(asset *d2asset.AssetManager, ui *d2ui.UIManager, heroName string, heroClass d2enum.Hero,
heroState *d2hero.HeroStatsState) *HeroStatsPanel {
originX := 0
originY := 0
return &HeroStatsPanel{
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, err = s.uiManager.NewSprite(d2resource.Frame, d2resource.PaletteSky)
if err != nil {
log.Print(err)
}
s.panel, err = s.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
if err != nil {
log.Print(err)
}
s.initStatValueLabels()
}
// IsOpen returns true if the hero status panel is open
func (s *HeroStatsPanel) IsOpen() bool {
return s.isOpen
}
// Toggle toggles the visibility of the hero status panel
func (s *HeroStatsPanel) Toggle() {
s.isOpen = !s.isOpen
}
// Open opens the hero status panel
func (s *HeroStatsPanel) Open() {
s.isOpen = true
}
// Close closed the hero status panel
func (s *HeroStatsPanel) Close() {
s.isOpen = false
}
// Render renders the hero status panel
func (s *HeroStatsPanel) Render(target d2interface.Surface) error {
if !s.isOpen {
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 {
return err
}
s.staticMenuImageCache = &surface
if err := s.renderStaticMenu(*s.staticMenuImageCache); err != nil {
return err
}
}
if err := target.Render(*s.staticMenuImageCache); err != nil {
return err
}
s.renderStatValues(target)
return nil
}
func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) error {
x, y := s.originX, s.originY
// Frame
// Top left
if err := s.frame.SetCurrentFrame(0); err != nil {
return err
}
w, h := s.frame.GetCurrentFrameSize()
s.frame.SetPosition(x, y+h)
if err := s.frame.Render(target); err != nil {
return err
}
x += w
y += h
// Top right
if err := s.frame.SetCurrentFrame(1); err != nil {
return err
}
_, h = s.frame.GetCurrentFrameSize()
s.frame.SetPosition(x, s.originY+h)
if err := s.frame.Render(target); err != nil {
return err
}
x = s.originX
// Right
if err := s.frame.SetCurrentFrame(2); err != nil {
return err
}
_, h = s.frame.GetCurrentFrameSize()
s.frame.SetPosition(x, y+h)
if err := s.frame.Render(target); err != nil {
return err
}
y += h
// Bottom left
if err := s.frame.SetCurrentFrame(3); err != nil {
return err
}
w, h = s.frame.GetCurrentFrameSize()
s.frame.SetPosition(x, y+h)
if err := s.frame.Render(target); err != nil {
return err
}
x += w
// Bottom right
if err := s.frame.SetCurrentFrame(4); err != nil {
return err
}
_, h = s.frame.GetCurrentFrameSize()
s.frame.SetPosition(x, y+h)
if err := s.frame.Render(target); err != nil {
return err
}
x, y = s.originX, s.originY
y += 64
x += 80
// Panel
// Top left
if err := s.panel.SetCurrentFrame(0); err != nil {
return err
}
w, h = s.panel.GetCurrentFrameSize()
s.panel.SetPosition(x, y+h)
if err := s.panel.Render(target); err != nil {
return err
}
x += w
// Top right
if err := s.panel.SetCurrentFrame(1); err != nil {
return err
}
_, h = s.panel.GetCurrentFrameSize()
s.panel.SetPosition(x, y+h)
if err := s.panel.Render(target); err != nil {
return err
}
y += h
// Bottom right
if err := s.panel.SetCurrentFrame(3); err != nil {
return err
}
_, h = s.panel.GetCurrentFrameSize()
s.panel.SetPosition(x, y+h)
if err := s.panel.Render(target); err != nil {
return err
}
// Bottom left
if err := s.panel.SetCurrentFrame(2); err != nil {
return err
}
w, h = s.panel.GetCurrentFrameSize()
s.panel.SetPosition(x-w, y+h)
if err := s.panel.Render(target); err != nil {
return err
}
var label *d2ui.Label
// all static labels are not stored since we use them only once to generate the image cache
//nolint:gomnd
var staticTextLabels = []PanelText{
{X: 110, Y: 100, Text: "Level", Font: d2resource.Font6, AlignCenter: true},
{X: 200, Y: 100, Text: "Experience", Font: d2resource.Font6, AlignCenter: true},
{X: 330, Y: 100, Text: "Next Level", Font: d2resource.Font6, AlignCenter: true},
{X: 100, Y: 150, Text: "Strength", Font: d2resource.Font6},
{X: 100, Y: 213, Text: "Dexterity", Font: d2resource.Font6},
{X: 100, Y: 300, Text: "Vitality", Font: d2resource.Font6},
{X: 100, Y: 360, Text: "Energy", Font: d2resource.Font6},
{X: 280, Y: 260, Text: "Defense", Font: d2resource.Font6},
{X: 280, Y: 300, Text: "Stamina", Font: d2resource.Font6, AlignCenter: true},
{X: 280, Y: 322, Text: "Life", Font: d2resource.Font6, AlignCenter: true},
{X: 280, Y: 360, Text: "Mana", Font: d2resource.Font6, AlignCenter: true},
// can't use "Fire\nResistance" because line spacing is too big and breaks the layout
{X: 310, Y: 395, Text: "Fire", Font: d2resource.Font6, AlignCenter: true},
{X: 310, Y: 402, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true},
{X: 310, Y: 420, Text: "Cold", Font: d2resource.Font6, AlignCenter: true},
{X: 310, Y: 427, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true},
{X: 310, Y: 445, Text: "Lightning", Font: d2resource.Font6, AlignCenter: true},
{X: 310, Y: 452, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true},
{X: 310, Y: 468, Text: "Poison", Font: d2resource.Font6, AlignCenter: true},
{X: 310, Y: 477, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true},
}
for _, textElement := range staticTextLabels {
label = s.createTextLabel(textElement)
label.Render(target)
}
// hero name and class are part of the static image cache since they don't change after we enter the world
label = s.createTextLabel(PanelText{X: 165, Y: 72, Text: s.heroName, Font: d2resource.Font16, AlignCenter: true})
label.Render(target)
label = s.createTextLabel(PanelText{X: 330, Y: 72, Text: s.heroClass.String(), Font: d2resource.Font16, AlignCenter: true})
label.Render(target)
return nil
}
func (s *HeroStatsPanel) initStatValueLabels() {
s.labels.Level = s.createStatValueLabel(s.heroState.Level, 112, 110)
s.labels.Experience = s.createStatValueLabel(s.heroState.Experience, 200, 110)
s.labels.NextLevelExp = s.createStatValueLabel(s.heroState.NextLevelExp, 330, 110)
s.labels.Strength = s.createStatValueLabel(s.heroState.Strength, 175, 147)
s.labels.Dexterity = s.createStatValueLabel(s.heroState.Dexterity, 175, 207)
s.labels.Vitality = s.createStatValueLabel(s.heroState.Vitality, 175, 295)
s.labels.Energy = s.createStatValueLabel(s.heroState.Energy, 175, 355)
s.labels.MaxStamina = s.createStatValueLabel(s.heroState.MaxStamina, 330, 295)
s.labels.Stamina = s.createStatValueLabel(int(s.heroState.Stamina), 370, 295)
s.labels.MaxHealth = s.createStatValueLabel(s.heroState.MaxHealth, 330, 320)
s.labels.Health = s.createStatValueLabel(s.heroState.Health, 370, 320)
s.labels.MaxMana = s.createStatValueLabel(s.heroState.MaxMana, 330, 355)
s.labels.Mana = s.createStatValueLabel(s.heroState.Mana, 370, 355)
}
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)
}
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
}