package d2player import ( "errors" "fmt" "strconv" "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/d2hero" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) const ( tabButtonX = 628 tabButton0Y = 385 tabButton1Y = 277 tabButton2Y = 170 availSPLabelX = 677 availSPLabelY = 72 skillCloseButtonXLeft = 416 skillCloseButtonXMiddle = 501 skillCloseButtonXRight = 572 skillCloseButtonY = 449 ) const ( firstTab = iota secondTab thirdTab numTabs ) const ( tabIndexOffset = 4 frameOffsetTop = 4 frameOffsetBottom = 6 ) const ( frameCommonTabTopLeft = iota frameCommonTabTopRight frameCommonTabBottomLeft frameCommonTabBottomRight ) const ( frameSelectedTab1Full = 7 frameSelectedTab2Top = 9 // tab2 top and bottom portions are in 2 frames :( frameSelectedTab2Bottom = 11 frameSelectedTab3Full = 13 ) const ( remainingPointsLabelX, remainingPointsLabelY = 677, 128 ) const ( skillTreePanelX = 401 skillTreePanelY = 64 ) const ( skillIconGreySat = 0.2 skillIconGreyBright = 0.44 ) type skillTreeTab struct { buttonText string button *d2ui.Button closeButtonPosX int } func (st *skillTreeTab) createButton(uiManager *d2ui.UIManager, x, y int) { st.button = uiManager.NewButton(d2ui.ButtonTypeSkillTreeTab, st.buttonText) st.button.SetPosition(x, y) } type skillTreeHeroTypeResources struct { skillSprite *d2ui.Sprite skillIconPath string skillPanel *d2ui.Sprite skillPanelPath string } func newSkillTree( skills map[int]*d2hero.HeroSkill, heroClass d2enum.Hero, hero *d2hero.HeroStatsState, asset *d2asset.AssetManager, l d2util.LogLevel, ui *d2ui.UIManager, ) *skillTree { st := &skillTree{ skills: skills, heroClass: heroClass, asset: asset, uiManager: ui, originX: skillTreePanelX, originY: skillTreePanelY, stats: hero, tab: [numTabs]*skillTreeTab{ {}, {}, {}, }, l: l, } st.Logger = d2util.NewLogger() st.Logger.SetLevel(l) st.Logger.SetPrefix(logPrefix) return st } type skillTree struct { resources *skillTreeHeroTypeResources asset *d2asset.AssetManager uiManager *d2ui.UIManager skills map[int]*d2hero.HeroSkill skillIcons []*skillIcon heroClass d2enum.Hero availSPLabel *d2ui.Label closeButton *d2ui.Button tab [numTabs]*skillTreeTab remainingPoints *d2ui.Label isOpen bool originX int originY int selectedTab int onCloseCb func() panelGroup *d2ui.WidgetGroup iconGroup *d2ui.WidgetGroup panel *d2ui.CustomWidget stats *d2hero.HeroStatsState *d2util.Logger l d2util.LogLevel } 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, 400, 600) s.panelGroup.AddWidget(s.panel) frame := s.uiManager.NewUIFrame(d2ui.FrameRight) s.panelGroup.AddWidget(frame) s.closeButton = s.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "") s.closeButton.SetVisible(false) s.closeButton.OnActivated(func() { s.Close() }) s.panelGroup.AddWidget(s.closeButton) s.remainingPoints = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) s.remainingPoints.SetPosition(remainingPointsLabelX, remainingPointsLabelY) s.remainingPoints.Alignment = d2ui.HorizontalAlignCenter s.remainingPoints.SetText(strconv.Itoa(s.stats.SkillPoints)) s.panelGroup.AddWidget(s.remainingPoints) if err := s.setHeroTypeResourcePath(); err != nil { s.Error(err.Error()) } s.loadForHeroType() for _, skill := range s.skills { si := newSkillIcon(s.uiManager, s.resources.skillSprite, s.l, skill) s.skillIcons = append(s.skillIcons, si) s.iconGroup.AddWidget(si) } s.panelGroup.SetVisible(false) s.setTab(0) s.iconGroup.SetVisible(false) } func (s *skillTree) loadForHeroType() { sp, err := s.uiManager.NewSprite(s.resources.skillPanelPath, d2resource.PaletteSky) if err != nil { s.Error(err.Error()) } s.resources.skillPanel = sp si, err := s.uiManager.NewSprite(s.resources.skillIconPath, d2resource.PaletteSky) if err != nil { s.Error(err.Error()) } s.resources.skillSprite = si s.tab[firstTab].createButton(s.uiManager, tabButtonX, tabButton0Y) s.tab[firstTab].button.OnActivated(func() { s.setTab(firstTab) }) s.panelGroup.AddWidget(s.tab[firstTab].button) s.tab[secondTab].createButton(s.uiManager, tabButtonX, tabButton1Y) s.tab[secondTab].button.OnActivated(func() { s.setTab(secondTab) }) s.panelGroup.AddWidget(s.tab[secondTab].button) s.tab[thirdTab].createButton(s.uiManager, tabButtonX, tabButton2Y) s.tab[thirdTab].button.OnActivated(func() { s.setTab(thirdTab) }) s.panelGroup.AddWidget(s.tab[thirdTab].button) s.availSPLabel = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) s.availSPLabel.SetPosition(availSPLabelX, availSPLabelY) s.availSPLabel.Alignment = d2ui.HorizontalAlignCenter s.availSPLabel.SetText(s.makeTabString("StrSklTree1", "StrSklTree2", "StrSklTree3")) s.panelGroup.AddWidget(s.availSPLabel) } type heroTabData struct { resources *skillTreeHeroTypeResources str1, str2, str3 string closeButtonPos [numTabs]int } func (s *skillTree) makeTabString(keys ...interface{}) string { translations := make([]interface{}, len(keys)) token := "%s" format := token for idx, key := range keys { if idx > 0 { format += "\n" + token } translations[idx] = s.asset.TranslateString(key.(string)) } return fmt.Sprintf(format, translations...) } func makeCloseButtonPos(close1, close2, close3 int) [numTabs]int { return [numTabs]int{close1, close2, close3} } func (s *skillTree) getTab(class d2enum.Hero) *heroTabData { tabMap := map[d2enum.Hero]*heroTabData{ d2enum.HeroBarbarian: { &skillTreeHeroTypeResources{ skillPanelPath: d2resource.SkillsPanelBarbarian, skillIconPath: d2resource.BarbarianSkills, }, s.makeTabString("StrSklTree21", "StrSklTree4"), s.makeTabString("StrSklTree21", "StrSklTree22"), s.makeTabString("StrSklTree20"), makeCloseButtonPos( skillCloseButtonXRight, skillCloseButtonXLeft, skillCloseButtonXRight), }, d2enum.HeroNecromancer: { &skillTreeHeroTypeResources{ skillPanelPath: d2resource.SkillsPanelNecromancer, skillIconPath: d2resource.NecromancerSkills, }, s.makeTabString("StrSklTree19"), s.makeTabString("StrSklTree17", "StrSklTree18", "StrSklTree5"), s.makeTabString("StrSklTree16", "StrSklTree5"), makeCloseButtonPos( skillCloseButtonXLeft, skillCloseButtonXRight, skillCloseButtonXLeft), }, d2enum.HeroPaladin: { &skillTreeHeroTypeResources{ skillPanelPath: d2resource.SkillsPanelPaladin, skillIconPath: d2resource.PaladinSkills, }, s.makeTabString("StrSklTree15", "StrSklTree4"), s.makeTabString("StrSklTree14", "StrSklTree13"), s.makeTabString("StrSklTree12", "StrSklTree13"), makeCloseButtonPos( skillCloseButtonXLeft, skillCloseButtonXMiddle, skillCloseButtonXLeft), }, d2enum.HeroAssassin: { &skillTreeHeroTypeResources{ skillPanelPath: d2resource.SkillsPanelAssassin, skillIconPath: d2resource.AssassinSkills, }, s.makeTabString("StrSklTree30"), s.makeTabString("StrSklTree31", "StrSklTree32"), s.makeTabString("StrSklTree33", "StrSklTree34"), makeCloseButtonPos( skillCloseButtonXMiddle, skillCloseButtonXRight, skillCloseButtonXLeft), }, d2enum.HeroSorceress: { &skillTreeHeroTypeResources{ skillPanelPath: d2resource.SkillsPanelSorcerer, skillIconPath: d2resource.SorcererSkills, }, s.makeTabString("StrSklTree25", "StrSklTree5"), s.makeTabString("StrSklTree24", "StrSklTree5"), s.makeTabString("StrSklTree23", "StrSklTree5"), makeCloseButtonPos( skillCloseButtonXLeft, skillCloseButtonXLeft, skillCloseButtonXRight), }, d2enum.HeroAmazon: { &skillTreeHeroTypeResources{ skillPanelPath: d2resource.SkillsPanelAmazon, skillIconPath: d2resource.AmazonSkills, }, s.makeTabString("StrSklTree10", "StrSklTree11", "StrSklTree4"), s.makeTabString("StrSklTree8", "StrSklTree9", "StrSklTree4"), s.makeTabString("StrSklTree6", "StrSklTree7", "StrSklTree4"), makeCloseButtonPos( skillCloseButtonXRight, skillCloseButtonXMiddle, skillCloseButtonXLeft), }, d2enum.HeroDruid: { &skillTreeHeroTypeResources{ skillPanelPath: d2resource.SkillsPanelDruid, skillIconPath: d2resource.DruidSkills, }, s.makeTabString("StrSklTree26"), s.makeTabString("StrSklTree27", "StrSklTree28"), s.makeTabString("StrSklTree29"), makeCloseButtonPos( skillCloseButtonXRight, skillCloseButtonXRight, skillCloseButtonXRight), }, } return tabMap[class] } func (s *skillTree) setHeroTypeResourcePath() error { entry := s.getTab(s.heroClass) if entry == nil { return errors.New("unknown hero type") } s.resources = entry.resources s.tab[firstTab].buttonText = entry.str1 s.tab[secondTab].buttonText = entry.str2 s.tab[thirdTab].buttonText = entry.str3 for i := 0; i < numTabs; i++ { s.tab[i].closeButtonPosX = entry.closeButtonPos[i] } return nil } // Toggle the skill tree visibility func (s *skillTree) Toggle() { s.Info("SkillTree toggled") if s.isOpen { s.Close() } else { s.Open() } } // Close the skill tree func (s *skillTree) Close() { s.isOpen = false s.panelGroup.SetVisible(false) s.iconGroup.SetVisible(false) s.onCloseCb() } // Open the skill tree func (s *skillTree) Open() { s.isOpen = true s.panelGroup.SetVisible(true) s.iconGroup.SetVisible(true) // we only want to enable the icons of our current tab again s.setTab(s.selectedTab) } func (s *skillTree) IsOpen() bool { return s.isOpen } // Set the callback run on closing the skilltree func (s *skillTree) SetOnCloseCb(cb func()) { s.onCloseCb = cb } func (s *skillTree) setTab(tab int) { s.selectedTab = tab s.closeButton.SetPosition(s.tab[tab].closeButtonPosX, skillCloseButtonY) for _, si := range s.skillIcons { si.SetVisible(si.skill.SkillPage == tab+1) } } func (s *skillTree) renderPanelSegment( target d2interface.Surface, frame int) { if err := s.resources.skillPanel.SetCurrentFrame(frame); err != nil { s.Error(err.Error()) return } s.resources.skillPanel.Render(target) } func (s *skillTree) renderTabCommon(target d2interface.Surface) { skillPanel := s.resources.skillPanel x, y := s.originX, s.originY // top w, h, err := skillPanel.GetFrameSize(frameCommonTabTopLeft) if err != nil { s.Error(err.Error()) return } y += h skillPanel.SetPosition(x, y) s.renderPanelSegment(target, frameCommonTabTopLeft) skillPanel.SetPosition(x+w, y) s.renderPanelSegment(target, frameCommonTabTopRight) // bottom _, h, err = skillPanel.GetFrameSize(frameCommonTabBottomLeft) if err != nil { s.Error(err.Error()) return } y += h skillPanel.SetPosition(x, y) s.renderPanelSegment(target, frameCommonTabBottomLeft) skillPanel.SetPosition(x+w, y) s.renderPanelSegment(target, frameCommonTabBottomRight) } func (s *skillTree) renderTab(target d2interface.Surface, tab int) { topFrame := frameOffsetTop + (tabIndexOffset * tab) bottomFrame := frameOffsetBottom + (tabIndexOffset * tab) skillPanel := s.resources.skillPanel x, y := s.originX, s.originY // top _, h0, err := skillPanel.GetFrameSize(topFrame) if err != nil { s.Error(err.Error()) return } y += h0 skillPanel.SetPosition(x, y) s.renderPanelSegment(target, topFrame) // bottom w, h1, err := skillPanel.GetFrameSize(bottomFrame) if err != nil { s.Error(err.Error()) return } skillPanel.SetPosition(x, y+h1) s.renderPanelSegment(target, bottomFrame) // tab button highlighted switch tab { case firstTab: skillPanel.SetPosition(x+w, y+h1) s.renderPanelSegment(target, frameSelectedTab1Full) case secondTab: x += w skillPanel.SetPosition(x, s.originY+h0) s.renderPanelSegment(target, frameSelectedTab2Top) skillPanel.SetPosition(x, y+h1) s.renderPanelSegment(target, frameSelectedTab2Bottom) case thirdTab: skillPanel.SetPosition(x+w, y) s.renderPanelSegment(target, frameSelectedTab3Full) } } // Render the skill tree panel func (s *skillTree) Render(target d2interface.Surface) { s.renderTabCommon(target) s.renderTab(target, s.selectedTab) }