1
1
mirror of https://github.com/OpenDiablo2/OpenDiablo2 synced 2025-02-20 23:47:16 -05:00
OpenDiablo2/d2game/d2player/hero_stats_panel.go
juander-ux e5dae4e5d8
Inital skilltree panel implementation (#782)
* d2ui/UIFrame: Refactor into its own class

it's not useful to have the handling of frames for the
inventory/herostate/skilltree/quest panels individually in each of
those.

* d2ui/button: Fix crash when a buttonlayout was not allowing FrameChange

When AllowFrameChange is false we do not create pressedSurface. So if we
press the button the game will crash.

* d2ui/button: Allow label-only buttons

At least for the skillmenu we need buttons were the graphic size does
not match the buttonsize. So let's render the graphic in there and make
the button label only.

* d2hero/hero_state_factory: Give all heroes their class specific skills

* d2player/gamecontrols: Fix wrong inventory/stats layouts for exp chars

For Druid/Assassin the inventory frame was rendered for a 640x480
resolution. This brings it in line with all other characters.

* d2player: Add inital Skilltree panel

* d2player/game_controls: Enable skilltree

Note here, that the inventory panel and skilltree panel can overlap.

* d2player/skilltree: Add skillicon rendering

Note here, that I couldn't figure out how to render them dark if no
skillpoints are invested.

Signed-off-by: juander <juander@rumtueddeln.de>
2020-10-22 12:54:45 -04:00

319 lines
9.2 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.UIFrame
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 = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft)
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 {
s.frame.Render(target)
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
}