1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2024-11-04 17:27:16 -05:00
OpenDiablo2/d2game/d2player/hero_stats_panel.go
gucio321 5cd404e4a5
d2ui.Frame refactor (#994)
* d2ui.Frame refactor

* removed unneccessery d2asset.AssetManager argument from d2ui.NewUIFrame

* d2ui.Frame refactor

* removed unneccessery d2asset.AssetManager argument from d2ui.NewUIFrame

Co-authored-by: M. Sz <mszeptuch@protonmail.com>
Co-authored-by: Tim Sarbin <tim.sarbin@gmail.com>
Co-authored-by: gravestench <dknuth0101@gmail.com>
2021-01-06 21:53:01 -08:00

457 lines
14 KiB
Go

package d2player
import (
"strconv"
"strings"
"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/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, 74
labelExperienceX, labelExperienceY = 200, 100
labelNextLevelX, labelNextLevelY = 330, 100
labelStrengthX, labelStrengthY = 100, 150
labelDexterityX, labelDexterityY = 100, 213
labelVitalityX, labelVitalityY = 95, 300
labelEnergyX, labelEnergyY = 100, 360
labelDefenseX, labelDefenseY = 280, 260
labelStaminaX, labelStaminaY = 280, 300
labelLifeX, labelLifeY = 280, 322
labelManaX, labelManaY = 280, 360
labelResFireLine1X, labelResFireLine1Y = 310, 396
labelResFireLine2X, labelResFireLine2Y = 310, 403
labelResColdLine1X, labelResColdLine1Y = 310, 444
labelResColdLine2X, labelResColdLine2Y = 310, 452
labelResLightLine1X, labelResLightLine1Y = 310, 420
labelResLightLine2X, labelResLightLine2Y = 310, 428
labelResPoisLine1X, labelResPoisLine1Y = 310, 468
labelResPoisLine2X, labelResPoisLine2Y = 310, 476
)
const (
heroStatsCloseButtonX, heroStatsCloseButtonY = 208, 453
addStatSocketOffsetX, addStatSocketOffsetY = -3, 34
)
const (
newStatsRemainingPointsFieldX, newStatsRemainingPointsFieldY = 83, 430
newStatsRemainingPointsLabelX = 92
newStatsRemainingPointsLabel1Y = 411
newStatsRemainingPointsLabel2Y = 418
newStatsRemainingPointsValueX, newStatsRemainingPointsValueY = 188, 411
)
// PanelText represents text on the panel
type PanelText struct {
X int
Y 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
}
// NewHeroStatsPanel creates a new hero status panel
func NewHeroStatsPanel(asset *d2asset.AssetManager,
ui *d2ui.UIManager,
heroName string,
heroClass d2enum.Hero,
l d2util.LogLevel,
heroState *d2hero.HeroStatsState) *HeroStatsPanel {
originX := 0
originY := 0
hsp := &HeroStatsPanel{
asset: asset,
uiManager: ui,
originX: originX,
originY: originY,
heroState: heroState,
heroName: heroName,
heroClass: heroClass,
labels: &StatsPanelLabels{},
}
hsp.Logger = d2util.NewLogger()
hsp.Logger.SetLevel(l)
hsp.Logger.SetPrefix(logPrefix)
return hsp
}
// HeroStatsPanel represents the hero status panel
type HeroStatsPanel struct {
asset *d2asset.AssetManager
uiManager *d2ui.UIManager
panel *d2ui.Sprite
heroState *d2hero.HeroStatsState
heroName string
heroClass d2enum.Hero
labels *StatsPanelLabels
onCloseCb func()
panelGroup *d2ui.WidgetGroup
newStatPoints *d2ui.WidgetGroup
remainingPoints *d2ui.Label
originX int
originY int
isOpen bool
*d2util.Logger
}
// Load the data for the hero status panel
func (s *HeroStatsPanel) Load() {
var err error
s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel)
s.newStatPoints = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel)
frame := s.uiManager.NewUIFrame(d2ui.FrameLeft)
s.panelGroup.AddWidget(frame)
s.panel, err = s.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
if err != nil {
s.Error(err.Error())
}
w, h := frame.GetSize()
staticPanel := s.uiManager.NewCustomWidgetCached(s.renderStaticMenu, w, h)
s.panelGroup.AddWidget(staticPanel)
closeButton := s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "")
closeButton.SetVisible(false)
closeButton.SetPosition(heroStatsCloseButtonX, heroStatsCloseButtonY)
closeButton.OnActivated(func() { s.Close() })
s.panelGroup.AddWidget(closeButton)
s.loadNewStatPoints()
s.setLayout()
s.initStatValueLabels()
s.panelGroup.SetVisible(false)
}
func (s *HeroStatsPanel) loadNewStatPoints() {
field, err := s.uiManager.NewSprite(d2resource.HeroStatsPanelStatsPoints, d2resource.PaletteSky)
if err != nil {
s.Error(err.Error())
}
field.SetPosition(newStatsRemainingPointsFieldX, newStatsRemainingPointsFieldY)
s.newStatPoints.AddWidget(field)
label1 := s.uiManager.NewLabel(d2resource.Font6, d2resource.PaletteSky)
label1.SetPosition(newStatsRemainingPointsLabelX, newStatsRemainingPointsLabel1Y)
label1.SetText(s.asset.TranslateString("strchrstat"))
label1.Color[0] = d2util.Color(d2gui.ColorRed)
s.newStatPoints.AddWidget(label1)
label2 := s.uiManager.NewLabel(d2resource.Font6, d2resource.PaletteSky)
label2.SetPosition(newStatsRemainingPointsLabelX, newStatsRemainingPointsLabel2Y)
label2.SetText(s.asset.TranslateString("strchrrema"))
label2.Color[0] = d2util.Color(d2gui.ColorRed)
s.newStatPoints.AddWidget(label2)
s.remainingPoints = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky)
s.remainingPoints.SetText(strconv.Itoa(s.heroState.StatsPoints))
s.remainingPoints.SetPosition(newStatsRemainingPointsValueX, newStatsRemainingPointsValueY)
s.remainingPoints.Alignment = d2ui.HorizontalAlignCenter
s.newStatPoints.AddWidget(s.remainingPoints)
buttons := []struct {
x int
y int
cb func()
}{
{205, 140, func() {
s.heroState.Strength++
}},
{205, 201, func() {
s.heroState.Dexterity++
}},
{205, 286, func() {
s.heroState.Vitality++
}},
{205, 347, func() {
s.heroState.Energy++
}},
}
var socket *d2ui.Sprite
var button *d2ui.Button
for _, i := range buttons {
currentValue := i
socket, err = s.uiManager.NewSprite(d2resource.HeroStatsPanelSocket, d2resource.PaletteSky)
if err != nil {
s.Error(err.Error())
}
socket.SetPosition(i.x+addStatSocketOffsetX, i.y+addStatSocketOffsetY)
s.newStatPoints.AddWidget(socket)
button = s.uiManager.NewButton(d2ui.ButtonTypeAddSkill, d2resource.PaletteSky)
button.SetPosition(i.x, i.y)
button.OnActivated(func() {
currentValue.cb()
s.heroState.StatsPoints--
s.remainingPoints.SetText(strconv.Itoa(s.heroState.StatsPoints))
s.setStatValues()
s.setLayout()
})
s.newStatPoints.AddWidget(button)
}
}
func (s *HeroStatsPanel) setLayout() {
s.newStatPoints.SetVisible(s.heroState.StatsPoints > 0 && s.IsOpen())
}
// 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() {
if s.isOpen {
s.Close()
} else {
s.Open()
}
}
// Open opens the hero status panel
func (s *HeroStatsPanel) Open() {
s.isOpen = true
s.panelGroup.SetVisible(true)
s.setLayout()
}
// Close closed the hero status panel
func (s *HeroStatsPanel) Close() {
s.isOpen = false
s.panelGroup.SetVisible(false)
s.setLayout()
s.onCloseCb()
}
// SetOnCloseCb the callback run on closing the HeroStatsPanel
func (s *HeroStatsPanel) SetOnCloseCb(cb func()) {
s.onCloseCb = cb
}
// Advance updates labels on the panel
func (s *HeroStatsPanel) Advance(elapsed float64) {
if !s.isOpen {
return
}
s.setStatValues()
}
func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) {
s.renderStaticPanelFrames(target)
s.renderStaticLabels(target)
}
// nolint:dupl // see quest_log.go.renderStaticPanelFrames comment
func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) {
frames := []int{
statsPanelTopLeft,
statsPanelTopRight,
statsPanelBottomRight,
statsPanelBottomLeft,
}
currentX := s.originX + statsPanelOffsetX
currentY := s.originY + statsPanelOffsetY
for _, frameIndex := range frames {
if err := s.panel.SetCurrentFrame(frameIndex); err != nil {
s.Error(err.Error())
}
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)
}
s.panel.Render(target)
}
}
func (s *HeroStatsPanel) renderStaticLabels(target d2interface.Surface) {
var label *d2ui.Label
fr := strings.Split(s.asset.TranslateString("strchrfir"), "\n")
lr := strings.Split(s.asset.TranslateString("strchrlit"), "\n")
cr := strings.Split(s.asset.TranslateString("strchrcol"), "\n")
pr := strings.Split(s.asset.TranslateString("strchrpos"), "\n")
// 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.asset.TranslateString(s.heroClass), d2resource.Font16, true},
{labelLevelX, labelLevelY, s.asset.TranslateString("strchrlvl"), d2resource.Font6, true},
{labelExperienceX, labelExperienceY, s.asset.TranslateString("strchrexp"), d2resource.Font6, true},
{labelNextLevelX, labelNextLevelY, s.asset.TranslateString("strchrnxtlvl"), d2resource.Font6, true},
{labelStrengthX, labelStrengthY, s.asset.TranslateString("strchrstr"), d2resource.Font6, false},
{labelDexterityX, labelDexterityY, s.asset.TranslateString("strchrdex"), d2resource.Font6, false},
{labelVitalityX, labelVitalityY, s.asset.TranslateString("strchrvit"), d2resource.Font6, false},
{labelEnergyX, labelEnergyY, s.asset.TranslateString("strchreng"), d2resource.Font6, false},
{labelDefenseX, labelDefenseY, s.asset.TranslateString("strchrdef"), d2resource.Font6, false},
{labelStaminaX, labelStaminaY, s.asset.TranslateString("strchrstm"), d2resource.Font6, true},
{labelLifeX, labelLifeY, s.asset.TranslateString("strchrlif"), d2resource.Font6, true},
{labelManaX, labelManaY, s.asset.TranslateString("strchrman"), d2resource.Font6, true},
// can't use "Fire\nResistance" because line spacing is too big and breaks the layout
{labelResFireLine1X, labelResFireLine1Y, fr[0], d2resource.Font6, true},
{labelResFireLine2X, labelResFireLine2Y, fr[len(fr)-1], d2resource.Font6, true},
{labelResColdLine1X, labelResColdLine1Y, cr[0], d2resource.Font6, true},
{labelResColdLine2X, labelResColdLine2Y, cr[len(cr)-1], d2resource.Font6, true},
{labelResLightLine1X, labelResLightLine1Y, lr[0], d2resource.Font6, true},
{labelResLightLine2X, labelResLightLine2Y, lr[len(lr)-1], d2resource.Font6, true},
{labelResPoisLine1X, labelResPoisLine1Y, pr[0], d2resource.Font6, true},
{labelResPoisLine2X, labelResPoisLine2Y, pr[len(pr)-1], d2resource.Font6, true},
}
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) setStatValues() {
s.labels.Level.SetText(strconv.Itoa(s.heroState.Level))
s.labels.Experience.SetText(strconv.Itoa(s.heroState.Experience))
s.labels.NextLevelExp.SetText(strconv.Itoa(s.heroState.NextLevelExp))
s.labels.Strength.SetText(strconv.Itoa(s.heroState.Strength))
s.labels.Dexterity.SetText(strconv.Itoa(s.heroState.Dexterity))
s.labels.Vitality.SetText(strconv.Itoa(s.heroState.Vitality))
s.labels.Energy.SetText(strconv.Itoa(s.heroState.Energy))
s.labels.MaxHealth.SetText(strconv.Itoa(s.heroState.MaxHealth))
s.labels.Health.SetText(strconv.Itoa(s.heroState.Health))
s.labels.MaxStamina.SetText(strconv.Itoa(s.heroState.MaxStamina))
s.labels.Stamina.SetText(strconv.Itoa(int(s.heroState.Stamina)))
s.labels.MaxMana.SetText(strconv.Itoa(s.heroState.MaxMana))
s.labels.Mana.SetText(strconv.Itoa(s.heroState.Mana))
}
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 = d2ui.HorizontalAlignCenter
}
label.SetText(element.Text)
label.SetPosition(element.X, element.Y)
s.panelGroup.AddWidget(label)
return label
}