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:
presiyan-ivanov 2020-09-20 18:55:44 +03:00 committed by GitHub
parent f4d78549c5
commit a4e9797431
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 244 additions and 58 deletions

View File

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

View File

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

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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