mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-09-06 11:34:15 -04:00
Co-authored-by: M. Sz <mszeptuch@protonmail.com> Co-authored-by: gravestench <dknuth0101@gmail.com>
685 lines
19 KiB
Go
685 lines
19 KiB
Go
package d2player
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"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/d2map/d2mapengine"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2maprenderer"
|
|
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
|
)
|
|
|
|
const (
|
|
runButtonX = 255
|
|
runButtonY = 570
|
|
)
|
|
|
|
const (
|
|
zoneChangeTextX = screenWidth / 2
|
|
zoneChangeTextY = screenHeight / 4
|
|
)
|
|
|
|
const (
|
|
expBarWidth = 120.0
|
|
expBarHeight = 4
|
|
staminaBarWidth = 102.0
|
|
staminaBarHeight = 19.0
|
|
hoverLabelOuterPad = 5
|
|
percentStaminaBarLow = 0.25
|
|
)
|
|
|
|
const (
|
|
frameHealthStatus = 0
|
|
frameManaStatus = 1
|
|
frameNewStatsSelector = 1
|
|
frameStamina = 2
|
|
framePotions = 3
|
|
frameNewSkillsSelector = 4
|
|
frameRightGlobeHolder = 5
|
|
frameRightGlobe = 1
|
|
)
|
|
|
|
const (
|
|
staminaBarOffsetX = 273
|
|
staminaBarOffsetY = 572
|
|
staminaExperienceY = 535
|
|
|
|
experienceBarOffsetX = 256
|
|
experienceBarOffsetY = 561
|
|
|
|
rightGlobeOffsetX = 8
|
|
rightGlobeOffsetY = -8
|
|
|
|
miniPanelButtonOffsetX = -8
|
|
miniPanelButtonOffsetY = -38
|
|
|
|
miniPanelTooltipOffsetX = 7
|
|
miniPanelTooltipOffsetY = -14
|
|
)
|
|
|
|
const (
|
|
lightBrownAlpha72 = 0xaf8848c8
|
|
redAlpha72 = 0xff0000c8
|
|
whiteAlpha100 = 0xffffffff
|
|
)
|
|
|
|
const (
|
|
addStatsButtonX, addStatsButtonY = 206, 561
|
|
addSkillButtonX, addSkillButtonY = 563, 561
|
|
)
|
|
|
|
// HUD represents the always visible user interface of the game
|
|
type HUD struct {
|
|
actionableRegions []actionableRegion
|
|
asset *d2asset.AssetManager
|
|
uiManager *d2ui.UIManager
|
|
mapEngine *d2mapengine.MapEngine
|
|
mapRenderer *d2maprenderer.MapRenderer
|
|
lastMouseX int
|
|
lastMouseY int
|
|
hero *d2mapentity.Player
|
|
mainPanel *d2ui.Sprite
|
|
globeSprite *d2ui.Sprite
|
|
hpManaStatusSprite *d2ui.Sprite
|
|
leftSkillResource *SkillResource
|
|
rightSkillResource *SkillResource
|
|
runButton *d2ui.Button
|
|
zoneChangeText *d2ui.Label
|
|
miniPanel *miniPanel
|
|
isZoneTextShown bool
|
|
hpStatsIsVisible bool
|
|
manaStatsIsVisible bool
|
|
skillSelectMenu *SkillSelectMenu
|
|
staminaTooltip *d2ui.Tooltip
|
|
runWalkTooltip *d2ui.Tooltip
|
|
experienceTooltip *d2ui.Tooltip
|
|
nameLabel *d2ui.Label
|
|
healthGlobe *globeWidget
|
|
manaGlobe *globeWidget
|
|
widgetStamina *d2ui.CustomWidget
|
|
widgetExperience *d2ui.CustomWidget
|
|
widgetLeftSkill *d2ui.CustomWidget
|
|
widgetRightSkill *d2ui.CustomWidget
|
|
panelBackground *d2ui.CustomWidget
|
|
addStatsButton *d2ui.Button
|
|
addSkillButton *d2ui.Button
|
|
panelGroup *d2ui.WidgetGroup
|
|
gameControls *GameControls
|
|
|
|
*d2util.Logger
|
|
}
|
|
|
|
// NewHUD creates a HUD object
|
|
func NewHUD(
|
|
asset *d2asset.AssetManager,
|
|
ui *d2ui.UIManager,
|
|
hero *d2mapentity.Player,
|
|
miniPanel *miniPanel,
|
|
actionableRegions []actionableRegion,
|
|
mapEngine *d2mapengine.MapEngine,
|
|
l d2util.LogLevel,
|
|
gameControls *GameControls,
|
|
mapRenderer *d2maprenderer.MapRenderer,
|
|
) *HUD {
|
|
nameLabel := ui.NewLabel(d2resource.Font16, d2resource.PaletteStatic)
|
|
nameLabel.Alignment = d2ui.HorizontalAlignCenter
|
|
nameLabel.SetText(d2ui.ColorTokenize("", d2ui.ColorTokenServer))
|
|
|
|
zoneLabel := ui.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
|
|
zoneLabel.Alignment = d2ui.HorizontalAlignCenter
|
|
|
|
healthGlobe := newGlobeWidget(ui, asset,
|
|
0, screenHeight,
|
|
typeHealthGlobe,
|
|
&hero.Stats.Health, &hero.Stats.MaxHealth,
|
|
l)
|
|
manaGlobe := newGlobeWidget(ui, asset,
|
|
screenWidth-manaGlobeScreenOffsetX, screenHeight,
|
|
typeManaGlobe,
|
|
&hero.Stats.Mana, &hero.Stats.MaxMana,
|
|
l)
|
|
|
|
hud := &HUD{
|
|
asset: asset,
|
|
uiManager: ui,
|
|
hero: hero,
|
|
mapEngine: mapEngine,
|
|
mapRenderer: mapRenderer,
|
|
miniPanel: miniPanel,
|
|
actionableRegions: actionableRegions,
|
|
nameLabel: nameLabel,
|
|
skillSelectMenu: NewSkillSelectMenu(asset, ui, l, hero),
|
|
zoneChangeText: zoneLabel,
|
|
healthGlobe: healthGlobe,
|
|
manaGlobe: manaGlobe,
|
|
gameControls: gameControls,
|
|
}
|
|
|
|
hud.Logger = d2util.NewLogger()
|
|
hud.Logger.SetPrefix(logPrefix)
|
|
hud.Logger.SetLevel(l)
|
|
|
|
return hud
|
|
}
|
|
|
|
// Load creates the ui elemets
|
|
func (h *HUD) Load() {
|
|
h.panelGroup = h.uiManager.NewWidgetGroup(d2ui.RenderPriorityHUDPanel)
|
|
|
|
h.loadSprites()
|
|
|
|
h.healthGlobe.load()
|
|
h.healthGlobe.SetRenderPriority(d2ui.RenderPriorityForeground)
|
|
h.panelGroup.AddWidget(h.healthGlobe)
|
|
|
|
h.manaGlobe.load()
|
|
h.manaGlobe.SetRenderPriority(d2ui.RenderPriorityForeground)
|
|
h.panelGroup.AddWidget(h.manaGlobe)
|
|
|
|
h.loadTooltips()
|
|
h.loadSkillResources()
|
|
h.loadCustomWidgets()
|
|
h.loadUIButtons()
|
|
|
|
// nolint:gomnd // dividing by 2 (const)
|
|
h.addStatsButton = h.uiManager.NewButton(d2ui.ButtonTypeAddSkill, "")
|
|
h.addStatsButton.SetPosition(addStatsButtonX, addStatsButtonY)
|
|
h.addStatsButton.SetVisible(false)
|
|
bw, bh := h.addStatsButton.GetSize()
|
|
statsTooltip := h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop)
|
|
statsTooltip.SetPosition(addStatsButtonX+bw/2, addStatsButtonY-bh/2)
|
|
statsTooltip.SetText(h.asset.TranslateString("strlvlup"))
|
|
h.addStatsButton.SetTooltip(statsTooltip)
|
|
h.panelGroup.AddWidget(h.addStatsButton)
|
|
|
|
h.addSkillButton = h.uiManager.NewButton(d2ui.ButtonTypeAddSkill, "")
|
|
h.addSkillButton.SetPosition(addSkillButtonX, addSkillButtonY)
|
|
h.addSkillButton.SetVisible(false)
|
|
bw, bh = h.addSkillButton.GetSize()
|
|
skillTooltip := h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop)
|
|
skillTooltip.SetPosition(addSkillButtonX+bw/2, addSkillButtonY-bh/2)
|
|
skillTooltip.SetText(h.asset.TranslateString("strnewskl"))
|
|
h.addSkillButton.SetTooltip(skillTooltip)
|
|
h.panelGroup.AddWidget(h.addSkillButton)
|
|
|
|
h.panelGroup.SetVisible(true)
|
|
}
|
|
|
|
func (h *HUD) loadCustomWidgets() {
|
|
// static background
|
|
_, height, err := h.mainPanel.GetFrameSize(0) // health globe is the frame with max height
|
|
if err != nil {
|
|
h.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
h.panelBackground = h.uiManager.NewCustomWidgetCached(h.renderPanelStatic, screenWidth, height)
|
|
h.panelBackground.SetPosition(0, screenHeight-height)
|
|
h.panelGroup.AddWidget(h.panelBackground)
|
|
|
|
// stamina bar
|
|
h.widgetStamina = h.uiManager.NewCustomWidget(h.renderStaminaBar, staminaBarWidth, staminaBarHeight)
|
|
h.widgetStamina.SetPosition(staminaBarOffsetX, staminaBarOffsetY)
|
|
h.widgetStamina.SetTooltip(h.staminaTooltip)
|
|
h.panelGroup.AddWidget(h.widgetStamina)
|
|
|
|
// experience bar
|
|
h.widgetExperience = h.uiManager.NewCustomWidget(h.renderExperienceBar, expBarWidth, expBarHeight)
|
|
h.widgetExperience.SetPosition(experienceBarOffsetX, experienceBarOffsetY)
|
|
h.widgetExperience.SetTooltip(h.experienceTooltip)
|
|
h.panelGroup.AddWidget(h.widgetExperience)
|
|
|
|
// 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)
|
|
h.panelGroup.AddWidget(h.widgetLeftSkill)
|
|
|
|
// 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)
|
|
h.panelGroup.AddWidget(h.widgetRightSkill)
|
|
}
|
|
|
|
func (h *HUD) loadSkillResources() {
|
|
genericSkillsSprite, err := h.uiManager.NewSprite(d2resource.GenericSkills, d2resource.PaletteSky)
|
|
if err != nil {
|
|
h.Error(err.Error())
|
|
}
|
|
|
|
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)
|
|
if err != nil {
|
|
h.Error(err.Error())
|
|
}
|
|
|
|
h.hpManaStatusSprite, err = h.uiManager.NewSprite(d2resource.HealthManaIndicator, d2resource.PaletteSky)
|
|
if err != nil {
|
|
h.Error(err.Error())
|
|
}
|
|
|
|
h.mainPanel, err = h.uiManager.NewSprite(d2resource.GamePanels, d2resource.PaletteSky)
|
|
if err != nil {
|
|
h.Error(err.Error())
|
|
}
|
|
}
|
|
|
|
func (h *HUD) loadTooltips() {
|
|
// stamina tooltip
|
|
h.staminaTooltip = h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop)
|
|
rect := &h.actionableRegions[stamina].rect
|
|
|
|
halfButtonWidth := rect.Width >> 1
|
|
centerX := rect.Left + halfButtonWidth
|
|
|
|
_, labelHeight := h.staminaTooltip.GetSize()
|
|
halfLabelHeight := labelHeight >> 1
|
|
|
|
labelX := centerX
|
|
labelY := staminaExperienceY - halfLabelHeight
|
|
h.staminaTooltip.SetPosition(labelX, labelY)
|
|
|
|
// experience tooltip
|
|
h.experienceTooltip = h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop)
|
|
rect = &h.actionableRegions[stamina].rect
|
|
|
|
halfButtonWidth = rect.Width >> 1
|
|
centerX = rect.Left + halfButtonWidth
|
|
|
|
_, labelHeight = h.experienceTooltip.GetSize()
|
|
halfLabelHeight = labelHeight >> 1
|
|
|
|
labelX = centerX
|
|
labelY = staminaExperienceY - halfLabelHeight
|
|
h.experienceTooltip.SetPosition(labelX, labelY)
|
|
}
|
|
|
|
func (h *HUD) loadUIButtons() {
|
|
// Run button
|
|
h.runButton = h.uiManager.NewButton(d2ui.ButtonTypeRun, "")
|
|
h.runButton.SetPosition(runButtonX, runButtonY)
|
|
h.runButton.OnActivated(func() { h.onToggleRunButton(false) })
|
|
|
|
h.runWalkTooltip = h.uiManager.NewTooltip(d2resource.Font16, d2resource.PaletteSky, d2ui.TooltipXCenter, d2ui.TooltipYTop)
|
|
// we must set text first, and then we're getting its height
|
|
h.updateRunTooltipText()
|
|
|
|
bw, bh := h.runButton.GetSize()
|
|
_, lh := h.runWalkTooltip.GetSize()
|
|
// nolint:gomnd // dividing by 2 (const)
|
|
labelX := runButtonX + bw/2
|
|
// nolint:gomnd // dividing by 2 (const)
|
|
labelY := runButtonY - bh/2 - lh/2
|
|
|
|
h.runWalkTooltip.SetPosition(labelX, labelY)
|
|
h.runButton.SetTooltip(h.runWalkTooltip)
|
|
|
|
h.panelGroup.AddWidget(h.runButton)
|
|
|
|
if h.hero.IsRunToggled() {
|
|
h.runButton.Toggle()
|
|
}
|
|
}
|
|
|
|
func (h *HUD) updateRunTooltipText() {
|
|
var stringTableKey string
|
|
if h.hero.IsRunToggled() {
|
|
stringTableKey = "RunOff"
|
|
} else {
|
|
stringTableKey = "RunOn"
|
|
}
|
|
|
|
h.runWalkTooltip.SetText(h.asset.TranslateString(stringTableKey))
|
|
}
|
|
|
|
func (h *HUD) onToggleRunButton(noButton bool) {
|
|
if !noButton {
|
|
h.runButton.Toggle()
|
|
}
|
|
|
|
h.hero.ToggleRunWalk()
|
|
h.updateRunTooltipText()
|
|
|
|
h.hero.SetIsRunning(h.hero.IsRunToggled())
|
|
}
|
|
|
|
// 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) renderPanelStatic(target d2interface.Surface) {
|
|
_, height := target.GetSize()
|
|
offsetX, offsetY := 0, height
|
|
|
|
// Main panel background
|
|
if err := h.renderPanel(offsetX, offsetY, target); err != nil {
|
|
h.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
// New Stats Button
|
|
w, _ := h.mainPanel.GetCurrentFrameSize()
|
|
offsetX += w + skillIconWidth
|
|
|
|
if err := h.renderNewStatsButton(offsetX, offsetY, target); err != nil {
|
|
h.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
// Stamina
|
|
w, _ = h.mainPanel.GetCurrentFrameSize()
|
|
offsetX += w
|
|
|
|
if err := h.renderStamina(offsetX, offsetY, target); err != nil {
|
|
h.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
// Potions
|
|
w, _ = h.mainPanel.GetCurrentFrameSize()
|
|
offsetX += w
|
|
|
|
if err := h.renderPotions(offsetX, offsetY, target); err != nil {
|
|
h.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
// New Skills Button
|
|
w, _ = h.mainPanel.GetCurrentFrameSize()
|
|
offsetX += w
|
|
|
|
if err := h.renderNewSkillsButton(offsetX, offsetY, target); err != nil {
|
|
h.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
// Empty Mana Globe
|
|
w, _ = h.mainPanel.GetCurrentFrameSize()
|
|
offsetX += w + skillIconWidth
|
|
|
|
if err := h.mainPanel.SetCurrentFrame(frameRightGlobeHolder); err != nil {
|
|
h.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
h.mainPanel.SetPosition(offsetX, height)
|
|
h.mainPanel.Render(target)
|
|
}
|
|
|
|
func (h *HUD) renderPanel(x, y int, target d2interface.Surface) error {
|
|
if err := h.mainPanel.SetCurrentFrame(0); err != nil {
|
|
return err
|
|
}
|
|
|
|
h.mainPanel.SetPosition(x, y)
|
|
h.mainPanel.Render(target)
|
|
|
|
return nil
|
|
}
|
|
|
|
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
|
|
h.leftSkillResource.SkillIcon, _ = h.uiManager.NewSprite(newSkillResourcePath, d2resource.PaletteSky)
|
|
}
|
|
|
|
if err := h.leftSkillResource.SkillIcon.SetCurrentFrame(h.hero.LeftSkill.IconCel); err != nil {
|
|
h.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
h.leftSkillResource.SkillIcon.SetPosition(x, y)
|
|
h.leftSkillResource.SkillIcon.Render(target)
|
|
}
|
|
|
|
func (h *HUD) renderRightSkill(x, _ int, target d2interface.Surface) {
|
|
_, height := target.GetSize()
|
|
|
|
newSkillResourcePath := h.getSkillResourceByClass(h.hero.RightSkill.Charclass)
|
|
if newSkillResourcePath != h.rightSkillResource.SkillResourcePath {
|
|
h.rightSkillResource.SkillIcon, _ = h.uiManager.NewSprite(newSkillResourcePath, d2resource.PaletteSky)
|
|
h.rightSkillResource.SkillResourcePath = newSkillResourcePath
|
|
}
|
|
|
|
if err := h.rightSkillResource.SkillIcon.SetCurrentFrame(h.hero.RightSkill.IconCel); err != nil {
|
|
h.Error(err.Error())
|
|
return
|
|
}
|
|
|
|
h.rightSkillResource.SkillIcon.SetPosition(x, height)
|
|
h.rightSkillResource.SkillIcon.Render(target)
|
|
}
|
|
|
|
func (h *HUD) renderNewStatsButton(x, y int, target d2interface.Surface) error {
|
|
if err := h.mainPanel.SetCurrentFrame(frameNewStatsSelector); err != nil {
|
|
return err
|
|
}
|
|
|
|
h.mainPanel.SetPosition(x, y)
|
|
h.mainPanel.Render(target)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *HUD) renderStamina(x, y int, target d2interface.Surface) error {
|
|
if err := h.mainPanel.SetCurrentFrame(frameStamina); err != nil {
|
|
return err
|
|
}
|
|
|
|
h.mainPanel.SetPosition(x, y)
|
|
h.mainPanel.Render(target)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *HUD) renderStaminaBar(target d2interface.Surface) {
|
|
target.PushTranslation(staminaBarOffsetX, staminaBarOffsetY)
|
|
defer target.Pop()
|
|
|
|
target.PushEffect(d2enum.DrawEffectModulate)
|
|
defer target.Pop()
|
|
|
|
staminaPercent := h.hero.Stats.Stamina / float64(h.hero.Stats.MaxStamina)
|
|
|
|
staminaBarColor := d2util.Color(lightBrownAlpha72)
|
|
if staminaPercent < percentStaminaBarLow {
|
|
staminaBarColor = d2util.Color(redAlpha72)
|
|
}
|
|
|
|
target.DrawRect(int(staminaPercent*staminaBarWidth), staminaBarHeight, staminaBarColor)
|
|
}
|
|
|
|
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))
|
|
}
|
|
|
|
func (h *HUD) renderPotions(x, _ int, target d2interface.Surface) error {
|
|
_, height := target.GetSize()
|
|
|
|
if err := h.mainPanel.SetCurrentFrame(framePotions); err != nil {
|
|
return err
|
|
}
|
|
|
|
h.mainPanel.SetPosition(x, height)
|
|
h.mainPanel.Render(target)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *HUD) renderNewSkillsButton(x, _ int, target d2interface.Surface) error {
|
|
_, height := target.GetSize()
|
|
|
|
if err := h.mainPanel.SetCurrentFrame(frameNewSkillsSelector); err != nil {
|
|
return err
|
|
}
|
|
|
|
h.mainPanel.SetPosition(x, height)
|
|
h.mainPanel.Render(target)
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *HUD) setStaminaTooltipText() {
|
|
// Create and format Stamina string from string lookup table.
|
|
fmtStamina := h.asset.TranslateString("panelstamina")
|
|
staminaCurr, staminaMax := int(h.hero.Stats.Stamina), h.hero.Stats.MaxStamina
|
|
strPanelStamina := fmt.Sprintf(fmtStamina, staminaCurr, staminaMax)
|
|
|
|
h.staminaTooltip.SetText(strPanelStamina)
|
|
}
|
|
|
|
func (h *HUD) setExperienceTooltipText() {
|
|
// Create and format Experience string from string lookup table.
|
|
fmtExp := h.asset.TranslateString("panelexp")
|
|
|
|
// The English string for "panelexp" is "Experience: %u / %u", however %u doesn't
|
|
// translate well. So we need to rewrite %u into a formatable Go verb. %d is used in other
|
|
// strings, so we go with that, keeping in mind that %u likely referred to
|
|
// an unsigned integer.
|
|
fmtExp = strings.ReplaceAll(fmtExp, "%u", "%d")
|
|
|
|
expCurr, expMax := uint(h.hero.Stats.Experience), uint(h.hero.Stats.NextLevelExp)
|
|
strPanelExp := fmt.Sprintf(fmtExp, expCurr, expMax)
|
|
|
|
h.experienceTooltip.SetText(strPanelExp)
|
|
}
|
|
|
|
func (h *HUD) renderForSelectableEntitiesHovered(target d2interface.Surface) {
|
|
mx, my := h.lastMouseX, h.lastMouseY
|
|
|
|
for entityIdx := range h.mapEngine.Entities() {
|
|
entity := (h.mapEngine.Entities())[entityIdx]
|
|
if !entity.Selectable() {
|
|
continue
|
|
}
|
|
|
|
entPos := entity.GetPosition()
|
|
entOffset := entPos.RenderOffset()
|
|
entScreenXf, entScreenYf := h.mapRenderer.WorldToScreenF(entity.GetPositionF())
|
|
entScreenX := int(math.Floor(entScreenXf))
|
|
entScreenY := int(math.Floor(entScreenYf))
|
|
entityWidth, entityHeight := entity.GetSize()
|
|
halfWidth, halfHeight := entityWidth>>1, entityHeight>>1
|
|
l, r := entScreenX-halfWidth-hoverLabelOuterPad, entScreenX+halfWidth+hoverLabelOuterPad
|
|
t, b := entScreenY-halfHeight-hoverLabelOuterPad, entScreenY+halfHeight-hoverLabelOuterPad
|
|
xWithin := (l <= mx) && (r >= mx)
|
|
yWithin := (t <= my) && (b >= my)
|
|
within := xWithin && yWithin
|
|
|
|
if within {
|
|
xOff, yOff := int(entOffset.X()), int(entOffset.Y())
|
|
|
|
h.nameLabel.SetText(entity.Label())
|
|
|
|
xLabel, yLabel := entScreenX-xOff, entScreenY-yOff-entityHeight-hoverLabelOuterPad
|
|
h.nameLabel.SetPosition(xLabel, yLabel)
|
|
|
|
h.nameLabel.Render(target)
|
|
entity.Highlight()
|
|
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Render draws the HUD to the screen
|
|
func (h *HUD) Render(target d2interface.Surface) error {
|
|
h.renderForSelectableEntitiesHovered(target)
|
|
|
|
if h.isZoneTextShown {
|
|
h.zoneChangeText.SetPosition(zoneChangeTextX, zoneChangeTextY)
|
|
h.zoneChangeText.Render(target)
|
|
}
|
|
|
|
if h.skillSelectMenu.IsOpen() {
|
|
h.skillSelectMenu.Render(target)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *HUD) getSkillResourceByClass(class string) string {
|
|
resourceMap := map[string]string{
|
|
"": d2resource.GenericSkills,
|
|
"bar": d2resource.BarbarianSkills,
|
|
"nec": d2resource.NecromancerSkills,
|
|
"pal": d2resource.PaladinSkills,
|
|
"ass": d2resource.AssassinSkills,
|
|
"sor": d2resource.SorcererSkills,
|
|
"ama": d2resource.AmazonSkills,
|
|
"dru": d2resource.DruidSkills,
|
|
}
|
|
|
|
entry, found := resourceMap[class]
|
|
if !found {
|
|
h.Errorf("Unknown class token: '%s'", class)
|
|
}
|
|
|
|
return entry
|
|
}
|
|
|
|
// Advance updates syncs data on widgets that might have changed. I.e. the current stamina value
|
|
// in the stamina tooltip
|
|
func (h *HUD) Advance(elapsed float64) {
|
|
h.setStaminaTooltipText()
|
|
h.setExperienceTooltipText()
|
|
|
|
if err := h.healthGlobe.Advance(elapsed); err != nil {
|
|
h.Error(err.Error())
|
|
}
|
|
|
|
if err := h.manaGlobe.Advance(elapsed); err != nil {
|
|
h.Error(err.Error())
|
|
}
|
|
}
|
|
|
|
// OnMouseMove handles mouse move events
|
|
func (h *HUD) OnMouseMove(event d2interface.MouseMoveEvent) bool {
|
|
mx, my := event.X(), event.Y()
|
|
h.lastMouseX = mx
|
|
h.lastMouseY = my
|
|
|
|
h.skillSelectMenu.LeftPanel.HandleMouseMove(mx, my)
|
|
h.skillSelectMenu.RightPanel.HandleMouseMove(mx, my)
|
|
|
|
return false
|
|
}
|