mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-09-06 11:34:15 -04:00
* 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>
457 lines
14 KiB
Go
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
|
|
}
|