mirror of
https://github.com/OpenDiablo2/OpenDiablo2
synced 2025-01-26 19:27:31 -05:00
Initial player Left skill and Right skill handling (#741)
* Initial player Left skill and Right skill handling * Handle empty skill names in charStats.BaseSkils + add Attack skill for all classes. Co-authored-by: Presiyan Ivanov <presiyan-ivanov@users.noreply.github.com>
This commit is contained in:
parent
f4d78549c5
commit
a4e9797431
@ -306,10 +306,17 @@ func createMissileRecord(line string) MissileRecord {
|
||||
// Missiles stores all of the MissileRecords
|
||||
//nolint:gochecknoglobals // Currently global by design, only written once
|
||||
var Missiles map[int]*MissileRecord
|
||||
var missilesByName map[string]*MissileRecord
|
||||
|
||||
// GetMissileByName allows lookup of a MissileRecord by a given name. The name will be lowercased and stripped of whitespaces.
|
||||
func GetMissileByName(missileName string) *MissileRecord {
|
||||
return missilesByName[sanitize(missileName)]
|
||||
}
|
||||
|
||||
// LoadMissiles loads MissileRecords from missiles.txt
|
||||
func LoadMissiles(file []byte) {
|
||||
Missiles = make(map[int]*MissileRecord)
|
||||
missilesByName = make(map[string]*MissileRecord)
|
||||
data := strings.Split(string(file), "\r\n")[1:]
|
||||
|
||||
for _, line := range data {
|
||||
@ -319,11 +326,16 @@ func LoadMissiles(file []byte) {
|
||||
|
||||
rec := createMissileRecord(line)
|
||||
Missiles[rec.Id] = &rec
|
||||
missilesByName[sanitize(rec.Name)] = &rec
|
||||
}
|
||||
|
||||
log.Printf("Loaded %d missiles", len(Missiles))
|
||||
}
|
||||
|
||||
func sanitize(missileName string) string {
|
||||
return strings.ToLower(strings.ReplaceAll(missileName, " ", ""))
|
||||
}
|
||||
|
||||
func loadMissileCalcParam(r *[]string, inc func() int) MissileCalcParam {
|
||||
result := MissileCalcParam{
|
||||
Param: d2util.StringToInt(d2util.EmptyToZero((*r)[inc()])),
|
||||
|
@ -17,7 +17,7 @@ type SkillDescriptionRecord struct {
|
||||
SkillColumn string // SkillColumn
|
||||
ListRow string // ListRow
|
||||
ListPool string // ListPool
|
||||
IconCel string // IconCel
|
||||
IconCel int // IconCel
|
||||
NameKey string // str name
|
||||
ShortKey string // str short
|
||||
LongKey string // str long
|
||||
@ -146,7 +146,7 @@ func LoadSkillDescriptions(file []byte) { //nolint:funlen // doesn't make sense
|
||||
d.String("SkillColumn"),
|
||||
d.String("ListRow"),
|
||||
d.String("ListPool"),
|
||||
d.String("IconCel"),
|
||||
d.Number("IconCel"),
|
||||
d.String("str name"),
|
||||
d.String("str short"),
|
||||
d.String("str long"),
|
||||
|
@ -12,6 +12,8 @@ import (
|
||||
//nolint:gochecknoglobals // Currently global by design, only written once
|
||||
var SkillDetails map[int]*SkillRecord
|
||||
|
||||
var skillDetailsByName map[string]*SkillRecord
|
||||
|
||||
// SkillRecord is a row from the skills.txt file. Here are two resources for more info on each field
|
||||
// [https://d2mods.info/forum/viewtopic.php?t=41556, https://d2mods.info/forum/kb/viewarticle?a=246]
|
||||
type SkillRecord struct {
|
||||
@ -263,6 +265,7 @@ type SkillRecord struct {
|
||||
// LoadCharStats loads charstats.txt file contents into map[d2enum.Hero]*CharStatsRecord
|
||||
func LoadSkills(file []byte) {
|
||||
SkillDetails = make(map[int]*SkillRecord)
|
||||
skillDetailsByName = make(map[string]*SkillRecord)
|
||||
|
||||
parser := d2parser.New()
|
||||
|
||||
@ -515,6 +518,7 @@ func LoadSkills(file []byte) {
|
||||
CostAdd: d.Number("cost add"),
|
||||
}
|
||||
SkillDetails[record.ID] = record
|
||||
skillDetailsByName[record.Skill] = record
|
||||
}
|
||||
|
||||
if d.Err != nil {
|
||||
@ -523,3 +527,8 @@ func LoadSkills(file []byte) {
|
||||
|
||||
log.Printf("Loaded %d Skill records", len(SkillDetails))
|
||||
}
|
||||
|
||||
// GetSkillByName returns the skill record for the given Skill name.
|
||||
func GetSkillByName(skillName string) *SkillRecord {
|
||||
return skillDetailsByName[skillName]
|
||||
}
|
||||
|
51
d2core/d2hero/hero_skill.go
Normal file
51
d2core/d2hero/hero_skill.go
Normal file
@ -0,0 +1,51 @@
|
||||
package d2hero
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||
)
|
||||
|
||||
// HeroSkill stores additional payload for a skill of a hero.
|
||||
type HeroSkill struct {
|
||||
*d2datadict.SkillRecord
|
||||
*d2datadict.SkillDescriptionRecord
|
||||
SkillPoints int
|
||||
}
|
||||
|
||||
// An auxilary struct which only stores the ID of the SkillRecord, instead of the whole SkillRecord and SkillDescrptionRecord.
|
||||
type shallowHeroSkill struct {
|
||||
SkillID int `json:"skillId"`
|
||||
SkillPoints int `json:"skillPoints"`
|
||||
}
|
||||
|
||||
// MarshalJSON overrides the default logic used when the HeroSkill is serialized to a byte array.
|
||||
func (hs *HeroSkill) MarshalJSON() ([]byte, error) {
|
||||
// only serialize the ID instead of the whole SkillRecord object.
|
||||
shallow := shallowHeroSkill{
|
||||
SkillID: hs.SkillRecord.ID,
|
||||
SkillPoints: hs.SkillPoints,
|
||||
}
|
||||
|
||||
bytes, err := json.Marshal(shallow)
|
||||
if err != nil {
|
||||
log.Fatalln(err)
|
||||
}
|
||||
|
||||
return bytes, err
|
||||
}
|
||||
|
||||
// UnmarshalJSON overrides the default logic used when the HeroSkill is deserialized from a byte array.
|
||||
func (hs *HeroSkill) UnmarshalJSON(data []byte) error {
|
||||
shallow := shallowHeroSkill{}
|
||||
if err := json.Unmarshal(data, &shallow); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
hs.SkillRecord = d2datadict.SkillDetails[shallow.SkillID]
|
||||
hs.SkillDescriptionRecord = d2datadict.SkillDescriptions[hs.SkillRecord.Skilldesc]
|
||||
hs.SkillPoints = shallow.SkillPoints
|
||||
|
||||
return nil
|
||||
}
|
26
d2core/d2hero/hero_skills_state.go
Normal file
26
d2core/d2hero/hero_skills_state.go
Normal file
@ -0,0 +1,26 @@
|
||||
package d2hero
|
||||
|
||||
import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2data/d2datadict"
|
||||
|
||||
// HeroSkillsState hold all spells that a hero has.
|
||||
type HeroSkillsState map[int] *HeroSkill
|
||||
|
||||
// CreateHeroSkillsState will assemble the hero skills from the class stats record.
|
||||
func CreateHeroSkillsState(classStats *d2datadict.CharStatsRecord) *HeroSkillsState {
|
||||
baseSkills := HeroSkillsState{}
|
||||
|
||||
for idx := range classStats.BaseSkill {
|
||||
skillName := &classStats.BaseSkill[idx]
|
||||
if len(*skillName) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
skillRecord := d2datadict.GetSkillByName(*skillName)
|
||||
baseSkills[skillRecord.ID] = &HeroSkill{SkillPoints: 1, SkillRecord: skillRecord}
|
||||
}
|
||||
|
||||
skillRecord := d2datadict.GetSkillByName("Attack")
|
||||
baseSkills[skillRecord.ID] = &HeroSkill{SkillPoints: 1, SkillRecord: skillRecord}
|
||||
|
||||
return &baseSkills
|
||||
}
|
@ -30,8 +30,8 @@ type HeroStatsState struct {
|
||||
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
|
||||
Stamina int `json:"-"` // only MaxStamina is saved, Stamina gets reset on entering world
|
||||
NextLevelExp int `json:"-"`
|
||||
}
|
||||
|
||||
// CreateHeroStatsState generates a running state from a hero stats.
|
||||
@ -56,8 +56,8 @@ func CreateHeroStatsState(heroClass d2enum.Hero, classStats *d2datadict.CharStat
|
||||
result.Stamina = result.MaxStamina
|
||||
|
||||
// TODO: For demonstration purposes (hp, mana, exp, & character stats panel gets updated depending on stats)
|
||||
result.Health = 20
|
||||
result.Mana = 9
|
||||
result.Health = 50
|
||||
result.Mana = 30
|
||||
result.Experience = 166
|
||||
|
||||
return &result
|
||||
|
@ -41,7 +41,7 @@ func NewAnimatedEntity(x, y int, animation d2interface.Animation) *AnimatedEntit
|
||||
|
||||
// NewPlayer creates a new player entity and returns a pointer to it.
|
||||
func (f *MapEntityFactory) NewPlayer(id, name string, x, y, direction int, heroType d2enum.Hero,
|
||||
stats *d2hero.HeroStatsState, equipment *d2inventory.CharacterEquipment) *Player {
|
||||
stats *d2hero.HeroStatsState, skills *d2hero.HeroSkillsState, equipment *d2inventory.CharacterEquipment) *Player {
|
||||
layerEquipment := &[d2enum.CompositeTypeMax]string{
|
||||
d2enum.CompositeTypeHead: equipment.Head.GetArmorClass(),
|
||||
d2enum.CompositeTypeTorso: equipment.Torso.GetArmorClass(),
|
||||
@ -62,11 +62,16 @@ func (f *MapEntityFactory) NewPlayer(id, name string, x, y, direction int, heroT
|
||||
stats.NextLevelExp = d2datadict.GetExperienceBreakpoint(heroType, stats.Level)
|
||||
stats.Stamina = stats.MaxStamina
|
||||
|
||||
attackSkillID := 0
|
||||
result := &Player{
|
||||
mapEntity: newMapEntity(x, y),
|
||||
composite: composite,
|
||||
Equipment: equipment,
|
||||
Stats: stats,
|
||||
Skills: skills,
|
||||
//TODO: active left & right skill should be loaded from save file instead
|
||||
LeftSkill: (*skills)[attackSkillID],
|
||||
RightSkill: (*skills)[attackSkillID],
|
||||
name: name,
|
||||
Class: heroType,
|
||||
//nameLabel: d2ui.NewLabel(d2resource.FontFormal11, d2resource.PaletteStatic),
|
||||
|
@ -19,6 +19,9 @@ type Player struct {
|
||||
composite *d2asset.Composite
|
||||
Equipment *d2inventory.CharacterEquipment
|
||||
Stats *d2hero.HeroStatsState
|
||||
Skills *d2hero.HeroSkillsState
|
||||
LeftSkill *d2hero.HeroSkill
|
||||
RightSkill *d2hero.HeroSkill
|
||||
Class d2enum.Hero
|
||||
lastPathSize int
|
||||
isInTown bool
|
||||
|
@ -288,6 +288,7 @@ func (v *CharacterSelect) updateCharacterBoxes() {
|
||||
v.characterImage[i] = v.NewPlayer("", "", 0, 0, 0,
|
||||
v.gameStates[idx].HeroType,
|
||||
v.gameStates[idx].Stats,
|
||||
v.gameStates[idx].Skills,
|
||||
&equipment,
|
||||
)
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ const hideZoneTextAfterSeconds = 2.0
|
||||
const (
|
||||
moveErrStr = "failed to send MovePlayer packet to the server, playerId: %s, x: %g, x: %g\n"
|
||||
bindControlsErrStr = "failed to add gameControls as input handler for player: %s\n"
|
||||
castErrStr = "failed to send CastSkill packet to the server, playerId: %s, missileId: %d, x: %g, x: %g\n"
|
||||
castErrStr = "failed to send CastSkill packet to the server, playerId: %s, skillId: %d, x: %g, x: %g\n"
|
||||
spawnItemErrStr = "failed to send SpawnItem packet to the server: (%d, %d) %+v"
|
||||
)
|
||||
|
||||
@ -312,10 +312,10 @@ func (v *Game) OnPlayerMove(targetX, targetY float64) {
|
||||
}
|
||||
|
||||
// OnPlayerCast sends the casting skill action to the server
|
||||
func (v *Game) OnPlayerCast(missileID int, targetX, targetY float64) {
|
||||
err := v.gameClient.SendPacketToServer(d2netpacket.CreateCastPacket(v.gameClient.PlayerID, missileID, targetX, targetY))
|
||||
func (v *Game) OnPlayerCast(skillID int, targetX, targetY float64) {
|
||||
err := v.gameClient.SendPacketToServer(d2netpacket.CreateCastPacket(v.gameClient.PlayerID, skillID, targetX, targetY))
|
||||
if err != nil {
|
||||
fmt.Printf(castErrStr, v.gameClient.PlayerID, missileID, targetX, targetY)
|
||||
fmt.Printf(castErrStr, v.gameClient.PlayerID, skillID, targetX, targetY)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2gui"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero"
|
||||
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2interface"
|
||||
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
|
||||
@ -35,7 +36,6 @@ type Panel interface {
|
||||
}
|
||||
|
||||
const (
|
||||
initialMissileID = 59
|
||||
expBarWidth = 120.0
|
||||
staminaBarWidth = 102.0
|
||||
globeHeight = 80
|
||||
@ -65,7 +65,8 @@ type GameControls struct {
|
||||
hpManaStatusSprite *d2ui.Sprite
|
||||
mainPanel *d2ui.Sprite
|
||||
menuButton *d2ui.Sprite
|
||||
skillIcon *d2ui.Sprite
|
||||
leftSkill *SkillResource
|
||||
rightSkill *SkillResource
|
||||
zoneChangeText *d2ui.Label
|
||||
nameLabel *d2ui.Label
|
||||
hpManaStatsLabel *d2ui.Label
|
||||
@ -86,15 +87,21 @@ type ActionableRegion struct {
|
||||
Rect d2geom.Rectangle
|
||||
}
|
||||
|
||||
type SkillResource struct {
|
||||
SkillResourcePath string
|
||||
IconNumber int
|
||||
SkillIcon *d2ui.Sprite
|
||||
}
|
||||
|
||||
const (
|
||||
// Since they require special handling, not considering (1) globes, (2) content of the mini panel, (3) belt
|
||||
leftSkill ActionableType = iota
|
||||
leftSelect
|
||||
newStats
|
||||
xp
|
||||
walkRun
|
||||
stamina
|
||||
miniPnl
|
||||
rightSelect
|
||||
newSkills
|
||||
rightSkill
|
||||
hpGlobe
|
||||
manaGlobe
|
||||
@ -115,14 +122,6 @@ func NewGameControls(
|
||||
guiManager *d2gui.GuiManager,
|
||||
isSinglePlayer bool,
|
||||
) (*GameControls, error) {
|
||||
missileID := initialMissileID
|
||||
|
||||
err := term.BindAction("setmissile", "set missile id to summon on right click", func(id int) {
|
||||
missileID = id
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
zoneLabel := ui.NewLabel(d2resource.Font30, d2resource.PaletteUnits)
|
||||
zoneLabel.Alignment = d2gui.HorizontalAlignCenter
|
||||
@ -175,18 +174,17 @@ func NewGameControls(
|
||||
heroStatsPanel: NewHeroStatsPanel(asset, ui, hero.Name(), hero.Class, hero.Stats),
|
||||
helpOverlay: help.NewHelpOverlay(asset, renderer, ui, guiManager),
|
||||
miniPanel: newMiniPanel(asset, ui, isSinglePlayer),
|
||||
missileID: missileID,
|
||||
nameLabel: hoverLabel,
|
||||
zoneChangeText: zoneLabel,
|
||||
hpManaStatsLabel: globeStatsLabel,
|
||||
actionableRegions: []ActionableRegion{
|
||||
{leftSkill, d2geom.Rectangle{Left: 115, Top: 550, Width: 50, Height: 50}},
|
||||
{leftSelect, d2geom.Rectangle{Left: 206, Top: 563, Width: 30, Height: 30}},
|
||||
{newStats, d2geom.Rectangle{Left: 206, Top: 563, Width: 30, Height: 30}},
|
||||
{xp, d2geom.Rectangle{Left: 253, Top: 560, Width: 125, Height: 5}},
|
||||
{walkRun, d2geom.Rectangle{Left: 255, Top: 573, Width: 17, Height: 20}},
|
||||
{stamina, d2geom.Rectangle{Left: 273, Top: 573, Width: 105, Height: 20}},
|
||||
{miniPnl, d2geom.Rectangle{Left: 393, Top: 563, Width: 12, Height: 23}},
|
||||
{rightSelect, d2geom.Rectangle{Left: 562, Top: 563, Width: 30, Height: 30}},
|
||||
{newSkills, d2geom.Rectangle{Left: 562, Top: 563, Width: 30, Height: 30}},
|
||||
{rightSkill, d2geom.Rectangle{Left: 634, Top: 550, Width: 50, Height: 50}},
|
||||
{hpGlobe, d2geom.Rectangle{Left: 30, Top: 525, Width: 65, Height: 50}},
|
||||
{manaGlobe, d2geom.Rectangle{Left: 700, Top: 525, Width: 65, Height: 50}},
|
||||
@ -198,7 +196,7 @@ func NewGameControls(
|
||||
isSinglePlayer: isSinglePlayer,
|
||||
}
|
||||
|
||||
err = term.BindAction("freecam", "toggle free camera movement", func() {
|
||||
err := term.BindAction("freecam", "toggle free camera movement", func() {
|
||||
gc.FreeCam = !gc.FreeCam
|
||||
})
|
||||
|
||||
@ -206,6 +204,20 @@ func NewGameControls(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = term.BindAction("setleftskill", "set skill to fire on left click", func(id int) {
|
||||
skillRecord := d2datadict.SkillDetails[id]
|
||||
gc.hero.LeftSkill = &d2hero.HeroSkill{SkillPoints: 0, SkillRecord: skillRecord, SkillDescriptionRecord: d2datadict.SkillDescriptions[skillRecord.Skilldesc]}
|
||||
})
|
||||
|
||||
err = term.BindAction("setrightskill", "set skill to fire on right click", func(id int) {
|
||||
skillRecord := d2datadict.SkillDetails[id]
|
||||
gc.hero.RightSkill = &d2hero.HeroSkill{SkillPoints: 0, SkillRecord: skillRecord, SkillDescriptionRecord: d2datadict.SkillDescriptions[skillRecord.Skilldesc]}
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return gc, nil
|
||||
}
|
||||
|
||||
@ -297,7 +309,11 @@ func (g *GameControls) OnMouseButtonRepeat(event d2interface.MouseEvent) bool {
|
||||
if isLeft && shouldDoLeft && inRect && !g.hero.IsCasting() {
|
||||
g.lastLeftBtnActionTime = now
|
||||
|
||||
g.inputListener.OnPlayerMove(px, py)
|
||||
if event.KeyMod() == d2enum.KeyModShift {
|
||||
g.inputListener.OnPlayerCast(g.hero.LeftSkill.ID, px, py)
|
||||
} else {
|
||||
g.inputListener.OnPlayerMove(px, py)
|
||||
}
|
||||
|
||||
if g.FreeCam {
|
||||
if event.Button() == d2enum.MouseButtonLeft {
|
||||
@ -319,7 +335,7 @@ func (g *GameControls) OnMouseButtonRepeat(event d2interface.MouseEvent) bool {
|
||||
if isRight && shouldDoRight && inRect && !g.hero.IsCasting() {
|
||||
g.lastRightBtnActionTime = now
|
||||
|
||||
g.inputListener.OnPlayerCast(g.missileID, px, py)
|
||||
g.inputListener.OnPlayerCast(g.hero.RightSkill.ID, px, py)
|
||||
|
||||
return true
|
||||
}
|
||||
@ -364,7 +380,11 @@ func (g *GameControls) OnMouseButtonDown(event d2interface.MouseEvent) bool {
|
||||
if event.Button() == d2enum.MouseButtonLeft && !g.isInActiveMenusRect(mx, my) && !g.hero.IsCasting() {
|
||||
g.lastLeftBtnActionTime = d2util.Now()
|
||||
|
||||
g.inputListener.OnPlayerMove(px, py)
|
||||
if event.KeyMod() == d2enum.KeyModShift {
|
||||
g.inputListener.OnPlayerCast(g.hero.LeftSkill.ID, px, py)
|
||||
} else {
|
||||
g.inputListener.OnPlayerMove(px, py)
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
@ -372,7 +392,7 @@ func (g *GameControls) OnMouseButtonDown(event d2interface.MouseEvent) bool {
|
||||
if event.Button() == d2enum.MouseButtonRight && !g.isInActiveMenusRect(mx, my) && !g.hero.IsCasting() {
|
||||
g.lastRightBtnActionTime = d2util.Now()
|
||||
|
||||
g.inputListener.OnPlayerCast(g.missileID, px, py)
|
||||
g.inputListener.OnPlayerCast(g.hero.RightSkill.ID, px, py)
|
||||
|
||||
return true
|
||||
}
|
||||
@ -391,7 +411,12 @@ func (g *GameControls) Load() {
|
||||
g.menuButton, _ = g.uiManager.NewSprite(d2resource.MenuButton, d2resource.PaletteSky)
|
||||
_ = g.menuButton.SetCurrentFrame(2)
|
||||
|
||||
g.skillIcon, _ = g.uiManager.NewSprite(d2resource.GenericSkills, d2resource.PaletteSky)
|
||||
// TODO: temporarily hardcoded to Attack, should come from saved state for hero
|
||||
genericSkillsSprite, _ := g.uiManager.NewSprite(d2resource.GenericSkills, d2resource.PaletteSky)
|
||||
attackIconID := 2
|
||||
|
||||
g.leftSkill = &SkillResource{SkillIcon: genericSkillsSprite, IconNumber: attackIconID, SkillResourcePath: d2resource.GenericSkills}
|
||||
g.rightSkill = &SkillResource{SkillIcon: genericSkillsSprite, IconNumber: attackIconID, SkillResourcePath: d2resource.GenericSkills}
|
||||
|
||||
g.loadUIButtons()
|
||||
|
||||
@ -570,21 +595,26 @@ func (g *GameControls) Render(target d2interface.Surface) error {
|
||||
offset += w
|
||||
|
||||
// Left skill
|
||||
if err := g.skillIcon.SetCurrentFrame(2); err != nil {
|
||||
skillResourcePath := g.getSkillResourceByClass(g.hero.LeftSkill.Charclass)
|
||||
if skillResourcePath != g.leftSkill.SkillResourcePath {
|
||||
g.leftSkill.SkillIcon, _ = g.uiManager.NewSprite(skillResourcePath, d2resource.PaletteSky)
|
||||
}
|
||||
|
||||
if err := g.leftSkill.SkillIcon.SetCurrentFrame(g.hero.LeftSkill.IconCel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w, _ = g.skillIcon.GetCurrentFrameSize()
|
||||
w, _ = g.leftSkill.SkillIcon.GetCurrentFrameSize()
|
||||
|
||||
g.skillIcon.SetPosition(offset, height)
|
||||
g.leftSkill.SkillIcon.SetPosition(offset, height)
|
||||
|
||||
if err := g.skillIcon.Render(target); err != nil {
|
||||
if err := g.leftSkill.SkillIcon.Render(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
offset += w
|
||||
|
||||
// Left skill selector
|
||||
// New Stats Selector
|
||||
if err := g.mainPanel.SetCurrentFrame(1); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -668,7 +698,7 @@ func (g *GameControls) Render(target d2interface.Surface) error {
|
||||
|
||||
offset += w
|
||||
|
||||
// Right skill selector
|
||||
// New Skills Selector
|
||||
if err := g.mainPanel.SetCurrentFrame(4); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -684,15 +714,20 @@ func (g *GameControls) Render(target d2interface.Surface) error {
|
||||
offset += w
|
||||
|
||||
// Right skill
|
||||
if err := g.skillIcon.SetCurrentFrame(2); err != nil {
|
||||
skillResourcePath = g.getSkillResourceByClass(g.hero.RightSkill.Charclass)
|
||||
if skillResourcePath != g.rightSkill.SkillResourcePath {
|
||||
g.rightSkill.SkillIcon, _ = g.uiManager.NewSprite(skillResourcePath, d2resource.PaletteSky)
|
||||
}
|
||||
|
||||
if err := g.rightSkill.SkillIcon.SetCurrentFrame(g.hero.RightSkill.IconCel); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w, _ = g.skillIcon.GetCurrentFrameSize()
|
||||
w, _ = g.rightSkill.SkillIcon.GetCurrentFrameSize()
|
||||
|
||||
g.skillIcon.SetPosition(offset, height)
|
||||
g.rightSkill.SkillIcon.SetPosition(offset, height)
|
||||
|
||||
if err := g.skillIcon.Render(target); err != nil {
|
||||
if err := g.rightSkill.SkillIcon.Render(target); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -800,7 +835,7 @@ func (g *GameControls) ManaStatsIsVisible() bool {
|
||||
return g.manaStatsIsVisible
|
||||
}
|
||||
|
||||
// ToggleHpStats toggles the visibility of the hp and mana stats placed above their respective globe
|
||||
// ToggleHpStats toggles the visibility of the hp and mana stats placed above their respective globe and load only if they do not match
|
||||
func (g *GameControls) ToggleHpStats() {
|
||||
g.hpStatsIsVisible = !g.hpStatsIsVisible
|
||||
}
|
||||
@ -815,7 +850,7 @@ func (g *GameControls) onHoverActionable(item ActionableType) {
|
||||
switch item {
|
||||
case leftSkill:
|
||||
return
|
||||
case leftSelect:
|
||||
case newStats:
|
||||
return
|
||||
case xp:
|
||||
return
|
||||
@ -825,7 +860,7 @@ func (g *GameControls) onHoverActionable(item ActionableType) {
|
||||
return
|
||||
case miniPnl:
|
||||
return
|
||||
case rightSelect:
|
||||
case newSkills:
|
||||
return
|
||||
case rightSkill:
|
||||
return
|
||||
@ -843,8 +878,8 @@ func (g *GameControls) onClickActionable(item ActionableType) {
|
||||
switch item {
|
||||
case leftSkill:
|
||||
log.Println("Left Skill Action Pressed")
|
||||
case leftSelect:
|
||||
log.Println("Left Skill Selector Action Pressed")
|
||||
case newStats:
|
||||
log.Println("New Stats Selector Action Pressed")
|
||||
case xp:
|
||||
log.Println("XP Action Pressed")
|
||||
case walkRun:
|
||||
@ -855,8 +890,8 @@ func (g *GameControls) onClickActionable(item ActionableType) {
|
||||
log.Println("Mini Panel Action Pressed")
|
||||
|
||||
g.miniPanel.Toggle()
|
||||
case rightSelect:
|
||||
log.Println("Right Skill Selector Action Pressed")
|
||||
case newSkills:
|
||||
log.Println("New Skills Selector Action Pressed")
|
||||
case rightSkill:
|
||||
log.Println("Right Skill Action Pressed")
|
||||
case hpGlobe:
|
||||
@ -879,3 +914,30 @@ func (g *GameControls) onClickActionable(item ActionableType) {
|
||||
log.Printf("Unrecognized ActionableType(%d) being clicked\n", item)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *GameControls) getSkillResourceByClass(class string) string {
|
||||
resource := ""
|
||||
|
||||
switch class {
|
||||
case "":
|
||||
resource = d2resource.GenericSkills
|
||||
case "bar":
|
||||
resource = d2resource.BarbarianSkills
|
||||
case "nec":
|
||||
resource = d2resource.NecromancerSkills
|
||||
case "pal":
|
||||
resource = d2resource.PaladinSkills
|
||||
case "ass":
|
||||
resource = d2resource.AssassinSkills
|
||||
case "sor":
|
||||
resource = d2resource.SorcererSkills
|
||||
case "ama":
|
||||
resource = d2resource.AmazonSkills
|
||||
case "dru":
|
||||
resource = d2resource.DruidSkills
|
||||
default:
|
||||
log.Fatalf("Unknown class token: '%s'", class)
|
||||
}
|
||||
|
||||
return resource
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ type PlayerState struct {
|
||||
FilePath string `json:"-"`
|
||||
Equipment d2inventory.CharacterEquipment `json:"equipment"`
|
||||
Stats *d2hero.HeroStatsState `json:"stats"`
|
||||
Skills *d2hero.HeroSkillsState `json:"skills"`
|
||||
X float64 `json:"x"`
|
||||
Y float64 `json:"y"`
|
||||
}
|
||||
@ -50,17 +51,20 @@ func GetAllPlayerStates() []*PlayerState {
|
||||
|
||||
gameState := LoadPlayerState(path.Join(basePath, file.Name()))
|
||||
if gameState == nil || gameState.HeroType == d2enum.HeroNone {
|
||||
// temporarily loading default class stats if the character was created before saving stats was introduced
|
||||
// to be removed in the future
|
||||
continue
|
||||
} else if gameState.Stats == nil {
|
||||
gameState.Stats = d2hero.CreateHeroStatsState(gameState.HeroType, d2datadict.CharStats[gameState.HeroType])
|
||||
} else if gameState.Stats == nil || gameState.Skills == nil {
|
||||
// temporarily loading default class stats if the character was created before saving stats/skills was introduced
|
||||
// to be removed in the future
|
||||
classStats := d2datadict.CharStats[gameState.HeroType]
|
||||
gameState.Stats = d2hero.CreateHeroStatsState(gameState.HeroType, classStats)
|
||||
gameState.Skills = d2hero.CreateHeroSkillsState(classStats)
|
||||
|
||||
if err := gameState.Save(); err != nil {
|
||||
fmt.Printf("failed to save game state!, err: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
result = append(result, gameState)
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
@ -98,6 +102,7 @@ func CreatePlayerState(heroName string, hero d2enum.Hero, classStats *d2datadict
|
||||
HeroType: hero,
|
||||
Act: 1,
|
||||
Stats: d2hero.CreateHeroStatsState(hero, classStats),
|
||||
Skills: d2hero.CreateHeroSkillsState(classStats),
|
||||
Equipment: d2inventory.HeroObjects[hero],
|
||||
FilePath: "",
|
||||
}
|
||||
|
@ -186,7 +186,7 @@ func (g *GameClient) handleAddPlayerPacket(packet d2netpacket.NetPacket) error {
|
||||
}
|
||||
|
||||
newPlayer := g.MapEngine.NewPlayer(player.ID, player.Name, player.X, player.Y, 0,
|
||||
player.HeroType, player.Stats, &player.Equipment)
|
||||
player.HeroType, player.Stats, player.Skills, &player.Equipment)
|
||||
|
||||
g.Players[newPlayer.ID()] = newPlayer
|
||||
g.MapEngine.AddEntity(newPlayer)
|
||||
@ -264,12 +264,19 @@ func (g *GameClient) handleCastSkillPacket(packet d2netpacket.NetPacket) error {
|
||||
|
||||
direction := player.Position.DirectionTo(*d2vector.NewVector(castX, castY))
|
||||
player.SetDirection(direction)
|
||||
skill := d2datadict.SkillDetails[playerCast.SkillID]
|
||||
missileRecord := d2datadict.GetMissileByName(skill.Cltmissile)
|
||||
|
||||
if missileRecord == nil {
|
||||
//TODO: handle casts that have no missiles(or have multiple missiles and require additional logic)
|
||||
log.Println("Missile not found for skill ID", skill.ID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// currently hardcoded to missile skill
|
||||
missile, err := g.MapEngine.NewMissile(
|
||||
int(player.Position.X()),
|
||||
int(player.Position.Y()),
|
||||
d2datadict.Missiles[playerCast.SkillID],
|
||||
d2datadict.Missiles[missileRecord.Id],
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
@ -281,6 +288,7 @@ func (g *GameClient) handleCastSkillPacket(packet d2netpacket.NetPacket) error {
|
||||
})
|
||||
|
||||
player.StartCasting(func() {
|
||||
// shoot the missile after the player finished casting
|
||||
g.MapEngine.AddEntity(missile)
|
||||
})
|
||||
|
||||
|
@ -20,12 +20,13 @@ type AddPlayerPacket struct {
|
||||
HeroType d2enum.Hero `json:"hero"`
|
||||
Equipment d2inventory.CharacterEquipment `json:"equipment"`
|
||||
Stats *d2hero.HeroStatsState `json:"heroStats"`
|
||||
Skills *d2hero.HeroSkillsState `json:"heroSkills"`
|
||||
}
|
||||
|
||||
// CreateAddPlayerPacket returns a NetPacket which declares an
|
||||
// AddPlayerPacket with the data in given parameters.
|
||||
func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero,
|
||||
stats *d2hero.HeroStatsState, equipment d2inventory.CharacterEquipment) NetPacket {
|
||||
stats *d2hero.HeroStatsState, skills *d2hero.HeroSkillsState, equipment d2inventory.CharacterEquipment) NetPacket {
|
||||
addPlayerPacket := AddPlayerPacket{
|
||||
ID: id,
|
||||
Name: name,
|
||||
@ -34,6 +35,7 @@ func CreateAddPlayerPacket(id, name string, x, y int, heroType d2enum.Hero,
|
||||
HeroType: heroType,
|
||||
Equipment: equipment,
|
||||
Stats: stats,
|
||||
Skills: skills,
|
||||
}
|
||||
b, _ := json.Marshal(addPlayerPacket)
|
||||
|
||||
|
@ -346,6 +346,7 @@ func handleClientConnection(gameServer *GameServer, client ClientConnection, x,
|
||||
playerY,
|
||||
playerState.HeroType,
|
||||
playerState.Stats,
|
||||
playerState.Skills,
|
||||
playerState.Equipment,
|
||||
)
|
||||
|
||||
@ -370,6 +371,7 @@ func handleClientConnection(gameServer *GameServer, client ClientConnection, x,
|
||||
playerY,
|
||||
conPlayerState.HeroType,
|
||||
conPlayerState.Stats,
|
||||
conPlayerState.Skills,
|
||||
conPlayerState.Equipment,
|
||||
),
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user