mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-10-16 15:13:40 -04:00
30b6f0cb4e
Bugs: 1. In D2 when Stamina is drained it does not regenerate until you completely stop 2. Stamina Bar does not get red when near the end
395 lines
10 KiB
Go
395 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
|
|
}
|