diff --git a/d2common/d2enum/hero.go b/d2common/d2enum/hero.go index bacfe5c3..b42a1b44 100644 --- a/d2common/d2enum/hero.go +++ b/d2common/d2enum/hero.go @@ -43,3 +43,26 @@ func (h Hero) GetToken() string { return "" } + +// GetToken3 returns a 3 letter token +func (h Hero) GetToken3() string { + switch h { + case HeroBarbarian: + return "BAR" + case HeroNecromancer: + return "NEC" + case HeroPaladin: + return "PAL" + case HeroAssassin: + return "ASS" + case HeroSorceress: + return "SOR" + case HeroAmazon: + return "AMA" + case HeroDruid: + return "DRU" + default: + log.Fatalf("Unknown hero token: %d", h) + } + return "" +} diff --git a/d2core/d2hero/hero_state_factory.go b/d2core/d2hero/hero_state_factory.go index 6b782536..5ecd46f4 100644 --- a/d2core/d2hero/hero_state_factory.go +++ b/d2core/d2hero/hero_state_factory.go @@ -53,7 +53,7 @@ func (f *HeroStateFactory) CreateHeroState( } defaultStats := f.asset.Records.Character.Stats[hero] - skillState, err := f.CreateHeroSkillsState(defaultStats) + skillState, err := f.CreateHeroSkillsState(defaultStats, hero) if err != nil { return nil, err @@ -85,7 +85,7 @@ func (f *HeroStateFactory) GetAllHeroStates() ([]*HeroState, error) { classStats := f.asset.Records.Character.Stats[gameState.HeroType] gameState.Stats = f.CreateHeroStatsState(gameState.HeroType, classStats) - skillState, err := f.CreateHeroSkillsState(classStats) + skillState, err := f.CreateHeroSkillsState(classStats, gameState.HeroType) if err != nil { return nil, err } @@ -104,7 +104,7 @@ func (f *HeroStateFactory) GetAllHeroStates() ([]*HeroState, error) { } // CreateHeroSkillsState will assemble the hero skills from the class stats record. -func (f *HeroStateFactory) CreateHeroSkillsState(classStats *d2records.CharStatsRecord) (map[int]*HeroSkill, error) { +func (f *HeroStateFactory) CreateHeroSkillsState(classStats *d2records.CharStatsRecord, heroType d2enum.Hero) (map[int]*HeroSkill, error) { baseSkills := map[int]*HeroSkill{} for idx := range classStats.BaseSkill { @@ -122,6 +122,16 @@ func (f *HeroStateFactory) CreateHeroSkillsState(classStats *d2records.CharStats baseSkills[skill.ID] = skill } + skillList := f.asset.Records.Skill.Details + token := strings.ToLower(heroType.GetToken3()) + for idx := range skillList { + if skillList[idx].Charclass == token { + skill, _ := f.CreateHeroSkill(0, skillList[idx].Skill) + baseSkills[skill.ID] = skill + } + } + + skillRecord, err := f.CreateHeroSkill(1, "Attack") if err != nil { return nil, err diff --git a/d2core/d2records/skill_description_loader.go b/d2core/d2records/skill_description_loader.go index fe223175..917cbf8f 100644 --- a/d2core/d2records/skill_description_loader.go +++ b/d2core/d2records/skill_description_loader.go @@ -18,9 +18,9 @@ func skillDescriptionLoader(r *RecordManager, d *d2txt.DataDictionary) error { for d.Next() { record := &SkillDescriptionRecord{ d.String("skilldesc"), - d.String("SkillPage"), - d.String("SkillRow"), - d.String("SkillColumn"), + d.Number("SkillPage"), + d.Number("SkillRow"), + d.Number("SkillColumn"), d.String("ListRow"), d.String("ListPool"), d.Number("IconCel"), diff --git a/d2core/d2records/skill_description_record.go b/d2core/d2records/skill_description_record.go index 011446fc..bb43b3b1 100644 --- a/d2core/d2records/skill_description_record.go +++ b/d2core/d2records/skill_description_record.go @@ -9,9 +9,9 @@ type SkillDescriptions map[string]*SkillDescriptionRecord // generating text strings for skills. type SkillDescriptionRecord struct { Name string // skilldesc - SkillPage string // SkillPage - SkillRow string // SkillRow - SkillColumn string // SkillColumn + SkillPage int // SkillPage + SkillRow int // SkillRow + SkillColumn int // SkillColumn ListRow string // ListRow ListPool string // ListPool IconCel int // IconCel diff --git a/d2core/d2ui/button.go b/d2core/d2ui/button.go index 275467b9..548888d1 100644 --- a/d2core/d2ui/button.go +++ b/d2core/d2ui/button.go @@ -41,6 +41,10 @@ const ( ButtonTypeMinipanelQuest ButtonType = 18 ButtonTypeMinipanelMen ButtonType = 19 ButtonTypeSquareClose ButtonType = 20 + ButtonTypeSkillTreeTab ButtonType = 21 + + ButtonNoFixedWidth int = -1 + ButtonNoFixedHeight int = -1 ) const ( @@ -50,6 +54,7 @@ const ( const ( greyAlpha100 = 0x646464ff lightGreyAlpha75 = 0x808080c3 + whiteAlpha100 = 0xffffffff ) // ButtonLayout defines the type of buttons @@ -65,6 +70,10 @@ type ButtonLayout struct { TextOffset int Toggleable bool AllowFrameChange bool + HasImage bool + FixedWidth int + FixedHeight int + LabelColor uint32 } const ( @@ -111,6 +120,10 @@ func getButtonLayouts() map[ButtonType]ButtonLayout { PaletteName: d2resource.PaletteUnits, FontPath: d2resource.FontExocet10, AllowFrameChange: true, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, }, ButtonTypeShort: { XSegments: buttonShortSegmentsX, @@ -121,6 +134,10 @@ func getButtonLayouts() map[ButtonType]ButtonLayout { PaletteName: d2resource.PaletteUnits, FontPath: d2resource.FontRediculous, AllowFrameChange: true, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, }, ButtonTypeMedium: { XSegments: buttonMediumSegmentsX, @@ -129,6 +146,10 @@ func getButtonLayouts() map[ButtonType]ButtonLayout { PaletteName: d2resource.PaletteUnits, FontPath: d2resource.FontExocet10, AllowFrameChange: true, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, }, ButtonTypeTall: { XSegments: buttonTallSegmentsX, @@ -138,6 +159,10 @@ func getButtonLayouts() map[ButtonType]ButtonLayout { PaletteName: d2resource.PaletteUnits, FontPath: d2resource.FontExocet10, AllowFrameChange: true, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, }, ButtonTypeOkCancel: { XSegments: buttonOkCancelSegmentsX, @@ -147,6 +172,10 @@ func getButtonLayouts() map[ButtonType]ButtonLayout { PaletteName: d2resource.PaletteUnits, FontPath: d2resource.FontRediculous, AllowFrameChange: true, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, }, ButtonTypeRun: { XSegments: buttonRunSegmentsX, @@ -157,6 +186,10 @@ func getButtonLayouts() map[ButtonType]ButtonLayout { Toggleable: true, FontPath: d2resource.FontRediculous, AllowFrameChange: true, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, }, ButtonTypeSquareClose: { XSegments: buttonBuySellSegmentsX, @@ -168,6 +201,25 @@ func getButtonLayouts() map[ButtonType]ButtonLayout { FontPath: d2resource.Font30, AllowFrameChange: true, BaseFrame: closeButtonBaseFrame, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + LabelColor: greyAlpha100, + }, + ButtonTypeSkillTreeTab: { + XSegments: 1, + YSegments: 1, + DisabledFrame: 7, + ResourceName: d2resource.SkillsPanelAmazon, + PaletteName: d2resource.PaletteSky, + Toggleable: false, + FontPath: d2resource.Font16, + AllowFrameChange: false, + BaseFrame: 7, + HasImage: false, + FixedWidth: 93, + FixedHeight: 107, + LabelColor: whiteAlpha100, }, } } @@ -209,7 +261,7 @@ func (ui *UIManager) NewButton(buttonType ButtonType, text string) *Button { lbl := ui.NewLabel(buttonLayout.FontPath, d2resource.PaletteUnits) lbl.SetText(text) - lbl.Color[0] = d2util.Color(greyAlpha100) + lbl.Color[0] = d2util.Color(buttonLayout.LabelColor) lbl.Alignment = d2gui.HorizontalAlignCenter buttonSprite, err := ui.NewSprite(buttonLayout.ResourceName, buttonLayout.PaletteName) @@ -217,25 +269,32 @@ func (ui *UIManager) NewButton(buttonType ButtonType, text string) *Button { log.Print(err) return nil } + if buttonLayout.FixedWidth > 0 { + btn.width = buttonLayout.FixedWidth + } else { + for i := 0; i < buttonLayout.XSegments; i++ { + w, _, err := buttonSprite.GetFrameSize(i) + if err != nil { + log.Print(err) + return nil + } - for i := 0; i < buttonLayout.XSegments; i++ { - w, _, err := buttonSprite.GetFrameSize(i) - if err != nil { - log.Print(err) - return nil - } - - btn.width += w + btn.width += w + } } - for i := 0; i < buttonLayout.YSegments; i++ { - _, h, err := buttonSprite.GetFrameSize(i * buttonLayout.YSegments) - if err != nil { - log.Print(err) - return nil - } + if buttonLayout.FixedHeight > 0 { + btn.height = buttonLayout.FixedHeight + } else { + for i := 0; i < buttonLayout.YSegments; i++ { + _, h, err := buttonSprite.GetFrameSize(i * buttonLayout.YSegments) + if err != nil { + log.Print(err) + return nil + } - btn.height += h + btn.height += h + } } btn.normalSurface, err = ui.renderer.NewSurface(btn.width, btn.height, d2enum.FilterNearest) @@ -259,10 +318,12 @@ func (v *Button) renderFrames(btnSprite *Sprite, btnLayout *ButtonLayout, label totalButtonTypes := btnSprite.GetFrameCount() / (btnLayout.XSegments * btnLayout.YSegments) - err = btnSprite.RenderSegmented(v.normalSurface, btnLayout.XSegments, btnLayout.YSegments, btnLayout.BaseFrame) + 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) + if err != nil { + fmt.Printf("failed to render button normalSurface, err: %v\n", err) + } } _, labelHeight := label.GetSize() @@ -272,7 +333,7 @@ func (v *Button) renderFrames(btnSprite *Sprite, btnLayout *ButtonLayout, label label.SetPosition(xOffset, textY) label.Render(v.normalSurface) - if btnLayout.AllowFrameChange { + if btnLayout.HasImage && btnLayout.AllowFrameChange { frameOffset := 0 xSeg, ySeg, baseFrame := btnLayout.XSegments, btnLayout.YSegments, btnLayout.BaseFrame @@ -395,7 +456,11 @@ func (v *Button) Render(target d2interface.Surface) error { case v.toggled && v.pressed: err = target.Render(v.pressedToggledSurface) case v.pressed: - err = target.Render(v.pressedSurface) + if v.buttonLayout.AllowFrameChange { + err = target.Render(v.pressedSurface) + } else { + err = target.Render(v.normalSurface) + } case v.toggled: err = target.Render(v.toggledSurface) default: diff --git a/d2core/d2ui/frame.go b/d2core/d2ui/frame.go new file mode 100644 index 00000000..cd925270 --- /dev/null +++ b/d2core/d2ui/frame.go @@ -0,0 +1,235 @@ +package d2ui + +import ( + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" +) + +type UIFrame struct { + asset *d2asset.AssetManager + uiManager *UIManager + frame *Sprite + originX int + originY int + frameOrientation FrameOrientation +} + +type FrameOrientation = int +const( + FrameLeft FrameOrientation = iota + FrameRight +) + +func NewUIFrame ( + asset *d2asset.AssetManager, + uiManager *UIManager, + frameOrientation FrameOrientation, +) *UIFrame { + var originX, originY = 0,0 + + switch frameOrientation { + case FrameLeft: + originX = 0 + originY = 0 + case FrameRight: + originX = 400 + originY = 0 + } + frame := &UIFrame { + asset : asset, + uiManager: uiManager, + frameOrientation: frameOrientation, + originX: originX, + originY: originY, + } + frame.Load() + return frame +} + +func (u *UIFrame) Load() { + sprite, err := u.uiManager.NewSprite(d2resource.Frame, d2resource.PaletteSky) + if err != nil { + log.Print(err) + } + u.frame = sprite +} + +func (u *UIFrame) Render(target d2interface.Surface) error { + switch u.frameOrientation { + case FrameLeft: + return u.renderLeft(target) + case FrameRight: + return u.renderRight(target) + } + return nil +} + +func (u *UIFrame) renderLeft(target d2interface.Surface) error { + x, y := u.originX, u.originY + + // Frame + // Top left + if err := u.frame.SetCurrentFrame(0); err != nil { + return err + } + + w, h := u.frame.GetCurrentFrameSize() + + u.frame.SetPosition(x, y+h) + + if err := u.frame.Render(target); err != nil { + return err + } + + x += w + y += h + + // Top right + if err := u.frame.SetCurrentFrame(1); err != nil { + return err + } + + _, h = u.frame.GetCurrentFrameSize() + + u.frame.SetPosition(x, u.originY+h) + + if err := u.frame.Render(target); err != nil { + return err + } + + x = u.originX + + // Right + if err := u.frame.SetCurrentFrame(2); err != nil { + return err + } + + _, h = u.frame.GetCurrentFrameSize() + u.frame.SetPosition(x, y+h) + + if err := u.frame.Render(target); err != nil { + return err + } + + y += h + + // Bottom left + if err := u.frame.SetCurrentFrame(3); err != nil { + return err + } + + w, h = u.frame.GetCurrentFrameSize() + + u.frame.SetPosition(x, y+h) + + if err := u.frame.Render(target); err != nil { + return err + } + + x += w + + // Bottom right + if err := u.frame.SetCurrentFrame(4); err != nil { + return err + } + + _, h = u.frame.GetCurrentFrameSize() + + u.frame.SetPosition(x, y+h) + + if err := u.frame.Render(target); err != nil { + return err + } + return nil +} + +func (u *UIFrame) renderRight(target d2interface.Surface) error { + x, y := u.originX, u.originY + + // Frame + // Top left + if err := u.frame.SetCurrentFrame(5); err != nil { + return err + } + + w, h := u.frame.GetCurrentFrameSize() + + u.frame.SetPosition(x, y+h) + + if err := u.frame.Render(target); err != nil { + return err + } + + x += w + + // Top right + if err := u.frame.SetCurrentFrame(6); err != nil { + return err + } + + w, h = u.frame.GetCurrentFrameSize() + + u.frame.SetPosition(x, y+h) + + if err := u.frame.Render(target); err != nil { + return err + } + + x += w + y += h + + // Right + if err := u.frame.SetCurrentFrame(7); err != nil { + return err + } + + w, h = u.frame.GetCurrentFrameSize() + + u.frame.SetPosition(x-w, y+h) + + if err := u.frame.Render(target); err != nil { + return err + } + + y += h + + // Bottom right + if err := u.frame.SetCurrentFrame(8); err != nil { + return err + } + + w, h = u.frame.GetCurrentFrameSize() + + u.frame.SetPosition(x-w, y+h) + + if err := u.frame.Render(target); err != nil { + return err + } + + x -= w + + // Bottom left + if err := u.frame.SetCurrentFrame(9); err != nil { + return err + } + + w, h = u.frame.GetCurrentFrameSize() + + u.frame.SetPosition(x-w, y+h) + + if err := u.frame.Render(target); err != nil { + return err + } + return nil +} + +func (u *UIFrame) GetFrameBounds() (width, height int) { + return u.frame.GetFrameBounds() +} + +func (u *UIFrame) GetFrameCount() int { + return u.frame.GetFrameCount() +} diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index 26225fa9..0ad88c20 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -58,6 +58,7 @@ type GameControls struct { escapeMenu *EscapeMenu ui *d2ui.UIManager inventory *Inventory + skilltree *SkillTree heroStatsPanel *HeroStatsPanel HelpOverlay *help.Overlay miniPanel *miniPanel @@ -151,13 +152,13 @@ func NewGameControls( switch hero.Class { case d2enum.HeroAssassin: - inventoryRecordKey = "Assassin" + inventoryRecordKey = "Assassin2" case d2enum.HeroAmazon: inventoryRecordKey = "Amazon2" case d2enum.HeroBarbarian: inventoryRecordKey = "Barbarian2" case d2enum.HeroDruid: - inventoryRecordKey = "Druid" + inventoryRecordKey = "Druid2" case d2enum.HeroNecromancer: inventoryRecordKey = "Necromancer2" case d2enum.HeroPaladin: @@ -191,6 +192,7 @@ func NewGameControls( inputListener: inputListener, mapRenderer: mapRenderer, inventory: NewInventory(asset, ui, inventoryRecord), + skilltree: NewSkillTree(hero.Skills, hero.Class, asset, renderer, ui, guiManager), heroStatsPanel: NewHeroStatsPanel(asset, ui, hero.Name(), hero.Class, hero.Stats), HelpOverlay: help.NewHelpOverlay(asset, renderer, ui, guiManager), miniPanel: newMiniPanel(asset, ui, isSinglePlayer), @@ -307,6 +309,9 @@ func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool { case d2enum.KeyI: g.inventory.Toggle() g.updateLayout() + case d2enum.KeyT: + g.skilltree.Toggle() + g.updateLayout() case d2enum.KeyC: g.heroStatsPanel.Toggle() g.updateLayout() @@ -347,6 +352,12 @@ func (g *GameControls) onEscKey() { escHandled = true } + if g.skilltree.IsOpen() { + g.skilltree.Close() + + escHandled = true + } + if g.heroStatsPanel.IsOpen() { g.heroStatsPanel.Close() @@ -521,6 +532,7 @@ func (g *GameControls) Load() { g.loadUIButtons() g.inventory.Load() + g.skilltree.Load() g.heroStatsPanel.Load() g.HelpOverlay.Load() } @@ -570,8 +582,7 @@ func (g *GameControls) isLeftPanelOpen() bool { } func (g *GameControls) isRightPanelOpen() bool { - // TODO: add skills tree panel - return g.inventory.IsOpen() + return g.inventory.IsOpen() || g.skilltree.IsOpen() } func (g *GameControls) isInActiveMenusRect(px, py int) bool { @@ -655,6 +666,10 @@ func (g *GameControls) Render(target d2interface.Surface) error { return err } + if err := g.skilltree.Render(target); err != nil { + return err + } + width, height := target.GetSize() offset := 0 @@ -1137,6 +1152,11 @@ func (g *GameControls) onClickActionable(item actionableType) { g.inventory.Toggle() g.updateLayout() + case miniPanelSkillTree: + log.Println("Skilltree button on mini panel is pressed") + + g.skilltree.Toggle() + g.updateLayout() case miniPanelGameMenu: g.miniPanel.Close() g.escapeMenu.open() diff --git a/d2game/d2player/hero_stats_panel.go b/d2game/d2player/hero_stats_panel.go index 82088c88..0bca828e 100644 --- a/d2game/d2player/hero_stats_panel.go +++ b/d2game/d2player/hero_stats_panel.go @@ -44,7 +44,7 @@ type StatsPanelLabels struct { type HeroStatsPanel struct { asset *d2asset.AssetManager uiManager *d2ui.UIManager - frame *d2ui.Sprite + frame *d2ui.UIFrame panel *d2ui.Sprite heroState *d2hero.HeroStatsState heroName string @@ -81,10 +81,7 @@ func NewHeroStatsPanel(asset *d2asset.AssetManager, ui *d2ui.UIManager, heroName func (s *HeroStatsPanel) Load() { var err error - s.frame, err = s.uiManager.NewSprite(d2resource.Frame, d2resource.PaletteSky) - if err != nil { - log.Print(err) - } + s.frame = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft) s.panel, err = s.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky) if err != nil { @@ -146,83 +143,9 @@ func (s *HeroStatsPanel) Render(target d2interface.Surface) error { } func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) error { + + s.frame.Render(target) x, y := s.originX, s.originY - - // Frame - // Top left - if err := s.frame.SetCurrentFrame(0); err != nil { - return err - } - - w, h := s.frame.GetCurrentFrameSize() - - s.frame.SetPosition(x, y+h) - - if err := s.frame.Render(target); err != nil { - return err - } - - x += w - y += h - - // Top right - if err := s.frame.SetCurrentFrame(1); err != nil { - return err - } - - _, h = s.frame.GetCurrentFrameSize() - - s.frame.SetPosition(x, s.originY+h) - - if err := s.frame.Render(target); err != nil { - return err - } - - x = s.originX - - // Right - if err := s.frame.SetCurrentFrame(2); err != nil { - return err - } - - _, h = s.frame.GetCurrentFrameSize() - s.frame.SetPosition(x, y+h) - - if err := s.frame.Render(target); err != nil { - return err - } - - y += h - - // Bottom left - if err := s.frame.SetCurrentFrame(3); err != nil { - return err - } - - w, h = s.frame.GetCurrentFrameSize() - - s.frame.SetPosition(x, y+h) - - if err := s.frame.Render(target); err != nil { - return err - } - - x += w - - // Bottom right - if err := s.frame.SetCurrentFrame(4); err != nil { - return err - } - - _, h = s.frame.GetCurrentFrameSize() - - s.frame.SetPosition(x, y+h) - - if err := s.frame.Render(target); err != nil { - return err - } - - x, y = s.originX, s.originY y += 64 x += 80 @@ -232,7 +155,7 @@ func (s *HeroStatsPanel) renderStaticMenu(target d2interface.Surface) error { return err } - w, h = s.panel.GetCurrentFrameSize() + w, h := s.panel.GetCurrentFrameSize() s.panel.SetPosition(x, y+h) diff --git a/d2game/d2player/inventory.go b/d2game/d2player/inventory.go index 664a54e3..0db1b6a8 100644 --- a/d2game/d2player/inventory.go +++ b/d2game/d2player/inventory.go @@ -3,7 +3,6 @@ package d2player import ( "fmt" "image/color" - "log" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" @@ -21,7 +20,7 @@ type Inventory struct { asset *d2asset.AssetManager item *diablo2item.ItemFactory uiManager *d2ui.UIManager - frame *d2ui.Sprite + frame *d2ui.UIFrame panel *d2ui.Sprite grid *ItemGrid hoverLabel *d2ui.Label @@ -77,12 +76,7 @@ func (g *Inventory) Close() { // Load the resources required by the inventory func (g *Inventory) Load() { - sprite, err := g.uiManager.NewSprite(d2resource.Frame, d2resource.PaletteSky) - if err != nil { - log.Print(err) - } - - g.frame = sprite + g.frame = d2ui.NewUIFrame(g.asset, g.uiManager, d2ui.FrameRight) g.panel, _ = g.uiManager.NewSprite(d2resource.InventoryCharacterPanel, d2resource.PaletteSky) @@ -129,7 +123,7 @@ func (g *Inventory) Load() { } // TODO: Load the player's actual items - _, err = g.grid.Add(inventoryItems...) + _, err := g.grid.Add(inventoryItems...) if err != nil { fmt.Printf("could not add items to the inventory, err: %v\n", err) } @@ -141,84 +135,9 @@ func (g *Inventory) Render(target d2interface.Surface) error { return nil } - x, y := g.originX, g.originY + g.frame.Render(target) - // Frame - // Top left - if err := g.frame.SetCurrentFrame(5); err != nil { - return err - } - - w, h := g.frame.GetCurrentFrameSize() - - g.frame.SetPosition(x, y+h) - - if err := g.frame.Render(target); err != nil { - return err - } - - x += w - - // Top right - if err := g.frame.SetCurrentFrame(6); err != nil { - return err - } - - w, h = g.frame.GetCurrentFrameSize() - - g.frame.SetPosition(x, y+h) - - if err := g.frame.Render(target); err != nil { - return err - } - - x += w - y += h - - // Right - if err := g.frame.SetCurrentFrame(7); err != nil { - return err - } - - w, h = g.frame.GetCurrentFrameSize() - - g.frame.SetPosition(x-w, y+h) - - if err := g.frame.Render(target); err != nil { - return err - } - - y += h - - // Bottom right - if err := g.frame.SetCurrentFrame(8); err != nil { - return err - } - - w, h = g.frame.GetCurrentFrameSize() - - g.frame.SetPosition(x-w, y+h) - - if err := g.frame.Render(target); err != nil { - return err - } - - x -= w - - // Bottom left - if err := g.frame.SetCurrentFrame(9); err != nil { - return err - } - - w, h = g.frame.GetCurrentFrameSize() - - g.frame.SetPosition(x-w, y+h) - - if err := g.frame.Render(target); err != nil { - return err - } - - x, y = g.originX+1, g.originY + x, y := g.originX+1, g.originY y += 64 // Panel @@ -227,7 +146,7 @@ func (g *Inventory) Render(target d2interface.Surface) error { return err } - w, h = g.panel.GetCurrentFrameSize() + w, h := g.panel.GetCurrentFrameSize() g.panel.SetPosition(x, y+h) diff --git a/d2game/d2player/skilltree.go b/d2game/d2player/skilltree.go new file mode 100644 index 00000000..ea404b81 --- /dev/null +++ b/d2game/d2player/skilltree.go @@ -0,0 +1,415 @@ +package d2player + +import ( + "fmt" + "log" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" + "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 + + SkillIconXOff = 346 + SkillIconYOff = 59 + SkillIconDistX = 69 + SkillIconDistY = 68 +) + +type SkillTreeTab struct { + buttonText string + button *d2ui.Button +} + +func (st *SkillTreeTab) CreateButton(uiManager *d2ui.UIManager, x int, y int) { + st.button = uiManager.NewButton(d2ui.ButtonTypeSkillTreeTab, st.buttonText) + st.button.SetVisible(false) + st.button.SetPosition(x, y) +} + +type SkillTreeHeroTypeResources struct { + skillIcon *d2ui.Sprite + skillIconPath string + skillPanel *d2ui.Sprite + skillPanelPath string +} + +type SkillTree struct { + resources *SkillTreeHeroTypeResources + asset *d2asset.AssetManager + renderer d2interface.Renderer + guiManager *d2gui.GuiManager + uiManager *d2ui.UIManager + layout *d2gui.Layout + skills map[int]*d2hero.HeroSkill + heroClass d2enum.Hero + frame *d2ui.UIFrame + availSPLabel *d2ui.Label + tab [3]*SkillTreeTab + isOpen bool + originX int + originY int + selectedTab int +} + +func NewSkillTree( + skills map[int]*d2hero.HeroSkill, + heroClass d2enum.Hero, + asset *d2asset.AssetManager, + renderer d2interface.Renderer, + ui *d2ui.UIManager, + guiManager *d2gui.GuiManager, +) *SkillTree { + st := &SkillTree { + skills: skills, + heroClass: heroClass, + asset: asset, + renderer: renderer, + uiManager: ui, + guiManager: guiManager, + originX: 401, + originY: 64, + tab: [3]*SkillTreeTab{ + {}, + {}, + {}, + }, + } + return st +} + +func (s *SkillTree) Load() { + + s.frame = d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameRight) + + s.setHeroTypeResourcePath() + s.LoadForHeroType() + s.setTab(0) +} + +func (s *SkillTree) LoadForHeroType() { + sp, err := s.uiManager.NewSprite(s.resources.skillPanelPath, d2resource.PaletteSky) + if err != nil { + log.Print(err) + } + s.resources.skillPanel = sp + + si, err := s.uiManager.NewSprite(s.resources.skillIconPath, d2resource.PaletteSky) + if err != nil { + log.Print(err) + } + s.resources.skillIcon = si + + s.tab[0].CreateButton(s.uiManager, TabButtonX, TabButton0Y) + s.tab[0].button.OnActivated(func() { s.setTab(0) }) + s.tab[1].CreateButton(s.uiManager, TabButtonX, TabButton1Y) + s.tab[1].button.OnActivated(func() { s.setTab(1) }) + s.tab[2].CreateButton(s.uiManager, TabButtonX, TabButton2Y) + s.tab[2].button.OnActivated(func() { s.setTab(2) }) + + s.availSPLabel = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) + s.availSPLabel.SetPosition(AvailSPLabelX, AvailSPLabelY) + s.availSPLabel.Alignment = d2gui.HorizontalAlignCenter + s.availSPLabel.SetText(fmt.Sprintf("%s\n%s\n%s", + d2tbl.TranslateString("StrSklTree1"), + d2tbl.TranslateString("StrSklTree2"), + d2tbl.TranslateString("StrSklTree3"), + )) +} + +func (s *SkillTree) setHeroTypeResourcePath() { + var res *SkillTreeHeroTypeResources + + switch s.heroClass { + case d2enum.HeroBarbarian: + res = &SkillTreeHeroTypeResources { + skillPanelPath: d2resource.SkillsPanelBarbarian, + skillIconPath: d2resource.BarbarianSkills, + } + + s.tab[0].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree21"), + d2tbl.TranslateString("StrSklTree4")) + s.tab[1].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree21"), + d2tbl.TranslateString("StrSklTree22")) + s.tab[2].buttonText = d2tbl.TranslateString("StrSklTree20") + + case d2enum.HeroNecromancer: + res = &SkillTreeHeroTypeResources { + skillPanelPath: d2resource.SkillsPanelNecromancer, + skillIconPath: d2resource.NecromancerSkills, + } + + s.tab[0].buttonText = d2tbl.TranslateString("StrSklTree19") + s.tab[1].buttonText = fmt.Sprintf("%s\n%s\n%s", + d2tbl.TranslateString("StrSklTree17"), + d2tbl.TranslateString("StrSklTree18"), + d2tbl.TranslateString("StrSklTree5")) + s.tab[2].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree16"), + d2tbl.TranslateString("StrSklTree5")) + case d2enum.HeroPaladin: + res = &SkillTreeHeroTypeResources { + skillPanelPath: d2resource.SkillsPanelPaladin, + skillIconPath: d2resource.PaladinSkills, + } + + s.tab[0].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree15"), + d2tbl.TranslateString("StrSklTree4")) + s.tab[1].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree14"), + d2tbl.TranslateString("StrSklTree13")) + s.tab[2].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree12"), + d2tbl.TranslateString("StrSklTree13")) + case d2enum.HeroAssassin: + res = &SkillTreeHeroTypeResources { + skillPanelPath: d2resource.SkillsPanelAssassin, + skillIconPath: d2resource.AssassinSkills, + } + + s.tab[0].buttonText = d2tbl.TranslateString("StrSklTree30") + s.tab[1].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree31"), + d2tbl.TranslateString("StrSklTree32")) + s.tab[2].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree33"), + d2tbl.TranslateString("StrSklTree34")) + case d2enum.HeroSorceress: + res = &SkillTreeHeroTypeResources { + skillPanelPath: d2resource.SkillsPanelSorcerer, + skillIconPath: d2resource.SorcererSkills, + } + s.tab[0].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree25"), + d2tbl.TranslateString("StrSklTree5")) + s.tab[1].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree24"), + d2tbl.TranslateString("StrSklTree5")) + s.tab[2].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree23"), + d2tbl.TranslateString("StrSklTree5")) + case d2enum.HeroAmazon: + res = &SkillTreeHeroTypeResources { + skillPanelPath: d2resource.SkillsPanelAmazon, + skillIconPath: d2resource.AmazonSkills, + } + s.tab[0].buttonText = fmt.Sprintf("%s\n%s\n%s", + d2tbl.TranslateString("StrSklTree10"), + d2tbl.TranslateString("StrSklTree11"), + d2tbl.TranslateString("StrSklTree4")) + s.tab[1].buttonText = fmt.Sprintf("%s\n%s\n%s", + d2tbl.TranslateString("StrSklTree8"), + d2tbl.TranslateString("StrSklTree9"), + d2tbl.TranslateString("StrSklTree4")) + s.tab[2].buttonText = fmt.Sprintf("%s\n%s\n%s", + d2tbl.TranslateString("StrSklTree6"), + d2tbl.TranslateString("StrSklTree7"), + d2tbl.TranslateString("StrSklTree4")) + case d2enum.HeroDruid: + res = &SkillTreeHeroTypeResources { + skillPanelPath: d2resource.SkillsPanelDruid, + skillIconPath: d2resource.DruidSkills, + } + s.tab[0].buttonText = d2tbl.TranslateString("StrSklTree26") + s.tab[1].buttonText = fmt.Sprintf("%s\n%s", + d2tbl.TranslateString("StrSklTree27"), + d2tbl.TranslateString("StrSklTree28")) + s.tab[2].buttonText = d2tbl.TranslateString("StrSklTree29") + default: + log.Fatal("Unknown Hero Type") + } + s.resources = res +} + +func (s *SkillTree) Toggle() { + fmt.Println("SkillTree toggled") + if s.isOpen { + s.Close() + } else { + s.Open() + } +} + +func (s *SkillTree) Close() { + s.isOpen = false + s.guiManager.SetLayout(nil) + for i:=0; i < 3; i++ { + s.tab[i].button.SetVisible(false) + } +} + +func (s *SkillTree) Open() { + s.isOpen = true + if s.layout == nil { + s.layout = d2gui.CreateLayout(s.renderer, d2gui.PositionTypeHorizontal, s.asset) + } + + for i:=0; i < 3; i++ { + s.tab[i].button.SetVisible(true) + } + s.guiManager.SetLayout(s.layout) + +} + +func (s *SkillTree) IsOpen() bool { + return s.isOpen +} + +func (s *SkillTree) setTab(tab int) { + s.selectedTab = tab +} + +func (s *SkillTree) renderPanelSegment( + target d2interface.Surface, + frame int) error { + if err := s.resources.skillPanel.SetCurrentFrame(frame); err != nil { + return err + } + if err := s.resources.skillPanel.Render(target); err != nil { + return err + } + return nil +} + +func (s *SkillTree) renderTabCommon (target d2interface.Surface) error { + skillPanel := s.resources.skillPanel + x, y := s.originX, s.originY + + // top + w, h, err := skillPanel.GetFrameSize(0) + if err != nil { + return err + } + y += h + skillPanel.SetPosition(x, y) + if err:= s.renderPanelSegment(target, 0); err != nil { + return err + } + + skillPanel.SetPosition(x+w, y) + if err:= s.renderPanelSegment(target, 1); err != nil { + return err + } + + // bottom + _, h, err = skillPanel.GetFrameSize(2) + if err != nil { + return err + } + y += h + skillPanel.SetPosition(x, y) + if err:= s.renderPanelSegment(target, 2); err != nil { + return err + } + + skillPanel.SetPosition(x+w, y) + if err:= s.renderPanelSegment(target, 3); err != nil { + return err + } + + // available skill points label + s.availSPLabel.Render(target) + return nil +} + +func (s *SkillTree) renderTab (target d2interface.Surface, tab int) error { + var frameID [2]int + + frameID[0] = 4 + (4*tab) + frameID[1] = 6 + (4*tab) + + skillPanel := s.resources.skillPanel + x, y := s.originX, s.originY + + // top + _, h0, err := skillPanel.GetFrameSize(frameID[0]) + if err != nil { + return err + } + y += h0 + skillPanel.SetPosition(x, y) + if err:= s.renderPanelSegment(target, frameID[0]); err != nil { + return err + } + + // bottom + w, h1, err := skillPanel.GetFrameSize(frameID[1]) + if err != nil { + return err + } + skillPanel.SetPosition(x, y+h1) + if err:= s.renderPanelSegment(target, frameID[1]); err != nil { + return err + } + + // tab button highlighted + switch tab { + case 0: + skillPanel.SetPosition(x+w, y+h1) + if err:= s.renderPanelSegment(target, 7); err != nil { + return err + } + case 1: + x += w + skillPanel.SetPosition(x, s.originY + h0) + if err:= s.renderPanelSegment(target, 9); err != nil { + return err + } + skillPanel.SetPosition(x, y + h1) + if err:= s.renderPanelSegment(target, 11); err != nil { + return err + } + case 2: + skillPanel.SetPosition(x+w, y) + if err:= s.renderPanelSegment(target, 13); err != nil { + return err + } + } + return nil +} + +func (s *SkillTree) renderSkillIcons(target d2interface.Surface, tab int) error { + + skillIcon := s.resources.skillIcon + for idx:=range s.skills { + skill := s.skills[idx] + if skill.SkillPage != tab + 1 { + continue + } + if err := skillIcon.SetCurrentFrame(skill.IconCel); err != nil { + return err + } + skillIcon.SetPosition(SkillIconXOff + skill.SkillColumn * SkillIconDistX, SkillIconYOff + skill.SkillRow * SkillIconDistY) + if err := skillIcon.Render(target); err != nil { + return err + } + } + return nil + +} +func (s *SkillTree) Render (target d2interface.Surface) error { + if !s.isOpen { + return nil + } + s.frame.Render(target) + s.renderTabCommon(target) + s.renderTab(target, s.selectedTab) + s.renderSkillIcons(target, s.selectedTab) + return nil +}