From 96916863ff2d4ae5d6f01f44a63b4ba9f770b8ba Mon Sep 17 00:00:00 2001 From: gucio321 <73652197+gucio321@users.noreply.github.com> Date: Wed, 2 Dec 2020 08:19:15 +0100 Subject: [PATCH] Quest log initial (#956) * Adding character quest panel --- d2common/d2resource/resource_paths.go | 59 ++-- d2core/d2ui/button.go | 168 ++++++++- d2core/d2ui/widget.go | 2 + d2game/d2player/game_controls.go | 31 +- d2game/d2player/hero_stats_panel.go | 1 + d2game/d2player/inventory.go | 2 +- d2game/d2player/quest_log.go | 478 ++++++++++++++++++++++++++ 7 files changed, 700 insertions(+), 41 deletions(-) create mode 100644 d2game/d2player/quest_log.go diff --git a/d2common/d2resource/resource_paths.go b/d2common/d2resource/resource_paths.go index a174fc57..7026aaf0 100644 --- a/d2common/d2resource/resource_paths.go +++ b/d2common/d2resource/resource_paths.go @@ -110,7 +110,7 @@ const ( HealthManaIndicator = "/data/global/ui/PANEL/hlthmana.DC6" AddSkillButton = "/data/global/ui/PANEL/level.DC6" GoldMoveDialog = "/data/global/ui/menu/dialogbackground.DC6" - WPTabs = "/data/global/ui/menu/expwaygatetabs.dc6" + WPTabs = "/data/global/ui/MENU/expwaygatetabs.dc6" WPBg = "/data/global/ui/menu/waygatebackground.dc6" WPIcons = "/data/global/ui/menu/waygateicons.dc6" @@ -186,34 +186,35 @@ const ( SkillIcon = "/data/global/ui/PANEL/Skillicon.DC6" // --- Quest Log--- - QuestLogBg = "/data/global/ui/menu/questbackground.dc6" - QuestLogDone = "/data/global/ui/menu/questdone.dc6" - QuestLogQDescr = "/data/global/ui/menu/questlast.dc6" - QuestLogSocket = "/data/global/ui/menu/questsocket.dc6" - QuestLogA1Q1 = "/data/global/ui/menu/a1q1.dc6" - QuestLogA1Q2 = "/data/global/ui/menu/a1q2.dc6" - QuestLogA1Q3 = "/data/global/ui/menu/a1q3.dc6" - QuestLogA1Q4 = "/data/global/ui/menu/a1q4.dc6" - QuestLogA1Q5 = "/data/global/ui/menu/a1q5.dc6" - QuestLogA1Q6 = "/data/global/ui/menu/a1q6.dc6" - QuestLogA2Q1 = "/data/global/ui/menu/a2q1.dc6" - QuestLogA2Q2 = "/data/global/ui/menu/a2q2.dc6" - QuestLogA2Q3 = "/data/global/ui/menu/a2q3.dc6" - QuestLogA2Q4 = "/data/global/ui/menu/a2q4.dc6" - QuestLogA2Q5 = "/data/global/ui/menu/a2q5.dc6" - QuestLogA2Q6 = "/data/global/ui/menu/a2q6.dc6" - QuestLogA3Q1 = "/data/global/ui/menu/a3q1.dc6" - QuestLogA3Q2 = "/data/global/ui/menu/a3q2.dc6" - QuestLogA3Q3 = "/data/global/ui/menu/a3q3.dc6" - QuestLogA3Q4 = "/data/global/ui/menu/a3q4.dc6" - QuestLogA3Q5 = "/data/global/ui/menu/a3q5.dc6" - QuestLogA3Q6 = "/data/global/ui/menu/a3q6.dc6" - QuestLogA4Q1 = "/data/global/ui/menu/a4q1.dc6" - QuestLogA4Q2 = "/data/global/ui/menu/a4q2.dc6" - QuestLogA4Q3 = "/data/global/ui/menu/a4q3.dc6" - QuestLogA4Q4 = "/data/global/ui/menu/a4q4.dc6" - QuestLogA4Q5 = "/data/global/ui/menu/a4q5.dc6" - QuestLogA4Q6 = "/data/global/ui/menu/a4q6.dc6" + QuestLogBg = "/data/global/ui/MENU/questbackground.dc6" + QuestLogDone = "/data/global/ui/MENU/questdone.dc6" + QuestLogTabs = "/data/global/ui/MENU/expquesttabs.dc6" + QuestLogQDescrBtn = "/data/global/ui/MENU/questlast.dc6" + QuestLogSocket = "/data/global/ui/MENU/questsockets.dc6" + QuestLogA1Q1 = "/data/global/ui/MENU/a1q1.dc6" + QuestLogA1Q2 = "/data/global/ui/MENU/a1q2.dc6" + QuestLogA1Q3 = "/data/global/ui/MENU/a1q3.dc6" + QuestLogA1Q4 = "/data/global/ui/MENU/a1q4.dc6" + QuestLogA1Q5 = "/data/global/ui/MENU/a1q5.dc6" + QuestLogA1Q6 = "/data/global/ui/MENU/a1q6.dc6" + QuestLogA2Q1 = "/data/global/ui/MENU/a2q1.dc6" + QuestLogA2Q2 = "/data/global/ui/MENU/a2q2.dc6" + QuestLogA2Q3 = "/data/global/ui/MENU/a2q3.dc6" + QuestLogA2Q4 = "/data/global/ui/MENU/a2q4.dc6" + QuestLogA2Q5 = "/data/global/ui/MENU/a2q5.dc6" + QuestLogA2Q6 = "/data/global/ui/MENU/a2q6.dc6" + QuestLogA3Q1 = "/data/global/ui/MENU/a3q1.dc6" + QuestLogA3Q2 = "/data/global/ui/MENU/a3q2.dc6" + QuestLogA3Q3 = "/data/global/ui/MENU/a3q3.dc6" + QuestLogA3Q4 = "/data/global/ui/MENU/a3q4.dc6" + QuestLogA3Q5 = "/data/global/ui/MENU/a3q5.dc6" + QuestLogA3Q6 = "/data/global/ui/MENU/a3q6.dc6" + QuestLogA4Q1 = "/data/global/ui/MENU/a4q1.dc6" + QuestLogA4Q2 = "/data/global/ui/MENU/a4q2.dc6" + QuestLogA4Q3 = "/data/global/ui/MENU/a4q3.dc6" + QuestLogA4Q4 = "/data/global/ui/MENU/a4q4.dc6" + QuestLogA4Q5 = "/data/global/ui/MENU/a4q5.dc6" + QuestLogA4Q6 = "/data/global/ui/MENU/a4q6.dc6" // --- Mouse Pointers --- diff --git a/d2core/d2ui/button.go b/d2core/d2ui/button.go index df95e66f..6435c4f8 100644 --- a/d2core/d2ui/button.go +++ b/d2core/d2ui/button.go @@ -40,16 +40,24 @@ const ( ButtonTypeSquareClose ButtonType = 20 ButtonTypeSquareOk ButtonType = 21 ButtonTypeSkillTreeTab ButtonType = 22 - ButtonTypeMinipanelOpenClose ButtonType = 23 - ButtonTypeMinipanelParty ButtonType = 24 - ButtonTypeBuy ButtonType = 25 - ButtonTypeSell ButtonType = 26 - ButtonTypeRepair ButtonType = 27 - ButtonTypeRepairAll ButtonType = 28 - ButtonTypeLeftArrow ButtonType = 29 - ButtonTypeRightArrow ButtonType = 30 - ButtonTypeQuery ButtonType = 31 - ButtonTypeSquelchChat ButtonType = 32 + ButtonTypeQuestDescr ButtonType = 23 // I suppose it should be also used in WP tabs + ButtonTypeMinipanelOpenClose ButtonType = 24 + ButtonTypeMinipanelParty ButtonType = 25 + ButtonTypeBuy ButtonType = 26 + ButtonTypeSell ButtonType = 27 + ButtonTypeRepair ButtonType = 28 + ButtonTypeRepairAll ButtonType = 29 + ButtonTypeLeftArrow ButtonType = 30 + ButtonTypeRightArrow ButtonType = 31 + ButtonTypeQuery ButtonType = 32 + ButtonTypeSquelchChat ButtonType = 33 + ButtonTypeTab1 ButtonType = 34 + ButtonTypeTab2 ButtonType = 35 + ButtonTypeTab3 ButtonType = 36 + ButtonTypeTab4 ButtonType = 37 + ButtonTypeTab5 ButtonType = 38 + ButtonTypeTabBlank ButtonType = 39 + ButtonTypeBlankQuestBtn ButtonType = 40 ButtonNoFixedWidth int = -1 ButtonNoFixedHeight int = -1 @@ -74,6 +82,14 @@ const ( squelchChatButtonBaseFrame = 20 // base frame offset of the "?" button dc6 ) +const ( + tab1BaseFrame = 0 + tab2BaseFrame = 2 + tab3BaseFrame = 4 + tab4BaseFrame = 6 + tab5BaseFrame = 8 +) + const ( greyAlpha100 = 0x646464ff lightGreyAlpha75 = 0x808080c3 @@ -160,10 +176,17 @@ const ( buttonSkillTreeTabFixedWidth = 93 buttonSkillTreeTabFixedHeight = 107 + buttonTabXSegments = 1 + buttonTabYSegments = 1 + buttonMinipanelOpenCloseBaseFrame = 0 buttonMinipanelXSegments = 1 buttonMinipanelYSegments = 1 + blankQuestButtonXSegments = 1 + blankQuestButtonYSegments = 1 + blankQuestButtonDisabledFrames = -1 + buttonMinipanelCharacterBaseFrame = 0 buttonMinipanelInventoryBaseFrame = 2 buttonMinipanelSkilltreeBaseFrame = 4 @@ -493,6 +516,131 @@ func getButtonLayouts() map[ButtonType]ButtonLayout { TooltipXOffset: buttonBuySellTooltipXOffset, TooltipYOffset: buttonBuySellTooltipYOffset, }, + ButtonTypeQuestDescr: { + XSegments: buttonBuySellSegmentsX, + YSegments: buttonBuySellSegmentsY, + DisabledFrame: buttonBuySellDisabledFrame, + DisabledColor: lightGreyAlpha75, + ResourceName: d2resource.QuestLogQDescrBtn, + PaletteName: d2resource.PaletteUnits, + Toggleable: true, + FontPath: d2resource.Font30, + AllowFrameChange: true, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, + }, + ButtonTypeTab1: { + XSegments: buttonTabXSegments, + YSegments: buttonTabYSegments, + DisabledFrame: tab1BaseFrame + 1, + DisabledColor: lightGreyAlpha75, + ResourceName: d2resource.WPTabs, + PaletteName: d2resource.PaletteUnits, + Toggleable: false, + FontPath: d2resource.Font30, + AllowFrameChange: true, + BaseFrame: tab1BaseFrame, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, + }, + ButtonTypeTab2: { + XSegments: buttonTabXSegments, + YSegments: buttonTabYSegments, + DisabledFrame: tab2BaseFrame + 1, + DisabledColor: lightGreyAlpha75, + ResourceName: d2resource.WPTabs, + PaletteName: d2resource.PaletteUnits, + Toggleable: false, + FontPath: d2resource.Font30, + AllowFrameChange: true, + BaseFrame: tab2BaseFrame, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, + }, + ButtonTypeTab3: { + XSegments: buttonTabXSegments, + YSegments: buttonTabYSegments, + DisabledFrame: tab3BaseFrame + 1, + DisabledColor: lightGreyAlpha75, + ResourceName: d2resource.WPTabs, + PaletteName: d2resource.PaletteUnits, + Toggleable: false, + FontPath: d2resource.Font30, + AllowFrameChange: true, + BaseFrame: tab3BaseFrame, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, + }, + ButtonTypeTab4: { + XSegments: buttonTabXSegments, + YSegments: buttonTabYSegments, + DisabledFrame: tab4BaseFrame + 1, + DisabledColor: lightGreyAlpha75, + ResourceName: d2resource.WPTabs, + PaletteName: d2resource.PaletteUnits, + Toggleable: false, + FontPath: d2resource.Font30, + AllowFrameChange: true, + BaseFrame: tab4BaseFrame, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, + }, + ButtonTypeTab5: { + XSegments: buttonTabXSegments, + YSegments: buttonTabYSegments, + DisabledFrame: tab5BaseFrame + 1, + DisabledColor: lightGreyAlpha75, + ResourceName: d2resource.WPTabs, + PaletteName: d2resource.PaletteUnits, + Toggleable: false, + FontPath: d2resource.Font30, + AllowFrameChange: true, + BaseFrame: tab5BaseFrame, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, + }, + ButtonTypeTabBlank: { + XSegments: buttonTabXSegments, + YSegments: buttonTabYSegments, + DisabledFrame: 0, + DisabledColor: lightGreyAlpha75, + ResourceName: d2resource.WPTabs, + PaletteName: d2resource.PaletteUnits, + Toggleable: true, + FontPath: d2resource.Font30, + AllowFrameChange: false, + HasImage: false, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, + }, + ButtonTypeBlankQuestBtn: { + XSegments: blankQuestButtonXSegments, + YSegments: blankQuestButtonYSegments, + DisabledFrame: blankQuestButtonDisabledFrames, + DisabledColor: lightGreyAlpha75, + ResourceName: d2resource.QuestLogDone, + PaletteName: d2resource.PaletteUnits, + Toggleable: true, + FontPath: d2resource.Font30, + AllowFrameChange: false, + HasImage: false, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, + }, ButtonTypeSkillTreeTab: { XSegments: buttonSkillTreeTabXSegments, YSegments: buttonSkillTreeTabYSegments, diff --git a/d2core/d2ui/widget.go b/d2core/d2ui/widget.go index 3a4ce439..77ce006a 100644 --- a/d2core/d2ui/widget.go +++ b/d2core/d2ui/widget.go @@ -9,8 +9,10 @@ type RenderPriority int const ( RenderPriorityBackground RenderPriority = iota RenderPrioritySkilltree + RenderPriorityInventory RenderPrioritySkilltreeIcon RenderPriorityHeroStatsPanel + RenderPriorityQuestLog RenderPriorityHUDPanel RenderPriorityMinipanel RenderPriorityHelpPanel diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index eb1ca883..ca105857 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -209,6 +209,7 @@ func NewGameControls( inventoryRecord := asset.Records.Layout.Inventory[inventoryRecordKey] heroStatsPanel := NewHeroStatsPanel(asset, ui, hero.Name(), hero.Class, l, hero.Stats) + questLog := NewQuestLog(asset, ui, l, 1) // need to be changed inventory := NewInventory(asset, ui, l, hero.Gold, inventoryRecord) skilltree := newSkillTree(hero.Skills, hero.Class, asset, l, ui) @@ -239,6 +240,7 @@ func NewGameControls( inventory: inventory, skilltree: skilltree, heroStatsPanel: heroStatsPanel, + questLog: questLog, HelpOverlay: helpOverlay, keyMap: keyMap, hud: hud, @@ -267,6 +269,7 @@ func NewGameControls( } gc.heroStatsPanel.SetOnCloseCb(gc.onCloseHeroStatsPanel) + gc.questLog.SetOnCloseCb(gc.onCloseQuestLog) gc.inventory.SetOnCloseCb(gc.onCloseInventory) gc.skilltree.SetOnCloseCb(gc.onCloseSkilltree) @@ -301,6 +304,7 @@ type GameControls struct { hud *HUD skilltree *skillTree heroStatsPanel *HeroStatsPanel + questLog *QuestLog HelpOverlay *HelpOverlay bottomMenuRect *d2geom.Rectangle leftMenuRect *d2geom.Rectangle @@ -385,6 +389,7 @@ func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool { g.inventory.Close() g.skilltree.Close() g.heroStatsPanel.Close() + g.questLog.Close() g.HelpOverlay.Close() g.updateLayout() case d2enum.ToggleInventoryPanel: @@ -393,6 +398,8 @@ func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool { g.toggleInventoryPanel() case d2enum.ToggleCharacterPanel: g.toggleHeroStatsPanel() + case d2enum.ToggleQuestLog: + g.toggleQuestLog() case d2enum.ToggleRunWalk: g.hud.onToggleRunButton(false) case d2enum.HoldRun: @@ -454,6 +461,12 @@ func (g *GameControls) onEscKey() { escHandled = true } + if g.questLog.IsOpen() { + g.questLog.Close() + + escHandled = true + } + if g.HelpOverlay.IsOpen() { g.HelpOverlay.Toggle() @@ -611,6 +624,7 @@ func (g *GameControls) OnMouseButtonDown(event d2interface.MouseEvent) bool { } func (g *GameControls) toggleHeroStatsPanel() { + g.questLog.Close() g.heroStatsPanel.Toggle() g.hud.miniPanel.SetMovedRight(g.heroStatsPanel.IsOpen()) g.updateLayout() @@ -621,6 +635,18 @@ func (g *GameControls) onCloseHeroStatsPanel() { g.updateLayout() } +func (g *GameControls) toggleQuestLog() { + g.heroStatsPanel.Close() + g.questLog.Toggle() + g.hud.miniPanel.SetMovedRight(g.questLog.IsOpen()) + g.updateLayout() +} + +func (g *GameControls) onCloseQuestLog() { + g.hud.miniPanel.SetMovedRight(g.questLog.IsOpen()) + g.updateLayout() +} + func (g *GameControls) toggleInventoryPanel() { g.skilltree.Close() g.inventory.Toggle() @@ -649,6 +675,7 @@ func (g *GameControls) openEscMenu() { g.inventory.Close() g.skilltree.Close() g.heroStatsPanel.Close() + g.questLog.Close() g.hud.miniPanel.closeDisabled() g.escapeMenu.open() g.updateLayout() @@ -660,6 +687,7 @@ func (g *GameControls) Load() { g.inventory.Load() g.skilltree.load() g.heroStatsPanel.Load() + g.questLog.Load() g.HelpOverlay.Load() miniPanelActions := &miniPanelActions{ @@ -667,6 +695,7 @@ func (g *GameControls) Load() { inventoryToggle: g.toggleInventoryPanel, skilltreeToggle: g.toggleSkilltreePanel, menuToggle: g.openEscMenu, + questToggle: g.toggleQuestLog, } g.hud.miniPanel.load(miniPanelActions) } @@ -699,7 +728,7 @@ func (g *GameControls) updateLayout() { func (g *GameControls) isLeftPanelOpen() bool { // https://github.com/OpenDiablo2/OpenDiablo2/issues/801 - return g.heroStatsPanel.IsOpen() + return g.heroStatsPanel.IsOpen() || g.questLog.IsOpen() } func (g *GameControls) isRightPanelOpen() bool { diff --git a/d2game/d2player/hero_stats_panel.go b/d2game/d2player/hero_stats_panel.go index 41cd0b59..9f9cb5c0 100644 --- a/d2game/d2player/hero_stats_panel.go +++ b/d2game/d2player/hero_stats_panel.go @@ -204,6 +204,7 @@ func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) { s.renderStaticLabels(target) } +// nolint:dupl // see quest_log.go.renderStaticPanelFrames comment func (s *HeroStatsPanel) renderStaticPanelFrames(target d2interface.Surface) { frames := []int{ statsPanelTopLeft, diff --git a/d2game/d2player/inventory.go b/d2game/d2player/inventory.go index 7867037c..3a6fde86 100644 --- a/d2game/d2player/inventory.go +++ b/d2game/d2player/inventory.go @@ -98,7 +98,7 @@ func (g *Inventory) Toggle() { func (g *Inventory) Load() { var err error - g.panelGroup = g.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel) + g.panelGroup = g.uiManager.NewWidgetGroup(d2ui.RenderPriorityInventory) g.panelMoveGold = g.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel) g.frame = d2ui.NewUIFrame(g.asset, g.uiManager, d2ui.FrameRight) diff --git a/d2game/d2player/quest_log.go b/d2game/d2player/quest_log.go new file mode 100644 index 00000000..4ee4ee9f --- /dev/null +++ b/d2game/d2player/quest_log.go @@ -0,0 +1,478 @@ +package d2player + +import ( + "fmt" + "image/color" + + "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/d2ui" +) + +const white = 0xffffffff + +const ( + act1 = iota + 1 + act2 + act3 + act4 + act5 +) + +const ( // for the dc6 frames + questLogTopLeft = iota + questLogTopRight + questLogBottomLeft + questLogBottomRight +) + +const ( + normalActQuestsNumber = 6 + act4QuestsNumber = 3 +) + +const ( + questLogOffsetX, questLogOffsetY = 80, 64 +) + +const ( + iconOffsetY = 88 + questOffsetX, questOffsetY = 4, 4 + q1SocketX, q1SocketY = 100, 95 + q2SocketX, q2SocketY = 200, 95 + q3SocketX, q3SocketY = 300, 95 + q4SocketX, q4SocketY = 100, 190 + q5SocketX, q5SocketY = 200, 190 + q6SocketX, q6SocketY = 300, 190 +) + +const ( + questLogCloseButtonX, questLogCloseButtonY = 358, 455 + questLogDescrButtonX, questLogDescrButtonY = 308, 457 + questLabelX, questLabelY = 240, 297 +) + +// toset +const ( + questTabY = 66 + questTab1X = 85 + questTab2X = 143 + questTab3X = 201 + questTab4X = 259 + questTab5X = 317 +) + +const ( + questLogTab1 = iota + questLogTab2 + questLogTab3 + questLogTab4 + questLogTab5 + questLogNumTabs +) + +const ( + questNone = 0 +) + +// NewQuestLog creates a new quest log +func NewQuestLog(asset *d2asset.AssetManager, + ui *d2ui.UIManager, + act int, + l d2util.LogLevel) *QuestLog { + originX := 0 + originY := 0 + + ql := &QuestLog{ + asset: asset, + uiManager: ui, + originX: originX, + originY: originY, + act: act, + tab: [questLogNumTabs]*questLogTab{ + {}, + {}, + {}, + {}, + {}, + }, + selectedQuest: 1, + } + + ql.Logger = d2util.NewLogger() + ql.Logger.SetLevel(l) + ql.Logger.SetPrefix(logPrefix) + + return ql +} + +// QuestLog represents the quest log +type QuestLog struct { + asset *d2asset.AssetManager + uiManager *d2ui.UIManager + panel *d2ui.Sprite + onCloseCb func() + panelGroup *d2ui.WidgetGroup + selectedTab int + selectedQuest int + act int + tab [questLogNumTabs]*questLogTab + + questName *d2ui.Label + questsa1 *d2ui.WidgetGroup + questsa2 *d2ui.WidgetGroup + questsa3 *d2ui.WidgetGroup + questsa4 *d2ui.WidgetGroup + questsa5 *d2ui.WidgetGroup + + originX int + originY int + isOpen bool + + *d2util.Logger +} + +type questLogTab struct { + button *d2ui.Button + invisibleButton *d2ui.Button +} + +func (q *questLogTab) newTab(ui *d2ui.UIManager, tabType d2ui.ButtonType, x int) { + q.button = ui.NewButton(tabType, "") + q.invisibleButton = ui.NewButton(d2ui.ButtonTypeTabBlank, "") + q.button.SetPosition(x, questTabY) + q.invisibleButton.SetPosition(x, questTabY) +} + +// Load the data for the hero status panel +func (s *QuestLog) Load() { + var err error + + s.questsa1 = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog) + s.questsa2 = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog) + s.questsa3 = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog) + s.questsa4 = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog) + s.questsa5 = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog) + s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog) + + frame := d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft) + s.panelGroup.AddWidget(frame) + + s.panel, err = s.uiManager.NewSprite(d2resource.QuestLogBg, d2resource.PaletteSky) + if err != nil { + s.Error(err.Error()) + } + + w, h := frame.GetSize() + staticPanel := s.uiManager.NewCustomWidgetCached(s.renderStaticMenu, w, h) + s.panelGroup.AddWidget(staticPanel) + + closeButton := s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "") + closeButton.SetVisible(false) + closeButton.SetPosition(questLogCloseButtonX, questLogCloseButtonY) + closeButton.OnActivated(func() { s.Close() }) + s.panelGroup.AddWidget(closeButton) + + descrButton := s.uiManager.NewButton(d2ui.ButtonTypeQuestDescr, "") + descrButton.SetVisible(false) + descrButton.SetPosition(questLogDescrButtonX, questLogDescrButtonY) + descrButton.OnActivated(s.onDescrClicked) + s.panelGroup.AddWidget(descrButton) + + s.questName = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteStatic) + s.questName.Alignment = d2ui.HorizontalAlignCenter + s.questName.Color[0] = rgbaColor(white) + s.questName.SetPosition(questLabelX, questLabelY) + s.panelGroup.AddWidget(s.questName) + + s.loadTabs() + s.loadQuestIcons() + + s.panelGroup.SetVisible(false) +} + +func (s *QuestLog) questTable(act, number int) struct { + name string + numberOfDescrs int + status int + frame int + x int + y int +} { + var quests = []struct { + name string // name of quest in string table + numberOfDescrs int // number of possible descriptions (not used yet) + status int // status of quest (not used yet) + frame int // frame of quest + x, y int // position of quest + }{ + {"qstsa1q1", 5, 0, 0, q1SocketX, q1SocketY}, + {"qstsa1q2", 0, 0, 1, q2SocketX, q2SocketY}, + {"qstsa1q3", 0, 0, 2, q3SocketX, q3SocketY}, + {"qstsa1q4", 0, 0, 3, q4SocketX, q4SocketY}, + {"qstsa1q5", 0, 0, 4, q5SocketX, q5SocketY}, + {"qstsa1q6", 0, 0, 5, q6SocketX, q6SocketY}, + {"qstsa2q1", 0, 0, 6, q1SocketX, q1SocketY}, + {"qstsa2q2", 0, 0, 7, q2SocketX, q2SocketY}, + {"qstsa2q3", 0, 0, 8, q3SocketX, q3SocketY}, + {"qstsa2q4", 0, 0, 9, q4SocketX, q4SocketY}, + {"qstsa2q5", 0, 0, 10, q5SocketX, q5SocketY}, + {"qstsa2q6", 0, 0, 11, q6SocketX, q6SocketY}, + {"qstsa3q1", 0, 0, 12, q1SocketX, q1SocketY}, + {"qstsa3q2", 0, 0, 13, q2SocketX, q2SocketY}, + {"qstsa3q3", 0, 0, 14, q3SocketX, q3SocketY}, + {"qstsa3q4", 0, 0, 15, q4SocketX, q4SocketY}, + {"qstsa3q5", 0, 0, 16, q5SocketX, q5SocketY}, + {"qstsa3q6", 0, 0, 17, q6SocketX, q6SocketY}, + {"qstsa4q1", 0, 0, 18, q1SocketX, q1SocketY}, + {"qstsa4q2", 0, 0, 19, q2SocketX, q2SocketY}, + {"qstsa4q3", 0, 0, 20, q3SocketX, q3SocketY}, + {"qstsa5q1", 0, 0, 21, q1SocketX, q1SocketY}, + {"qstsa5q2", 0, 0, 22, q2SocketX, q2SocketY}, + {"qstsa5q3", 0, 0, 23, q3SocketX, q3SocketY}, + {"qstsa5q4", 0, 0, 24, q4SocketX, q4SocketY}, + {"qstsa5q5", 0, 0, 25, q5SocketX, q5SocketY}, + {"qstsa5q6", 0, 0, 26, q6SocketX, q6SocketY}, + } + + key := (act-1)*normalActQuestsNumber + number + if act > act4 { + key -= act4QuestsNumber + } + + return quests[key] +} + +func (s *QuestLog) loadQuestIcons() { + s.questsa1 = s.loadQuestIconsForAct(act1) + s.questsa2 = s.loadQuestIconsForAct(act2) + s.questsa3 = s.loadQuestIconsForAct(act3) + s.questsa4 = s.loadQuestIconsForAct(act4) + s.questsa5 = s.loadQuestIconsForAct(act5) +} + +func (s *QuestLog) makeQuestCallback(n int) func() { + return func() { + s.onQuestClicked(n + 1) + } +} + +func (s *QuestLog) loadQuestIconsForAct(act int) *d2ui.WidgetGroup { + wg := s.uiManager.NewWidgetGroup(d2ui.RenderPriorityQuestLog) + + var questsInAct int + if act == act4 { + questsInAct = act4QuestsNumber + } else { + questsInAct = normalActQuestsNumber + } + + for n := 0; n < questsInAct; n++ { + q := s.questTable(act, n) + + button := s.uiManager.NewButton(d2ui.ButtonTypeBlankQuestBtn, "") + button.SetPosition(q.x+questOffsetX, q.y+questOffsetY) + button.OnActivated(s.makeQuestCallback(n)) + + socket, err := s.uiManager.NewSprite(d2resource.QuestLogSocket, d2resource.PaletteSky) + if err != nil { + s.Error(err.Error()) + } + + socket.SetPosition(q.x+questOffsetX, q.y+iconOffsetY+2*questOffsetY) + + icon, err := s.uiManager.NewSprite(d2resource.QuestLogDone, d2resource.PaletteSky) + if err != nil { + s.Error(err.Error()) + } + + err = icon.SetCurrentFrame(q.frame) + if err != nil { + s.Error(err.Error()) + } + + icon.SetPosition(q.x+questOffsetX, q.y+questOffsetY+iconOffsetY) + + wg.AddWidget(icon) + wg.AddWidget(socket) + wg.AddWidget(button) + } + wg.SetVisible(false) + + return wg +} + +func (s *QuestLog) loadQuestLabels() { + if s.selectedQuest == 0 { + s.questName.SetText("") + return + } + + s.questName.SetText(s.asset.TranslateString(fmt.Sprintf("qstsa%dq%d", s.selectedTab+1, s.selectedQuest))) +} + +func (s *QuestLog) loadTabs() { + s.tab[questLogTab1].newTab(s.uiManager, d2ui.ButtonTypeTab1, questTab1X) + s.tab[questLogTab1].invisibleButton.OnActivated(func() { s.setTab(questLogTab1) }) + s.panelGroup.AddWidget(s.tab[questLogTab1].button) + s.panelGroup.AddWidget(s.tab[questLogTab1].invisibleButton) + + s.tab[questLogTab2].newTab(s.uiManager, d2ui.ButtonTypeTab2, questTab2X) + s.tab[questLogTab2].invisibleButton.OnActivated(func() { s.setTab(questLogTab2) }) + s.panelGroup.AddWidget(s.tab[questLogTab2].button) + s.panelGroup.AddWidget(s.tab[questLogTab2].invisibleButton) + + s.tab[questLogTab3].newTab(s.uiManager, d2ui.ButtonTypeTab3, questTab3X) + s.tab[questLogTab3].invisibleButton.OnActivated(func() { s.setTab(questLogTab3) }) + s.panelGroup.AddWidget(s.tab[questLogTab3].button) + s.panelGroup.AddWidget(s.tab[questLogTab3].invisibleButton) + + s.tab[questLogTab4].newTab(s.uiManager, d2ui.ButtonTypeTab4, questTab4X) + s.tab[questLogTab4].invisibleButton.OnActivated(func() { s.setTab(questLogTab4) }) + s.panelGroup.AddWidget(s.tab[questLogTab4].button) + s.panelGroup.AddWidget(s.tab[questLogTab4].invisibleButton) + + s.tab[questLogTab5].newTab(s.uiManager, d2ui.ButtonTypeTab5, questTab5X) + s.tab[questLogTab5].invisibleButton.OnActivated(func() { s.setTab(questLogTab5) }) + s.panelGroup.AddWidget(s.tab[questLogTab5].button) + s.panelGroup.AddWidget(s.tab[questLogTab5].invisibleButton) + + s.setTab(s.act - 1) +} + +func (s *QuestLog) setTab(tab int) { + s.selectedTab = tab + s.selectedQuest = questNone + s.loadQuestLabels() + + s.questsa1.SetVisible(tab == questLogTab1) + s.questsa2.SetVisible(tab == questLogTab2) + s.questsa3.SetVisible(tab == questLogTab3) + s.questsa4.SetVisible(tab == questLogTab4) + s.questsa5.SetVisible(tab == questLogTab5) + + for i := 0; i < questLogNumTabs; i++ { + s.tab[i].button.SetEnabled(i == tab) + } +} + +func (s *QuestLog) onQuestClicked(number int) { + s.selectedQuest = number + s.loadQuestLabels() + s.Infof("Quest number %d in tab %d clicked", number, s.selectedTab) +} + +func (s *QuestLog) onDescrClicked() { + // +} + +// IsOpen returns true if the hero status panel is open +func (s *QuestLog) IsOpen() bool { + return s.isOpen +} + +// Toggle toggles the visibility of the hero status panel +func (s *QuestLog) Toggle() { + if s.isOpen { + s.Close() + } else { + s.Open() + } +} + +// Open opens the hero status panel +func (s *QuestLog) Open() { + s.isOpen = true + s.panelGroup.SetVisible(true) + s.setTab(s.selectedTab) +} + +// Close closed the hero status panel +func (s *QuestLog) Close() { + s.isOpen = false + s.panelGroup.SetVisible(false) + s.questsa1.SetVisible(false) + s.questsa2.SetVisible(false) + s.questsa3.SetVisible(false) + s.questsa4.SetVisible(false) + s.questsa5.SetVisible(false) + s.onCloseCb() +} + +// SetOnCloseCb the callback run on closing the HeroStatsPanel +func (s *QuestLog) SetOnCloseCb(cb func()) { + s.onCloseCb = cb +} + +// Advance updates labels on the panel +func (s *QuestLog) Advance(elapsed float64) { + // +} + +func (s *QuestLog) renderStaticMenu(target d2interface.Surface) { + s.renderStaticPanelFrames(target) +} + +// nolint:dupl // I think it is OK, to duplicate this function +func (s *QuestLog) renderStaticPanelFrames(target d2interface.Surface) { + frames := []int{ + questLogTopLeft, + questLogTopRight, + questLogBottomRight, + questLogBottomLeft, + } + + currentX := s.originX + questLogOffsetX + currentY := s.originY + questLogOffsetY + + for _, frameIndex := range frames { + if err := s.panel.SetCurrentFrame(frameIndex); err != nil { + s.Error(err.Error()) + } + + w, h := s.panel.GetCurrentFrameSize() + + switch frameIndex { + case questLogTopLeft: + s.panel.SetPosition(currentX, currentY+h) + currentX += w + case questLogTopRight: + s.panel.SetPosition(currentX, currentY+h) + currentY += h + case questLogBottomRight: + s.panel.SetPosition(currentX, currentY+h) + case questLogBottomLeft: + s.panel.SetPosition(currentX-w, currentY+h) + } + + s.panel.Render(target) + } +} + +// copy from character select +func rgbaColor(rgba uint32) color.RGBA { + result := color.RGBA{} + a, b, g, r := 0, 1, 2, 3 + byteWidth := 8 + byteMask := 0xff + + for idx := 0; idx < 4; idx++ { + shift := idx * byteWidth + component := uint8(rgba>>shift) & uint8(byteMask) + + switch idx { + case a: + result.A = component + case b: + result.B = component + case g: + result.G = component + case r: + result.R = component + } + } + + return result +}