Inital skilltree panel implementation (#782)

* d2ui/UIFrame: Refactor into its own class

it's not useful to have the handling of frames for the
inventory/herostate/skilltree/quest panels individually in each of
those.

* d2ui/button: Fix crash when a buttonlayout was not allowing FrameChange

When AllowFrameChange is false we do not create pressedSurface. So if we
press the button the game will crash.

* d2ui/button: Allow label-only buttons

At least for the skillmenu we need buttons were the graphic size does
not match the buttonsize. So let's render the graphic in there and make
the button label only.

* d2hero/hero_state_factory: Give all heroes their class specific skills

* d2player/gamecontrols: Fix wrong inventory/stats layouts for exp chars

For Druid/Assassin the inventory frame was rendered for a 640x480
resolution. This brings it in line with all other characters.

* d2player: Add inital Skilltree panel

* d2player/game_controls: Enable skilltree

Note here, that the inventory panel and skilltree panel can overlap.

* d2player/skilltree: Add skillicon rendering

Note here, that I couldn't figure out how to render them dark if no
skillpoints are invested.

Signed-off-by: juander <juander@rumtueddeln.de>
This commit is contained in:
juander-ux 2020-10-22 18:54:45 +02:00 committed by GitHub
parent 209cc19c89
commit e5dae4e5d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 813 additions and 203 deletions

View File

@ -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 ""
}

View File

@ -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

View File

@ -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"),

View File

@ -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

View File

@ -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:

235
d2core/d2ui/frame.go Normal file
View File

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

View File

@ -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()

View File

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

View File

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

View File

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