OpenDiablo2/d2game/d2player/skilltree.go

513 lines
12 KiB
Go

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)
}