diff --git a/README.md b/README.md index 1b143564..3a103eff 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ ALL OTHER TRADEMARKS ARE THE PROPERTY OF THEIR RESPECTIVE OWNERS. ## Status -At the moment (october 2020) the game starts, you can select any character and run around Act1 town. +At the moment (december 2020) the game starts, you can select any character and run around Act1 town. +You can also open any of the game's panels. Much work has been made in the background, but a lot of work still has to be done for the game to be playable. @@ -128,6 +129,8 @@ which will be updated over time with new requirements. ![Inventory Window](docs/Inventory.png) +![Game Panels](docs/game_panels.png) + ## Additional Credits - Diablo2 Logo diff --git a/d2common/d2resource/resource_paths.go b/d2common/d2resource/resource_paths.go index a18a196f..dc950ebc 100644 --- a/d2common/d2resource/resource_paths.go +++ b/d2common/d2resource/resource_paths.go @@ -243,16 +243,18 @@ const ( MinipanelSmall = "/data/global/ui/PANEL/minipanel_s.dc6" MinipanelButton = "/data/global/ui/PANEL/minipanelbtn.DC6" - Frame = "/data/global/ui/PANEL/800borderframe.dc6" - InventoryCharacterPanel = "/data/global/ui/PANEL/invchar6.DC6" - InventoryWeaponsTab = "/data/global/ui/PANEL/invchar6Tab.DC6" - SkillsPanelAmazon = "/data/global/ui/SPELLS/skltree_a_back.DC6" - SkillsPanelBarbarian = "/data/global/ui/SPELLS/skltree_b_back.DC6" - SkillsPanelDruid = "/data/global/ui/SPELLS/skltree_d_back.DC6" - SkillsPanelAssassin = "/data/global/ui/SPELLS/skltree_i_back.DC6" - SkillsPanelNecromancer = "/data/global/ui/SPELLS/skltree_n_back.DC6" - SkillsPanelPaladin = "/data/global/ui/SPELLS/skltree_p_back.DC6" - SkillsPanelSorcerer = "/data/global/ui/SPELLS/skltree_s_back.DC6" + Frame = "/data/global/ui/PANEL/800borderframe.dc6" + InventoryCharacterPanel = "/data/global/ui/PANEL/invchar6.DC6" + HeroStatsPanelStatsPoints = "/data/global/ui/PANEL/skillpoints.dc6" + HeroStatsPanelSocket = "/data/global/ui/PANEL/levelsocket.dc6" + InventoryWeaponsTab = "/data/global/ui/PANEL/invchar6Tab.DC6" + SkillsPanelAmazon = "/data/global/ui/SPELLS/skltree_a_back.DC6" + SkillsPanelBarbarian = "/data/global/ui/SPELLS/skltree_b_back.DC6" + SkillsPanelDruid = "/data/global/ui/SPELLS/skltree_d_back.DC6" + SkillsPanelAssassin = "/data/global/ui/SPELLS/skltree_i_back.DC6" + SkillsPanelNecromancer = "/data/global/ui/SPELLS/skltree_n_back.DC6" + SkillsPanelPaladin = "/data/global/ui/SPELLS/skltree_p_back.DC6" + SkillsPanelSorcerer = "/data/global/ui/SPELLS/skltree_s_back.DC6" GenericSkills = "/data/global/ui/SPELLS/Skillicon.DC6" AmazonSkills = "/data/global/ui/SPELLS/AmSkillicon.DC6" diff --git a/d2core/d2hero/hero_stats_state.go b/d2core/d2hero/hero_stats_state.go index 04ad52a4..96400b2f 100644 --- a/d2core/d2hero/hero_stats_state.go +++ b/d2core/d2hero/hero_stats_state.go @@ -10,28 +10,23 @@ type HeroStatsState struct { Level int `json:"level"` Experience int `json:"experience"` - Vitality int `json:"vitality"` - Energy int `json:"energy"` Strength int `json:"strength"` + Energy int `json:"energy"` Dexterity int `json:"dexterity"` + Vitality int `json:"vitality"` + // there are stats and skills points remaining to add. + StatsPoints int `json:"statsPoints"` + SkillPoints int `json:"skillPoints"` - AttackRating int `json:"attackRating"` - DefenseRating int `json:"defenseRating"` - - MaxStamina int `json:"maxStamina"` - Health int `json:"health"` - MaxHealth int `json:"maxHealth"` - Mana int `json:"mana"` - MaxMana int `json:"maxMana"` - - FireResistance int `json:"fireResistance"` - ColdResistance int `json:"coldResistance"` - LightningResistance int `json:"lightningResistance"` - PoisonResistance int `json:"poisonResistance"` + Health int `json:"health"` + MaxHealth int `json:"maxHealth"` + Mana int `json:"mana"` + MaxMana int `json:"maxMana"` + Stamina float64 `json:"-"` // only MaxStamina is saved, Stamina gets reset on entering world + MaxStamina int `json:"maxStamina"` // values which are not saved/loaded(computed) - Stamina float64 `json:"-"` // only MaxStamina is saved, Stamina gets reset on entering world - NextLevelExp int `json:"-"` + NextLevelExp int `json:"-"` } // CreateHeroStatsState generates a running state from a hero stats. @@ -44,6 +39,8 @@ func (f *HeroStateFactory) CreateHeroStatsState(heroClass d2enum.Hero, classStat Dexterity: classStats.InitDex, Vitality: classStats.InitVit, Energy: classStats.InitEne, + StatsPoints: 0, + SkillPoints: 0, MaxHealth: classStats.InitVit * classStats.LifePerVit, MaxMana: classStats.InitEne * classStats.ManaPerEne, diff --git a/d2core/d2ui/button.go b/d2core/d2ui/button.go index cdaf9c24..b558969a 100644 --- a/d2core/d2ui/button.go +++ b/d2core/d2ui/button.go @@ -55,6 +55,7 @@ const ( ButtonTypeSquelchChat ButtonType = 35 ButtonTypeTabBlank ButtonType = 36 ButtonTypeBlankQuestBtn ButtonType = 37 + ButtonTypeAddSkill ButtonType = 38 ButtonNoFixedWidth int = -1 ButtonNoFixedHeight int = -1 @@ -199,6 +200,10 @@ const ( buttonGoldCoinSegmentsY = 1 buttonGoldCoinDisabledFrame = -1 + buttonAddSkillSegmentsX = 1 + buttonAddSkillSegmentsY = 1 + buttonAddSkillDisabledFrame = 2 + pressedButtonOffset = 2 ) @@ -746,6 +751,20 @@ func getButtonLayouts() map[ButtonType]ButtonLayout { FixedHeight: ButtonNoFixedHeight, LabelColor: whiteAlpha100, }, + ButtonTypeAddSkill: { + XSegments: buttonAddSkillSegmentsX, + YSegments: buttonAddSkillSegmentsY, + DisabledFrame: buttonAddSkillDisabledFrame, + DisabledColor: whiteAlpha100, + ResourceName: d2resource.AddSkillButton, + PaletteName: d2resource.PaletteSky, + Toggleable: true, + FontPath: d2resource.Font16, + AllowFrameChange: true, + HasImage: true, + FixedWidth: ButtonNoFixedWidth, + FixedHeight: ButtonNoFixedHeight, + }, } } diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index 2335cc52..97bd2aa7 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -214,7 +214,7 @@ func NewGameControls( return nil, err } - skilltree := newSkillTree(hero.Skills, hero.Class, asset, l, ui) + skilltree := newSkillTree(hero.Skills, hero.Class, hero.Stats, asset, l, ui) miniPanel := newMiniPanel(asset, ui, l, isSinglePlayer) @@ -224,13 +224,9 @@ func NewGameControls( } helpOverlay := NewHelpOverlay(asset, ui, l, keyMap) - hud := NewHUD(asset, ui, hero, miniPanel, actionableRegions, mapEngine, l, mapRenderer) const blackAlpha50percent = 0x0000007f - hoverLabel := hud.nameLabel - hoverLabel.SetBackgroundColor(d2util.Color(blackAlpha50percent)) - gc := &GameControls{ asset: asset, ui: ui, @@ -246,7 +242,6 @@ func NewGameControls( questLog: questLog, HelpOverlay: helpOverlay, keyMap: keyMap, - hud: hud, bottomMenuRect: &d2geom.Rectangle{ Left: menuBottomRectX, Top: menuBottomRectY, @@ -271,6 +266,12 @@ func NewGameControls( isSinglePlayer: isSinglePlayer, } + hud := NewHUD(asset, ui, hero, miniPanel, actionableRegions, mapEngine, l, gc, mapRenderer) + gc.hud = hud + + hoverLabel := hud.nameLabel + hoverLabel.SetBackgroundColor(d2util.Color(blackAlpha50percent)) + gc.heroStatsPanel.SetOnCloseCb(gc.onCloseHeroStatsPanel) gc.questLog.SetOnCloseCb(gc.onCloseQuestLog) gc.inventory.SetOnCloseCb(gc.onCloseInventory) @@ -713,6 +714,9 @@ func (g *GameControls) Load() { g.questLog.Load() g.HelpOverlay.Load() + g.loadAddButtons() + g.setAddButtons() + miniPanelActions := &miniPanelActions{ characterToggle: g.toggleHeroStatsPanel, inventoryToggle: g.toggleInventoryPanel, @@ -733,6 +737,10 @@ func (g *GameControls) Advance(elapsed float64) error { return err } + if g.heroStatsPanel.IsOpen() || g.skilltree.IsOpen() { + g.setAddButtons() + } + return nil } @@ -1099,3 +1107,13 @@ func (g *GameControls) bindTerminalCommands(term d2interface.Terminal) error { return nil } + +func (g *GameControls) setAddButtons() { + g.hud.addStatsButton.SetEnabled(g.hero.Stats.StatsPoints > 0) + g.hud.addSkillButton.SetEnabled(g.hero.Stats.SkillPoints > 0) +} + +func (g *GameControls) loadAddButtons() { + g.hud.addStatsButton.OnActivated(func() { g.toggleHeroStatsPanel() }) + g.hud.addSkillButton.OnActivated(func() { g.toggleSkilltreePanel() }) +} diff --git a/d2game/d2player/hero_stats_panel.go b/d2game/d2player/hero_stats_panel.go index 9f9cb5c0..8bc889aa 100644 --- a/d2game/d2player/hero_stats_panel.go +++ b/d2game/d2player/hero_stats_panel.go @@ -9,6 +9,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui" ) @@ -55,6 +56,15 @@ const ( const ( heroStatsCloseButtonX, heroStatsCloseButtonY = 208, 453 + addStatSocketOffsetX, addStatSocketOffsetY = -3, 34 +) + +const ( + newStatsRemainingPointsFieldX, newStatsRemainingPointsFieldY = 83, 430 + newStatsRemainingPointsLabelX = 92 + newStatsRemainingPointsLabel1Y = 411 + newStatsRemainingPointsLabel2Y = 418 + newStatsRemainingPointsValueX, newStatsRemainingPointsValueY = 188, 411 ) // PanelText represents text on the panel @@ -113,15 +123,17 @@ func NewHeroStatsPanel(asset *d2asset.AssetManager, // HeroStatsPanel represents the hero status panel type HeroStatsPanel struct { - asset *d2asset.AssetManager - uiManager *d2ui.UIManager - panel *d2ui.Sprite - heroState *d2hero.HeroStatsState - heroName string - heroClass d2enum.Hero - labels *StatsPanelLabels - onCloseCb func() - panelGroup *d2ui.WidgetGroup + asset *d2asset.AssetManager + uiManager *d2ui.UIManager + panel *d2ui.Sprite + heroState *d2hero.HeroStatsState + heroName string + heroClass d2enum.Hero + labels *StatsPanelLabels + onCloseCb func() + panelGroup *d2ui.WidgetGroup + newStatPoints *d2ui.WidgetGroup + remainingPoints *d2ui.Label originX int originY int @@ -135,6 +147,7 @@ func (s *HeroStatsPanel) Load() { var err error s.panelGroup = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel) + s.newStatPoints = s.uiManager.NewWidgetGroup(d2ui.RenderPriorityHeroStatsPanel) frame := d2ui.NewUIFrame(s.asset, s.uiManager, d2ui.FrameLeft) s.panelGroup.AddWidget(frame) @@ -154,10 +167,91 @@ func (s *HeroStatsPanel) Load() { closeButton.OnActivated(func() { s.Close() }) s.panelGroup.AddWidget(closeButton) + s.loadNewStatPoints() + s.setLayout() + s.initStatValueLabels() s.panelGroup.SetVisible(false) } +func (s *HeroStatsPanel) loadNewStatPoints() { + field, err := s.uiManager.NewSprite(d2resource.HeroStatsPanelStatsPoints, d2resource.PaletteSky) + if err != nil { + s.Error(err.Error()) + } + + field.SetPosition(newStatsRemainingPointsFieldX, newStatsRemainingPointsFieldY) + s.newStatPoints.AddWidget(field) + + label1 := s.uiManager.NewLabel(d2resource.Font6, d2resource.PaletteSky) + label1.SetPosition(newStatsRemainingPointsLabelX, newStatsRemainingPointsLabel1Y) + label1.SetText(s.asset.TranslateString("strchrstat")) + label1.Color[0] = d2util.Color(d2gui.ColorRed) + s.newStatPoints.AddWidget(label1) + + label2 := s.uiManager.NewLabel(d2resource.Font6, d2resource.PaletteSky) + label2.SetPosition(newStatsRemainingPointsLabelX, newStatsRemainingPointsLabel2Y) + label2.SetText(s.asset.TranslateString("strchrrema")) + label2.Color[0] = d2util.Color(d2gui.ColorRed) + s.newStatPoints.AddWidget(label2) + + s.remainingPoints = s.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) + s.remainingPoints.SetText(strconv.Itoa(s.heroState.StatsPoints)) + s.remainingPoints.SetPosition(newStatsRemainingPointsValueX, newStatsRemainingPointsValueY) + s.remainingPoints.Alignment = d2ui.HorizontalAlignCenter + s.newStatPoints.AddWidget(s.remainingPoints) + + buttons := []struct { + x int + y int + cb func() + }{ + {205, 140, func() { + s.heroState.Strength++ + }}, + {205, 201, func() { + s.heroState.Dexterity++ + }}, + {205, 286, func() { + s.heroState.Vitality++ + }}, + {205, 347, func() { + s.heroState.Energy++ + }}, + } + + var socket *d2ui.Sprite + + var button *d2ui.Button + + for _, i := range buttons { + currentValue := i + + socket, err = s.uiManager.NewSprite(d2resource.HeroStatsPanelSocket, d2resource.PaletteSky) + if err != nil { + s.Error(err.Error()) + } + + socket.SetPosition(i.x+addStatSocketOffsetX, i.y+addStatSocketOffsetY) + s.newStatPoints.AddWidget(socket) + + button = s.uiManager.NewButton(d2ui.ButtonTypeAddSkill, d2resource.PaletteSky) + button.SetPosition(i.x, i.y) + button.OnActivated(func() { + currentValue.cb() + s.heroState.StatsPoints-- + s.remainingPoints.SetText(strconv.Itoa(s.heroState.StatsPoints)) + s.setStatValues() + s.setLayout() + }) + s.newStatPoints.AddWidget(button) + } +} + +func (s *HeroStatsPanel) setLayout() { + s.newStatPoints.SetVisible(s.heroState.StatsPoints > 0 && s.IsOpen()) +} + // IsOpen returns true if the hero status panel is open func (s *HeroStatsPanel) IsOpen() bool { return s.isOpen @@ -176,12 +270,14 @@ func (s *HeroStatsPanel) Toggle() { func (s *HeroStatsPanel) Open() { s.isOpen = true s.panelGroup.SetVisible(true) + s.setLayout() } // Close closed the hero status panel func (s *HeroStatsPanel) Close() { s.isOpen = false s.panelGroup.SetVisible(false) + s.setLayout() s.onCloseCb() } diff --git a/d2game/d2player/hud.go b/d2game/d2player/hud.go index be455a3f..eefb3784 100644 --- a/d2game/d2player/hud.go +++ b/d2game/d2player/hud.go @@ -70,6 +70,11 @@ const ( whiteAlpha100 = 0xffffffff ) +const ( + addStatsButtonX, addStatsButtonY = 206, 561 + addSkillButtonX, addSkillButtonY = 563, 561 +) + // HUD represents the always visible user interface of the game type HUD struct { actionableRegions []actionableRegion @@ -103,7 +108,11 @@ type HUD struct { widgetLeftSkill *d2ui.CustomWidget widgetRightSkill *d2ui.CustomWidget panelBackground *d2ui.CustomWidget + addStatsButton *d2ui.Button + addSkillButton *d2ui.Button panelGroup *d2ui.WidgetGroup + gameControls *GameControls + *d2util.Logger } @@ -116,6 +125,7 @@ func NewHUD( actionableRegions []actionableRegion, mapEngine *d2mapengine.MapEngine, l d2util.LogLevel, + gameControls *GameControls, mapRenderer *d2maprenderer.MapRenderer, ) *HUD { nameLabel := ui.NewLabel(d2resource.Font16, d2resource.PaletteStatic) @@ -149,6 +159,7 @@ func NewHUD( zoneChangeText: zoneLabel, healthGlobe: healthGlobe, manaGlobe: manaGlobe, + gameControls: gameControls, } hud.Logger = d2util.NewLogger() @@ -177,6 +188,16 @@ func (h *HUD) Load() { h.loadCustomWidgets() h.loadUIButtons() + h.addStatsButton = h.uiManager.NewButton(d2ui.ButtonTypeAddSkill, "") + h.addStatsButton.SetPosition(addStatsButtonX, addStatsButtonY) + h.addStatsButton.SetVisible(false) + h.panelGroup.AddWidget(h.addStatsButton) + + h.addSkillButton = h.uiManager.NewButton(d2ui.ButtonTypeAddSkill, "") + h.addSkillButton.SetPosition(addSkillButtonX, addSkillButtonY) + h.addSkillButton.SetVisible(false) + h.panelGroup.AddWidget(h.addSkillButton) + h.panelGroup.SetVisible(true) } diff --git a/d2game/d2player/skilltree.go b/d2game/d2player/skilltree.go index 3f5ddae0..91bceec4 100644 --- a/d2game/d2player/skilltree.go +++ b/d2game/d2player/skilltree.go @@ -3,6 +3,7 @@ package d2player import ( "errors" "fmt" + "strconv" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface" @@ -56,6 +57,10 @@ const ( frameSelectedTab3Full = 13 ) +const ( + remainingPointsLabelX, remainingPointsLabelY = 677, 128 +) + const ( skillTreePanelX = 401 skillTreePanelY = 64 @@ -87,6 +92,7 @@ type skillTreeHeroTypeResources struct { func newSkillTree( skills map[int]*d2hero.HeroSkill, heroClass d2enum.Hero, + hero *d2hero.HeroStatsState, asset *d2asset.AssetManager, l d2util.LogLevel, ui *d2ui.UIManager, @@ -98,6 +104,7 @@ func newSkillTree( uiManager: ui, originX: skillTreePanelX, originY: skillTreePanelY, + stats: hero, tab: [numTabs]*skillTreeTab{ {}, {}, @@ -114,24 +121,26 @@ func newSkillTree( } type skillTree struct { - resources *skillTreeHeroTypeResources - asset *d2asset.AssetManager - uiManager *d2ui.UIManager - skills map[int]*d2hero.HeroSkill - skillIcons []*skillIcon - heroClass d2enum.Hero - frame *d2ui.UIFrame - availSPLabel *d2ui.Label - closeButton *d2ui.Button - tab [numTabs]*skillTreeTab - isOpen bool - originX int - originY int - selectedTab int - onCloseCb func() - panelGroup *d2ui.WidgetGroup - iconGroup *d2ui.WidgetGroup - panel *d2ui.CustomWidget + resources *skillTreeHeroTypeResources + asset *d2asset.AssetManager + uiManager *d2ui.UIManager + skills map[int]*d2hero.HeroSkill + skillIcons []*skillIcon + heroClass d2enum.Hero + frame *d2ui.UIFrame + 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 @@ -152,6 +161,12 @@ func (s *skillTree) load() { 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()) } diff --git a/docs/game_panels.png b/docs/game_panels.png new file mode 100644 index 00000000..15bf933d Binary files /dev/null and b/docs/game_panels.png differ