mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2024-12-26 12:06:24 -05:00
Character stats (#458)
* Save/load hero stats and display them in stats panel. * Load default hero state for characters created before saving stats was introduced Co-authored-by: Presiyan Ivanov <presiyan-ivanov@users.noreply.github.com>
This commit is contained in:
parent
6778658606
commit
c64e9be78b
@ -18,6 +18,7 @@ Gürkan Kaymak
|
||||
Maxime "malavv" Lavigne
|
||||
Ripolak
|
||||
dafe
|
||||
presiyan
|
||||
|
||||
* DIABLO2 LOGO
|
||||
Jose Pardilla (th3-prophetman)
|
||||
|
@ -98,8 +98,8 @@ func LoadCharStats(file []byte) {
|
||||
|
||||
InitStr: d.GetNumber("str", idx),
|
||||
InitDex: d.GetNumber("dex", idx),
|
||||
InitVit: d.GetNumber("int", idx),
|
||||
InitEne: d.GetNumber("vit", idx),
|
||||
InitVit: d.GetNumber("vit", idx),
|
||||
InitEne: d.GetNumber("int", idx),
|
||||
InitStamina: d.GetNumber("stamina", idx),
|
||||
|
||||
ManaRegen: d.GetNumber("ManaRegen", idx),
|
||||
|
@ -88,7 +88,7 @@ const (
|
||||
|
||||
GamePanels = "/data/global/ui/PANEL/800ctrlpnl7.dc6"
|
||||
GameGlobeOverlap = "/data/global/ui/PANEL/overlap.DC6"
|
||||
HealthMana = "/data/global/ui/PANEL/hlthmana.DC6"
|
||||
HealthManaIndicator = "/data/global/ui/PANEL/hlthmana.DC6"
|
||||
GameSmallMenuButton = "/data/global/ui/PANEL/menubutton.DC6" // TODO: Used for inventory popout
|
||||
SkillIcon = "/data/global/ui/PANEL/Skillicon.DC6" // TODO: Used for skill icon button
|
||||
AddSkillButton = "/data/global/ui/PANEL/level.DC6"
|
||||
|
61
d2core/d2hero/hero_stats_state.go
Normal file
61
d2core/d2hero/hero_stats_state.go
Normal file
@ -0,0 +1,61 @@
|
||||
package d2hero
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
)
|
||||
|
||||
type HeroStatsState struct {
|
||||
Level int `json:"level"`
|
||||
Experience int `json:"experience"`
|
||||
|
||||
Vitality int `json:"vitality"`
|
||||
Energy int `json:"energy"`
|
||||
Strength int `json:"strength"`
|
||||
Dexterity int `json:"dexterity"`
|
||||
|
||||
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"`
|
||||
|
||||
// values which are not saved/loaded(computed)
|
||||
Stamina int // only MaxStamina is saved, Stamina gets reset on entering world
|
||||
NextLevelExp int
|
||||
}
|
||||
|
||||
func CreateHeroStatsState(heroClass d2enum.Hero, classStats d2datadict.CharStatsRecord, level int, exp int) *HeroStatsState {
|
||||
result := HeroStatsState{
|
||||
Level: level,
|
||||
Experience: exp,
|
||||
NextLevelExp: d2datadict.GetExperienceBreakpoint(heroClass, 1),
|
||||
Strength: classStats.InitStr,
|
||||
Dexterity: classStats.InitDex,
|
||||
Vitality: classStats.InitVit,
|
||||
Energy: classStats.InitEne,
|
||||
//TODO: proper formula for calculating health and mana
|
||||
Health: classStats.InitVit * classStats.LifePerVit / 4,
|
||||
MaxHealth: classStats.InitVit * classStats.LifePerVit / 4,
|
||||
Mana: classStats.InitEne * classStats.ManaPerEne / 4,
|
||||
MaxMana: classStats.InitEne * classStats.ManaPerEne / 4,
|
||||
Stamina: classStats.InitStamina,
|
||||
MaxStamina: classStats.InitStamina,
|
||||
//TODO chance to hit, defense rating
|
||||
}
|
||||
|
||||
//TODO: those are added only for demonstration purposes(to show that hp mana exp status bars and character stats panel get updated depending on current stats)
|
||||
result.Health /= 2
|
||||
result.Mana /= 3
|
||||
result.Experience = result.NextLevelExp / 3
|
||||
|
||||
return &result
|
||||
}
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||
@ -16,6 +17,8 @@ type Player struct {
|
||||
mapEntity
|
||||
composite *d2asset.Composite
|
||||
Equipment d2inventory.CharacterEquipment
|
||||
Stats d2hero.HeroStatsState
|
||||
Class d2enum.Hero
|
||||
Id string
|
||||
direction int
|
||||
name string
|
||||
@ -31,7 +34,7 @@ type Player struct {
|
||||
var baseWalkSpeed = 6.0
|
||||
var baseRunSpeed = 9.0
|
||||
|
||||
func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero, equipment d2inventory.CharacterEquipment) *Player {
|
||||
func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) *Player {
|
||||
object := &d2datadict.ObjectLookupRecord{
|
||||
Mode: d2enum.AnimationModePlayerTownNeutral.String(),
|
||||
Base: "/data/global/chars",
|
||||
@ -53,13 +56,18 @@ func CreatePlayer(id, name string, x, y int, direction int, heroType d2enum.Hero
|
||||
panic(err)
|
||||
}
|
||||
|
||||
stats.NextLevelExp = d2datadict.GetExperienceBreakpoint(heroType, stats.Level)
|
||||
stats.Stamina = stats.MaxStamina
|
||||
|
||||
result := &Player{
|
||||
Id: id,
|
||||
mapEntity: createMapEntity(x, y),
|
||||
composite: composite,
|
||||
Equipment: equipment,
|
||||
Stats: stats,
|
||||
direction: direction,
|
||||
name: name,
|
||||
Class: heroType,
|
||||
nameLabel: d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic),
|
||||
isRunToggled: true,
|
||||
isInTown: true,
|
||||
|
@ -165,6 +165,7 @@ func (v *CharacterSelect) updateCharacterBoxes() {
|
||||
// TODO: Generate or load the object from the actual player data...
|
||||
v.characterImage[i] = d2mapentity.CreatePlayer("", "", 0, 0, 0,
|
||||
v.gameStates[idx].HeroType,
|
||||
*v.gameStates[idx].Stats,
|
||||
d2inventory.HeroObjects[v.gameStates[idx].HeroType],
|
||||
)
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
|
||||
@ -433,7 +434,7 @@ func (v SelectHeroClass) onExitButtonClicked() {
|
||||
}
|
||||
|
||||
func (v SelectHeroClass) onOkButtonClicked() {
|
||||
gameState := d2player.CreatePlayerState(v.heroNameTextbox.GetText(), v.selectedHero, v.hardcoreCheckbox.GetCheckState())
|
||||
gameState := d2player.CreatePlayerState(v.heroNameTextbox.GetText(), v.selectedHero, *d2datadict.CharStats[v.selectedHero], v.hardcoreCheckbox.GetCheckState())
|
||||
gameClient, _ := d2client.Create(d2clientconnectiontype.Local)
|
||||
gameClient.Open(v.connectionHost, gameState.FilePath)
|
||||
d2screen.SetNextScreen(CreateGame(gameClient))
|
||||
|
@ -28,29 +28,37 @@ type Panel interface {
|
||||
|
||||
// ID of missile to create when user right clicks.
|
||||
var missileID = 59
|
||||
var expBarWidth = 120.0
|
||||
var staminaBarWidth = 102.0
|
||||
var globeHeight = 80
|
||||
var globeWidth = 80
|
||||
|
||||
type GameControls struct {
|
||||
hero *d2mapentity.Player
|
||||
mapEngine *d2mapengine.MapEngine
|
||||
mapRenderer *d2maprenderer.MapRenderer
|
||||
inventory *Inventory
|
||||
heroStats *HeroStats
|
||||
|
||||
inputListener InputCallbackListener
|
||||
FreeCam bool
|
||||
lastMouseX int
|
||||
lastMouseY int
|
||||
hero *d2mapentity.Player
|
||||
mapEngine *d2mapengine.MapEngine
|
||||
mapRenderer *d2maprenderer.MapRenderer
|
||||
inventory *Inventory
|
||||
heroStatsPanel *HeroStatsPanel
|
||||
inputListener InputCallbackListener
|
||||
FreeCam bool
|
||||
lastMouseX int
|
||||
lastMouseY int
|
||||
lastHealthPercent float64
|
||||
lastManaPercent float64
|
||||
|
||||
// UI
|
||||
globeSprite *d2ui.Sprite
|
||||
mainPanel *d2ui.Sprite
|
||||
menuButton *d2ui.Sprite
|
||||
skillIcon *d2ui.Sprite
|
||||
zoneChangeText *d2ui.Label
|
||||
nameLabel *d2ui.Label
|
||||
runButton d2ui.Button
|
||||
isZoneTextShown bool
|
||||
actionableRegions []ActionableRegion
|
||||
globeSprite *d2ui.Sprite
|
||||
hpManaStatusSprite *d2ui.Sprite
|
||||
hpStatusBar d2render.Surface
|
||||
manaStatusBar d2render.Surface
|
||||
mainPanel *d2ui.Sprite
|
||||
menuButton *d2ui.Sprite
|
||||
skillIcon *d2ui.Sprite
|
||||
zoneChangeText *d2ui.Label
|
||||
nameLabel *d2ui.Label
|
||||
runButton d2ui.Button
|
||||
isZoneTextShown bool
|
||||
actionableRegions []ActionableRegion
|
||||
}
|
||||
|
||||
type ActionableType int
|
||||
@ -76,9 +84,9 @@ func NewGameControls(hero *d2mapentity.Player, mapEngine *d2mapengine.MapEngine,
|
||||
missileID = id
|
||||
})
|
||||
|
||||
label := d2ui.CreateLabel(d2resource.Font30, d2resource.PaletteUnits)
|
||||
label.Color = color.RGBA{R: 255, G: 88, B: 82, A: 255}
|
||||
label.Alignment = d2ui.LabelAlignCenter
|
||||
zoneLabel := d2ui.CreateLabel(d2resource.Font30, d2resource.PaletteUnits)
|
||||
zoneLabel.Color = color.RGBA{R: 255, G: 88, B: 82, A: 255}
|
||||
zoneLabel.Alignment = d2ui.LabelAlignCenter
|
||||
|
||||
nameLabel := d2ui.CreateLabel(d2resource.FontFormal11, d2resource.PaletteStatic)
|
||||
nameLabel.Alignment = d2ui.LabelAlignCenter
|
||||
@ -91,9 +99,9 @@ func NewGameControls(hero *d2mapentity.Player, mapEngine *d2mapengine.MapEngine,
|
||||
inputListener: inputListener,
|
||||
mapRenderer: mapRenderer,
|
||||
inventory: NewInventory(),
|
||||
heroStats: NewHeroStats(),
|
||||
heroStatsPanel: NewHeroStatsPanel(hero.Name(), hero.Class, hero.Stats),
|
||||
nameLabel: &nameLabel,
|
||||
zoneChangeText: &label,
|
||||
zoneChangeText: &zoneLabel,
|
||||
actionableRegions: []ActionableRegion{
|
||||
{leftSkill, d2common.Rectangle{Left: 115, Top: 550, Width: 50, Height: 50}},
|
||||
{leftSelec, d2common.Rectangle{Left: 206, Top: 563, Width: 30, Height: 30}},
|
||||
@ -147,9 +155,9 @@ func (g *GameControls) OnKeyRepeat(event d2input.KeyEvent) bool {
|
||||
func (g *GameControls) OnKeyDown(event d2input.KeyEvent) bool {
|
||||
switch event.Key {
|
||||
case d2input.KeyEscape:
|
||||
if g.inventory.IsOpen() || g.heroStats.IsOpen() {
|
||||
if g.inventory.IsOpen() || g.heroStatsPanel.IsOpen() {
|
||||
g.inventory.Close()
|
||||
g.heroStats.Close()
|
||||
g.heroStatsPanel.Close()
|
||||
g.updateLayout()
|
||||
break
|
||||
}
|
||||
@ -157,7 +165,7 @@ func (g *GameControls) OnKeyDown(event d2input.KeyEvent) bool {
|
||||
g.inventory.Toggle()
|
||||
g.updateLayout()
|
||||
case d2input.KeyC:
|
||||
g.heroStats.Toggle()
|
||||
g.heroStatsPanel.Toggle()
|
||||
g.updateLayout()
|
||||
case d2input.KeyR:
|
||||
g.onToggleRunButton()
|
||||
@ -265,6 +273,11 @@ func (g *GameControls) Load() {
|
||||
animation, _ := d2asset.LoadAnimation(d2resource.GameGlobeOverlap, d2resource.PaletteSky)
|
||||
g.globeSprite, _ = d2ui.LoadSprite(animation)
|
||||
|
||||
animation, _ = d2asset.LoadAnimation(d2resource.HealthManaIndicator, d2resource.PaletteSky)
|
||||
g.hpManaStatusSprite, _ = d2ui.LoadSprite(animation)
|
||||
|
||||
g.hpStatusBar, _ = d2render.NewSurface(globeWidth, globeHeight, d2render.FilterNearest)
|
||||
|
||||
animation, _ = d2asset.LoadAnimation(d2resource.GamePanels, d2resource.PaletteSky)
|
||||
g.mainPanel, _ = d2ui.LoadSprite(animation)
|
||||
|
||||
@ -277,7 +290,7 @@ func (g *GameControls) Load() {
|
||||
g.loadUIButtons()
|
||||
|
||||
g.inventory.Load()
|
||||
g.heroStats.Load()
|
||||
g.heroStatsPanel.Load()
|
||||
}
|
||||
|
||||
func (g *GameControls) loadUIButtons() {
|
||||
@ -309,7 +322,7 @@ func (g *GameControls) updateLayout() {
|
||||
|
||||
// todo : add same logic when adding quest log and skill tree
|
||||
isRightPanelOpen = g.inventory.isOpen || isRightPanelOpen
|
||||
isLeftPanelOpen = g.heroStats.isOpen || isLeftPanelOpen
|
||||
isLeftPanelOpen = g.heroStatsPanel.isOpen || isLeftPanelOpen
|
||||
|
||||
if isRightPanelOpen == isLeftPanelOpen {
|
||||
g.mapRenderer.ViewportDefault()
|
||||
@ -342,7 +355,7 @@ func (g *GameControls) Render(target d2render.Surface) {
|
||||
}
|
||||
|
||||
g.inventory.Render(target)
|
||||
g.heroStats.Render(target)
|
||||
g.heroStatsPanel.Render(target)
|
||||
|
||||
width, height := target.GetSize()
|
||||
offset := 0
|
||||
@ -357,6 +370,24 @@ func (g *GameControls) Render(target d2render.Surface) {
|
||||
g.globeSprite.SetCurrentFrame(0)
|
||||
g.globeSprite.SetPosition(offset+28, height-5)
|
||||
g.globeSprite.Render(target)
|
||||
|
||||
// Health status bar
|
||||
healthPercent := float64(g.hero.Stats.Health) / float64(g.hero.Stats.MaxHealth)
|
||||
hpBarHeight := int(healthPercent * float64(globeHeight))
|
||||
if g.lastHealthPercent != healthPercent {
|
||||
g.hpStatusBar, _ = d2render.NewSurface(globeWidth, hpBarHeight, d2render.FilterNearest)
|
||||
g.hpManaStatusSprite.SetCurrentFrame(0)
|
||||
g.hpStatusBar.PushTranslation(0, hpBarHeight)
|
||||
|
||||
g.hpManaStatusSprite.Render(g.hpStatusBar)
|
||||
g.hpStatusBar.Pop()
|
||||
g.lastHealthPercent = healthPercent
|
||||
}
|
||||
|
||||
target.PushTranslation(30, 508+(globeHeight-hpBarHeight))
|
||||
target.Render(g.hpStatusBar)
|
||||
target.Pop()
|
||||
|
||||
offset += w
|
||||
|
||||
// Left skill
|
||||
@ -380,6 +411,19 @@ func (g *GameControls) Render(target d2render.Surface) {
|
||||
g.mainPanel.Render(target)
|
||||
offset += w
|
||||
|
||||
// Stamina status bar
|
||||
target.PushTranslation(273, 572)
|
||||
target.PushCompositeMode(d2render.CompositeModeLighter)
|
||||
staminaPercent := float64(g.hero.Stats.Stamina) / float64(g.hero.Stats.MaxStamina)
|
||||
target.DrawRect(int(staminaPercent*staminaBarWidth), 19, color.RGBA{R: 175, G: 136, B: 72, A: 200})
|
||||
target.PopN(2)
|
||||
|
||||
// Experience status bar
|
||||
target.PushTranslation(256, 561)
|
||||
expPercent := float64(g.hero.Stats.Experience) / float64(g.hero.Stats.NextLevelExp)
|
||||
target.DrawRect(int(expPercent*expBarWidth), 2, color.RGBA{R: 255, G: 255, B: 255, A: 255})
|
||||
target.Pop()
|
||||
|
||||
// Center menu button
|
||||
g.menuButton.SetCurrentFrame(0)
|
||||
w, _ = g.mainPanel.GetCurrentFrameSize()
|
||||
@ -401,7 +445,7 @@ func (g *GameControls) Render(target d2render.Surface) {
|
||||
offset += w
|
||||
|
||||
// Right skill
|
||||
g.skillIcon.SetCurrentFrame(10)
|
||||
g.skillIcon.SetCurrentFrame(2)
|
||||
w, _ = g.skillIcon.GetCurrentFrameSize()
|
||||
g.skillIcon.SetPosition(offset, height)
|
||||
g.skillIcon.Render(target)
|
||||
@ -417,11 +461,30 @@ func (g *GameControls) Render(target d2render.Surface) {
|
||||
g.globeSprite.SetCurrentFrame(1)
|
||||
g.globeSprite.SetPosition(offset+8, height-8)
|
||||
g.globeSprite.Render(target)
|
||||
g.globeSprite.Render(target)
|
||||
|
||||
// Mana status bar
|
||||
manaPercent := float64(g.hero.Stats.Mana) / float64(g.hero.Stats.MaxMana)
|
||||
manaBarHeight := int(manaPercent * float64(globeHeight))
|
||||
if manaPercent != g.lastManaPercent {
|
||||
g.manaStatusBar, _ = d2render.NewSurface(globeWidth, manaBarHeight, d2render.FilterNearest)
|
||||
g.hpManaStatusSprite.SetCurrentFrame(1)
|
||||
|
||||
g.manaStatusBar.PushTranslation(0, manaBarHeight)
|
||||
g.hpManaStatusSprite.Render(g.manaStatusBar)
|
||||
g.manaStatusBar.Pop()
|
||||
|
||||
g.lastManaPercent = manaPercent
|
||||
}
|
||||
target.PushTranslation(offset+8, 508+(globeHeight-manaBarHeight))
|
||||
target.Render(g.manaStatusBar)
|
||||
target.Pop()
|
||||
|
||||
if g.isZoneTextShown {
|
||||
g.zoneChangeText.SetPosition(width/2, height/4)
|
||||
g.zoneChangeText.Render(target)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (g *GameControls) SetZoneChangeText(text string) {
|
||||
|
283
d2game/d2player/hero_stats_panel.go
Normal file
283
d2game/d2player/hero_stats_panel.go
Normal file
@ -0,0 +1,283 @@
|
||||
package d2player
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||
)
|
||||
|
||||
type PanelText struct {
|
||||
X int
|
||||
Y int
|
||||
Height int
|
||||
Text string
|
||||
Font string
|
||||
AlignCenter bool
|
||||
}
|
||||
|
||||
type StatsPanelLabels struct {
|
||||
Level d2ui.Label
|
||||
Experience d2ui.Label
|
||||
NextLevelExp d2ui.Label
|
||||
Strength d2ui.Label
|
||||
Dexterity d2ui.Label
|
||||
Vitality d2ui.Label
|
||||
Energy d2ui.Label
|
||||
Health d2ui.Label
|
||||
MaxHealth d2ui.Label
|
||||
Mana d2ui.Label
|
||||
MaxMana d2ui.Label
|
||||
MaxStamina d2ui.Label
|
||||
Stamina d2ui.Label
|
||||
}
|
||||
|
||||
var StaticTextLabels = []PanelText{
|
||||
{X: 110, Y: 100, Text: "Level", Font: d2resource.Font6, AlignCenter: true},
|
||||
{X: 200, Y: 100, Text: "Experience", Font: d2resource.Font6, AlignCenter: true},
|
||||
{X: 330, Y: 100, Text: "Next Level", Font: d2resource.Font6, AlignCenter: true},
|
||||
{X: 100, Y: 150, Text: "Strength", Font: d2resource.Font6},
|
||||
{X: 100, Y: 213, Text: "Dexterity", Font: d2resource.Font6},
|
||||
{X: 100, Y: 300, Text: "Vitality", Font: d2resource.Font6},
|
||||
{X: 100, Y: 360, Text: "Energy", Font: d2resource.Font6},
|
||||
{X: 280, Y: 260, Text: "Defense", Font: d2resource.Font6},
|
||||
{X: 280, Y: 300, Text: "Stamina", Font: d2resource.Font6, AlignCenter: true},
|
||||
{X: 280, Y: 322, Text: "Life", Font: d2resource.Font6, AlignCenter: true},
|
||||
{X: 280, Y: 360, Text: "Mana", Font: d2resource.Font6, AlignCenter: true},
|
||||
|
||||
// can't use "Fire\nResistance" because line spacing is too big and breaks the layout
|
||||
{X: 310, Y: 395, Text: "Fire", Font: d2resource.Font6, AlignCenter: true},
|
||||
{X: 310, Y: 402, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true},
|
||||
|
||||
{X: 310, Y: 420, Text: "Cold", Font: d2resource.Font6, AlignCenter: true},
|
||||
{X: 310, Y: 427, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true},
|
||||
|
||||
{X: 310, Y: 445, Text: "Lightning", Font: d2resource.Font6, AlignCenter: true},
|
||||
{X: 310, Y: 452, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true},
|
||||
|
||||
{X: 310, Y: 468, Text: "Poison", Font: d2resource.Font6, AlignCenter: true},
|
||||
{X: 310, Y: 477, Text: "Resistance", Font: d2resource.Font6, AlignCenter: true},
|
||||
}
|
||||
|
||||
// stores all the labels that can change during gameplay(e.g. current level, current hp, mana, etc.)
|
||||
var StatValueLabels = make([]d2ui.Label, 13)
|
||||
|
||||
type HeroStatsPanel struct {
|
||||
frame *d2ui.Sprite
|
||||
panel *d2ui.Sprite
|
||||
heroState *d2hero.HeroStatsState
|
||||
heroName string
|
||||
heroClass d2enum.Hero
|
||||
staticMenuImageCache *d2render.Surface
|
||||
labels *StatsPanelLabels
|
||||
|
||||
originX int
|
||||
originY int
|
||||
isOpen bool
|
||||
}
|
||||
|
||||
func NewHeroStatsPanel(heroName string, heroClass d2enum.Hero, heroState d2hero.HeroStatsState) *HeroStatsPanel {
|
||||
originX := 0
|
||||
originY := 0
|
||||
|
||||
return &HeroStatsPanel{
|
||||
originX: originX,
|
||||
originY: originY,
|
||||
heroState: &heroState,
|
||||
heroName: heroName,
|
||||
heroClass: heroClass,
|
||||
labels: &StatsPanelLabels{},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) Load() {
|
||||
animation, _ := d2asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
|
||||
s.frame, _ = d2ui.LoadSprite(animation)
|
||||
animation, _ = d2asset.LoadAnimation(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
|
||||
s.panel, _ = d2ui.LoadSprite(animation)
|
||||
s.initStatValueLabels()
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) IsOpen() bool {
|
||||
return s.isOpen
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) Toggle() {
|
||||
s.isOpen = !s.isOpen
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) Open() {
|
||||
s.isOpen = true
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) Close() {
|
||||
s.isOpen = false
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) Render(target d2render.Surface) {
|
||||
if !s.isOpen {
|
||||
return
|
||||
}
|
||||
|
||||
if s.staticMenuImageCache == nil {
|
||||
frameWidth, frameHeight := s.frame.GetFrameBounds()
|
||||
framesCount := s.frame.GetFrameCount()
|
||||
surface, err := d2render.NewSurface(frameWidth*framesCount, frameHeight*framesCount, d2render.FilterNearest)
|
||||
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.staticMenuImageCache = &surface
|
||||
s.renderStaticMenu(*s.staticMenuImageCache)
|
||||
}
|
||||
target.Render(*s.staticMenuImageCache)
|
||||
s.renderStatValues(target)
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) renderStaticMenu(target d2render.Surface) {
|
||||
x, y := s.originX, s.originY
|
||||
|
||||
// Frame
|
||||
// Top left
|
||||
s.frame.SetCurrentFrame(0)
|
||||
w, h := s.frame.GetCurrentFrameSize()
|
||||
s.frame.SetPosition(x, y+h)
|
||||
s.frame.Render(target)
|
||||
x += w
|
||||
y += h
|
||||
|
||||
// Top right
|
||||
s.frame.SetCurrentFrame(1)
|
||||
w, h = s.frame.GetCurrentFrameSize()
|
||||
s.frame.SetPosition(x, s.originY+h)
|
||||
s.frame.Render(target)
|
||||
x = s.originX
|
||||
|
||||
// Right
|
||||
s.frame.SetCurrentFrame(2)
|
||||
w, h = s.frame.GetCurrentFrameSize()
|
||||
s.frame.SetPosition(x, y+h)
|
||||
s.frame.Render(target)
|
||||
y += h
|
||||
|
||||
// Bottom left
|
||||
s.frame.SetCurrentFrame(3)
|
||||
w, h = s.frame.GetCurrentFrameSize()
|
||||
s.frame.SetPosition(x, y+h)
|
||||
s.frame.Render(target)
|
||||
x += w
|
||||
|
||||
// Bottom right
|
||||
s.frame.SetCurrentFrame(4)
|
||||
w, h = s.frame.GetCurrentFrameSize()
|
||||
s.frame.SetPosition(x, y+h)
|
||||
s.frame.Render(target)
|
||||
|
||||
x, y = s.originX, s.originY
|
||||
y += 64
|
||||
x += 80
|
||||
|
||||
// Panel
|
||||
// Top left
|
||||
s.panel.SetCurrentFrame(0)
|
||||
w, h = s.panel.GetCurrentFrameSize()
|
||||
s.panel.SetPosition(x, y+h)
|
||||
s.panel.Render(target)
|
||||
x += w
|
||||
|
||||
// Top right
|
||||
s.panel.SetCurrentFrame(1)
|
||||
w, h = s.panel.GetCurrentFrameSize()
|
||||
s.panel.SetPosition(x, y+h)
|
||||
s.panel.Render(target)
|
||||
y += h
|
||||
|
||||
// Bottom right
|
||||
s.panel.SetCurrentFrame(3)
|
||||
w, h = s.panel.GetCurrentFrameSize()
|
||||
s.panel.SetPosition(x, y+h)
|
||||
s.panel.Render(target)
|
||||
|
||||
// Bottom left
|
||||
s.panel.SetCurrentFrame(2)
|
||||
w, h = s.panel.GetCurrentFrameSize()
|
||||
s.panel.SetPosition(x-w, y+h)
|
||||
s.panel.Render(target)
|
||||
|
||||
var label d2ui.Label
|
||||
// all static labels are not stored since we use them only once to generate the image cache
|
||||
for _, textElement := range StaticTextLabels {
|
||||
label = s.createTextLabel(textElement)
|
||||
label.Render(target)
|
||||
}
|
||||
// hero name and class are part of the static image cache since they don't change after we enter the world
|
||||
label = s.createTextLabel(PanelText{X: 165, Y: 72, Text: s.heroName, Font: d2resource.Font16, AlignCenter: true})
|
||||
label.Render(target)
|
||||
label = s.createTextLabel(PanelText{X: 330, Y: 72, Text: s.heroClass.String(), Font: d2resource.Font16, AlignCenter: true})
|
||||
label.Render(target)
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) initStatValueLabels() {
|
||||
s.labels.Level = s.createStatValueLabel(s.heroState.Level, 112, 110)
|
||||
s.labels.Experience = s.createStatValueLabel(s.heroState.Experience, 200, 110)
|
||||
s.labels.NextLevelExp = s.createStatValueLabel(s.heroState.NextLevelExp, 330, 110)
|
||||
|
||||
s.labels.Strength = s.createStatValueLabel(s.heroState.Strength, 175, 147)
|
||||
s.labels.Dexterity = s.createStatValueLabel(s.heroState.Dexterity, 175, 207)
|
||||
s.labels.Vitality = s.createStatValueLabel(s.heroState.Vitality, 175, 295)
|
||||
s.labels.Energy = s.createStatValueLabel(s.heroState.Energy, 175, 355)
|
||||
|
||||
s.labels.MaxStamina = s.createStatValueLabel(s.heroState.MaxStamina, 330, 295)
|
||||
s.labels.Stamina = s.createStatValueLabel(s.heroState.Stamina, 370, 295)
|
||||
|
||||
s.labels.MaxHealth = s.createStatValueLabel(s.heroState.MaxHealth, 330, 320)
|
||||
s.labels.Health = s.createStatValueLabel(s.heroState.Health, 370, 320)
|
||||
|
||||
s.labels.MaxMana = s.createStatValueLabel(s.heroState.MaxMana, 330, 355)
|
||||
s.labels.Mana = s.createStatValueLabel(s.heroState.Mana, 370, 355)
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) renderStatValues(target d2render.Surface) {
|
||||
s.renderStatValueNum(s.labels.Level, s.heroState.Level, target)
|
||||
s.renderStatValueNum(s.labels.Experience, s.heroState.Experience, target)
|
||||
s.renderStatValueNum(s.labels.NextLevelExp, s.heroState.NextLevelExp, target)
|
||||
|
||||
s.renderStatValueNum(s.labels.Strength, s.heroState.Strength, target)
|
||||
s.renderStatValueNum(s.labels.Dexterity, s.heroState.Dexterity, target)
|
||||
s.renderStatValueNum(s.labels.Vitality, s.heroState.Vitality, target)
|
||||
s.renderStatValueNum(s.labels.Energy, s.heroState.Energy, target)
|
||||
|
||||
s.renderStatValueNum(s.labels.MaxHealth, s.heroState.MaxHealth, target)
|
||||
s.renderStatValueNum(s.labels.Health, s.heroState.Health, target)
|
||||
|
||||
s.renderStatValueNum(s.labels.MaxStamina, s.heroState.MaxStamina, target)
|
||||
s.renderStatValueNum(s.labels.Stamina, s.heroState.Stamina, target)
|
||||
|
||||
s.renderStatValueNum(s.labels.MaxMana, s.heroState.MaxMana, target)
|
||||
s.renderStatValueNum(s.labels.Mana, s.heroState.Mana, target)
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) renderStatValueNum(label d2ui.Label, value int, target d2render.Surface) {
|
||||
label.SetText(strconv.Itoa(value))
|
||||
label.Render(target)
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) createStatValueLabel(stat int, x int, y int) d2ui.Label {
|
||||
text := strconv.Itoa(stat)
|
||||
return s.createTextLabel(PanelText{X: x, Y: y, Text: text, Font: d2resource.Font16, AlignCenter: true})
|
||||
}
|
||||
|
||||
func (s *HeroStatsPanel) createTextLabel(element PanelText) d2ui.Label {
|
||||
label := d2ui.CreateLabel(element.Font, d2resource.PaletteStatic)
|
||||
if element.AlignCenter {
|
||||
label.Alignment = d2ui.LabelAlignCenter
|
||||
}
|
||||
|
||||
label.SetText(element.Text)
|
||||
label.SetPosition(element.X, element.Y)
|
||||
return label
|
||||
}
|
@ -9,7 +9,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory"
|
||||
)
|
||||
|
||||
@ -20,6 +22,7 @@ type PlayerState struct {
|
||||
Act int `json:"act"`
|
||||
FilePath string `json:"-"`
|
||||
Equipment d2inventory.CharacterEquipment `json:"equipment"`
|
||||
Stats *d2hero.HeroStatsState `json:"stats"`
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
}
|
||||
@ -42,7 +45,13 @@ func GetAllPlayerStates() []*PlayerState {
|
||||
gameState := LoadPlayerState(path.Join(basePath, file.Name()))
|
||||
if gameState == nil {
|
||||
continue
|
||||
// temporarily loading default class stats if the character was created before saving stats was introduced
|
||||
// to be removed in the future
|
||||
} else if gameState.Stats == nil {
|
||||
gameState.Stats = d2hero.CreateHeroStatsState(gameState.HeroType, *d2datadict.CharStats[gameState.HeroType], 1, 0)
|
||||
gameState.Save()
|
||||
}
|
||||
|
||||
result = append(result, gameState)
|
||||
}
|
||||
return result
|
||||
@ -70,11 +79,12 @@ func LoadPlayerState(path string) *PlayerState {
|
||||
return result
|
||||
}
|
||||
|
||||
func CreatePlayerState(heroName string, hero d2enum.Hero, hardcore bool) *PlayerState {
|
||||
func CreatePlayerState(heroName string, hero d2enum.Hero, classStats d2datadict.CharStatsRecord, hardcore bool) *PlayerState {
|
||||
result := &PlayerState{
|
||||
HeroName: heroName,
|
||||
HeroType: hero,
|
||||
Act: 1,
|
||||
Stats: d2hero.CreateHeroStatsState(hero, classStats, 1, 0),
|
||||
Equipment: d2inventory.HeroObjects[hero],
|
||||
FilePath: "",
|
||||
}
|
||||
|
@ -1,124 +0,0 @@
|
||||
package d2player
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2render"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2ui"
|
||||
)
|
||||
|
||||
type HeroStats struct {
|
||||
frame *d2ui.Sprite
|
||||
panel *d2ui.Sprite
|
||||
originX int
|
||||
originY int
|
||||
isOpen bool
|
||||
}
|
||||
|
||||
func NewHeroStats() *HeroStats {
|
||||
originX := 0
|
||||
originY := 0
|
||||
return &HeroStats{
|
||||
originX: originX,
|
||||
originY: originY,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *HeroStats) Load() {
|
||||
animation, _ := d2asset.LoadAnimation(d2resource.Frame, d2resource.PaletteSky)
|
||||
s.frame, _ = d2ui.LoadSprite(animation)
|
||||
animation, _ = d2asset.LoadAnimation(d2resource.InventoryCharacterPanel, d2resource.PaletteSky)
|
||||
s.panel, _ = d2ui.LoadSprite(animation)
|
||||
}
|
||||
|
||||
func (s *HeroStats) IsOpen() bool {
|
||||
return s.isOpen
|
||||
}
|
||||
|
||||
func (s *HeroStats) Toggle() {
|
||||
s.isOpen = !s.isOpen
|
||||
}
|
||||
|
||||
func (s *HeroStats) Open() {
|
||||
s.isOpen = true
|
||||
}
|
||||
|
||||
func (s *HeroStats) Close() {
|
||||
s.isOpen = false
|
||||
}
|
||||
|
||||
func (s *HeroStats) Render(target d2render.Surface) {
|
||||
if !s.isOpen {
|
||||
return
|
||||
}
|
||||
|
||||
x, y := s.originX, s.originY
|
||||
|
||||
// Frame
|
||||
// Top left
|
||||
s.frame.SetCurrentFrame(0)
|
||||
w, h := s.frame.GetCurrentFrameSize()
|
||||
s.frame.SetPosition(x, y+h)
|
||||
s.frame.Render(target)
|
||||
x += w
|
||||
y += h
|
||||
|
||||
// Top right
|
||||
s.frame.SetCurrentFrame(1)
|
||||
w, h = s.frame.GetCurrentFrameSize()
|
||||
s.frame.SetPosition(x, s.originY+h)
|
||||
s.frame.Render(target)
|
||||
x = s.originX
|
||||
|
||||
// Right
|
||||
s.frame.SetCurrentFrame(2)
|
||||
w, h = s.frame.GetCurrentFrameSize()
|
||||
s.frame.SetPosition(x, y+h)
|
||||
s.frame.Render(target)
|
||||
y += h
|
||||
|
||||
// Bottom left
|
||||
s.frame.SetCurrentFrame(3)
|
||||
w, h = s.frame.GetCurrentFrameSize()
|
||||
s.frame.SetPosition(x, y+h)
|
||||
s.frame.Render(target)
|
||||
x += w
|
||||
|
||||
// Bottom right
|
||||
s.frame.SetCurrentFrame(4)
|
||||
w, h = s.frame.GetCurrentFrameSize()
|
||||
s.frame.SetPosition(x, y+h)
|
||||
s.frame.Render(target)
|
||||
|
||||
x, y = s.originX, s.originY
|
||||
y += 64
|
||||
x += 80
|
||||
|
||||
// Panel
|
||||
// Top left
|
||||
s.panel.SetCurrentFrame(0)
|
||||
w, h = s.panel.GetCurrentFrameSize()
|
||||
s.panel.SetPosition(x, y+h)
|
||||
s.panel.Render(target)
|
||||
x += w
|
||||
|
||||
// Top right
|
||||
s.panel.SetCurrentFrame(1)
|
||||
w, h = s.panel.GetCurrentFrameSize()
|
||||
s.panel.SetPosition(x, y+h)
|
||||
s.panel.Render(target)
|
||||
y += h
|
||||
|
||||
// Bottom right
|
||||
s.panel.SetCurrentFrame(3)
|
||||
w, h = s.panel.GetCurrentFrameSize()
|
||||
s.panel.SetPosition(x, y+h)
|
||||
s.panel.Render(target)
|
||||
|
||||
// Bottom left
|
||||
s.panel.SetCurrentFrame(2)
|
||||
w, h = s.panel.GetCurrentFrameSize()
|
||||
s.panel.SetPosition(x-w, y+h)
|
||||
s.panel.Render(target)
|
||||
|
||||
}
|
@ -80,7 +80,7 @@ func (g *GameClient) OnPacketReceived(packet d2netpacket.NetPacket) error {
|
||||
log.Printf("Player id set to %s", serverInfo.PlayerId)
|
||||
case d2netpackettype.AddPlayer:
|
||||
player := packet.PacketData.(d2netpacket.AddPlayerPacket)
|
||||
newPlayer := d2mapentity.CreatePlayer(player.Id, player.Name, player.X, player.Y, 0, player.HeroType, player.Equipment)
|
||||
newPlayer := d2mapentity.CreatePlayer(player.Id, player.Name, player.X, player.Y, 0, player.HeroType, player.Stats, player.Equipment)
|
||||
g.Players[newPlayer.Id] = newPlayer
|
||||
g.MapEngine.AddEntity(newPlayer)
|
||||
case d2netpackettype.MovePlayer:
|
||||
|
@ -2,6 +2,7 @@ package d2netpacket
|
||||
|
||||
import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2inventory"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket/d2netpackettype"
|
||||
)
|
||||
@ -13,9 +14,10 @@ type AddPlayerPacket struct {
|
||||
Y int `json:"y"`
|
||||
HeroType d2enum.Hero `json:"hero"`
|
||||
Equipment d2inventory.CharacterEquipment `json:"equipment"`
|
||||
Stats d2hero.HeroStatsState `json:"heroStats"`
|
||||
}
|
||||
|
||||
func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero, equipment d2inventory.CharacterEquipment) NetPacket {
|
||||
func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero, stats d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) NetPacket {
|
||||
return NetPacket{
|
||||
PacketType: d2netpackettype.AddPlayer,
|
||||
PacketData: AddPlayerPacket{
|
||||
@ -25,6 +27,7 @@ func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero, equi
|
||||
Y: y,
|
||||
HeroType: heroType,
|
||||
Equipment: equipment,
|
||||
Stats: stats,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -172,7 +172,7 @@ func OnClientConnected(client ClientConnection) {
|
||||
|
||||
playerState := client.GetPlayerState()
|
||||
createPlayerPacket := d2netpacket.CreateAddPlayerPacket(client.GetUniqueId(), playerState.HeroName, int(sx*5)+3, int(sy*5)+3,
|
||||
playerState.HeroType, playerState.Equipment)
|
||||
playerState.HeroType, *playerState.Stats, playerState.Equipment)
|
||||
for _, connection := range singletonServer.clientConnections {
|
||||
connection.SendPacketToClient(createPlayerPacket)
|
||||
if connection.GetUniqueId() == client.GetUniqueId() {
|
||||
@ -181,7 +181,7 @@ func OnClientConnected(client ClientConnection) {
|
||||
|
||||
conPlayerState := connection.GetPlayerState()
|
||||
client.SendPacketToClient(d2netpacket.CreateAddPlayerPacket(connection.GetUniqueId(), conPlayerState.HeroName,
|
||||
int(conPlayerState.X*5)+3, int(conPlayerState.Y*5)+3, conPlayerState.HeroType, conPlayerState.Equipment))
|
||||
int(conPlayerState.X*5)+3, int(conPlayerState.Y*5)+3, conPlayerState.HeroType, *conPlayerState.Stats, conPlayerState.Equipment))
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user