From 33368e5aa358e08cf38cd4a892383c4b516bd62a Mon Sep 17 00:00:00 2001 From: gravestench Date: Mon, 26 Oct 2020 22:55:13 +0000 Subject: [PATCH] Lint cleanup funlen (#854) * d2ui/button.go: refactored button state prerender to fix funlen lint error * d2player/help/help.go: break up Load method to reduce complexity * d2game/d2player/game_controls.go: refactored renderHUD method, fixed funlen lint error * d2player/skilltree.go: fixed funlen lint error * map_engine_test.go: fixed funlen lint error --- d2core/d2ui/button.go | 152 ++--- d2game/d2gamescreen/main_menu.go | 8 +- d2game/d2gamescreen/map_engine_testing.go | 8 + d2game/d2player/game_controls.go | 647 +++++++++++++--------- d2game/d2player/help/help.go | 56 +- d2game/d2player/skilltree.go | 16 +- 6 files changed, 524 insertions(+), 363 deletions(-) diff --git a/d2core/d2ui/button.go b/d2core/d2ui/button.go index ef3c82ba..c12698f7 100644 --- a/d2core/d2ui/button.go +++ b/d2core/d2ui/button.go @@ -47,6 +47,12 @@ const ( ButtonNoFixedHeight int = -1 ) +const ( + buttonStatePressed = iota + 1 + buttonStateToggled + buttonStatePressedToggled +) + const ( closeButtonBaseFrame = 10 // base frame offset of the "close" button dc6 ) @@ -315,21 +321,28 @@ func (ui *UIManager) NewButton(buttonType ButtonType, text string) *Button { buttonSprite.SetPosition(0, 0) buttonSprite.SetEffect(d2enum.DrawEffectModulate) - ui.addWidget(btn) // important that this comes before renderFrames! + ui.addWidget(btn) // important that this comes before prerenderStates! - btn.renderFrames(buttonSprite, &buttonLayout, lbl) + btn.prerenderStates(buttonSprite, &buttonLayout, lbl) return btn } -func (v *Button) renderFrames(btnSprite *Sprite, btnLayout *ButtonLayout, label *Label) { +type buttonStateDescriptor struct { + baseFrame int + offsetX, offsetY int + prerenderdestination *d2interface.Surface + fmtErr string +} + +func (v *Button) prerenderStates(btnSprite *Sprite, btnLayout *ButtonLayout, label *Label) { var err error - totalButtonTypes := btnSprite.GetFrameCount() / (btnLayout.XSegments * btnLayout.YSegments) + numButtonStates := btnSprite.GetFrameCount() / (btnLayout.XSegments * btnLayout.YSegments) + // buttons always have a base image if v.buttonLayout.HasImage { err = btnSprite.RenderSegmented(v.normalSurface, btnLayout.XSegments, btnLayout.YSegments, btnLayout.BaseFrame) - if err != nil { fmt.Printf("failed to render button normalSurface, err: %v\n", err) } @@ -342,89 +355,82 @@ func (v *Button) renderFrames(btnSprite *Sprite, btnLayout *ButtonLayout, label label.SetPosition(xOffset, textY) label.Render(v.normalSurface) - if btnLayout.HasImage && btnLayout.AllowFrameChange { - frameOffset := 0 - xSeg, ySeg, baseFrame := btnLayout.XSegments, btnLayout.YSegments, btnLayout.BaseFrame + if !btnLayout.HasImage || !btnLayout.AllowFrameChange { + return + } - totalButtonTypes-- - if totalButtonTypes > 0 { // button has more than one type - frameOffset++ + xSeg, ySeg, baseFrame := btnLayout.XSegments, btnLayout.YSegments, btnLayout.BaseFrame - v.pressedSurface, err = v.manager.renderer.NewSurface(v.width, v.height, - d2enum.FilterNearest) - if err != nil { - log.Print(err) - } + buttonStateConfigs := make([]*buttonStateDescriptor, 0) - err = btnSprite.RenderSegmented(v.pressedSurface, xSeg, ySeg, baseFrame+frameOffset) - if err != nil { - fmt.Printf("failed to render button pressedSurface, err: %v\n", err) - } - - label.SetPosition(xOffset-pressedButtonOffset, textY+pressedButtonOffset) - label.Render(v.pressedSurface) + // pressed button + if numButtonStates >= buttonStatePressed { + state := &buttonStateDescriptor{ + baseFrame + buttonStatePressed, + xOffset - pressedButtonOffset, textY + pressedButtonOffset, + &v.pressedSurface, + "failed to render button pressedSurface, err: %v\n", } - if btnLayout.ResourceName == d2resource.BuySellButton { - // Without returning early, the button UI gets all subsequent (unrelated) frames stacked on top - // Only 2 frames from this sprite are applicable to the button in question - // The presentation is incorrect without this hack - return + buttonStateConfigs = append(buttonStateConfigs, state) + } + + // toggle button + if numButtonStates >= buttonStateToggled { + buttonStateConfigs = append(buttonStateConfigs, &buttonStateDescriptor{ + baseFrame + buttonStateToggled, + xOffset, textY, + &v.toggledSurface, + "failed to render button toggledSurface, err: %v\n", + }) + } + + // pressed+toggled + if numButtonStates >= buttonStatePressedToggled { + buttonStateConfigs = append(buttonStateConfigs, &buttonStateDescriptor{ + baseFrame + buttonStatePressedToggled, + xOffset, textY, + &v.pressedToggledSurface, + "failed to render button pressedToggledSurface, err: %v\n", + }) + } + + // disabled button + if btnLayout.DisabledFrame != -1 { + disabledState := &buttonStateDescriptor{ + btnLayout.DisabledFrame, + xOffset, textY, + &v.disabledSurface, + "failed to render button disabledSurface, err: %v\n", } - totalButtonTypes-- - if totalButtonTypes > 0 { // button has more than two types - frameOffset++ + buttonStateConfigs = append(buttonStateConfigs, disabledState) + } - v.toggledSurface, err = v.manager.renderer.NewSurface(v.width, v.height, - d2enum.FilterNearest) - if err != nil { - log.Print(err) - } + for stateIdx, w, h := 0, v.width, v.height; stateIdx < len(buttonStateConfigs); stateIdx++ { + state := buttonStateConfigs[stateIdx] - err = btnSprite.RenderSegmented(v.toggledSurface, xSeg, ySeg, baseFrame+frameOffset) - if err != nil { - fmt.Printf("failed to render button toggledSurface, err: %v\n", err) - } - - label.SetPosition(xOffset, textY) - label.Render(v.toggledSurface) + if stateIdx >= 2 && btnLayout.ResourceName == d2resource.BuySellButton { + // Without returning early, the button UI gets all subsequent (unrelated) frames + // stacked on top. Only 2 frames from this sprite are applicable to the button + // in question. The presentation is incorrect without this hack! + continue } - totalButtonTypes-- - if totalButtonTypes > 0 { // button has more than three types - frameOffset++ - - v.pressedToggledSurface, err = v.manager.renderer.NewSurface(v.width, v.height, - d2enum.FilterNearest) - if err != nil { - log.Print(err) - } - - err = btnSprite.RenderSegmented(v.pressedSurface, xSeg, ySeg, baseFrame+frameOffset) - if err != nil { - fmt.Printf("failed to render button pressedToggledSurface, err: %v\n", err) - } - - label.SetPosition(xOffset, textY) - label.Render(v.pressedToggledSurface) + surface, err := v.manager.renderer.NewSurface(w, h, d2enum.FilterNearest) + if err != nil { + log.Print(err) } - if btnLayout.DisabledFrame != -1 { - v.disabledSurface, err = v.manager.renderer.NewSurface(v.width, v.height, - d2enum.FilterNearest) - if err != nil { - log.Print(err) - } + *state.prerenderdestination = surface - err = btnSprite.RenderSegmented(v.disabledSurface, xSeg, ySeg, btnLayout.DisabledFrame) - if err != nil { - fmt.Printf("failed to render button disabledSurface, err: %v\n", err) - } - - label.SetPosition(xOffset, textY) - label.Render(v.disabledSurface) + err = btnSprite.RenderSegmented(*state.prerenderdestination, xSeg, ySeg, state.baseFrame) + if err != nil { + fmt.Printf(state.fmtErr, err) } + + label.SetPosition(state.offsetX, state.offsetY) + label.Render(*state.prerenderdestination) } } diff --git a/d2game/d2gamescreen/main_menu.go b/d2game/d2gamescreen/main_menu.go index 5492bef9..82e5ff1f 100644 --- a/d2game/d2gamescreen/main_menu.go +++ b/d2game/d2gamescreen/main_menu.go @@ -522,12 +522,8 @@ func (v *MainMenu) OnMouseButtonDown(event d2interface.MouseEvent) bool { } // OnKeyUp is called when a key is released -func (v *MainMenu) OnKeyUp(event d2interface.KeyEvent) bool { - /* - On retail version of D2 any key event puts you onto the main menu, so this is a supplement to that code up there - on line 515. - */ - +func (v *MainMenu) OnKeyUp(_ d2interface.KeyEvent) bool { + // On retail version of D2, any key event puts you onto the main menu. if v.screenMode == ScreenModeTrademark { v.SetScreenMode(ScreenModeMainMenu) return true diff --git a/d2game/d2gamescreen/map_engine_testing.go b/d2game/d2gamescreen/map_engine_testing.go index dc43918b..bc71cc00 100644 --- a/d2game/d2gamescreen/map_engine_testing.go +++ b/d2game/d2gamescreen/map_engine_testing.go @@ -265,6 +265,14 @@ func (met *MapEngineTest) Render(screen d2interface.Surface) error { screen.PushTranslation(0, lineNormalOffsetY) defer screen.Pop() + if err := met.renderTileInfo(screen); err != nil { + return err + } + + return nil +} + +func (met *MapEngineTest) renderTileInfo(screen d2interface.Surface) error { if met.selectedTile == nil { screen.PushTranslation(lineNormalIndentX, lineNormalOffsetY) defer screen.Pop() diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index 33261af0..43a8b001 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -161,6 +161,8 @@ const ( manaLabelX = 785 manaLabelY = 487 + + staminaExperienceY = 535 ) const ( @@ -978,24 +980,139 @@ func (g *GameControls) renderPanels(target d2interface.Surface) error { } func (g *GameControls) renderHUD(target d2interface.Surface) error { - mx, my := g.lastMouseX, g.lastMouseY - width, height := target.GetSize() - offsetX := 0 + if err := g.renderGameControlPanelElements(target); err != nil { + return err + } - // Left globe holder + if err := g.HelpOverlay.Render(target); err != nil { + return err + } + + if g.isZoneTextShown { + g.zoneChangeText.SetPosition(zoneChangeTextX, zoneChangeTextY) + g.zoneChangeText.Render(target) + } + + g.renderHealthTooltip(target) + g.renderManaTooltip(target) + g.renderRunWalkTooltip(target) + g.renderStaminaTooltip(target) + g.renderExperienceTooltip(target) + + if g.skillSelectMenu.IsOpen() { + g.skillSelectMenu.Render(target) + } + + return nil +} + +// 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 (g *GameControls) renderGameControlPanelElements(target d2interface.Surface) error { + _, height := target.GetSize() + offsetX, offsetY := 0, 0 + + // Main panel background + offsetY = height + if err := g.renderPanel(offsetX, offsetY, target); err != nil { + return err + } + + // Health globe + w, _ := g.mainPanel.GetCurrentFrameSize() + + if err := g.renderHealthGlobe(offsetX, offsetY, target); err != nil { + return err + } + + // Left Skill + offsetX += w + if err := g.renderLeftSkill(offsetX, offsetY, target); err != nil { + return err + } + + // New Stats Button + w, _ = g.leftSkillResource.SkillIcon.GetCurrentFrameSize() + offsetX += w + + if err := g.renderNewStatsButton(offsetX, offsetY, target); err != nil { + return err + } + + // Stamina + w, _ = g.mainPanel.GetCurrentFrameSize() + offsetX += w + + if err := g.renderStamina(offsetX, offsetY, target); err != nil { + return err + } + + // Stamina status bar + w, _ = g.mainPanel.GetCurrentFrameSize() + offsetX += w + + if err := g.renderStaminaBar(target); err != nil { + return err + } + + // Experience status bar + if err := g.renderExperienceBar(target); err != nil { + return err + } + + // Mini Panel and button + if err := g.renderMiniPanel(target); err != nil { + return err + } + + // Potions + if err := g.renderPotions(offsetX, offsetY, target); err != nil { + return err + } + + // New Skills Button + w, _ = g.mainPanel.GetCurrentFrameSize() + offsetX += w + + if err := g.renderNewSkillsButton(offsetX, offsetY, target); err != nil { + return err + } + + // Right skill + w, _ = g.mainPanel.GetCurrentFrameSize() + offsetX += w + + if err := g.renderRightSkill(offsetX, offsetY, target); err != nil { + return err + } + + // Mana Globe + w, _ = g.rightSkillResource.SkillIcon.GetCurrentFrameSize() + offsetX += w + + if err := g.renderManaGlobe(offsetX, offsetY, target); err != nil { + return err + } + + return nil +} + +func (g *GameControls) renderPanel(x, y int, target d2interface.Surface) error { if err := g.mainPanel.SetCurrentFrame(0); err != nil { return err } - w, _ := g.mainPanel.GetCurrentFrameSize() - - g.mainPanel.SetPosition(offsetX, height) + g.mainPanel.SetPosition(x, y) if err := g.mainPanel.Render(target); err != nil { return err } - // Health status bar + return nil +} + +func (g *GameControls) renderHealthGlobe(x, y int, target d2interface.Surface) error { healthPercent := float64(g.hero.Stats.Health) / float64(g.hero.Stats.MaxHealth) hpBarHeight := int(healthPercent * float64(globeHeight)) @@ -1003,7 +1120,7 @@ func (g *GameControls) renderHUD(target d2interface.Surface) error { return err } - g.hpManaStatusSprite.SetPosition(offsetX+healthStatusOffsetX, height+healthStatusOffsetY) + g.hpManaStatusSprite.SetPosition(x+healthStatusOffsetX, y+healthStatusOffsetY) healthMaskRect := image.Rect(0, globeHeight-hpBarHeight, globeWidth, globeHeight) if err := g.hpManaStatusSprite.RenderSection(target, healthMaskRect); err != nil { @@ -1015,15 +1132,16 @@ func (g *GameControls) renderHUD(target d2interface.Surface) error { return err } - g.globeSprite.SetPosition(offsetX+globeSpriteOffsetX, height+globeSpriteOffsetY) + g.globeSprite.SetPosition(x+globeSpriteOffsetX, y+globeSpriteOffsetY) if err := g.globeSprite.Render(target); err != nil { return err } - offsetX += w + return nil +} - // Left skill +func (g *GameControls) renderLeftSkill(x, y int, target d2interface.Surface) error { newSkillResourcePath := g.getSkillResourceByClass(g.hero.LeftSkill.Charclass) if newSkillResourcePath != g.leftSkillResource.SkillResourcePath { g.leftSkillResource.SkillResourcePath = newSkillResourcePath @@ -1034,49 +1152,49 @@ func (g *GameControls) renderHUD(target d2interface.Surface) error { return err } - w, _ = g.leftSkillResource.SkillIcon.GetCurrentFrameSize() - - g.leftSkillResource.SkillIcon.SetPosition(offsetX, height) + g.leftSkillResource.SkillIcon.SetPosition(x, y) if err := g.leftSkillResource.SkillIcon.Render(target); err != nil { return err } - offsetX += w + return nil +} - // New Stats Selector +func (g *GameControls) renderNewStatsButton(x, y int, target d2interface.Surface) error { if err := g.mainPanel.SetCurrentFrame(frameNewStatsSelector); err != nil { return err } - w, _ = g.mainPanel.GetCurrentFrameSize() - - g.mainPanel.SetPosition(offsetX, height) + g.mainPanel.SetPosition(x, y) if err := g.mainPanel.Render(target); err != nil { return err } - offsetX += w + return nil +} - // Stamina +func (g *GameControls) renderStamina(x, y int, target d2interface.Surface) error { if err := g.mainPanel.SetCurrentFrame(frameStamina); err != nil { return err } - w, _ = g.mainPanel.GetCurrentFrameSize() - - g.mainPanel.SetPosition(offsetX, height) + g.mainPanel.SetPosition(x, y) if err := g.mainPanel.Render(target); err != nil { return err } - offsetX += w + return nil +} - // Stamina status bar +func (g *GameControls) renderStaminaBar(target d2interface.Surface) error { target.PushTranslation(staminaBarOffsetX, staminaBarOffsetY) + defer target.Pop() + target.PushEffect(d2enum.DrawEffectModulate) + defer target.Pop() staminaPercent := g.hero.Stats.Stamina / float64(g.hero.Stats.MaxStamina) @@ -1086,18 +1204,25 @@ func (g *GameControls) renderHUD(target d2interface.Surface) error { } target.DrawRect(int(staminaPercent*staminaBarWidth), staminaBarHeight, staminaBarColor) - target.Pop() - target.Pop() - // Experience status bar + return nil +} + +func (g *GameControls) renderExperienceBar(target d2interface.Surface) error { target.PushTranslation(experienceBarOffsetX, experienceBarOffsetY) + defer target.Pop() expPercent := float64(g.hero.Stats.Experience) / float64(g.hero.Stats.NextLevelExp) target.DrawRect(int(expPercent*expBarWidth), 2, d2util.Color(whiteAlpha100)) - target.Pop() - // Center menu button + return nil +} + +func (g *GameControls) renderMiniPanel(target d2interface.Surface) error { + width, height := target.GetSize() + mx, my := g.lastMouseX, g.lastMouseY + menuButtonFrameIndex := 0 if g.miniPanel.isOpen { menuButtonFrameIndex = 2 @@ -1119,135 +1244,6 @@ func (g *GameControls) renderHUD(target d2interface.Surface) error { return err } - // Potions - if err := g.mainPanel.SetCurrentFrame(framePotions); err != nil { - return err - } - - w, _ = g.mainPanel.GetCurrentFrameSize() - - g.mainPanel.SetPosition(offsetX, height) - - if err := g.mainPanel.Render(target); err != nil { - return err - } - - offsetX += w - - // New Skills Selector - if err := g.mainPanel.SetCurrentFrame(frameNewSkillsSelector); err != nil { - return err - } - - w, _ = g.mainPanel.GetCurrentFrameSize() - - g.mainPanel.SetPosition(offsetX, height) - - if err := g.mainPanel.Render(target); err != nil { - return err - } - - offsetX += w - - // Right skill - newSkillResourcePath = g.getSkillResourceByClass(g.hero.RightSkill.Charclass) - if newSkillResourcePath != g.rightSkillResource.SkillResourcePath { - g.rightSkillResource.SkillIcon, _ = g.ui.NewSprite(newSkillResourcePath, d2resource.PaletteSky) - g.rightSkillResource.SkillResourcePath = newSkillResourcePath - } - - if err := g.rightSkillResource.SkillIcon.SetCurrentFrame(g.hero.RightSkill.IconCel); err != nil { - return err - } - - w, _ = g.rightSkillResource.SkillIcon.GetCurrentFrameSize() - - g.rightSkillResource.SkillIcon.SetPosition(offsetX, height) - - if err := g.rightSkillResource.SkillIcon.Render(target); err != nil { - return err - } - - offsetX += w - - // Right globe holder - if err := g.mainPanel.SetCurrentFrame(frameRightGlobeHolder); err != nil { - return err - } - - g.mainPanel.GetCurrentFrameSize() - - g.mainPanel.SetPosition(offsetX, height) - - if err := g.mainPanel.Render(target); err != nil { - return err - } - - // Mana status bar - manaPercent := float64(g.hero.Stats.Mana) / float64(g.hero.Stats.MaxMana) - manaBarHeight := int(manaPercent * float64(globeHeight)) - - if err := g.hpManaStatusSprite.SetCurrentFrame(frameManaStatus); err != nil { - return err - } - - g.hpManaStatusSprite.SetPosition(offsetX+manaStatusOffsetX, height+manaStatusOffsetY) - - manaMaskRect := image.Rect(0, globeHeight-manaBarHeight, globeWidth, globeHeight) - if err := g.hpManaStatusSprite.RenderSection(target, manaMaskRect); err != nil { - return err - } - - // Right globe - if err := g.globeSprite.SetCurrentFrame(frameRightGlobe); err != nil { - return err - } - - g.globeSprite.SetPosition(offsetX+rightGlobeOffsetX, height+rightGlobeOffsetY) - - if err := g.globeSprite.Render(target); err != nil { - return err - } - - if err := g.globeSprite.Render(target); err != nil { - return err - } - - if g.isZoneTextShown { - g.zoneChangeText.SetPosition(zoneChangeTextX, zoneChangeTextY) - g.zoneChangeText.Render(target) - } - - // Create and format Health string from string lookup table. - fmtHealth := d2tbl.TranslateString("panelhealth") - healthCurr, healthMax := g.hero.Stats.Health, g.hero.Stats.MaxHealth - strPanelHealth := fmt.Sprintf(fmtHealth, healthCurr, healthMax) - - // Display current hp and mana stats hpGlobe or manaGlobe region is clicked - if g.actionableRegions[hpGlobe].rect.IsInRect(mx, my) || g.hpStatsIsVisible { - g.hpManaStatsLabel.SetText(strPanelHealth) - g.hpManaStatsLabel.SetPosition(hpLabelX, hpLabelY) - g.hpManaStatsLabel.Render(target) - } - - // Create and format Mana string from string lookup table. - fmtMana := d2tbl.TranslateString("panelmana") - manaCurr, manaMax := g.hero.Stats.Mana, g.hero.Stats.MaxMana - strPanelMana := fmt.Sprintf(fmtMana, manaCurr, manaMax) - - if g.actionableRegions[manaGlobe].rect.IsInRect(mx, my) || g.manaStatsIsVisible { - g.hpManaStatsLabel.SetText(strPanelMana) - // In case if the mana value gets higher, we need to shift the label to the left a little, hence widthManaLabel. - widthManaLabel, _ := g.hpManaStatsLabel.GetSize() - xManaLabel := manaLabelX - widthManaLabel - g.hpManaStatsLabel.SetPosition(xManaLabel, manaLabelY) - g.hpManaStatsLabel.Render(target) - } - - if err := g.HelpOverlay.Render(target); err != nil { - return err - } - miniPanelButtons := map[actionableType]string{ miniPanelCharacter: "minipanelchar", miniPanelInventory: "minipanelinv", @@ -1258,46 +1254,18 @@ func (g *GameControls) renderHUD(target d2interface.Surface) error { miniPanelGameMenu: "minipanelmenubtn", } + if !g.miniPanel.IsOpen() { + return nil + } + for miniPanelButton, stringTableKey := range miniPanelButtons { - if !g.miniPanel.IsOpen() { + if !g.actionableRegions[miniPanelButton].rect.IsInRect(mx, my) { continue } - if g.actionableRegions[miniPanelButton].rect.IsInRect(mx, my) { - rect := &g.actionableRegions[miniPanelButton].rect - g.nameLabel.SetText(d2tbl.TranslateString(stringTableKey)) - - halfButtonWidth := rect.Width >> 1 - halfButtonHeight := rect.Height >> 1 - - centerX := rect.Left + halfButtonWidth - centerY := rect.Top + halfButtonHeight - - _, labelHeight := g.nameLabel.GetSize() - - labelX := centerX - labelY := centerY - halfButtonHeight - labelHeight - - g.nameLabel.SetPosition(labelX, labelY) - g.nameLabel.Render(target) - } - } - - // Display run/walk tooltip when hovered. - // Note that whether the player is walking or running, the tooltip is the same in Diablo 2. - if g.actionableRegions[walkRun].rect.IsInRect(mx, my) { - var stringTableKey string - - if g.hero.IsRunToggled() { - stringTableKey = "RunOff" - } else { - stringTableKey = "RunOn" - } - + rect := &g.actionableRegions[miniPanelButton].rect g.nameLabel.SetText(d2tbl.TranslateString(stringTableKey)) - rect := &g.actionableRegions[walkRun].rect - halfButtonWidth := rect.Width >> 1 halfButtonHeight := rect.Height >> 1 @@ -1313,71 +1281,250 @@ func (g *GameControls) renderHUD(target d2interface.Surface) error { g.nameLabel.Render(target) } - const ( - staminaExperienceY = 535 - ) + return nil +} - // Display stamina tooltip when hovered. - if g.actionableRegions[stamina].rect.IsInRect(mx, my) { - // Create and format Stamina string from string lookup table. - fmtStamina := d2tbl.TranslateString("panelstamina") - staminaCurr, staminaMax := int(g.hero.Stats.Stamina), g.hero.Stats.MaxStamina - strPanelStamina := fmt.Sprintf(fmtStamina, staminaCurr, staminaMax) +func (g *GameControls) renderPotions(x, _ int, target d2interface.Surface) error { + _, height := target.GetSize() - g.nameLabel.SetText(strPanelStamina) - - rect := &g.actionableRegions[stamina].rect - - halfButtonWidth := rect.Width >> 1 - centerX := rect.Left + halfButtonWidth - - _, labelHeight := g.nameLabel.GetSize() - halfLabelHeight := labelHeight >> 1 - - labelX := centerX - labelY := staminaExperienceY - halfLabelHeight - - g.nameLabel.SetPosition(labelX, labelY) - g.nameLabel.Render(target) + if err := g.mainPanel.SetCurrentFrame(framePotions); err != nil { + return err } - // Display experience tooltip when hovered. - if g.actionableRegions[xp].rect.IsInRect(mx, my) { - // Create and format Experience string from string lookup table. - fmtExp := d2tbl.TranslateString("panelexp") + g.mainPanel.SetPosition(x, height) - // 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(g.hero.Stats.Experience), uint(g.hero.Stats.NextLevelExp) - strPanelExp := fmt.Sprintf(fmtExp, expCurr, expMax) - - g.nameLabel.SetText(strPanelExp) - rect := &g.actionableRegions[stamina].rect - - halfButtonWidth := rect.Width >> 1 - centerX := rect.Left + halfButtonWidth - - _, labelHeight := g.nameLabel.GetSize() - halfLabelHeight := labelHeight >> 1 - - labelX := centerX - labelY := staminaExperienceY - halfLabelHeight - - g.nameLabel.SetPosition(labelX, labelY) - g.nameLabel.Render(target) - } - - if g.skillSelectMenu.IsOpen() { - g.skillSelectMenu.Render(target) + if err := g.mainPanel.Render(target); err != nil { + return err } return nil } +func (g *GameControls) renderNewSkillsButton(x, _ int, target d2interface.Surface) error { + _, height := target.GetSize() + + if err := g.mainPanel.SetCurrentFrame(frameNewSkillsSelector); err != nil { + return err + } + + g.mainPanel.SetPosition(x, height) + + if err := g.mainPanel.Render(target); err != nil { + return err + } + + return nil +} + +func (g *GameControls) renderRightSkill(x, _ int, target d2interface.Surface) error { + _, height := target.GetSize() + + newSkillResourcePath := g.getSkillResourceByClass(g.hero.RightSkill.Charclass) + if newSkillResourcePath != g.rightSkillResource.SkillResourcePath { + g.rightSkillResource.SkillIcon, _ = g.ui.NewSprite(newSkillResourcePath, d2resource.PaletteSky) + g.rightSkillResource.SkillResourcePath = newSkillResourcePath + } + + if err := g.rightSkillResource.SkillIcon.SetCurrentFrame(g.hero.RightSkill.IconCel); err != nil { + return err + } + + g.rightSkillResource.SkillIcon.SetPosition(x, height) + + if err := g.rightSkillResource.SkillIcon.Render(target); err != nil { + return err + } + + return nil +} + +func (g *GameControls) renderManaGlobe(x, _ int, target d2interface.Surface) error { + _, height := target.GetSize() + + if err := g.mainPanel.SetCurrentFrame(frameRightGlobeHolder); err != nil { + return err + } + + g.mainPanel.SetPosition(x, height) + + if err := g.mainPanel.Render(target); err != nil { + return err + } + + // Mana status bar + manaPercent := float64(g.hero.Stats.Mana) / float64(g.hero.Stats.MaxMana) + manaBarHeight := int(manaPercent * float64(globeHeight)) + + if err := g.hpManaStatusSprite.SetCurrentFrame(frameManaStatus); err != nil { + return err + } + + g.hpManaStatusSprite.SetPosition(x+manaStatusOffsetX, height+manaStatusOffsetY) + + manaMaskRect := image.Rect(0, globeHeight-manaBarHeight, globeWidth, globeHeight) + if err := g.hpManaStatusSprite.RenderSection(target, manaMaskRect); err != nil { + return err + } + + // Right globe + if err := g.globeSprite.SetCurrentFrame(frameRightGlobe); err != nil { + return err + } + + g.globeSprite.SetPosition(x+rightGlobeOffsetX, height+rightGlobeOffsetY) + + if err := g.globeSprite.Render(target); err != nil { + return err + } + + if err := g.globeSprite.Render(target); err != nil { + return err + } + + return nil +} + +func (g *GameControls) renderHealthTooltip(target d2interface.Surface) { + mx, my := g.lastMouseX, g.lastMouseY + + // Create and format Health string from string lookup table. + fmtHealth := d2tbl.TranslateString("panelhealth") + healthCurr, healthMax := g.hero.Stats.Health, g.hero.Stats.MaxHealth + strPanelHealth := fmt.Sprintf(fmtHealth, healthCurr, healthMax) + + // Display current hp and mana stats hpGlobe or manaGlobe region is clicked + if !g.actionableRegions[hpGlobe].rect.IsInRect(mx, my) || g.hpStatsIsVisible { + return + } + + g.hpManaStatsLabel.SetText(strPanelHealth) + g.hpManaStatsLabel.SetPosition(hpLabelX, hpLabelY) + g.hpManaStatsLabel.Render(target) +} + +func (g *GameControls) renderManaTooltip(target d2interface.Surface) { + mx, my := g.lastMouseX, g.lastMouseY + + // Create and format Mana string from string lookup table. + fmtMana := d2tbl.TranslateString("panelmana") + manaCurr, manaMax := g.hero.Stats.Mana, g.hero.Stats.MaxMana + strPanelMana := fmt.Sprintf(fmtMana, manaCurr, manaMax) + + if !g.actionableRegions[manaGlobe].rect.IsInRect(mx, my) || g.manaStatsIsVisible { + return + } + + g.hpManaStatsLabel.SetText(strPanelMana) + // In case if the mana value gets higher, we need to shift the + // label to the left a little, hence widthManaLabel. + widthManaLabel, _ := g.hpManaStatsLabel.GetSize() + xManaLabel := manaLabelX - widthManaLabel + g.hpManaStatsLabel.SetPosition(xManaLabel, manaLabelY) + g.hpManaStatsLabel.Render(target) +} + +func (g *GameControls) renderRunWalkTooltip(target d2interface.Surface) { + mx, my := g.lastMouseX, g.lastMouseY + + // Display run/walk tooltip when hovered. + // Note that whether the player is walking or running, the tooltip is the same in Diablo 2. + if !g.actionableRegions[walkRun].rect.IsInRect(mx, my) { + return + } + + var stringTableKey string + + if g.hero.IsRunToggled() { + stringTableKey = "RunOff" + } else { + stringTableKey = "RunOn" + } + + g.nameLabel.SetText(d2tbl.TranslateString(stringTableKey)) + + rect := &g.actionableRegions[walkRun].rect + + halfButtonWidth := rect.Width >> 1 + halfButtonHeight := rect.Height >> 1 + + centerX := rect.Left + halfButtonWidth + centerY := rect.Top + halfButtonHeight + + _, labelHeight := g.nameLabel.GetSize() + + labelX := centerX + labelY := centerY - halfButtonHeight - labelHeight + + g.nameLabel.SetPosition(labelX, labelY) + g.nameLabel.Render(target) +} + +func (g *GameControls) renderStaminaTooltip(target d2interface.Surface) { + mx, my := g.lastMouseX, g.lastMouseY + + // Display stamina tooltip when hovered. + if !g.actionableRegions[stamina].rect.IsInRect(mx, my) { + return + } + + // Create and format Stamina string from string lookup table. + fmtStamina := d2tbl.TranslateString("panelstamina") + staminaCurr, staminaMax := int(g.hero.Stats.Stamina), g.hero.Stats.MaxStamina + strPanelStamina := fmt.Sprintf(fmtStamina, staminaCurr, staminaMax) + + g.nameLabel.SetText(strPanelStamina) + + rect := &g.actionableRegions[stamina].rect + + halfButtonWidth := rect.Width >> 1 + centerX := rect.Left + halfButtonWidth + + _, labelHeight := g.nameLabel.GetSize() + halfLabelHeight := labelHeight >> 1 + + labelX := centerX + labelY := staminaExperienceY - halfLabelHeight + + g.nameLabel.SetPosition(labelX, labelY) + g.nameLabel.Render(target) +} + +func (g *GameControls) renderExperienceTooltip(target d2interface.Surface) { + mx, my := g.lastMouseX, g.lastMouseY + + // Display experience tooltip when hovered. + if !g.actionableRegions[xp].rect.IsInRect(mx, my) { + return + } + + // Create and format Experience string from string lookup table. + fmtExp := d2tbl.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(g.hero.Stats.Experience), uint(g.hero.Stats.NextLevelExp) + strPanelExp := fmt.Sprintf(fmtExp, expCurr, expMax) + + g.nameLabel.SetText(strPanelExp) + rect := &g.actionableRegions[stamina].rect + + halfButtonWidth := rect.Width >> 1 + centerX := rect.Left + halfButtonWidth + + _, labelHeight := g.nameLabel.GetSize() + halfLabelHeight := labelHeight >> 1 + + labelX := centerX + labelY := staminaExperienceY - halfLabelHeight + + g.nameLabel.SetPosition(labelX, labelY) + g.nameLabel.Render(target) +} + // SetZoneChangeText sets the zoneChangeText func (g *GameControls) SetZoneChangeText(text string) { g.zoneChangeText.SetText(text) diff --git a/d2game/d2player/help/help.go b/d2game/d2player/help/help.go index b9ec1ace..c8776157 100644 --- a/d2game/d2player/help/help.go +++ b/d2game/d2player/help/help.go @@ -15,6 +15,25 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) +/* + the 800x600 help screen dc6 file frames look like this + the position we set for frames is the lower-left corner x,y + +----+------------------+-------------------+------------+----+ + | 1 | 3 | 4 | 5 | 6 | + | |------------------+-------------------| | | + | | | | | + | | | | | + +----+ +------------+----+ + | 2 | | 7 | + | | | | + | | | | + +----+ +----+ +*/ +const ( + // if you add up frame widths 1,3,4,5,6 you get (65+255+255+245+20) = 840 + magicHelpBorderOffsetX = -40 +) + const ( frameTopLeft = iota frameBottomLeft @@ -217,25 +236,13 @@ func (h *Overlay) IsInRect(px, py int) bool { // Load the overlay graphical assets func (h *Overlay) Load() { - /* - the 800x600 help screen dc6 file frames look like this - the position we set for frames is the lower-left corner x,y - +----+------------------+-------------------+------------+----+ - | 1 | 3 | 4 | 5 | 6 | - | |------------------+-------------------| | | - | | | | | - | | | | | - +----+ +------------+----+ - | 2 | | 7 | - | | | | - | | | | - +----+ +----+ - */ - const ( - // if you add up frame widths 1,3,4,5,6 you get (65+255+255+245+20) = 840 - magicHelpBorderOffsetX = -40 - ) + h.setupOverlayFrame() + h.setupTitleAndButton() + h.setupBulletedList() + h.setupLabelsWithLines() +} +func (h *Overlay) setupOverlayFrame() { frames := []int{ frameTopLeft, frameBottomLeft, @@ -295,9 +302,10 @@ func (h *Overlay) Load() { h.frames = append(h.frames, f) } +} +func (h *Overlay) setupTitleAndButton() { // Title - text := d2tbl.TranslateString("Strhelp1") // "Diablo II Help" newLabel := h.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) newLabel.SetText(text) @@ -307,8 +315,7 @@ func (h *Overlay) Load() { newLabel.SetPosition((windowWidth/inHalf)-(titleLabelWidth/inHalf)+titleLabelOffsetX, 0) h.text = append(h.text, newLabel) - // Close - + // Button h.closeButton = h.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "") h.closeButton.SetPosition(closeButtonX, closeButtonY) h.closeButton.SetVisible(false) @@ -318,7 +325,9 @@ func (h *Overlay) Load() { newLabel.SetText(d2tbl.TranslateString("strClose")) // "Close" newLabel.SetPosition(closeButtonLabelX, closeButtonLabelY) h.text = append(h.text, newLabel) +} +func (h *Overlay) setupBulletedList() { // Bullets // the hotkeys displayed here should be pulled from a mapping of input events to game events // https://github.com/OpenDiablo2/OpenDiablo2/issues/793 @@ -360,9 +369,10 @@ func (h *Overlay) Load() { DotY: listBulletRootY + listItemOffsetY, }) } +} - // Callouts - +// nolint:funlen // can't reduce +func (h *Overlay) setupLabelsWithLines() { h.createCallout(callout{ LabelText: d2tbl.TranslateString("strlvlup"), // "New Stats" LabelX: newStatsLabelX, diff --git a/d2game/d2player/skilltree.go b/d2game/d2player/skilltree.go index 227fbfa6..65b91c54 100644 --- a/d2game/d2player/skilltree.go +++ b/d2game/d2player/skilltree.go @@ -201,8 +201,8 @@ func makeCloseButtonPos(close1, close2, close3 int) [numTabs]int { return [numTabs]int{close1, close2, close3} } -func (s *skillTree) getTab(class d2enum.Hero) (heroTabData, bool) { - tabMap := map[d2enum.Hero]heroTabData{ +func (s *skillTree) getTab(class d2enum.Hero) *heroTabData { + tabMap := map[d2enum.Hero]*heroTabData{ d2enum.HeroBarbarian: { &skillTreeHeroTypeResources{ skillPanelPath: d2resource.SkillsPanelBarbarian, @@ -242,13 +242,11 @@ func (s *skillTree) getTab(class d2enum.Hero) (heroTabData, bool) { skillCloseButtonXMiddle, skillCloseButtonXLeft), }, - d2enum.HeroAssassin: { &skillTreeHeroTypeResources{ skillPanelPath: d2resource.SkillsPanelAssassin, skillIconPath: d2resource.AssassinSkills, }, - makeTabString("StrSklTree30"), makeTabString("StrSklTree31", "StrSklTree32"), makeTabString("StrSklTree33", "StrSklTree34"), @@ -270,7 +268,6 @@ func (s *skillTree) getTab(class d2enum.Hero) (heroTabData, bool) { skillCloseButtonXLeft, skillCloseButtonXRight), }, - d2enum.HeroAmazon: { &skillTreeHeroTypeResources{ skillPanelPath: d2resource.SkillsPanelAmazon, @@ -284,7 +281,6 @@ func (s *skillTree) getTab(class d2enum.Hero) (heroTabData, bool) { skillCloseButtonXMiddle, skillCloseButtonXLeft), }, - d2enum.HeroDruid: { &skillTreeHeroTypeResources{ skillPanelPath: d2resource.SkillsPanelDruid, @@ -300,14 +296,12 @@ func (s *skillTree) getTab(class d2enum.Hero) (heroTabData, bool) { }, } - entry, found := tabMap[class] - - return entry, found + return tabMap[class] } func (s *skillTree) setHeroTypeResourcePath() { - entry, found := s.getTab(s.heroClass) - if !found { + entry := s.getTab(s.heroClass) + if entry == nil { log.Fatal("Unknown Hero Type") }