mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-20 23:47:16 -05:00
* 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>
319 lines
9.2 KiB
Go
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
|
|
}
|