mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-02-04 07:37:48 -05:00
Ui panel refactor part 2 (#921)
* d2ui/skilltree: Don't render availSPLabel this is handled by the ui_manager now. * d2ui/custom_widget: Allow them to be cached into static images * d2player/hero_stats_panel: Remove render() function from game_controls all ui elements are now grouped into a WidgetGroup, thus rendering is done by the ui manager. * d2player/hero_stats_panel: Remove unnecessary widgets from struct we don't need to store them in the HeroStatsPanel struct anymore as they are completly handled by the uiManager. * d2ui/widget_group: Remove priority member this is already defined by the BaseWidget. * d2ui/widget: Move uiManager.contains() to the baseWidgets this method makes more sense on a widget anyways. * d2ui/widget: Add methods to handle widget hovering * d2ui/custom_widget: Require define width/height since the custom render() method can do whatever, we need the user to specify the width/height such that GetSize() calls are meaningful. * d2ui/widget: Allow widgets to return the uiManager * d2player/HUD: Refactor health/mana globe into its own widget * d2player/hud: Refactor load() seperate each type of loading into its own method. * d2player/HUD: Move stamina/exp bar into widgets * d2player/HUD: Refactor left/right skills into widget * d2ui/custom_widget: cached custom widgets should use widget.x/y since we render to an image, we use widget.x/y to position the cached image. * d2player/HUD: User cached custom widget for all static images
This commit is contained in:
parent
0d691dbffa
commit
12821147ce
@ -9,11 +9,29 @@ var _ Widget = &CustomWidget{}
|
||||
type CustomWidget struct {
|
||||
*BaseWidget
|
||||
renderFunc func(target d2interface.Surface)
|
||||
cached bool
|
||||
cachedImg *d2interface.Surface
|
||||
}
|
||||
|
||||
// NewCustomWidgetCached creates a new widget and caches anything rendered via the
|
||||
// renderFunc into a static image to be displayed
|
||||
func (ui *UIManager) NewCustomWidgetCached(renderFunc func(target d2interface.Surface), width, height int) *CustomWidget {
|
||||
c := ui.NewCustomWidget(renderFunc, width, height)
|
||||
c.cached = true
|
||||
|
||||
// render using the renderFunc to a cache
|
||||
surface := ui.Renderer().NewSurface(width, height)
|
||||
c.cachedImg = &surface
|
||||
renderFunc(*c.cachedImg)
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// NewCustomWidget creates a new widget with custom render function
|
||||
func (ui *UIManager) NewCustomWidget(renderFunc func(target d2interface.Surface)) *CustomWidget {
|
||||
func (ui *UIManager) NewCustomWidget(renderFunc func(target d2interface.Surface), width, height int) *CustomWidget {
|
||||
base := NewBaseWidget(ui)
|
||||
base.width = width
|
||||
base.height = height
|
||||
|
||||
return &CustomWidget{
|
||||
BaseWidget: base,
|
||||
@ -23,7 +41,13 @@ func (ui *UIManager) NewCustomWidget(renderFunc func(target d2interface.Surface)
|
||||
|
||||
// Render draws the custom widget
|
||||
func (c *CustomWidget) Render(target d2interface.Surface) {
|
||||
c.renderFunc(target)
|
||||
if c.cached {
|
||||
target.PushTranslation(c.GetPosition())
|
||||
target.Render(*c.cachedImg)
|
||||
target.Pop()
|
||||
} else {
|
||||
c.renderFunc(target)
|
||||
}
|
||||
}
|
||||
|
||||
// Advance is a no-op
|
||||
|
@ -54,8 +54,9 @@ func (ui *UIManager) Reset() {
|
||||
// addWidgetGroup adds a widgetGroup to the UI manager and sorts by priority
|
||||
func (ui *UIManager) addWidgetGroup(group *WidgetGroup) {
|
||||
ui.widgetsGroups = append(ui.widgetsGroups, group)
|
||||
|
||||
sort.SliceStable(ui.widgetsGroups, func(i, j int) bool {
|
||||
return ui.widgetsGroups[i].priority < ui.widgetsGroups[j].priority
|
||||
return ui.widgetsGroups[i].renderPriority < ui.widgetsGroups[j].renderPriority
|
||||
})
|
||||
}
|
||||
|
||||
@ -83,7 +84,7 @@ func (ui *UIManager) OnMouseButtonUp(event d2interface.MouseEvent) bool {
|
||||
// activate previously pressed widget if cursor is still hovering
|
||||
w := ui.pressedWidget
|
||||
|
||||
if w != nil && ui.contains(w, ui.CursorX, ui.CursorY) && w.GetVisible() && w.GetEnabled() {
|
||||
if w != nil && w.Contains(ui.CursorX, ui.CursorY) && w.GetVisible() && w.GetEnabled() {
|
||||
w.Activate()
|
||||
}
|
||||
|
||||
@ -96,6 +97,17 @@ func (ui *UIManager) OnMouseButtonUp(event d2interface.MouseEvent) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// OnMouseMove is the mouse move event handler
|
||||
func (ui *UIManager) OnMouseMove(event d2interface.MouseMoveEvent) bool {
|
||||
for _, w := range ui.widgetsGroups {
|
||||
if w.GetVisible() {
|
||||
w.OnMouseMove(event.X(), event.Y())
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// OnMouseButtonDown is the mouse button down event handler
|
||||
func (ui *UIManager) OnMouseButtonDown(event d2interface.MouseEvent) bool {
|
||||
ui.CursorX, ui.CursorY = event.X(), event.Y()
|
||||
@ -103,7 +115,7 @@ func (ui *UIManager) OnMouseButtonDown(event d2interface.MouseEvent) bool {
|
||||
// find and press a widget on screen
|
||||
ui.pressedWidget = nil
|
||||
for _, w := range ui.clickableWidgets {
|
||||
if ui.contains(w, ui.CursorX, ui.CursorY) && w.GetVisible() && w.GetEnabled() {
|
||||
if w.Contains(ui.CursorX, ui.CursorY) && w.GetVisible() && w.GetEnabled() {
|
||||
w.SetPressed(true)
|
||||
ui.pressedWidget = w
|
||||
ui.clickSfx.Play()
|
||||
@ -135,14 +147,6 @@ func (ui *UIManager) Render(target d2interface.Surface) {
|
||||
}
|
||||
}
|
||||
|
||||
// contains determines whether a given x,y coordinate lands within a Widget
|
||||
func (ui *UIManager) contains(w Widget, x, y int) bool {
|
||||
wx, wy := w.GetPosition()
|
||||
ww, wh := w.GetSize()
|
||||
|
||||
return x >= wx && x <= wx+ww && y >= wy && y <= wy+wh
|
||||
}
|
||||
|
||||
// Advance updates all of the UI elements
|
||||
func (ui *UIManager) Advance(elapsed float64) {
|
||||
for _, widget := range ui.widgets {
|
||||
|
@ -11,6 +11,8 @@ const (
|
||||
RenderPrioritySkilltree
|
||||
// RenderPrioritySkilltreeIcon is the priority for the skilltree icons
|
||||
RenderPrioritySkilltreeIcon
|
||||
// RenderPriorityHeroStatsPanel is the priority for the hero_stats_panel
|
||||
RenderPriorityHeroStatsPanel
|
||||
// RenderPriorityForeground is the last element drawn
|
||||
RenderPriorityForeground
|
||||
)
|
||||
@ -19,6 +21,13 @@ const (
|
||||
type Widget interface {
|
||||
Drawable
|
||||
bindManager(ui *UIManager)
|
||||
GetManager() (ui *UIManager)
|
||||
OnHoverStart(callback func())
|
||||
OnHoverEnd(callback func())
|
||||
isHovered() bool
|
||||
hoverStart()
|
||||
hoverEnd()
|
||||
Contains(x, y int) (contained bool)
|
||||
}
|
||||
|
||||
// ClickableWidget defines an object that can be clicked
|
||||
@ -41,6 +50,10 @@ type BaseWidget struct {
|
||||
height int
|
||||
renderPriority RenderPriority
|
||||
visible bool
|
||||
|
||||
hovered bool
|
||||
onHoverStartCb func()
|
||||
onHoverEndCb func()
|
||||
}
|
||||
|
||||
// NewBaseWidget creates a new BaseWidget with defaults
|
||||
@ -100,3 +113,46 @@ func (b *BaseWidget) GetRenderPriority() (prio RenderPriority) {
|
||||
func (b *BaseWidget) SetRenderPriority(prio RenderPriority) {
|
||||
b.renderPriority = prio
|
||||
}
|
||||
|
||||
// OnHoverStart sets a function that is called if the hovering of the widget starts
|
||||
func (b *BaseWidget) OnHoverStart(callback func()) {
|
||||
b.onHoverStartCb = callback
|
||||
}
|
||||
|
||||
// HoverStart is called when the hovering of the widget starts
|
||||
func (b *BaseWidget) hoverStart() {
|
||||
b.hovered = true
|
||||
if b.onHoverStartCb != nil {
|
||||
b.onHoverStartCb()
|
||||
}
|
||||
}
|
||||
|
||||
// OnHoverEnd sets a function that is called if the hovering of the widget ends
|
||||
func (b *BaseWidget) OnHoverEnd(callback func()) {
|
||||
b.onHoverEndCb = callback
|
||||
}
|
||||
|
||||
// hoverEnd is called when the widget hovering ends
|
||||
func (b *BaseWidget) hoverEnd() {
|
||||
b.hovered = false
|
||||
if b.onHoverEndCb != nil {
|
||||
b.onHoverEndCb()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *BaseWidget) isHovered() bool {
|
||||
return b.hovered
|
||||
}
|
||||
|
||||
// Contains determines whether a given x,y coordinate lands within a Widget
|
||||
func (b *BaseWidget) Contains(x, y int) bool {
|
||||
wx, wy := b.GetPosition()
|
||||
ww, wh := b.GetSize()
|
||||
|
||||
return x >= wx && x <= wx+ww && y >= wy && y <= wy+wh
|
||||
}
|
||||
|
||||
// GetManager returns the uiManager
|
||||
func (b *BaseWidget) GetManager() (ui *UIManager) {
|
||||
return b.manager
|
||||
}
|
||||
|
@ -13,8 +13,7 @@ var _ Widget = &WidgetGroup{}
|
||||
// widgets at once.
|
||||
type WidgetGroup struct {
|
||||
*BaseWidget
|
||||
entries []Widget
|
||||
priority RenderPriority
|
||||
entries []Widget
|
||||
}
|
||||
|
||||
// NewWidgetGroup creates a new widget group
|
||||
@ -85,3 +84,16 @@ func (wg *WidgetGroup) SetVisible(visible bool) {
|
||||
entry.SetVisible(visible)
|
||||
}
|
||||
}
|
||||
|
||||
// OnMouseMove handles mouse move events
|
||||
func (wg *WidgetGroup) OnMouseMove(x, y int) {
|
||||
for _, entry := range wg.entries {
|
||||
if entry.Contains(x, y) && entry.GetVisible() {
|
||||
if !entry.isHovered() {
|
||||
entry.hoverStart()
|
||||
}
|
||||
} else if entry.isHovered() {
|
||||
entry.hoverEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ const (
|
||||
leftSkillX,
|
||||
leftSkillY,
|
||||
leftSkillWidth,
|
||||
leftSkillHeight = 115, 550, 50, 50
|
||||
leftSkillHeight = 117, 550, 50, 50
|
||||
|
||||
newStatsX,
|
||||
newStatsY,
|
||||
@ -92,7 +92,7 @@ const (
|
||||
rightSkillX,
|
||||
rightSkillY,
|
||||
rightSkillWidth,
|
||||
rightSkillHeight = 634, 550, 50, 50
|
||||
rightSkillHeight = 635, 550, 50, 50
|
||||
|
||||
hpGlobeX,
|
||||
hpGlobeY,
|
||||
@ -783,7 +783,6 @@ func (g *GameControls) Render(target d2interface.Surface) error {
|
||||
}
|
||||
|
||||
func (g *GameControls) renderPanels(target d2interface.Surface) error {
|
||||
g.heroStatsPanel.Render(target)
|
||||
g.inventory.Render(target)
|
||||
|
||||
return nil
|
||||
|
145
d2game/d2player/globeWidget.go
Normal file
145
d2game/d2player/globeWidget.go
Normal file
@ -0,0 +1,145 @@
|
||||
package d2player
|
||||
|
||||
import (
|
||||
"image"
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||
)
|
||||
|
||||
type globeType = int
|
||||
|
||||
const (
|
||||
typeHealthGlobe globeType = iota
|
||||
typeManaGlobe
|
||||
)
|
||||
|
||||
const (
|
||||
globeHeight = 80
|
||||
globeWidth = 80
|
||||
|
||||
globeSpriteOffsetX = 28
|
||||
globeSpriteOffsetY = -5
|
||||
|
||||
healthStatusOffsetX = 30
|
||||
healthStatusOffsetY = -13
|
||||
|
||||
manaStatusOffsetX = 7
|
||||
manaStatusOffsetY = -12
|
||||
|
||||
manaGlobeScreenOffsetX = 117
|
||||
)
|
||||
|
||||
// static check that globeWidget implements Widget
|
||||
var _ d2ui.Widget = &globeWidget{}
|
||||
|
||||
type globeFrame struct {
|
||||
sprite *d2ui.Sprite
|
||||
offsetX int
|
||||
offsetY int
|
||||
idx int
|
||||
}
|
||||
|
||||
func (gf *globeFrame) setFrameIndex() {
|
||||
if err := gf.sprite.SetCurrentFrame(gf.idx); err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (gf *globeFrame) setPosition(x, y int) {
|
||||
gf.sprite.SetPosition(x+gf.offsetX, y+gf.offsetY)
|
||||
}
|
||||
|
||||
func (gf *globeFrame) getSize() (x, y int) {
|
||||
w, h := gf.sprite.GetSize()
|
||||
return w + gf.offsetX, h + gf.offsetY
|
||||
}
|
||||
|
||||
type globeWidget struct {
|
||||
*d2ui.BaseWidget
|
||||
value *int
|
||||
valueMax *int
|
||||
globe *globeFrame
|
||||
overlap *globeFrame
|
||||
}
|
||||
|
||||
func newGlobeWidget(ui *d2ui.UIManager, x, y int, gtype globeType, value, valueMax *int) *globeWidget {
|
||||
var globe, overlap *globeFrame
|
||||
|
||||
base := d2ui.NewBaseWidget(ui)
|
||||
base.SetPosition(x, y)
|
||||
|
||||
if gtype == typeHealthGlobe {
|
||||
globe = &globeFrame{
|
||||
offsetX: healthStatusOffsetX,
|
||||
offsetY: healthStatusOffsetY,
|
||||
idx: frameHealthStatus,
|
||||
}
|
||||
overlap = &globeFrame{
|
||||
offsetX: globeSpriteOffsetX,
|
||||
offsetY: globeSpriteOffsetY,
|
||||
idx: frameHealthStatus,
|
||||
}
|
||||
} else if gtype == typeManaGlobe {
|
||||
globe = &globeFrame{
|
||||
offsetX: manaStatusOffsetX,
|
||||
offsetY: manaStatusOffsetY,
|
||||
idx: frameManaStatus,
|
||||
}
|
||||
overlap = &globeFrame{
|
||||
offsetX: rightGlobeOffsetX,
|
||||
offsetY: rightGlobeOffsetY,
|
||||
idx: frameRightGlobe,
|
||||
}
|
||||
}
|
||||
|
||||
return &globeWidget{
|
||||
BaseWidget: base,
|
||||
value: value,
|
||||
valueMax: valueMax,
|
||||
globe: globe,
|
||||
overlap: overlap,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *globeWidget) load() {
|
||||
var err error
|
||||
|
||||
g.globe.sprite, err = g.GetManager().NewSprite(d2resource.HealthManaIndicator, d2resource.PaletteSky)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
g.globe.setFrameIndex()
|
||||
|
||||
g.overlap.sprite, err = g.GetManager().NewSprite(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
g.overlap.setFrameIndex()
|
||||
}
|
||||
|
||||
// Render draws the widget to the screen
|
||||
func (g *globeWidget) Render(target d2interface.Surface) {
|
||||
valuePercent := float64(*g.value) / float64(*g.valueMax)
|
||||
barHeight := int(valuePercent * float64(globeHeight))
|
||||
|
||||
maskRect := image.Rect(0, globeHeight-barHeight, globeWidth, globeHeight)
|
||||
|
||||
g.globe.setPosition(g.GetPosition())
|
||||
g.globe.sprite.RenderSection(target, maskRect)
|
||||
|
||||
g.overlap.setPosition(g.GetPosition())
|
||||
g.overlap.sprite.Render(target)
|
||||
}
|
||||
|
||||
func (g *globeWidget) GetSize() (x, y int) {
|
||||
return g.overlap.getSize()
|
||||
}
|
||||
|
||||
func (g *globeWidget) Advance(elapsed float64) error {
|
||||
return nil
|
||||
}
|
@ -84,18 +84,15 @@ type StatsPanelLabels struct {
|
||||
|
||||
// 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
|
||||
closeButton *d2ui.Button
|
||||
onCloseCb func()
|
||||
asset *d2asset.AssetManager
|
||||
uiManager *d2ui.UIManager
|
||||
panel *d2ui.Sprite
|
||||
heroState *d2hero.HeroStatsState
|
||||
heroName string
|
||||
heroClass d2enum.Hero
|
||||
labels *StatsPanelLabels
|
||||
onCloseCb func()
|
||||
panelGroup *d2ui.WidgetGroup
|
||||
|
||||
originX int
|
||||
originY int
|
||||
@ -111,7 +108,6 @@ func NewHeroStatsPanel(asset *d2asset.AssetManager, ui *d2ui.UIManager, heroName
|
||||
return &HeroStatsPanel{
|
||||
asset: asset,
|
||||
uiManager: ui,
|
||||
renderer: ui.Renderer(),
|
||||
originX: originX,
|
||||
originY: originY,
|
||||
heroState: heroState,
|
||||
@ -125,19 +121,30 @@ func NewHeroStatsPanel(asset *d2asset.AssetManager, ui *d2ui.UIManager, heroName
|
||||
func (s *HeroStatsPanel) Load() {
|
||||
var err error
|
||||
|
||||
s.frame = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft)
|
||||
s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel)
|
||||
|
||||
s.closeButton = s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "")
|
||||
s.closeButton.SetVisible(false)
|
||||
s.closeButton.SetPosition(heroStatsCloseButtonX, heroStatsCloseButtonY)
|
||||
s.closeButton.OnActivated(func() { s.Close() })
|
||||
frame := d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft)
|
||||
s.panelGroup.AddWidget(frame)
|
||||
|
||||
s.panel, err = s.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
fw, fh := frame.GetFrameBounds()
|
||||
fc := frame.GetFrameCount()
|
||||
w, h := fw*fc, fh*fc
|
||||
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.initStatValueLabels()
|
||||
s.panelGroup.SetVisible(false)
|
||||
}
|
||||
|
||||
// IsOpen returns true if the hero status panel is open
|
||||
@ -157,13 +164,13 @@ func (s *HeroStatsPanel) Toggle() {
|
||||
// Open opens the hero status panel
|
||||
func (s *HeroStatsPanel) Open() {
|
||||
s.isOpen = true
|
||||
s.closeButton.SetVisible(true)
|
||||
s.panelGroup.SetVisible(true)
|
||||
}
|
||||
|
||||
// Close closed the hero status panel
|
||||
func (s *HeroStatsPanel) Close() {
|
||||
s.isOpen = false
|
||||
s.closeButton.SetVisible(false)
|
||||
s.panelGroup.SetVisible(false)
|
||||
s.onCloseCb()
|
||||
}
|
||||
|
||||
@ -172,43 +179,21 @@ func (s *HeroStatsPanel) SetOnCloseCb(cb func()) {
|
||||
s.onCloseCb = cb
|
||||
}
|
||||
|
||||
// Render renders the hero status panel
|
||||
func (s *HeroStatsPanel) Render(target d2interface.Surface) {
|
||||
// Advance updates labels on the panel
|
||||
func (s *HeroStatsPanel) Advance(elapsed float64) {
|
||||
if !s.isOpen {
|
||||
return
|
||||
}
|
||||
|
||||
if s.staticMenuImageCache == nil {
|
||||
frameWidth, frameHeight := s.frame.GetFrameBounds()
|
||||
framesCount := s.frame.GetFrameCount()
|
||||
surface := s.renderer.NewSurface(frameWidth*framesCount, frameHeight*framesCount)
|
||||
|
||||
s.staticMenuImageCache = &surface
|
||||
|
||||
err := s.renderStaticMenu(*s.staticMenuImageCache)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
}
|
||||
}
|
||||
|
||||
target.Render(*s.staticMenuImageCache)
|
||||
|
||||
s.renderStatValues(target)
|
||||
s.setStatValues()
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) error {
|
||||
if err := s.renderStaticPanelFrames(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) {
|
||||
s.renderStaticPanelFrames(target)
|
||||
s.renderStaticLabels(target)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) error {
|
||||
s.frame.Render(target)
|
||||
|
||||
func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) {
|
||||
frames := []int{
|
||||
statsPanelTopLeft,
|
||||
statsPanelTopRight,
|
||||
@ -221,7 +206,7 @@ func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) err
|
||||
|
||||
for _, frameIndex := range frames {
|
||||
if err := s.panel.SetCurrentFrame(frameIndex); err != nil {
|
||||
return err
|
||||
log.Printf("%e", err)
|
||||
}
|
||||
|
||||
w, h := s.panel.GetCurrentFrameSize()
|
||||
@ -241,8 +226,6 @@ func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) err
|
||||
|
||||
s.panel.Render(target)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) renderStaticLabels(target d2interface.Surface) {
|
||||
@ -322,30 +305,24 @@ func (s *HeroStatsPanel) initStatValueLabels() {
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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.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.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.renderStatValueNum(s.labels.MaxHealth, s.heroState.MaxHealth, target)
|
||||
s.renderStatValueNum(s.labels.Health, s.heroState.Health, target)
|
||||
s.labels.MaxHealth.SetText(strconv.Itoa(s.heroState.MaxHealth))
|
||||
s.labels.Health.SetText(strconv.Itoa(s.heroState.Health))
|
||||
|
||||
s.renderStatValueNum(s.labels.MaxStamina, s.heroState.MaxStamina, target)
|
||||
s.renderStatValueNum(s.labels.Stamina, int(s.heroState.Stamina), target)
|
||||
s.labels.MaxStamina.SetText(strconv.Itoa(s.heroState.MaxStamina))
|
||||
s.labels.Stamina.SetText(strconv.Itoa(int(s.heroState.Stamina)))
|
||||
|
||||
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)
|
||||
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 {
|
||||
@ -361,6 +338,7 @@ func (s *HeroStatsPanel) createTextLabel(element PanelText) *d2ui.Label {
|
||||
|
||||
label.SetText(element.Text)
|
||||
label.SetPosition(element.X, element.Y)
|
||||
s.panelGroup.AddWidget(label)
|
||||
|
||||
return label
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ package d2player
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"log"
|
||||
"math"
|
||||
"strings"
|
||||
@ -30,10 +29,9 @@ const (
|
||||
|
||||
const (
|
||||
expBarWidth = 120.0
|
||||
expBarHeight = 4
|
||||
staminaBarWidth = 102.0
|
||||
staminaBarHeight = 19.0
|
||||
globeHeight = 80
|
||||
globeWidth = 80
|
||||
hoverLabelOuterPad = 5
|
||||
percentStaminaBarLow = 0.25
|
||||
)
|
||||
@ -61,15 +59,6 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
manaStatusOffsetX = 7
|
||||
manaStatusOffsetY = -12
|
||||
|
||||
healthStatusOffsetX = 30
|
||||
healthStatusOffsetY = -13
|
||||
|
||||
globeSpriteOffsetX = 28
|
||||
globeSpriteOffsetY = -5
|
||||
|
||||
staminaBarOffsetX = 273
|
||||
staminaBarOffsetY = 572
|
||||
|
||||
@ -120,6 +109,13 @@ type HUD struct {
|
||||
manaTooltip *d2ui.Tooltip
|
||||
miniPanelTooltip *d2ui.Tooltip
|
||||
nameLabel *d2ui.Label
|
||||
healthGlobe *globeWidget
|
||||
manaGlobe *globeWidget
|
||||
widgetStamina *d2ui.CustomWidget
|
||||
widgetExperience *d2ui.CustomWidget
|
||||
widgetLeftSkill *d2ui.CustomWidget
|
||||
widgetRightSkill *d2ui.CustomWidget
|
||||
panelBackground *d2ui.CustomWidget
|
||||
}
|
||||
|
||||
// NewHUD creates a HUD object
|
||||
@ -140,6 +136,9 @@ func NewHUD(
|
||||
zoneLabel := ui.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
|
||||
zoneLabel.Alignment = d2ui.HorizontalAlignCenter
|
||||
|
||||
healthGlobe := newGlobeWidget(ui, 0, screenHeight, typeHealthGlobe, &hero.Stats.Health, &hero.Stats.MaxHealth)
|
||||
manaGlobe := newGlobeWidget(ui, screenWidth-manaGlobeScreenOffsetX, screenHeight, typeManaGlobe, &hero.Stats.Mana, &hero.Stats.MaxMana)
|
||||
|
||||
return &HUD{
|
||||
asset: asset,
|
||||
uiManager: ui,
|
||||
@ -152,11 +151,84 @@ func NewHUD(
|
||||
nameLabel: nameLabel,
|
||||
skillSelectMenu: NewSkillSelectMenu(asset, ui, hero),
|
||||
zoneChangeText: zoneLabel,
|
||||
healthGlobe: healthGlobe,
|
||||
manaGlobe: manaGlobe,
|
||||
}
|
||||
}
|
||||
|
||||
// Load creates the ui elemets
|
||||
func (h *HUD) Load() {
|
||||
h.loadSprites()
|
||||
|
||||
h.healthGlobe.load()
|
||||
h.manaGlobe.load()
|
||||
|
||||
h.loadSkillResources()
|
||||
h.loadCustomWidgets()
|
||||
h.loadUIButtons()
|
||||
h.loadTooltips()
|
||||
}
|
||||
|
||||
func (h *HUD) loadCustomWidgets() {
|
||||
// static background
|
||||
_, height, err := h.mainPanel.GetFrameSize(0) // health globe is the frame with max height
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
h.panelBackground = h.uiManager.NewCustomWidgetCached(h.renderPanelStatic, screenWidth, height)
|
||||
h.panelBackground.SetPosition(0, screenHeight-height)
|
||||
|
||||
// stamina bar
|
||||
h.widgetStamina = h.uiManager.NewCustomWidget(h.renderStaminaBar, staminaBarWidth, staminaBarHeight)
|
||||
h.widgetStamina.SetPosition(staminaBarOffsetX, staminaBarOffsetY)
|
||||
|
||||
// experience bar
|
||||
h.widgetExperience = h.uiManager.NewCustomWidget(h.renderExperienceBar, expBarWidth, expBarHeight)
|
||||
|
||||
// Left skill widget
|
||||
leftRenderFunc := func(target d2interface.Surface) {
|
||||
x, y := h.widgetLeftSkill.GetPosition()
|
||||
h.renderLeftSkill(x, y, target)
|
||||
}
|
||||
|
||||
h.widgetLeftSkill = h.uiManager.NewCustomWidget(leftRenderFunc, skillIconWidth, skillIconHeight)
|
||||
h.widgetLeftSkill.SetPosition(leftSkillX, screenHeight)
|
||||
|
||||
// Right skill widget
|
||||
rightRenderFunc := func(target d2interface.Surface) {
|
||||
x, y := h.widgetRightSkill.GetPosition()
|
||||
h.renderRightSkill(x, y, target)
|
||||
}
|
||||
|
||||
h.widgetRightSkill = h.uiManager.NewCustomWidget(rightRenderFunc, skillIconWidth, skillIconHeight)
|
||||
h.widgetRightSkill.SetPosition(rightSkillX, screenHeight)
|
||||
}
|
||||
|
||||
func (h *HUD) loadSkillResources() {
|
||||
// https://github.com/OpenDiablo2/OpenDiablo2/issues/799
|
||||
genericSkillsSprite, err := h.uiManager.NewSprite(d2resource.GenericSkills, d2resource.PaletteSky)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
attackIconID := 2
|
||||
|
||||
h.leftSkillResource = &SkillResource{
|
||||
SkillIcon: genericSkillsSprite,
|
||||
IconNumber: attackIconID,
|
||||
SkillResourcePath: d2resource.GenericSkills,
|
||||
}
|
||||
|
||||
h.rightSkillResource = &SkillResource{
|
||||
SkillIcon: genericSkillsSprite,
|
||||
IconNumber: attackIconID,
|
||||
SkillResourcePath: d2resource.GenericSkills,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *HUD) loadSprites() {
|
||||
var err error
|
||||
|
||||
h.globeSprite, err = h.uiManager.NewSprite(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
|
||||
@ -183,29 +255,6 @@ func (h *HUD) Load() {
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
// https://github.com/OpenDiablo2/OpenDiablo2/issues/799
|
||||
genericSkillsSprite, err := h.uiManager.NewSprite(d2resource.GenericSkills, d2resource.PaletteSky)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
}
|
||||
|
||||
attackIconID := 2
|
||||
|
||||
h.leftSkillResource = &SkillResource{
|
||||
SkillIcon: genericSkillsSprite,
|
||||
IconNumber: attackIconID,
|
||||
SkillResourcePath: d2resource.GenericSkills,
|
||||
}
|
||||
|
||||
h.rightSkillResource = &SkillResource{
|
||||
SkillIcon: genericSkillsSprite,
|
||||
IconNumber: attackIconID,
|
||||
SkillResourcePath: d2resource.GenericSkills,
|
||||
}
|
||||
|
||||
h.loadUIButtons()
|
||||
h.loadTooltips()
|
||||
}
|
||||
|
||||
func (h *HUD) loadTooltips() {
|
||||
@ -292,35 +341,23 @@ func (h *HUD) onToggleRunButton(noButton bool) {
|
||||
// NOTE: the positioning of all of the panel elements is coupled to the rendering order :(
|
||||
// don't change the order in which the render methods are called, as there is an x,y offset
|
||||
// that is updated between render calls
|
||||
func (h *HUD) renderGameControlPanelElements(target d2interface.Surface) error {
|
||||
func (h *HUD) renderPanelStatic(target d2interface.Surface) {
|
||||
_, height := target.GetSize()
|
||||
offsetX, offsetY := 0, 0
|
||||
offsetX, offsetY := 0, height
|
||||
|
||||
// Main panel background
|
||||
offsetY = height
|
||||
if err := h.renderPanel(offsetX, offsetY, target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Health globe
|
||||
w, _ := h.mainPanel.GetCurrentFrameSize()
|
||||
|
||||
if err := h.renderHealthGlobe(offsetX, offsetY, target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Left Skill
|
||||
offsetX += w
|
||||
if err := h.renderLeftSkill(offsetX, offsetY, target); err != nil {
|
||||
return err
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
// New Stats Button
|
||||
w, _ = h.leftSkillResource.SkillIcon.GetCurrentFrameSize()
|
||||
offsetX += w
|
||||
w, _ := h.mainPanel.GetCurrentFrameSize()
|
||||
offsetX += w + skillIconWidth
|
||||
|
||||
if err := h.renderNewStatsButton(offsetX, offsetY, target); err != nil {
|
||||
return err
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Stamina
|
||||
@ -328,30 +365,17 @@ func (h *HUD) renderGameControlPanelElements(target d2interface.Surface) error {
|
||||
offsetX += w
|
||||
|
||||
if err := h.renderStamina(offsetX, offsetY, target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Stamina status bar
|
||||
w, _ = h.mainPanel.GetCurrentFrameSize()
|
||||
offsetX += w
|
||||
|
||||
if err := h.renderStaminaBar(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Experience status bar
|
||||
if err := h.renderExperienceBar(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mini Panel and button
|
||||
if err := h.renderMiniPanel(target); err != nil {
|
||||
return err
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Potions
|
||||
w, _ = h.mainPanel.GetCurrentFrameSize()
|
||||
offsetX += w
|
||||
|
||||
if err := h.renderPotions(offsetX, offsetY, target); err != nil {
|
||||
return err
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
// New Skills Button
|
||||
@ -359,87 +383,21 @@ func (h *HUD) renderGameControlPanelElements(target d2interface.Surface) error {
|
||||
offsetX += w
|
||||
|
||||
if err := h.renderNewSkillsButton(offsetX, offsetY, target); err != nil {
|
||||
return err
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
// Right skill
|
||||
// Empty Mana Globe
|
||||
w, _ = h.mainPanel.GetCurrentFrameSize()
|
||||
offsetX += w
|
||||
|
||||
if err := h.renderRightSkill(offsetX, offsetY, target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Mana Globe
|
||||
w, _ = h.rightSkillResource.SkillIcon.GetCurrentFrameSize()
|
||||
offsetX += w
|
||||
|
||||
if err := h.renderManaGlobe(offsetX, offsetY, target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HUD) renderManaGlobe(x, _ int, target d2interface.Surface) error {
|
||||
_, height := target.GetSize()
|
||||
offsetX += w + skillIconWidth
|
||||
|
||||
if err := h.mainPanel.SetCurrentFrame(frameRightGlobeHolder); err != nil {
|
||||
return err
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
h.mainPanel.SetPosition(x, height)
|
||||
|
||||
h.mainPanel.SetPosition(offsetX, height)
|
||||
h.mainPanel.Render(target)
|
||||
|
||||
// Mana status bar
|
||||
manaPercent := float64(h.hero.Stats.Mana) / float64(h.hero.Stats.MaxMana)
|
||||
manaBarHeight := int(manaPercent * float64(globeHeight))
|
||||
|
||||
if err := h.hpManaStatusSprite.SetCurrentFrame(frameManaStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.hpManaStatusSprite.SetPosition(x+manaStatusOffsetX, height+manaStatusOffsetY)
|
||||
|
||||
manaMaskRect := image.Rect(0, globeHeight-manaBarHeight, globeWidth, globeHeight)
|
||||
h.hpManaStatusSprite.RenderSection(target, manaMaskRect)
|
||||
|
||||
// Right globe
|
||||
if err := h.globeSprite.SetCurrentFrame(frameRightGlobe); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.globeSprite.SetPosition(x+rightGlobeOffsetX, height+rightGlobeOffsetY)
|
||||
|
||||
h.globeSprite.Render(target)
|
||||
h.globeSprite.Render(target)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HUD) renderHealthGlobe(x, y int, target d2interface.Surface) error {
|
||||
healthPercent := float64(h.hero.Stats.Health) / float64(h.hero.Stats.MaxHealth)
|
||||
hpBarHeight := int(healthPercent * float64(globeHeight))
|
||||
|
||||
if err := h.hpManaStatusSprite.SetCurrentFrame(0); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.hpManaStatusSprite.SetPosition(x+healthStatusOffsetX, y+healthStatusOffsetY)
|
||||
|
||||
healthMaskRect := image.Rect(0, globeHeight-hpBarHeight, globeWidth, globeHeight)
|
||||
h.hpManaStatusSprite.RenderSection(target, healthMaskRect)
|
||||
|
||||
// Left globe
|
||||
if err := h.globeSprite.SetCurrentFrame(frameHealthStatus); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.globeSprite.SetPosition(x+globeSpriteOffsetX, y+globeSpriteOffsetY)
|
||||
h.globeSprite.Render(target)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HUD) renderPanel(x, y int, target d2interface.Surface) error {
|
||||
@ -453,7 +411,7 @@ func (h *HUD) renderPanel(x, y int, target d2interface.Surface) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HUD) renderLeftSkill(x, y int, target d2interface.Surface) error {
|
||||
func (h *HUD) renderLeftSkill(x, y int, target d2interface.Surface) {
|
||||
newSkillResourcePath := h.getSkillResourceByClass(h.hero.LeftSkill.Charclass)
|
||||
if newSkillResourcePath != h.leftSkillResource.SkillResourcePath {
|
||||
h.leftSkillResource.SkillResourcePath = newSkillResourcePath
|
||||
@ -461,16 +419,15 @@ func (h *HUD) renderLeftSkill(x, y int, target d2interface.Surface) error {
|
||||
}
|
||||
|
||||
if err := h.leftSkillResource.SkillIcon.SetCurrentFrame(h.hero.LeftSkill.IconCel); err != nil {
|
||||
return err
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
h.leftSkillResource.SkillIcon.SetPosition(x, y)
|
||||
h.leftSkillResource.SkillIcon.Render(target)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HUD) renderRightSkill(x, _ int, target d2interface.Surface) error {
|
||||
func (h *HUD) renderRightSkill(x, _ int, target d2interface.Surface) {
|
||||
_, height := target.GetSize()
|
||||
|
||||
newSkillResourcePath := h.getSkillResourceByClass(h.hero.RightSkill.Charclass)
|
||||
@ -480,13 +437,12 @@ func (h *HUD) renderRightSkill(x, _ int, target d2interface.Surface) error {
|
||||
}
|
||||
|
||||
if err := h.rightSkillResource.SkillIcon.SetCurrentFrame(h.hero.RightSkill.IconCel); err != nil {
|
||||
return err
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
h.rightSkillResource.SkillIcon.SetPosition(x, height)
|
||||
h.rightSkillResource.SkillIcon.Render(target)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HUD) renderNewStatsButton(x, y int, target d2interface.Surface) error {
|
||||
@ -511,7 +467,7 @@ func (h *HUD) renderStamina(x, y int, target d2interface.Surface) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HUD) renderStaminaBar(target d2interface.Surface) error {
|
||||
func (h *HUD) renderStaminaBar(target d2interface.Surface) {
|
||||
target.PushTranslation(staminaBarOffsetX, staminaBarOffsetY)
|
||||
defer target.Pop()
|
||||
|
||||
@ -526,19 +482,15 @@ func (h *HUD) renderStaminaBar(target d2interface.Surface) error {
|
||||
}
|
||||
|
||||
target.DrawRect(int(staminaPercent*staminaBarWidth), staminaBarHeight, staminaBarColor)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HUD) renderExperienceBar(target d2interface.Surface) error {
|
||||
func (h *HUD) renderExperienceBar(target d2interface.Surface) {
|
||||
target.PushTranslation(experienceBarOffsetX, experienceBarOffsetY)
|
||||
defer target.Pop()
|
||||
|
||||
expPercent := float64(h.hero.Stats.Experience) / float64(h.hero.Stats.NextLevelExp)
|
||||
|
||||
target.DrawRect(int(expPercent*expBarWidth), 2, d2util.Color(whiteAlpha100))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *HUD) renderMiniPanel(target d2interface.Surface) error {
|
||||
@ -765,7 +717,17 @@ func (h *HUD) renderForSelectableEntitiesHovered(target d2interface.Surface) {
|
||||
func (h *HUD) Render(target d2interface.Surface) error {
|
||||
h.renderForSelectableEntitiesHovered(target)
|
||||
|
||||
if err := h.renderGameControlPanelElements(target); err != nil {
|
||||
h.panelBackground.Render(target)
|
||||
|
||||
h.healthGlobe.Render(target)
|
||||
h.widgetLeftSkill.Render(target)
|
||||
h.widgetRightSkill.Render(target)
|
||||
h.manaGlobe.Render(target)
|
||||
h.widgetStamina.Render(target)
|
||||
h.widgetExperience.Render(target)
|
||||
|
||||
// Mini Panel and button
|
||||
if err := h.renderMiniPanel(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -131,7 +131,7 @@ func (s *skillTree) load() {
|
||||
s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPrioritySkilltree)
|
||||
s.iconGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPrioritySkilltreeIcon)
|
||||
|
||||
s.panel = s.uiManager.NewCustomWidget(s.Render)
|
||||
s.panel = s.uiManager.NewCustomWidget(s.Render, 400, 600)
|
||||
s.panelGroup.AddWidget(s.panel)
|
||||
|
||||
s.frame = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameRight)
|
||||
@ -424,9 +424,6 @@ func (s *skillTree) renderTabCommon(target d2interface.Surface) {
|
||||
|
||||
skillPanel.SetPosition(x+w, y)
|
||||
s.renderPanelSegment(target, frameCommonTabBottomRight)
|
||||
|
||||
// available skill points label
|
||||
s.availSPLabel.Render(target)
|
||||
}
|
||||
|
||||
func (s *skillTree) renderTab(target d2interface.Surface, tab int) {
|
||||
|
Loading…
Reference in New Issue
Block a user