diff --git a/d2common/d2enum/player_animation_mode.go b/d2common/d2enum/player_animation_mode.go index 64c817a8..13a993a4 100644 --- a/d2common/d2enum/player_animation_mode.go +++ b/d2common/d2enum/player_animation_mode.go @@ -27,4 +27,5 @@ const ( PlayerAnimationModeDead // DD PlayerAnimationModeSequence // GH PlayerAnimationModeKnockBack // GH + PlayerAnimationModeNone // "" - aura skills, e.g. Paladin's Concentration Aura ) diff --git a/d2core/d2hero/hero_skill.go b/d2core/d2hero/hero_skill.go index f7c46305..fd54655c 100644 --- a/d2core/d2hero/hero_skill.go +++ b/d2core/d2hero/hero_skill.go @@ -23,13 +23,8 @@ type shallowHeroSkill struct { // 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) + // only serialize the shallow object instead of the SkillRecord & SkillDescriptionRecord + bytes, err := json.Marshal(hs.shallow) if err != nil { log.Fatalln(err) } diff --git a/d2core/d2hero/hero_skill_util.go b/d2core/d2hero/hero_skill_util.go new file mode 100644 index 00000000..478b39a9 --- /dev/null +++ b/d2core/d2hero/hero_skill_util.go @@ -0,0 +1,15 @@ +package d2hero + +import "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + +// HydrateSkills will load the SkillRecord & SkillDescriptionRecord from the asset manager, using the skill ID. +// This is done to avoid serializing the whole record data of HeroSkill to a game save or network packets. +// We cant do this while unmarshalling because there is no reference to the asset manager. +func HydrateSkills(skills map[int]*HeroSkill, asset *d2asset.AssetManager) { + for skillID := range skills { + heroSkill := skills[skillID] + heroSkill.SkillRecord = asset.Records.Skill.Details[skillID] + heroSkill.SkillDescriptionRecord = asset.Records.Skill.Descriptions[heroSkill.SkillRecord.Skilldesc] + heroSkill.SkillPoints = skills[skillID].SkillPoints + } +} diff --git a/d2core/d2map/d2mapengine/engine.go b/d2core/d2map/d2mapengine/engine.go index e69e4352..7f8606fe 100644 --- a/d2core/d2map/d2mapengine/engine.go +++ b/d2core/d2map/d2mapengine/engine.go @@ -31,6 +31,8 @@ type MapEngine struct { startSubTileX int // Starting X position startSubTileY int // Starting Y position dt1Files []string // List of DS1 strings + // TODO: remove this flag and show loading screen until the initial server packets are handled and the map is generated (only for remote client) + IsLoading bool // (temp) Whether we have processed the GenerateMapPacket(only for remote client) } // CreateMapEngine creates a new instance of the map engine and returns a pointer to it. @@ -42,6 +44,8 @@ func CreateMapEngine(asset *d2asset.AssetManager) *MapEngine { asset: asset, MapEntityFactory: entity, StampFactory: stamp, + // This will be set to true when we are using a remote client connection, and then set to false after we process the GenerateMapPacket + IsLoading: false, } return engine @@ -285,6 +289,12 @@ func (m *MapEngine) GetCenterPosition() (x, y float64) { // Advance calls the Advance() method for all entities, // processing a single tick. func (m *MapEngine) Advance(tickTime float64) { + // TODO:(temp hack) prevents concurrent map read & write exceptions that occur when we join a TCP game as a remote client + // due to the engine updating entities before handling the GenerateMapPacket + if m.IsLoading { + return + } + for ID := range m.entities { m.entities[ID].Advance(tickTime) } diff --git a/d2core/d2map/d2mapentity/cast_overlay.go b/d2core/d2map/d2mapentity/cast_overlay.go new file mode 100644 index 00000000..ce74820a --- /dev/null +++ b/d2core/d2map/d2mapentity/cast_overlay.go @@ -0,0 +1,57 @@ +package d2mapentity + +import ( + "math" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" +) + +// CastOverlay is an animated entity representing a projectile that is a result of a skill cast. +type CastOverlay struct { + *AnimatedEntity + record *d2records.OverlayRecord + playLoop bool + onDoneFunc func() +} + +// ID returns the overlay uuid +func (co *CastOverlay) ID() string { + return co.AnimatedEntity.uuid +} + +// GetPosition returns the position of the overlay +func (co *CastOverlay) GetPosition() d2vector.Position { + return co.AnimatedEntity.Position +} + +// GetVelocity returns the velocity vector of the overlay +func (co *CastOverlay) GetVelocity() d2vector.Vector { + return co.AnimatedEntity.velocity +} + +// SetRadians adjusts the entity target based on it's range, rotating it's +// current destination by the value of angle in radians. +func (co *CastOverlay) SetRadians(angle float64, done func()) { + rads := float64(co.record.Height2) // TODO: + + x := co.Position.X() + (rads * math.Cos(angle)) + y := co.Position.Y() + (rads * math.Sin(angle)) + + co.setTarget(d2vector.NewPosition(x, y), done) +} + +// SetOnDoneFunc changes the handler func that gets called when the overlay finishes playing. +func (co *CastOverlay) SetOnDoneFunc(onDoneFunc func()) { + co.onDoneFunc = onDoneFunc +} + +// Advance is called once per frame and processes a single game tick. +func (co *CastOverlay) Advance(tickTime float64) { + co.Step(tickTime) + co.AnimatedEntity.Advance(tickTime) + + if !co.playLoop && co.AnimatedEntity.animation.GetPlayedCount() >= 1 { + co.onDoneFunc() + } +} diff --git a/d2core/d2map/d2mapentity/factory.go b/d2core/d2map/d2mapentity/factory.go index 4139d04c..ae67e383 100644 --- a/d2core/d2map/d2mapentity/factory.go +++ b/d2core/d2map/d2mapentity/factory.go @@ -218,6 +218,43 @@ func (f *MapEntityFactory) NewNPC(x, y int, monstat *d2records.MonStatsRecord, d return result, nil } +// NewCastOverlay creates a cast overlay map entity +func (f *MapEntityFactory) NewCastOverlay(x, y int, overlayRecord *d2records.OverlayRecord) (*CastOverlay, error) { + animation, err := f.asset.LoadAnimationWithEffect( + fmt.Sprintf("/data/Global/Overlays/%s.dcc", overlayRecord.Filename), + d2resource.PaletteUnits, + d2enum.DrawEffectModulate, + ) + + // TODO: Frame index and played count seem to be shared across the cloned animation objects when we retrieve the animation from the asset manager cache. + animation.Rewind() + animation.ResetPlayedCount() + + if err != nil { + return nil, err + } + + animationSpeed := float64(overlayRecord.AnimRate*25.0) / 1000.0 + playLoop := false // TODO: should be based on the overlay record, some overlays can repeat(e.g. Bone Shield, Frozen Armor) + + animation.SetPlayLength(animationSpeed) + animation.SetPlayLoop(playLoop) + animation.PlayForward() + + targetX := x + overlayRecord.XOffset + targetY := y + overlayRecord.YOffset + + entity := NewAnimatedEntity(targetX, targetY, animation) + + result := &CastOverlay{ + AnimatedEntity: entity, + record: overlayRecord, + playLoop: playLoop, + } + + return result, nil +} + // NewObject creates an instance of AnimatedComposite func (f *MapEntityFactory) NewObject(x, y int, objectRec *d2records.ObjectDetailsRecord, palettePath string) (*Object, error) { diff --git a/d2core/d2map/d2mapentity/player.go b/d2core/d2map/d2mapentity/player.go index 254c1dae..8f8c66cd 100644 --- a/d2core/d2map/d2mapentity/player.go +++ b/d2core/d2map/d2mapentity/player.go @@ -185,12 +185,19 @@ func (p *Player) IsCasting() bool { // StartCasting sets a flag indicating the player is casting a skill and // sets the animation mode to the casting animation. -func (p *Player) StartCasting(onFinishedCasting func()) { +// This handles all types of skills - melee, ranged, kick, summon, etc. +func (p *Player) StartCasting(animMode d2enum.PlayerAnimationMode, onFinishedCasting func()) { + // passive skills, auras, etc. + if animMode == d2enum.PlayerAnimationModeNone { + return + } + p.isCasting = true p.onFinishedCasting = onFinishedCasting - if err := p.SetAnimationMode(d2enum.PlayerAnimationModeCast); err != nil { + + if err := p.SetAnimationMode(animMode); err != nil { fmtStr := "failed to set animationMode of player: %s to: %d, err: %v\n" - fmt.Printf(fmtStr, p.ID(), d2enum.PlayerAnimationModeCast, err) + fmt.Printf(fmtStr, p.ID(), animMode, err) } } diff --git a/d2core/d2map/d2maprenderer/renderer.go b/d2core/d2map/d2maprenderer/renderer.go index b613be05..11c7b9e1 100644 --- a/d2core/d2map/d2maprenderer/renderer.go +++ b/d2core/d2map/d2maprenderer/renderer.go @@ -124,6 +124,12 @@ func (mr *MapRenderer) SetMapEngine(mapEngine *d2mapengine.MapEngine) { // // Pass 4: Roof tiles. func (mr *MapRenderer) Render(target d2interface.Surface) { + // TODO:(temp hack) should not render before the map has been fully generated - + // Prevents concurrent map read & write exceptions that otherwise occur when we join a TCP game + // as a remote client, due to rendering before we have handled the GenerateMapPacket. + if mr.mapEngine.IsLoading { + return + } mapSize := mr.mapEngine.Size() stxf, styf := mr.viewport.ScreenToWorld(screenMiddleX, -200) diff --git a/d2core/d2records/skill_details_loader.go b/d2core/d2records/skill_details_loader.go index 2259c607..f320e663 100644 --- a/d2core/d2records/skill_details_loader.go +++ b/d2core/d2records/skill_details_loader.go @@ -4,6 +4,7 @@ import ( "log" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation/d2parser" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2txt" ) @@ -140,7 +141,7 @@ func skillDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error { Itypeb3: d.String("itypeb3"), Etypeb1: d.String("etypeb1"), Etypeb2: d.String("etypeb2"), - Anim: d.String("anim"), + Anim: animToEnum(d.String("anim")), Seqtrans: d.String("seqtrans"), Monanim: d.String("monanim"), Seqnum: d.Number("seqnum"), @@ -275,3 +276,47 @@ func skillDetailsLoader(r *RecordManager, d *d2txt.DataDictionary) error { return nil } + + +func animToEnum(anim string) d2enum.PlayerAnimationMode { + switch anim { + case "SC": + return d2enum.PlayerAnimationModeCast + + case "TH": + return d2enum.PlayerAnimationModeThrow + + case "KK": + return d2enum.PlayerAnimationModeKick + + case "SQ": + return d2enum.PlayerAnimationModeSequence + + case "S1": + return d2enum.PlayerAnimationModeSkill1 + + case "S2": + return d2enum.PlayerAnimationModeSkill1 + + case "S3": + return d2enum.PlayerAnimationModeSkill3 + + case "S4": + return d2enum.PlayerAnimationModeSkill4 + + case "A1": + return d2enum.PlayerAnimationModeAttack1 + + case "A2": + return d2enum.PlayerAnimationModeAttack2 + + case "": + return d2enum.PlayerAnimationModeNone + + default: + log.Fatalf("Unknown skill anim value [%s]", anim) + } + + // should not be reached + return d2enum.PlayerAnimationModeNone +} diff --git a/d2core/d2records/skill_details_record.go b/d2core/d2records/skill_details_record.go index 46d05ef8..7cadca33 100644 --- a/d2core/d2records/skill_details_record.go +++ b/d2core/d2records/skill_details_record.go @@ -1,6 +1,9 @@ package d2records -import "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation" +import ( + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2calculation" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +) // [https://d2mods.info/forum/viewtopic.php?t=41556, https://d2mods.info/forum/kb/viewarticle?a=246] @@ -101,7 +104,7 @@ type SkillRecord struct { Itypeb3 string Etypeb1 string Etypeb2 string - Anim string + Anim d2enum.PlayerAnimationMode Seqtrans string Monanim string ItemCastSound string diff --git a/d2game/d2gamescreen/game.go b/d2game/d2gamescreen/game.go index 2bf7b0d0..6d08f9ba 100644 --- a/d2game/d2gamescreen/game.go +++ b/d2game/d2gamescreen/game.go @@ -194,6 +194,7 @@ func (v *Game) Render(screen d2interface.Surface) error { if v.gameClient.RegenMap { v.gameClient.RegenMap = false v.mapRenderer.RegenerateTileCache() + v.gameClient.MapEngine.IsLoading = false } if err := screen.Clear(color.Black); err != nil { diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index b7dad9f9..52fce180 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -68,8 +68,8 @@ type GameControls struct { hpManaStatusSprite *d2ui.Sprite mainPanel *d2ui.Sprite menuButton *d2ui.Sprite - leftSkill *SkillResource - rightSkill *SkillResource + leftSkillResource *SkillResource + rightSkillResource *SkillResource zoneChangeText *d2ui.Label nameLabel *d2ui.Label hpManaStatsLabel *d2ui.Label @@ -90,9 +90,12 @@ type ActionableRegion struct { Rect d2geom.Rectangle } +// SkillResource represents a Skill with its corresponding icon sprite, path to DC6 file and icon number. +// SkillResourcePath points to a DC6 resource which contains the icons of multiple skills as frames. +// The IconNumber is the frame at which we can find our skill sprite in the DC6 file. type SkillResource struct { - SkillResourcePath string - IconNumber int + SkillResourcePath string // path to a skills DC6 file(see getSkillResourceByClass) + IconNumber int // the index of the frame in the DC6 file SkillIcon *d2ui.Sprite } @@ -230,7 +233,8 @@ func NewGameControls( skillRecord := gc.asset.Records.Skill.Details[id] skill, err := heroState.CreateHeroSkill(0, skillRecord.Skill) if err != nil { - term.OutputErrorf("cannot create skill with ID of %d", id) + term.OutputErrorf("cannot create skill with ID of %d, error: %s", id, err) + return } gc.hero.LeftSkill = skill @@ -240,7 +244,8 @@ func NewGameControls( skillRecord := gc.asset.Records.Skill.Details[id] skill, err := heroState.CreateHeroSkill(0, skillRecord.Skill) if err != nil { - term.OutputErrorf("cannot create skill with ID of %d", id) + term.OutputErrorf("cannot create skill with ID of %d, error: %s", id, err) + return } gc.hero.RightSkill = skill @@ -503,8 +508,8 @@ func (g *GameControls) Load() { attackIconID := 2 - g.leftSkill = &SkillResource{SkillIcon: genericSkillsSprite, IconNumber: attackIconID, SkillResourcePath: d2resource.GenericSkills} - g.rightSkill = &SkillResource{SkillIcon: genericSkillsSprite, IconNumber: attackIconID, SkillResourcePath: d2resource.GenericSkills} + g.leftSkillResource = &SkillResource{SkillIcon: genericSkillsSprite, IconNumber: attackIconID, SkillResourcePath: d2resource.GenericSkills} + g.rightSkillResource = &SkillResource{SkillIcon: genericSkillsSprite, IconNumber: attackIconID, SkillResourcePath: d2resource.GenericSkills} g.loadUIButtons() @@ -688,19 +693,19 @@ func (g *GameControls) Render(target d2interface.Surface) error { // Left skill skillResourcePath := g.getSkillResourceByClass(g.hero.LeftSkill.Charclass) - if skillResourcePath != g.leftSkill.SkillResourcePath { - g.leftSkill.SkillIcon, _ = g.ui.NewSprite(skillResourcePath, d2resource.PaletteSky) + if skillResourcePath != g.leftSkillResource.SkillResourcePath { + g.leftSkillResource.SkillIcon, _ = g.ui.NewSprite(skillResourcePath, d2resource.PaletteSky) } - if err := g.leftSkill.SkillIcon.SetCurrentFrame(g.hero.LeftSkill.IconCel); err != nil { + if err := g.leftSkillResource.SkillIcon.SetCurrentFrame(g.hero.LeftSkill.IconCel); err != nil { return err } - w, _ = g.leftSkill.SkillIcon.GetCurrentFrameSize() + w, _ = g.leftSkillResource.SkillIcon.GetCurrentFrameSize() - g.leftSkill.SkillIcon.SetPosition(offset, height) + g.leftSkillResource.SkillIcon.SetPosition(offset, height) - if err := g.leftSkill.SkillIcon.Render(target); err != nil { + if err := g.leftSkillResource.SkillIcon.Render(target); err != nil { return err } @@ -807,19 +812,19 @@ func (g *GameControls) Render(target d2interface.Surface) error { // Right skill skillResourcePath = g.getSkillResourceByClass(g.hero.RightSkill.Charclass) - if skillResourcePath != g.rightSkill.SkillResourcePath { - g.rightSkill.SkillIcon, _ = g.ui.NewSprite(skillResourcePath, d2resource.PaletteSky) + if skillResourcePath != g.rightSkillResource.SkillResourcePath { + g.rightSkillResource.SkillIcon, _ = g.ui.NewSprite(skillResourcePath, d2resource.PaletteSky) } - if err := g.rightSkill.SkillIcon.SetCurrentFrame(g.hero.RightSkill.IconCel); err != nil { + if err := g.rightSkillResource.SkillIcon.SetCurrentFrame(g.hero.RightSkill.IconCel); err != nil { return err } - w, _ = g.rightSkill.SkillIcon.GetCurrentFrameSize() + w, _ = g.rightSkillResource.SkillIcon.GetCurrentFrameSize() - g.rightSkill.SkillIcon.SetPosition(offset, height) + g.rightSkillResource.SkillIcon.SetPosition(offset, height) - if err := g.rightSkill.SkillIcon.Render(target); err != nil { + if err := g.rightSkillResource.SkillIcon.Render(target); err != nil { return err } diff --git a/d2networking/d2client/d2remoteclient/remote_client_connection.go b/d2networking/d2client/d2remoteclient/remote_client_connection.go index 116147fc..06adc0ae 100644 --- a/d2networking/d2client/d2remoteclient/remote_client_connection.go +++ b/d2networking/d2client/d2remoteclient/remote_client_connection.go @@ -198,6 +198,13 @@ func (r *RemoteClientConnection) decodeToPacket(t d2netpackettype.NetPacketType, if err = json.Unmarshal([]byte(data), &p); err != nil { break } + np = d2netpacket.NetPacket{PacketType: t, PacketData: d2netpacket.MarshalPacket(p)} + + case d2netpackettype.CastSkill: + var p d2netpacket.CastPacket + if err = json.Unmarshal([]byte(data), &p); err != nil { + break + } np = d2netpacket.NetPacket{PacketType: t, PacketData: d2netpacket.MarshalPacket(p)} diff --git a/d2networking/d2client/game_client.go b/d2networking/d2client/game_client.go index 1015d54d..044c9c87 100644 --- a/d2networking/d2client/game_client.go +++ b/d2networking/d2client/game_client.go @@ -17,6 +17,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapentity" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2records" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2clientconnectiontype" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2localclient" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2client/d2remoteclient" @@ -56,6 +57,10 @@ func Create(connectionType d2clientconnectiontype.ClientConnectionType, scriptEngine: scriptEngine, } + // for a remote client connection, set loading to true - wait until we process the GenerateMapPacket + // before we start updating map entites + result.MapEngine.IsLoading = connectionType == d2clientconnectiontype.LANClient + mapGen, err := d2mapgen.NewMapGenerator(asset, result.MapEngine) if err != nil { return nil, err @@ -198,6 +203,8 @@ func (g *GameClient) handleAddPlayerPacket(packet d2netpacket.NetPacket) error { return err } + d2hero.HydrateSkills(player.Skills, g.asset) + newPlayer := g.MapEngine.NewPlayer(player.ID, player.Name, player.X, player.Y, 0, player.HeroType, player.Stats, player.Skills, &player.Equipment) @@ -268,42 +275,79 @@ func (g *GameClient) handleCastSkillPacket(packet d2netpacket.NetPacket) error { castX := playerCast.TargetX * numSubtilesPerTile castY := playerCast.TargetY * numSubtilesPerTile - rads := d2math.GetRadiansBetween( + direction := player.Position.DirectionTo(*d2vector.NewVector(castX, castY)) + player.SetDirection(direction) + + skillRecord := g.asset.Records.Skill.Details[playerCast.SkillID] + missileEntity, err := g.createMissileEntity(skillRecord, player, castX, castY) + if err != nil { + return err + } + + player.StartCasting(skillRecord.Anim, func() { + if missileEntity != nil { + // shoot the missile after the player has finished casting + g.MapEngine.AddEntity(missileEntity) + } + }) + + overlayRecord := g.asset.Records.Layout.Overlays[skillRecord.Castoverlay] + g.playCastOverlay(overlayRecord, int(player.Position.X()), int(player.Position.Y())) + + return nil +} + +func (g *GameClient) createMissileEntity(skillRecord *d2records.SkillRecord, player *d2mapentity.Player, castX float64, castY float64) (*d2mapentity.Missile, error) { + missileRecord := g.asset.Records.GetMissileByName(skillRecord.Cltmissile) + if missileRecord == nil { + return nil, nil + } + + var missileEntity *d2mapentity.Missile + + radians := d2math.GetRadiansBetween( player.Position.X(), player.Position.Y(), castX, castY, ) - direction := player.Position.DirectionTo(*d2vector.NewVector(castX, castY)) - player.SetDirection(direction) - skill := g.asset.Records.Skill.Details[playerCast.SkillID] - missileRecord := g.asset.Records.GetMissileByName(skill.Cltmissile) + missileEntity, err := g.MapEngine.NewMissile( + int(player.Position.X()), + int(player.Position.Y()), + g.asset.Records.Missiles[missileRecord.Id], + ) - 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) + if err != nil { + return nil, err + } + + missileEntity.SetRadians(radians, func() { + g.MapEngine.RemoveEntity(missileEntity) + }) + + return missileEntity, nil +} + +func (g *GameClient) playCastOverlay(overlayRecord *d2records.OverlayRecord, x int, y int) error { + if overlayRecord == nil { return nil } - missile, err := g.MapEngine.NewMissile( - int(player.Position.X()), - int(player.Position.Y()), - missileRecord, + overlayEntity, err := g.MapEngine.NewCastOverlay( + x, + y, + overlayRecord, ) - if err != nil { return err } - missile.SetRadians(rads, func() { - g.MapEngine.RemoveEntity(missile) + overlayEntity.SetOnDoneFunc(func() { + g.MapEngine.RemoveEntity(overlayEntity) }) - player.StartCasting(func() { - // shoot the missile after the player finished casting - g.MapEngine.AddEntity(missile) - }) + g.MapEngine.AddEntity(overlayEntity) return nil } diff --git a/d2networking/d2server/game_server.go b/d2networking/d2server/game_server.go index df35da30..8cdd25ce 100644 --- a/d2networking/d2server/game_server.go +++ b/d2networking/d2server/game_server.go @@ -14,6 +14,7 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2asset" + "github.com/OpenDiablo2/OpenDiablo2/d2core/d2hero" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapengine" "github.com/OpenDiablo2/OpenDiablo2/d2core/d2map/d2mapgen" "github.com/OpenDiablo2/OpenDiablo2/d2networking/d2netpacket" @@ -181,6 +182,14 @@ func (g *GameServer) packetManager() { } g.sendPacketToClients(move) + case d2netpackettype.CastSkill: + castSkill, err := d2netpacket.UnmarshalNetPacket(p) + if err != nil { + log.Println(err) + continue + } + + g.sendPacketToClients(castSkill) case d2netpackettype.SpawnItem: item, err := d2netpacket.UnmarshalNetPacket(p) if err != nil { @@ -345,6 +354,8 @@ func handleClientConnection(gameServer *GameServer, client ClientConnection, x, playerX := int(x*subtilesPerTile) + middleOfTileOffset playerY := int(y*subtilesPerTile) + middleOfTileOffset + d2hero.HydrateSkills(playerState.Skills, gameServer.asset) + createPlayerPacket := d2netpacket.CreateAddPlayerPacket( client.GetUniqueID(), playerState.HeroName, @@ -430,6 +441,8 @@ func OnPacketReceived(client ClientConnection, packet d2netpacket.NetPacket) err log.Printf("GameServer: error sending %T to client %s: %s", packet, player.GetUniqueID(), err) } } + default: + log.Printf("GameServer: received unknown packet %T", packet) } return nil