From 56c7c4544ec28ef36358992a1703a0d6388e6cff Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 10 May 2019 23:11:49 -0400 Subject: [PATCH 01/12] added flags to indicate the projectile must be handled using the server as an intermediary; eliminated obsolete enumeration data; added analysis for the oicw_little_buddy --- .../psforever/objects/GlobalDefinitions.scala | 24 +++++++++++++++++++ .../objects/ballistics/Projectiles.scala | 1 + .../definition/ProjectileDefinition.scala | 8 +++++++ .../game/objectcreate/ObjectClass.scala | 1 + .../objectcreate/TrackedProjectileData.scala | 23 ++++-------------- .../TrackedProjectileDataTest.scala | 4 ++-- 6 files changed, 40 insertions(+), 21 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index f833b0fb..09be1073 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects +import net.psforever.objects.GlobalDefinitions.sparrow_projectile import net.psforever.objects.ballistics.Projectiles import net.psforever.objects.ce.{DeployableCategory, DeployedItem} import net.psforever.objects.definition._ @@ -297,6 +298,8 @@ object GlobalDefinitions { val oicw_projectile = ProjectileDefinition(Projectiles.oicw_projectile) + val oicw_little_buddy = ProjectileDefinition(Projectiles.oicw_little_buddy) + val pellet_gun_projectile = ProjectileDefinition(Projectiles.pellet_gun_projectile) val peregrine_dual_machine_gun_projectile = ProjectileDefinition(Projectiles.peregrine_dual_machine_gun_projectile) @@ -2216,6 +2219,7 @@ object GlobalDefinitions { aphelion_starfire_projectile.InitialVelocity = 45 aphelion_starfire_projectile.Lifespan = 7f aphelion_starfire_projectile.ProjectileDamageType = DamageType.Aggravated + aphelion_starfire_projectile.ExistsOnRemoteClients = true ProjectileDefinition.CalculateDerivedFields(aphelion_starfire_projectile) bolt_projectile.Name = "bolt_projectile" @@ -2692,6 +2696,7 @@ object GlobalDefinitions { hunter_seeker_missile_projectile.ProjectileDamageType = DamageType.Splash hunter_seeker_missile_projectile.InitialVelocity = 40 hunter_seeker_missile_projectile.Lifespan = 6.3f + hunter_seeker_missile_projectile.ExistsOnRemoteClients = true ProjectileDefinition.CalculateDerivedFields(hunter_seeker_missile_projectile) jammer_cartridge_projectile.Name = "jammer_cartridge_projectile" @@ -2977,8 +2982,21 @@ object GlobalDefinitions { oicw_projectile.ProjectileDamageType = DamageType.Splash oicw_projectile.InitialVelocity = 5 oicw_projectile.Lifespan = 6.1f + oicw_projectile.ExistsOnRemoteClients = true ProjectileDefinition.CalculateDerivedFields(oicw_projectile) + oicw_little_buddy.Name = "oicw_projectile" + oicw_little_buddy.Damage0 = 75 + oicw_little_buddy.Damage1 = 75 + oicw_little_buddy.DamageAtEdge = 0.1f + oicw_little_buddy.DamageRadius = 7.5f + oicw_little_buddy.ProjectileDamageType = DamageType.Splash + oicw_little_buddy.InitialVelocity = 40 + oicw_little_buddy.Lifespan = 0.5f + oicw_little_buddy.ExistsOnRemoteClients = true + //add_property oicw_little_buddy multi_stage_spawn_server_side true ... + ProjectileDefinition.CalculateDerivedFields(oicw_little_buddy) + pellet_gun_projectile.Name = "pellet_gun_projectile" // TODO for later, maybe : set_resource_parent pellet_gun_projectile game_objects shotgun_shell_projectile pellet_gun_projectile.Damage0 = 12 @@ -3056,6 +3074,7 @@ object GlobalDefinitions { peregrine_sparrow_projectile.ProjectileDamageType = DamageType.Splash peregrine_sparrow_projectile.InitialVelocity = 45 peregrine_sparrow_projectile.Lifespan = 7.5f + peregrine_sparrow_projectile.ExistsOnRemoteClients = true ProjectileDefinition.CalculateDerivedFields(peregrine_sparrow_projectile) phalanx_av_projectile.Name = "phalanx_av_projectile" @@ -3383,6 +3402,7 @@ object GlobalDefinitions { sparrow_projectile.ProjectileDamageType = DamageType.Splash sparrow_projectile.InitialVelocity = 60 sparrow_projectile.Lifespan = 5.85f + sparrow_projectile.ExistsOnRemoteClients = true ProjectileDefinition.CalculateDerivedFields(sparrow_projectile) sparrow_secondary_projectile.Name = "sparrow_secondary_projectile" @@ -3397,6 +3417,7 @@ object GlobalDefinitions { sparrow_secondary_projectile.ProjectileDamageType = DamageType.Splash sparrow_secondary_projectile.InitialVelocity = 60 sparrow_secondary_projectile.Lifespan = 5.85f + sparrow_secondary_projectile.ExistsOnRemoteClients = true ProjectileDefinition.CalculateDerivedFields(sparrow_secondary_projectile) spiker_projectile.Name = "spiker_projectile" @@ -3447,6 +3468,7 @@ object GlobalDefinitions { starfire_projectile.ProjectileDamageType = DamageType.Aggravated starfire_projectile.InitialVelocity = 45 starfire_projectile.Lifespan = 7.8f + starfire_projectile.ExistsOnRemoteClients = true ProjectileDefinition.CalculateDerivedFields(starfire_projectile) striker_missile_projectile.Name = "striker_missile_projectile" @@ -3478,6 +3500,7 @@ object GlobalDefinitions { striker_missile_targeting_projectile.ProjectileDamageType = DamageType.Splash striker_missile_targeting_projectile.InitialVelocity = 30 striker_missile_targeting_projectile.Lifespan = 4.2f + striker_missile_targeting_projectile.ExistsOnRemoteClients = true ProjectileDefinition.CalculateDerivedFields(striker_missile_targeting_projectile) trek_projectile.Name = "trek_projectile" @@ -3565,6 +3588,7 @@ object GlobalDefinitions { wasp_rocket_projectile.ProjectileDamageType = DamageType.Splash wasp_rocket_projectile.InitialVelocity = 60 wasp_rocket_projectile.Lifespan = 6.5f + wasp_rocket_projectile.ExistsOnRemoteClients = true ProjectileDefinition.CalculateDerivedFields(wasp_rocket_projectile) winchester_projectile.Name = "winchester_projectile" diff --git a/common/src/main/scala/net/psforever/objects/ballistics/Projectiles.scala b/common/src/main/scala/net/psforever/objects/ballistics/Projectiles.scala index 45ed97d4..cb305609 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/Projectiles.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/Projectiles.scala @@ -92,6 +92,7 @@ object Projectiles extends Enumeration { final val mine_projectile = Value(551) final val mine_sweeper_projectile = Value(554) final val mine_sweeper_projectile_enh = Value(555) + final val oicw_little_buddy = Value(601) final val oicw_projectile = Value(602) final val pellet_gun_projectile = Value(631) final val peregrine_dual_machine_gun_projectile = Value(639) diff --git a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index 3a36cef9..0d01ec43 100644 --- a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -23,6 +23,7 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) private var damageAtEdge : Float = 1f private var damageRadius : Float = 1f private var useDamage1Subtract : Boolean = false + private var existsOnRemoteClients : Boolean = false //`true` spawns a server-managed object //derived calculations private var distanceMax : Float = 0f private var distanceFromAcceleration : Float = 0f @@ -109,6 +110,13 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) DamageRadius } + def ExistsOnRemoteClients : Boolean = existsOnRemoteClients + + def ExistsOnRemoteClients_=(existsOnRemoteClients : Boolean) : Boolean = { + this.existsOnRemoteClients = existsOnRemoteClients + ExistsOnRemoteClients + } + def DistanceMax : Float = distanceMax //accessor only def DistanceFromAcceleration : Float = distanceFromAcceleration //accessor only diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala index 527fdc5d..7308f84e 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala @@ -1230,6 +1230,7 @@ object ObjectClass { case ObjectClass.meteor_projectile_large => ConstructorData(TrackedProjectileData.codec, "meteor") case ObjectClass.meteor_projectile_medium => ConstructorData(TrackedProjectileData.codec, "meteor") case ObjectClass.meteor_projectile_small => ConstructorData(TrackedProjectileData.codec, "meteor") + case ObjectClass.oicw_little_buddy => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.oicw_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.sparrow_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.starfire_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala index dece6e29..89daa598 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala @@ -1,34 +1,19 @@ // Copyright (c) 2017 PSForever package net.psforever.packet.game.objectcreate -import net.psforever.packet.{Marshallable, PacketHelpers} +import net.psforever.packet.Marshallable import scodec.{Attempt, Codec, Err} import scodec.codecs._ import shapeless.{::, HNil} -object TrackedProjectile extends Enumeration { - type Type = Value - - val Meteor = Value(32) - val WaspRocket = Value(208) - val Sparrow = Value(3355579) - val OICW = Value(3355587) - val Striker = Value(6710918) - val HunterSeeker = Value(10131913) - val Starfire = Value(10131961) - - implicit val codec = PacketHelpers.createEnumerationCodec(this, uint24) -} - /** * A representation of a projectile that the server must intentionally convey to players other than the shooter. * @param data na - * @param unk2 na; - * data specific to the type of projectile(?) + * @param unk2 na * @param unk3 na */ final case class TrackedProjectileData(data : CommonFieldDataWithPlacement, - unk2 : TrackedProjectile.Value, + unk2 : Int, unk3 : Int = 0 ) extends ConstructorData { override def bitsize : Long = 33L + data.bitsize @@ -37,7 +22,7 @@ final case class TrackedProjectileData(data : CommonFieldDataWithPlacement, object TrackedProjectileData extends Marshallable[TrackedProjectileData] { implicit val codec : Codec[TrackedProjectileData] = ( ("data" | CommonFieldDataWithPlacement.codec) :: - ("unk2" | TrackedProjectile.codec) :: + ("unk2" | uint24) :: uint4 :: uint(3) :: uint2 diff --git a/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala b/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala index 998a50a3..def883bd 100644 --- a/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala +++ b/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala @@ -33,7 +33,7 @@ class TrackedProjectileDataTest extends Specification { deploy.v5.isEmpty mustEqual true deploy.guid mustEqual PlanetSideGUID(0) - unk2 mustEqual TrackedProjectile.Striker + unk2 mustEqual 6710918 unk3 mustEqual 0 case _ => @@ -50,7 +50,7 @@ class TrackedProjectileDataTest extends Specification { PlacementData(4644.5938f, 5472.0938f, 82.375f, 0f, 30.9375f, 171.5625f), CommonFieldData(PlanetSideEmpire.TR, false, false, true, None, false, None, None, PlanetSideGUID(0)) ), - TrackedProjectile.Striker, + 6710918, 0 ) val msg = ObjectCreateMessage(ObjectClass.striker_missile_targeting_projectile, PlanetSideGUID(40192), obj) From fa7365e8af5d0b21c3934ed745895edd46f741a6 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 14 May 2019 05:34:07 -0400 Subject: [PATCH 02/12] modified TrackedProjectileData codec and tests; changed Projectile into a game object; added comments and entries missing from the basic ObjectClass lists; projectile packet converter added but untested --- .../psforever/objects/GlobalDefinitions.scala | 14 ++++++ .../objects/ballistics/Projectile.scala | 4 +- .../converter/ProjectileConverter.scala | 43 +++++++++++++++++ .../game/objectcreate/ObjectClass.scala | 16 ++++--- .../objectcreate/TrackedProjectileData.scala | 47 ++++++++++++++----- .../TrackedProjectileDataTest.scala | 14 +++--- 6 files changed, 113 insertions(+), 25 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 09be1073..acaf71e6 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1950,6 +1950,8 @@ object GlobalDefinitions { * Initialize `ProjectileDefinition` globals. */ private def init_projectile() : Unit = { + val projectileConverter : ProjectileConverter = new ProjectileConverter + bullet_105mm_projectile.Name = "105mmbullet_projectile" bullet_105mm_projectile.Damage0 = 150 bullet_105mm_projectile.Damage1 = 300 @@ -2220,6 +2222,7 @@ object GlobalDefinitions { aphelion_starfire_projectile.Lifespan = 7f aphelion_starfire_projectile.ProjectileDamageType = DamageType.Aggravated aphelion_starfire_projectile.ExistsOnRemoteClients = true + aphelion_starfire_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(aphelion_starfire_projectile) bolt_projectile.Name = "bolt_projectile" @@ -2697,6 +2700,7 @@ object GlobalDefinitions { hunter_seeker_missile_projectile.InitialVelocity = 40 hunter_seeker_missile_projectile.Lifespan = 6.3f hunter_seeker_missile_projectile.ExistsOnRemoteClients = true + hunter_seeker_missile_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(hunter_seeker_missile_projectile) jammer_cartridge_projectile.Name = "jammer_cartridge_projectile" @@ -2983,6 +2987,7 @@ object GlobalDefinitions { oicw_projectile.InitialVelocity = 5 oicw_projectile.Lifespan = 6.1f oicw_projectile.ExistsOnRemoteClients = true + oicw_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(oicw_projectile) oicw_little_buddy.Name = "oicw_projectile" @@ -2994,6 +2999,7 @@ object GlobalDefinitions { oicw_little_buddy.InitialVelocity = 40 oicw_little_buddy.Lifespan = 0.5f oicw_little_buddy.ExistsOnRemoteClients = true + oicw_little_buddy.Packet = projectileConverter //add_property oicw_little_buddy multi_stage_spawn_server_side true ... ProjectileDefinition.CalculateDerivedFields(oicw_little_buddy) @@ -3075,6 +3081,7 @@ object GlobalDefinitions { peregrine_sparrow_projectile.InitialVelocity = 45 peregrine_sparrow_projectile.Lifespan = 7.5f peregrine_sparrow_projectile.ExistsOnRemoteClients = true + peregrine_sparrow_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(peregrine_sparrow_projectile) phalanx_av_projectile.Name = "phalanx_av_projectile" @@ -3126,6 +3133,8 @@ object GlobalDefinitions { phoenix_missile_guided_projectile.ProjectileDamageType = DamageType.Splash phoenix_missile_guided_projectile.InitialVelocity = 0 phoenix_missile_guided_projectile.Lifespan = 3f + phoenix_missile_guided_projectile.ExistsOnRemoteClients = true + phoenix_missile_guided_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(phoenix_missile_guided_projectile) phoenix_missile_projectile.Name = "phoenix_missile_projectile" @@ -3403,6 +3412,7 @@ object GlobalDefinitions { sparrow_projectile.InitialVelocity = 60 sparrow_projectile.Lifespan = 5.85f sparrow_projectile.ExistsOnRemoteClients = true + sparrow_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(sparrow_projectile) sparrow_secondary_projectile.Name = "sparrow_secondary_projectile" @@ -3418,6 +3428,7 @@ object GlobalDefinitions { sparrow_secondary_projectile.InitialVelocity = 60 sparrow_secondary_projectile.Lifespan = 5.85f sparrow_secondary_projectile.ExistsOnRemoteClients = true + sparrow_secondary_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(sparrow_secondary_projectile) spiker_projectile.Name = "spiker_projectile" @@ -3469,6 +3480,7 @@ object GlobalDefinitions { starfire_projectile.InitialVelocity = 45 starfire_projectile.Lifespan = 7.8f starfire_projectile.ExistsOnRemoteClients = true + starfire_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(starfire_projectile) striker_missile_projectile.Name = "striker_missile_projectile" @@ -3501,6 +3513,7 @@ object GlobalDefinitions { striker_missile_targeting_projectile.InitialVelocity = 30 striker_missile_targeting_projectile.Lifespan = 4.2f striker_missile_targeting_projectile.ExistsOnRemoteClients = true + striker_missile_targeting_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(striker_missile_targeting_projectile) trek_projectile.Name = "trek_projectile" @@ -3589,6 +3602,7 @@ object GlobalDefinitions { wasp_rocket_projectile.InitialVelocity = 60 wasp_rocket_projectile.Lifespan = 6.5f wasp_rocket_projectile.ExistsOnRemoteClients = true + wasp_rocket_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(wasp_rocket_projectile) winchester_projectile.Name = "winchester_projectile" diff --git a/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index fdc9ad8e..136c20bd 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -35,7 +35,7 @@ final case class Projectile(profile : ProjectileDefinition, attribute_to : Int, shot_origin : Vector3, shot_angle : Vector3, - fire_time: Long = System.nanoTime) { + fire_time: Long = System.nanoTime) extends PlanetSideGameObject { /** Information about the current world coordinates and orientation of the projectile */ val current : SimpleWorldEntity = new SimpleWorldEntity() private var resolved : ProjectileResolution.Value = ProjectileResolution.Unresolved @@ -54,6 +54,8 @@ final case class Projectile(profile : ProjectileDefinition, def isResolved : Boolean = resolved == ProjectileResolution.Resolved || resolved == ProjectileResolution.MissedShot def isMiss : Boolean = resolved == ProjectileResolution.MissedShot + + def Definition = profile } object Projectile { diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala new file mode 100644 index 00000000..a1534461 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala @@ -0,0 +1,43 @@ +// Copyright (c) 2019 PSForever +package net.psforever.objects.definition.converter + +import net.psforever.objects.ballistics.Projectile +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.packet.game.objectcreate.{CommonFieldData, CommonFieldDataWithPlacement, FlightPhysics, PlacementData, TrackedProjectileData} + +import scala.util.{Failure, Success, Try} + +class ProjectileConverter extends ObjectCreateConverter[Projectile]() { + override def ConstructorData(obj : Projectile) : Try[TrackedProjectileData] = { + Success( + TrackedProjectileData( + CommonFieldDataWithPlacement( + PlacementData( + obj.Position, + obj.Orientation, + obj.Velocity + ), + CommonFieldData( + obj.owner.Faction, + false, + false, + true, + None, + false, + None, + None, + PlanetSideGUID(0) + ) + ), + 0, + 0, + FlightPhysics.State3, + 7, + 2 + ) + ) + } + + override def DetailedConstructorData(obj : Projectile) : Try[TrackedProjectileData] = + Failure(new Exception("ProjectileConverter should not be used to generate detailed projectile data (nothing should)")) +} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala index 7308f84e..e3318427 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala @@ -295,7 +295,7 @@ object ObjectClass { final val portable_manned_turret_tr = 687 final val portable_manned_turret_vs = 688 //projectiles - final val hunter_seeker_missile_projectile = 405 + final val hunter_seeker_missile_projectile = 405 //phoenix projectile final val meteor_common = 543 final val meteor_projectile_b_large = 544 final val meteor_projectile_b_medium = 545 @@ -303,13 +303,14 @@ object ObjectClass { final val meteor_projectile_large = 547 final val meteor_projectile_medium = 548 final val meteor_projectile_small = 549 - final val oicw_little_buddy = 601 - final val oicw_projectile = 602 + final val phoenix_missile_guided_projectile = 675 //decimator projectile + final val oicw_little_buddy = 601 //scorpion projectile's projectiles + final val oicw_projectile = 602 //scorpion projectile final val radiator_cload = 717 - final val sparrow_projectile = 792 - final val starfire_projectile = 831 - final val striker_missile_targeting_projectile = 841 - final val wasp_rocket_projectile = 1001 + final val sparrow_projectile = 792 //nc aa max projectile + final val starfire_projectile = 831 //vs aa max projectile + final val striker_missile_targeting_projectile = 841 //striker projectile + final val wasp_rocket_projectile = 1001 //wasp projectile //vehicles final val apc_destroyed = 65 final val apc_tr = 67 //juggernaut @@ -1230,6 +1231,7 @@ object ObjectClass { case ObjectClass.meteor_projectile_large => ConstructorData(TrackedProjectileData.codec, "meteor") case ObjectClass.meteor_projectile_medium => ConstructorData(TrackedProjectileData.codec, "meteor") case ObjectClass.meteor_projectile_small => ConstructorData(TrackedProjectileData.codec, "meteor") + case ObjectClass.phoenix_missile_guided_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.oicw_little_buddy => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.oicw_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.sparrow_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala index 89daa598..a70e2845 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala @@ -1,20 +1,44 @@ // Copyright (c) 2017 PSForever package net.psforever.packet.game.objectcreate -import net.psforever.packet.Marshallable +import net.psforever.packet.{Marshallable, PacketHelpers} import scodec.{Attempt, Codec, Err} import scodec.codecs._ import shapeless.{::, HNil} +object FlightPhysics extends Enumeration { + type Type = Value + + //valid (extremely small distance) (requires non-zero unk4, unk5) + val State3 = Value(3) + //valid (infinite) (if unk4 == 0 unk5 == 0, minimum distance + time) + val State4 = Value(4) + //valid(infinite) + val State5 = Value(5) + //valid (uses velocity) (infinite) + val State6 = Value(6) + //valid (uses velocity) (infinite) + val State7 = Value(7) + //valid (uses velocity) (time > 0 is infinite) (unk5 == 2) + val State15 = Value(15) + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L) +} + /** * A representation of a projectile that the server must intentionally convey to players other than the shooter. - * @param data na + * @param data common game object information * @param unk2 na + * @param unit_distance_limit how quickly the projectile travels before naturally being destroyed + * `FlightPhysics` needs to be * @param unk3 na */ final case class TrackedProjectileData(data : CommonFieldDataWithPlacement, unk2 : Int, - unk3 : Int = 0 + unit_distance_limit : Int, + unk3 : FlightPhysics.Value, + unk4 : Int, + unk5 : Int ) extends ConstructorData { override def bitsize : Long = 33L + data.bitsize } @@ -22,21 +46,22 @@ final case class TrackedProjectileData(data : CommonFieldDataWithPlacement, object TrackedProjectileData extends Marshallable[TrackedProjectileData] { implicit val codec : Codec[TrackedProjectileData] = ( ("data" | CommonFieldDataWithPlacement.codec) :: - ("unk2" | uint24) :: - uint4 :: - uint(3) :: - uint2 + ("unk2" | uint16) :: + ("unit_distance_limit" | uint8) :: + ("unk3" | FlightPhysics.codec) :: + ("unk4" | uint(3)) :: + ("unk5" | uint2) ).exmap[TrackedProjectileData] ( { - case data :: unk2 :: 4 :: unk3 :: 0 :: HNil => - Attempt.successful(TrackedProjectileData(data, unk2, unk3)) + case data :: unk2 :: lim :: unk3 :: unk4 :: unk5 :: HNil => + Attempt.successful(TrackedProjectileData(data, unk2, lim, unk3, unk4, unk5)) case data => Attempt.failure(Err(s"invalid projectile data format - $data")) }, { - case TrackedProjectileData(data, unk2, unk3) => - Attempt.successful(data :: unk2 :: 4 :: unk3 :: 0 :: HNil) + case TrackedProjectileData(data, unk2, lim, unk3, unk4, unk5) => + Attempt.successful(data :: unk2 :: lim :: unk3 :: unk4 :: unk5 :: HNil) } ) } diff --git a/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala b/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala index def883bd..586345bd 100644 --- a/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala +++ b/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala @@ -20,7 +20,7 @@ class TrackedProjectileDataTest extends Specification { guid mustEqual PlanetSideGUID(40192) parent.isDefined mustEqual false data match { - case TrackedProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, unk3) => + case TrackedProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, lim, unk3, unk4, unk5) => pos.coord mustEqual Vector3(4644.5938f, 5472.0938f, 82.375f) pos.orient mustEqual Vector3(0, 30.9375f, 171.5625f) deploy.faction mustEqual PlanetSideEmpire.TR @@ -33,9 +33,11 @@ class TrackedProjectileDataTest extends Specification { deploy.v5.isEmpty mustEqual true deploy.guid mustEqual PlanetSideGUID(0) - unk2 mustEqual 6710918 - - unk3 mustEqual 0 + unk2 mustEqual 26214 + lim mustEqual 134 + unk3 mustEqual FlightPhysics.State4 + unk4 mustEqual 0 + unk5 mustEqual 0 case _ => ko } @@ -50,11 +52,11 @@ class TrackedProjectileDataTest extends Specification { PlacementData(4644.5938f, 5472.0938f, 82.375f, 0f, 30.9375f, 171.5625f), CommonFieldData(PlanetSideEmpire.TR, false, false, true, None, false, None, None, PlanetSideGUID(0)) ), - 6710918, - 0 + 26214, 134, FlightPhysics.State4, 0, 0 ) val msg = ObjectCreateMessage(ObjectClass.striker_missile_targeting_projectile, PlanetSideGUID(40192), obj) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + //pkt mustEqual string_striker_projectile pkt.toBitVector.take(132) mustEqual string_striker_projectile.toBitVector.take(132) pkt.toBitVector.drop(133).take(7) mustEqual string_striker_projectile.toBitVector.drop(133).take(7) From f1c73688f73799bcc86489073d0a6c76985a6ba1 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 14 May 2019 16:20:24 -0400 Subject: [PATCH 03/12] removed fire mode override for the Phoenix; added events for loading and manipulation of remote projectiles to AvatarService; ProjectileStateMessage handles projectile data in a simple way; remote projectiles can now be registered and unregistered --- .../psforever/objects/GlobalDefinitions.scala | 5 +- .../packet/game/ProjectileStateMessage.scala | 63 ++++++---- .../scala/services/avatar/AvatarService.scala | 10 ++ .../avatar/AvatarServiceMessage.scala | 8 +- .../avatar/AvatarServiceResponse.scala | 6 + .../game/ProjectileStateMessageTest.scala | 27 ++--- .../src/main/scala/WorldSessionActor.scala | 108 +++++++++++++++++- 7 files changed, 181 insertions(+), 46 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index acaf71e6..a038eede 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.GlobalDefinitions.sparrow_projectile import net.psforever.objects.ballistics.Projectiles import net.psforever.objects.ce.{DeployableCategory, DeployedItem} import net.psforever.objects.definition._ @@ -641,8 +640,8 @@ object GlobalDefinitions { } val hunterseeker = new ToolDefinition(ObjectClass.hunterseeker) { - override def NextFireModeIndex(index : Int) : Int = index - DefaultFireModeIndex = 1 +// override def NextFireModeIndex(index : Int) : Int = index +// DefaultFireModeIndex = 1 } //phoenix val lancer = ToolDefinition(ObjectClass.lancer) diff --git a/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala b/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala index 329d6742..4f949b66 100644 --- a/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala @@ -2,48 +2,56 @@ package net.psforever.packet.game import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} -import net.psforever.types.Vector3 +import net.psforever.types.{Angular, Vector3} import scodec.Codec import scodec.codecs._ +import shapeless.{::, HNil} /** - * Dispatched to deliberately render certain projectiles of a weapon on other players' clients.
+ * Dispatched to deliberately control certain projectiles of a weapon on other players' clients.
*
- * This packet is generated by firing specific weapons in specific fire modes. + * This packet should be generated by firing specific weapons in specific fire modes. * For example, the Phoenix (`hunterseeker`) discharged in its primary fire mode generates this packet; * but, the Phoenix in secondary fire mode does not. * The Striker (`striker`) discharged in its primary fire mode generates this packet; * but, the Striker in secondary fire mode does not. * The chosen fire mode(s) are not a straight-fire projectile but one that has special control asserted over it. - * For the Phoenix, it is user-operated. - * For the Striker, it tracks towards a target while the weapon's reticle hovers over that target.
+ * For the Phoenix, it is user operated (camera-guided). + * For the Striker, it tracks towards a valid target while the weapon's reticle hovers over that target.
*
* This packet will continue to be dispatched by the client for as long as the projectile being tracked is in the air. * All projectiles have a maximum lifespan before they will lose control and either despawn and/or explode. * This number is tracked in the packet for simplicity. * If the projectile strikes a valid target, the count will jump to a significantly enormous value beyond its normal lifespan. * This ensures that the projectile - locally and the shared model - will despawn. - * @param projectile_guid the projectile + *
+ * This control can not be exerted until that projectile is physically constructed on the other clients + * in the same way that a player or a vehicle is constructed. + * A projectile that exhibits intentional construction behavior is flagged using the property `exists_on_remote_client`. + * The model comes with a number of caveats, + * some that originate from the object construction process itself, + * but also some from this packet. + * For example, + * as indicated by the static `shot_orient` values reported by this packet. + * a discharged controlled projectile will not normally rotate. + * A minor loss of lifespan may be levied. + * @see `ProjectileDefinition` + * @see `TrackedProjectileData` + * @param projectile_guid the client-specific local unique identifier of the projectile; + * this is __not__ the global unique identifier for the synchronized projectile object * @param shot_pos the position of the projectile * @param shot_vel the velocity of the projectile - * @param unk1 na; - * usually 0 - * @param unk2 na; - * will remain consistent for the lifespan of a given projectile in most cases - * @param unk3 na; - * will remain consistent for the lifespan of a given projectile in most cases - * @param unk4 na; - * usually false + * @param shot_orient the orientation of the projectile + * @param unk na; + * usually `false` * @param time_alive how long the projectile has been in the air; * often expressed in multiples of 2 */ final case class ProjectileStateMessage(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, - unk1 : Int, - unk2 : Int, - unk3 : Int, - unk4 : Boolean, + shot_orient : Vector3, + unk : Boolean, time_alive : Int) extends PlanetSideGamePacket { type Packet = ProjectileStateMessage @@ -56,10 +64,19 @@ object ProjectileStateMessage extends Marshallable[ProjectileStateMessage] { ("projectile_guid" | PlanetSideGUID.codec) :: ("shot_pos" | Vector3.codec_pos) :: ("shot_vel" | Vector3.codec_float) :: - ("unk1" | uint8L) :: - ("unk2" | uint8L) :: - ("unk3" | uint8L) :: - ("unk4" | bool) :: + ("roll" | Angular.codec_roll) :: + ("pitch" | Angular.codec_pitch) :: + ("yaw" | Angular.codec_yaw()) :: + ("unk" | bool) :: ("time_alive" | uint16L) - ).as[ProjectileStateMessage] + ).xmap[ProjectileStateMessage] ( + { + case guid :: pos :: vel :: roll :: pitch :: yaw :: unk :: time :: HNil => + ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), unk, time) + }, + { + case ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), unk, time) => + guid :: pos :: vel :: roll :: pitch :: yaw :: unk :: time :: HNil + } + ) } diff --git a/common/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala index 4249a0d7..0fdca89c 100644 --- a/common/src/main/scala/services/avatar/AvatarService.scala +++ b/common/src/main/scala/services/avatar/AvatarService.scala @@ -127,6 +127,12 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(pkt)) ) + case AvatarAction.LoadProjectile(player_guid, object_id, obj, cdata) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer( + ObjectCreateMessage(object_id, obj.GUID, cdata) + )) + ) case AvatarAction.ObjectDelete(player_guid, item_guid, unk) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ObjectDelete(item_guid, unk)) @@ -151,6 +157,10 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(pos, vel, yaw, pitch, yaw_upper, seq_time, is_crouching, is_jumping, jump_thrust, is_cloaking, spectating, weaponInHand)) ) + case AvatarAction.ProjectileState(player_guid, projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive)) + ) case AvatarAction.PickupItem(player_guid, zone, target, slot, item, unk) => janitor forward RemoverActor.ClearSpecific(List(item), zone) AvatarEvents.publish( diff --git a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala index 636d56de..5035ceba 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala @@ -1,8 +1,12 @@ // Copyright (c) 2017 PSForever package services.avatar +<<<<<<< fa7365e8af5d0b21c3934ed745895edd46f741a6:common/src/main/scala/services/avatar/AvatarServiceMessage.scala import net.psforever.objects.{PlanetSideGameObject, Player} import net.psforever.objects.ballistics.SourceEntry +======= +import net.psforever.objects.ballistics.{Projectile, SourceEntry} +>>>>>>> removed fire mode override for the Phoenix; added events for loading and manipulation of remote projectiles to AvatarService; ProjectileStateMessage handles projectile data in a simple way; remote projectiles can now be registered and unregistered:common/src/main/scala/services/avatar/AvatarAction.scala import net.psforever.objects.ce.Deployable import net.psforever.objects.equipment.Equipment import net.psforever.objects.inventory.Container @@ -30,7 +34,7 @@ object AvatarAction { final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action - final case class EnvironmentalDamage(player_guid : PlanetSideGUID, amont: Int) extends Action + final case class EnvironmentalDamage(player_guid : PlanetSideGUID, amount: Int) extends Action final case class Damage(player_guid : PlanetSideGUID, target : Player, resolution_function : Any=>Unit) extends Action final case class DeployItem(player_guid : PlanetSideGUID, item : PlanetSideGameObject with Deployable) extends Action final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Action @@ -40,6 +44,7 @@ object AvatarAction { final case class HitHint(source_guid : PlanetSideGUID, player_guid : PlanetSideGUID) extends Action final case class KilledWhileInVehicle(player_guid : PlanetSideGUID) extends Action final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action + final case class LoadProjectile(player_guid : PlanetSideGUID, object_id : Int, obj : Projectile, cdata : ConstructorData) extends Action final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action final case class ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action @@ -47,6 +52,7 @@ object AvatarAction { final case class PlanetsideAttributeSelf(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action final case class PlayerState(player_guid : PlanetSideGUID, pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Action final case class PickupItem(player_guid : PlanetSideGUID, zone : Zone, target : PlanetSideGameObject with Container, slot : Int, item : Equipment, unk : Int = 0) extends Action + final case class ProjectileState(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk : Boolean, time_alive : Int) extends Action final case class PutDownFDU(player_guid : PlanetSideGUID) extends Action final case class Release(player : Player, zone : Zone, time : Option[FiniteDuration] = None) extends Action final case class Revive(target_guid: PlanetSideGUID) extends Action diff --git a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala index 40481db8..d009c160 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala @@ -33,12 +33,18 @@ object AvatarResponse { final case class HitHint(source_guid : PlanetSideGUID) extends Response final case class KilledWhileInVehicle() extends Response final case class LoadPlayer(pkt : ObjectCreateMessage) extends Response + final case class LoadProjectile(pkt : ObjectCreateMessage) extends Response final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response final case class ObjectHeld(slot : Int) extends Response final case class PlanetsideAttribute(attribute_type : Int, attribute_value : Long) extends Response +<<<<<<< fa7365e8af5d0b21c3934ed745895edd46f741a6:common/src/main/scala/services/avatar/AvatarServiceResponse.scala final case class PlanetsideAttributeToAll(attribute_type : Int, attribute_value : Long) extends Response final case class PlanetsideAttributeSelf(attribute_type : Int, attribute_value : Long) extends Response final case class PlayerState(pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Response +======= + final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response + final case class ProjectileState(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk : Boolean, time_alive : Int) extends Response +>>>>>>> removed fire mode override for the Phoenix; added events for loading and manipulation of remote projectiles to AvatarService; ProjectileStateMessage handles projectile data in a simple way; remote projectiles can now be registered and unregistered:common/src/main/scala/services/avatar/AvatarResponse.scala final case class PutDownFDU(target_guid : PlanetSideGUID) extends Response final case class Release(player : Player) extends Response final case class Reload(weapon_guid : PlanetSideGUID) extends Response diff --git a/common/src/test/scala/game/ProjectileStateMessageTest.scala b/common/src/test/scala/game/ProjectileStateMessageTest.scala index f62e181e..80891f88 100644 --- a/common/src/test/scala/game/ProjectileStateMessageTest.scala +++ b/common/src/test/scala/game/ProjectileStateMessageTest.scala @@ -12,18 +12,12 @@ class ProjectileStateMessageTest extends Specification { "decode" in { PacketCoding.DecodePacket(string).require match { - case ProjectileStateMessage(projectile, pos, vel, unk1, unk2, unk3, unk4, time_alive) => + case ProjectileStateMessage(projectile, pos, vel, orient, unk, time_alive) => projectile mustEqual PlanetSideGUID(40229) - pos.x mustEqual 4611.539f - pos.y mustEqual 5576.375f - pos.z mustEqual 82.328125f - vel.x mustEqual 18.64686f - vel.y mustEqual -33.43247f - vel.z mustEqual 11.599553f - unk1 mustEqual 0 - unk2 mustEqual 248 - unk3 mustEqual 236 - unk4 mustEqual false + pos mustEqual Vector3(4611.539f, 5576.375f, 82.328125f) + vel mustEqual Vector3(18.64686f, -33.43247f, 11.599553f) + orient mustEqual Vector3(0, 22.5f, 56.25f) + unk mustEqual false time_alive mustEqual 4 case _ => ko @@ -35,10 +29,17 @@ class ProjectileStateMessageTest extends Specification { PlanetSideGUID(40229), Vector3(4611.539f, 5576.375f, 82.328125f), Vector3(18.64686f, -33.43247f, 11.599553f), - 0, 248, 236, false, 4 + Vector3(0, 22.5f, 56.25f), + false, + 4 ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector - pkt mustEqual string + //pkt mustEqual string + val pkt_bits = pkt.toBitVector + val str_bits = string.toBitVector + pkt_bits.take(184) mustEqual str_bits.take(184) //skip 1 bit + pkt_bits.drop(185).take(7) mustEqual str_bits.drop(185).take(7) //skip 1 bit + pkt_bits.drop(193) mustEqual str_bits.drop(193) } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 2601b6cd..fa402f3f 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1402,6 +1402,11 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(pkt) } + case AvatarResponse.LoadProjectile(pkt) => + if(tplayer_guid != guid) { + sendResponse(pkt) + } + case AvatarResponse.ObjectDelete(item_guid, unk) => if(tplayer_guid != guid) { sendResponse(ObjectDeleteMessage(item_guid, unk)) @@ -1463,6 +1468,11 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) => + if(tplayer_guid != guid) { + sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive)) + } + case AvatarResponse.PutDownFDU(target) => if(tplayer_guid != guid) { sendResponse(GenericObjectActionMessage(target, 212)) @@ -3989,8 +3999,22 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) => //log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2") - case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vector, unk1, unk2, unk3, unk4, time_alive) => - //log.info("ProjectileState: " + msg) + case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) => + log.info(s"ProjectileState: $msg") + projectiles + .collect { + case Some(projectile) if projectile.HasGUID => + projectile + } + .find(_.GUID == projectile_guid) match { + case Some(projectile) => + projectile.Position = shot_pos + projectile.Orientation = shot_orient + projectile.Velocity = shot_vel + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive)) + case None => + log.info(s"ProjectileState: the projectile GUID#${projectile_guid.guid} can not be found") + } case msg @ ReleaseAvatarRequestMessage() => log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") @@ -5385,7 +5409,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case msg @ WeaponFireMessage(seq_time, weapon_guid, projectile_guid, shot_origin, unk1, unk2, unk3, unk4, unk5, unk6, unk7) => - log.info("WeaponFire: " + msg) + log.info(s"WeaponFire: $msg") FindContainedWeapon match { case (Some(obj), Some(tool : Tool)) => if(tool.Magazine <= 0) { //safety: enforce ammunition depletion @@ -5405,7 +5429,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } prefire = shooting.orElse(Some(weapon_guid)) - tool.Discharge + tool.Discharge //always val projectileIndex = projectile_guid.guid - Projectile.BaseUID val projectilePlace = projectiles(projectileIndex) if(projectilePlace match { @@ -5426,8 +5450,12 @@ class WorldSessionActor extends Actor with MDCContextAware { } val distanceToOwner = Vector3.DistanceSquared(shot_origin, player.Position) if(distanceToOwner <= acceptableDistanceToOwner) { - projectiles(projectileIndex) = - Some(Projectile(tool.Projectile, tool.Definition, tool.FireMode, player, attribution, shot_origin, angle)) + val projectile_info = tool.Projectile + val projectile = Projectile(projectile_info, tool.Definition, tool.FireMode, player, attribution, shot_origin, angle) + projectiles(projectileIndex) = Some(projectile) + if(projectile_info.ExistsOnRemoteClients) { + taskResolver ! ReregisterProjectile(projectile) + } } else { log.warn(s"WeaponFireMessage: $player's ${tool.Definition.Name} projectile is too far from owner position at time of discharge ($distanceToOwner > $acceptableDistanceToOwner); suspect") @@ -6049,6 +6077,34 @@ class WorldSessionActor extends Actor with MDCContextAware { }, List(GUIDTask.RegisterAvatar(driver)(continent.GUID), GUIDTask.RegisterVehicle(obj)(continent.GUID))) } + def RegisterProjectile(obj : Projectile) : TaskResolver.GiveTask = { + val definition = obj.Definition + TaskResolver.GiveTask( + new Task() { + private val globalProjectile = obj + private val localAnnounce = avatarService + private val localMsg = AvatarServiceMessage( + continent.Id, + AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, obj, definition.Packet.ConstructorData(obj).get) + ) + + override def isComplete : Task.Resolution.Value = { + if(globalProjectile.HasGUID) { + Task.Resolution.Success + } + else { + Task.Resolution.Incomplete + } + } + + def Execute(resolver : ActorRef) : Unit = { + localAnnounce ! localMsg + resolver ! scala.util.Success(this) + } + }, List(GUIDTask.RegisterObjectTask(obj)(continent.GUID)) + ) + } + def UnregisterDrivenVehicle(obj : Vehicle, driver : Player) : TaskResolver.GiveTask = { TaskResolver.GiveTask( new Task() { @@ -6070,6 +6126,30 @@ class WorldSessionActor extends Actor with MDCContextAware { }, List(GUIDTask.UnregisterAvatar(driver)(continent.GUID), GUIDTask.UnregisterVehicle(obj)(continent.GUID))) } + def UnregisterProjectile(obj : Projectile) : TaskResolver.GiveTask = { + TaskResolver.GiveTask( + new Task() { + private val globalProjectile = obj + private val localAnnounce = avatarService + private val localMsg = AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, obj.GUID)) + + override def isComplete : Task.Resolution.Value = { + if(!globalProjectile.HasGUID) { + Task.Resolution.Success + } + else { + Task.Resolution.Incomplete + } + } + + def Execute(resolver : ActorRef) : Unit = { + localAnnounce ! localMsg + resolver ! scala.util.Success(this) + } + }, List(GUIDTask.UnregisterObjectTask(obj)(continent.GUID)) + ) + } + /** * Construct tasking that removes the `Equipment` to `target`. * @param target what object that contains the `Equipment` @@ -6112,6 +6192,22 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } + def ReregisterProjectile(obj : Projectile) : TaskResolver.GiveTask = { + val reg = RegisterProjectile(obj) + if(obj.HasGUID) { + TaskResolver.GiveTask( + reg.task, + List(TaskResolver.GiveTask( + reg.subs(0).task, + List(UnregisterProjectile(obj)) + )) + ) + } + else { + reg + } + } + /** * After some subtasking is completed, draw a particular slot, as if an `ObjectHeldMessage` packet was sent/received.
*
From 8952ab86f60126d65532550183b5d00a80b3142d Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 17 May 2019 01:54:27 -0400 Subject: [PATCH 04/12] events for future expansion; added custom projectile remote client data for OCM packet creation; initial projectile velocity calculation; (too many) details when it comes to Enumerations for projectiles; hooked up theoretical projectile creatiuon, management, and deletion --- .../psforever/objects/GlobalDefinitions.scala | 20 ++- .../objects/ballistics/Projectile.scala | 9 ++ .../definition/ProjectileDefinition.scala | 8 ++ .../converter/ProjectileConverter.scala | 10 +- .../objectcreate/TrackedProjectileData.scala | 77 +++++++--- .../scala/services/avatar/AvatarService.scala | 6 +- .../avatar/AvatarServiceMessage.scala | 1 + .../avatar/AvatarServiceResponse.scala | 1 + .../TrackedProjectileDataTest.scala | 56 +++++++- .../src/main/scala/WorldSessionActor.scala | 135 +++++++++++++----- 10 files changed, 258 insertions(+), 65 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index a038eede..ab8f93d2 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -630,9 +630,7 @@ object GlobalDefinitions { val maelstrom = ToolDefinition(ObjectClass.maelstrom) - val phoenix = new ToolDefinition(ObjectClass.phoenix) { - override def NextFireModeIndex(index : Int) : Int = index - } //decimator + val phoenix = ToolDefinition(ObjectClass.phoenix) //decimator val striker = new ToolDefinition(ObjectClass.striker) { override def NextFireModeIndex(index : Int) : Int = index @@ -640,8 +638,8 @@ object GlobalDefinitions { } val hunterseeker = new ToolDefinition(ObjectClass.hunterseeker) { -// override def NextFireModeIndex(index : Int) : Int = index -// DefaultFireModeIndex = 1 + override def NextFireModeIndex(index : Int) : Int = index + DefaultFireModeIndex = 1 } //phoenix val lancer = ToolDefinition(ObjectClass.lancer) @@ -2221,6 +2219,7 @@ object GlobalDefinitions { aphelion_starfire_projectile.Lifespan = 7f aphelion_starfire_projectile.ProjectileDamageType = DamageType.Aggravated aphelion_starfire_projectile.ExistsOnRemoteClients = true + aphelion_starfire_projectile.RemoteClientData = (39577, 249) //starfire_projectile data aphelion_starfire_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(aphelion_starfire_projectile) @@ -2699,6 +2698,7 @@ object GlobalDefinitions { hunter_seeker_missile_projectile.InitialVelocity = 40 hunter_seeker_missile_projectile.Lifespan = 6.3f hunter_seeker_missile_projectile.ExistsOnRemoteClients = true + hunter_seeker_missile_projectile.RemoteClientData = (39577, 201) hunter_seeker_missile_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(hunter_seeker_missile_projectile) @@ -2986,6 +2986,7 @@ object GlobalDefinitions { oicw_projectile.InitialVelocity = 5 oicw_projectile.Lifespan = 6.1f oicw_projectile.ExistsOnRemoteClients = true + oicw_projectile.RemoteClientData = (13107, 195) oicw_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(oicw_projectile) @@ -2997,7 +2998,7 @@ object GlobalDefinitions { oicw_little_buddy.ProjectileDamageType = DamageType.Splash oicw_little_buddy.InitialVelocity = 40 oicw_little_buddy.Lifespan = 0.5f - oicw_little_buddy.ExistsOnRemoteClients = true + oicw_little_buddy.ExistsOnRemoteClients = false //TODO true oicw_little_buddy.Packet = projectileConverter //add_property oicw_little_buddy multi_stage_spawn_server_side true ... ProjectileDefinition.CalculateDerivedFields(oicw_little_buddy) @@ -3080,6 +3081,7 @@ object GlobalDefinitions { peregrine_sparrow_projectile.InitialVelocity = 45 peregrine_sparrow_projectile.Lifespan = 7.5f peregrine_sparrow_projectile.ExistsOnRemoteClients = true + peregrine_sparrow_projectile.RemoteClientData = (13107, 187) //sparrow_projectile data peregrine_sparrow_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(peregrine_sparrow_projectile) @@ -3133,6 +3135,7 @@ object GlobalDefinitions { phoenix_missile_guided_projectile.InitialVelocity = 0 phoenix_missile_guided_projectile.Lifespan = 3f phoenix_missile_guided_projectile.ExistsOnRemoteClients = true + phoenix_missile_guided_projectile.RemoteClientData = (39577, 201) //hunter_seeker_missile_projectile data phoenix_missile_guided_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(phoenix_missile_guided_projectile) @@ -3411,6 +3414,7 @@ object GlobalDefinitions { sparrow_projectile.InitialVelocity = 60 sparrow_projectile.Lifespan = 5.85f sparrow_projectile.ExistsOnRemoteClients = true + sparrow_projectile.RemoteClientData = (13107, 187) sparrow_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(sparrow_projectile) @@ -3427,6 +3431,7 @@ object GlobalDefinitions { sparrow_secondary_projectile.InitialVelocity = 60 sparrow_secondary_projectile.Lifespan = 5.85f sparrow_secondary_projectile.ExistsOnRemoteClients = true + sparrow_secondary_projectile.RemoteClientData = (13107, 187) sparrow_secondary_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(sparrow_secondary_projectile) @@ -3479,6 +3484,7 @@ object GlobalDefinitions { starfire_projectile.InitialVelocity = 45 starfire_projectile.Lifespan = 7.8f starfire_projectile.ExistsOnRemoteClients = true + starfire_projectile.RemoteClientData = (39577, 249) starfire_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(starfire_projectile) @@ -3512,6 +3518,7 @@ object GlobalDefinitions { striker_missile_targeting_projectile.InitialVelocity = 30 striker_missile_targeting_projectile.Lifespan = 4.2f striker_missile_targeting_projectile.ExistsOnRemoteClients = true + striker_missile_targeting_projectile.RemoteClientData = (26214, 134) striker_missile_targeting_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(striker_missile_targeting_projectile) @@ -3601,6 +3608,7 @@ object GlobalDefinitions { wasp_rocket_projectile.InitialVelocity = 60 wasp_rocket_projectile.Lifespan = 6.5f wasp_rocket_projectile.ExistsOnRemoteClients = true + wasp_rocket_projectile.RemoteClientData = (0, 208) wasp_rocket_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(wasp_rocket_projectile) diff --git a/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index 136c20bd..1db6d3e4 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -36,6 +36,15 @@ final case class Projectile(profile : ProjectileDefinition, shot_origin : Vector3, shot_angle : Vector3, fire_time: Long = System.nanoTime) extends PlanetSideGameObject { + Position = shot_origin + Orientation = shot_angle + Velocity = { + val initVel : Int = profile.InitialVelocity //initial velocity + val radAngle : Double = math.toRadians(shot_angle.y) //angle of elevation + val rise : Float = initVel * math.sin(radAngle).toFloat //z + val ground : Float = initVel * math.cos(radAngle).toFloat //base + Vector3.Rz(Vector3(0, -ground, 0), shot_angle.z) + Vector3.z(rise) + } /** Information about the current world coordinates and orientation of the projectile */ val current : SimpleWorldEntity = new SimpleWorldEntity() private var resolved : ProjectileResolution.Value = ProjectileResolution.Unresolved diff --git a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index 0d01ec43..9d221d63 100644 --- a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -24,6 +24,7 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) private var damageRadius : Float = 1f private var useDamage1Subtract : Boolean = false private var existsOnRemoteClients : Boolean = false //`true` spawns a server-managed object + private var remoteClientData : (Int, Int) = (0, 0) //artificial values; for ObjectCreateMessage packet (oicw_little_buddy is undefined) //derived calculations private var distanceMax : Float = 0f private var distanceFromAcceleration : Float = 0f @@ -117,6 +118,13 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) ExistsOnRemoteClients } + def RemoteClientData : (Int, Int) = remoteClientData + + def RemoteClientData_=(remoteClientData : (Int, Int)) : (Int, Int) = { + this.remoteClientData = remoteClientData + RemoteClientData + } + def DistanceMax : Float = distanceMax //accessor only def DistanceFromAcceleration : Float = distanceFromAcceleration //accessor only diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala index a1534461..fb60a576 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala @@ -15,7 +15,7 @@ class ProjectileConverter extends ObjectCreateConverter[Projectile]() { PlacementData( obj.Position, obj.Orientation, - obj.Velocity + None ), CommonFieldData( obj.owner.Faction, @@ -29,11 +29,11 @@ class ProjectileConverter extends ObjectCreateConverter[Projectile]() { PlanetSideGUID(0) ) ), + obj.profile.RemoteClientData._1, + obj.profile.RemoteClientData._2, + FlightPhysics.State4, 0, - 0, - FlightPhysics.State3, - 7, - 2 + 0 ) ) } diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala index a70e2845..2a659762 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala @@ -2,10 +2,56 @@ package net.psforever.packet.game.objectcreate import net.psforever.packet.{Marshallable, PacketHelpers} -import scodec.{Attempt, Codec, Err} +import scodec.{Attempt, Codec} import scodec.codecs._ import shapeless.{::, HNil} +object TrackedProjectile extends Enumeration { + type Type = Value + + val OICWLittleBuddy = Value(-1) //?, ? + val Meteor = Value(32) //0, 32 + val Wasp = Value(208) //0, 208 + val Sparrow = Value(3355579) //13107, 187 + val OICW = Value(3355587) //13107, 195 + val Striker = Value(6710918) //26214, 134 + val HunterSeeker = Value(10131913) //39577, 201 + val Starfire = Value(10131961) //39577, 249 + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint24) +} + +object TrackedProjectiles { + abstract class Data(val is : TrackedProjectile.Value, val a : Int, val b : Int) + + final case object Meteor extends Data(TrackedProjectile.Meteor, 0, 32) + final case object Wasp extends Data(TrackedProjectile.Wasp, 0, 208) + final case object Sparrow extends Data(TrackedProjectile.Sparrow, 13107, 187) + final case object OICW extends Data(TrackedProjectile.OICW, 13107, 195) + final case object Striker extends Data(TrackedProjectile.Striker, 26214, 134) + final case object HunterSeeker extends Data(TrackedProjectile.HunterSeeker, 39577, 201) + final case object Starfire extends Data(TrackedProjectile.Starfire, 39577, 249) + class OICWLittleBuddy(x : Int, y : Int) extends Data(TrackedProjectile.OICWLittleBuddy, x, y) + + val values: Seq[TrackedProjectiles.Data] = Seq(Meteor, Wasp, Sparrow, OICW, Striker, HunterSeeker, Starfire) + + def apply(x : Int, y : Int) : TrackedProjectiles.Data = { + values.find(p => p.a == x && p.b == y) match { + case Some(projectileData) => projectileData + case None => + throw new IllegalArgumentException("no combination of projectile data equates to a defined projectile type") + } + } + + def apply(is : TrackedProjectile.Value) : TrackedProjectiles.Data = { + values.find(p => p.is == is) match { + case Some(projectileData) => projectileData + case None => + throw new IllegalArgumentException("unknown projectile type") + } + } +} + object FlightPhysics extends Enumeration { type Type = Value @@ -27,41 +73,38 @@ object FlightPhysics extends Enumeration { /** * A representation of a projectile that the server must intentionally convey to players other than the shooter. - * @param data common game object information - * @param unk2 na - * @param unit_distance_limit how quickly the projectile travels before naturally being destroyed - * `FlightPhysics` needs to be + * @param common_data common game object information * @param unk3 na */ -final case class TrackedProjectileData(data : CommonFieldDataWithPlacement, - unk2 : Int, - unit_distance_limit : Int, +final case class TrackedProjectileData(common_data : CommonFieldDataWithPlacement, + u1 : Int, + u2 : Int, unk3 : FlightPhysics.Value, unk4 : Int, unk5 : Int ) extends ConstructorData { - override def bitsize : Long = 33L + data.bitsize + override def bitsize : Long = 33L + common_data.bitsize } object TrackedProjectileData extends Marshallable[TrackedProjectileData] { implicit val codec : Codec[TrackedProjectileData] = ( ("data" | CommonFieldDataWithPlacement.codec) :: - ("unk2" | uint16) :: - ("unit_distance_limit" | uint8) :: + ("u1" | uint16) :: + ("u2" | uint8) :: ("unk3" | FlightPhysics.codec) :: ("unk4" | uint(3)) :: ("unk5" | uint2) ).exmap[TrackedProjectileData] ( { - case data :: unk2 :: lim :: unk3 :: unk4 :: unk5 :: HNil => - Attempt.successful(TrackedProjectileData(data, unk2, lim, unk3, unk4, unk5)) + case data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil => + Attempt.successful(TrackedProjectileData(data, u1, u2, unk3, unk4, unk5)) - case data => - Attempt.failure(Err(s"invalid projectile data format - $data")) +// case data => +// Attempt.failure(Err(s"invalid projectile data format - $data")) }, { - case TrackedProjectileData(data, unk2, lim, unk3, unk4, unk5) => - Attempt.successful(data :: unk2 :: lim :: unk3 :: unk4 :: unk5 :: HNil) + case TrackedProjectileData(data, u1, u2, unk3, unk4, unk5) => + Attempt.successful(data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil) } ) } diff --git a/common/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala index 0fdca89c..56af1513 100644 --- a/common/src/main/scala/services/avatar/AvatarService.scala +++ b/common/src/main/scala/services/avatar/AvatarService.scala @@ -109,6 +109,10 @@ class AvatarService extends Actor { AvatarResponse.EquipmentInHand(ObjectCreateMessage(definition.ObjectId, item.GUID, containerData, objectData)) ) ) + case AvatarAction.GenericObjectAction(player_guid, object_guid, action_code) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.GenericObjectAction(object_guid, action_code)) + ) case AvatarAction.HitHint(source_guid, player_guid) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.HitHint(source_guid)) @@ -129,7 +133,7 @@ class AvatarService extends Actor { ) case AvatarAction.LoadProjectile(player_guid, object_id, obj, cdata) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadProjectile( ObjectCreateMessage(object_id, obj.GUID, cdata) )) ) diff --git a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala index 5035ceba..2d8ca712 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala @@ -41,6 +41,7 @@ object AvatarAction { final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) extends Action final case class DropItem(player_guid : PlanetSideGUID, item : Equipment, zone : Zone) extends Action final case class EquipmentInHand(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action + final case class GenericObjectAction(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, action_code : Int) extends Action final case class HitHint(source_guid : PlanetSideGUID, player_guid : PlanetSideGUID) extends Action final case class KilledWhileInVehicle(player_guid : PlanetSideGUID) extends Action final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action diff --git a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala index d009c160..2b163547 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala @@ -30,6 +30,7 @@ object AvatarResponse { final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int) extends Response final case class DropItem(pkt : ObjectCreateMessage) extends Response final case class EquipmentInHand(pkt : ObjectCreateMessage) extends Response + final case class GenericObjectAction(object_guid : PlanetSideGUID, action_code : Int) extends Response final case class HitHint(source_guid : PlanetSideGUID) extends Response final case class KilledWhileInVehicle() extends Response final case class LoadPlayer(pkt : ObjectCreateMessage) extends Response diff --git a/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala b/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala index 586345bd..d1d4d9c1 100644 --- a/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala +++ b/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala @@ -10,9 +10,10 @@ import scodec.bits._ class TrackedProjectileDataTest extends Specification { val string_striker_projectile = hex"17 C5000000 A4B 009D 4C129 0CB0A 9814 00 F5 E3 040000666686400" + val string_hunter_seeker_missile_projectile = hex"17 c5000000 ca9 ab9e af127 ec465 3723 00 15 c4 2400009a99c9400" "TrackedProjectileData" should { - "decode" in { + "decode (striker_missile_targeting_projectile)" in { PacketCoding.DecodePacket(string_striker_projectile).require match { case ObjectCreateMessage(len, cls, guid, parent, data) => len mustEqual 197 @@ -46,7 +47,41 @@ class TrackedProjectileDataTest extends Specification { } } - "encode" in { + "decode (hunter_seeker_missile_projectile)" in { + PacketCoding.DecodePacket(string_hunter_seeker_missile_projectile).require match { + case ObjectCreateMessage(len, cls, guid, parent, data) => + len mustEqual 197 + cls mustEqual ObjectClass.hunter_seeker_missile_projectile + guid mustEqual PlanetSideGUID(40619) + parent.isDefined mustEqual false + data match { + case TrackedProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, lim, unk3, unk4, unk5) => + pos.coord mustEqual Vector3(3621.3672f, 2701.8438f, 140.85938f) + pos.orient mustEqual Vector3(0, 300.9375f, 258.75f) + deploy.faction mustEqual PlanetSideEmpire.NC + deploy.bops mustEqual false + deploy.alternate mustEqual false + deploy.v1 mustEqual true + deploy.v2.isEmpty mustEqual true + deploy.v3 mustEqual false + deploy.v4.isEmpty mustEqual true + deploy.v5.isEmpty mustEqual true + deploy.guid mustEqual PlanetSideGUID(0) + + unk2 mustEqual 39577 + lim mustEqual 201 + unk3 mustEqual FlightPhysics.State4 + unk4 mustEqual 0 + unk5 mustEqual 0 + case _ => + ko + } + case _ => + ko + } + } + + "encode (striker_missile_targeting_projectile)" in { val obj = TrackedProjectileData( CommonFieldDataWithPlacement( PlacementData(4644.5938f, 5472.0938f, 82.375f, 0f, 30.9375f, 171.5625f), @@ -63,4 +98,21 @@ class TrackedProjectileDataTest extends Specification { pkt.toBitVector.drop(141) mustEqual string_striker_projectile.toBitVector.drop(141) } } + + "encode (hunter_seeker_missile_projectile)" in { + val obj = TrackedProjectileData( + CommonFieldDataWithPlacement( + PlacementData(3621.3672f, 2701.8438f, 140.85938f, 0, 300.9375f, 258.75f), + CommonFieldData(PlanetSideEmpire.NC, false, false, true, None, false, None, None, PlanetSideGUID(0)) + ), + 39577, 201, FlightPhysics.State4, 0, 0 + ) + val msg = ObjectCreateMessage(ObjectClass.hunter_seeker_missile_projectile, PlanetSideGUID(40619), obj) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + //pkt mustEqual string_hunter_seeker_missile_projectile + + pkt.toBitVector.take(132) mustEqual string_hunter_seeker_missile_projectile.toBitVector.take(132) + pkt.toBitVector.drop(133).take(7) mustEqual string_hunter_seeker_missile_projectile.toBitVector.drop(133).take(7) + pkt.toBitVector.drop(141) mustEqual string_hunter_seeker_missile_projectile.toBitVector.drop(141) + } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index fa402f3f..94b47aa5 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1364,6 +1364,11 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(pkt) } + case AvatarResponse.GenericObjectAction(object_guid, action_code) => + if(tplayer_guid != guid) { + sendResponse(GenericObjectActionMessage(object_guid, action_code)) + } + case AvatarResponse.HitHint(source_guid) => if(player.isAlive) { sendResponse(HitHint(source_guid, guid)) @@ -3453,6 +3458,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //all head features, faction, and sex also match that test character import net.psforever.objects.GlobalDefinitions._ import net.psforever.types.CertificationType._ + val faction = PlanetSideEmpire.VS val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1) avatar.Certifications += StandardAssault @@ -3893,6 +3899,27 @@ class WorldSessionActor extends Actor with MDCContextAware { avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) } + if(!player.Crouching && is_crouching) { + sendResponse( + ObjectCreateMessage( + 405, + PlanetSideGUID(40288), + TrackedProjectileData( + CommonFieldDataWithPlacement( + PlacementData(Vector3(3561.0f, 2854.0f, 92.859375f), Vector3(0f, 348.75f, 267.1875f), None), + CommonFieldData(PlanetSideEmpire.NC, false, false, true, None, false, None, None, PlanetSideGUID(0)) + ), + 39577, 201, FlightPhysics.State4, 0, 0 + ) + ) + ) + } + else if(player.Crouching && !is_crouching) { + sendResponse( + ObjectDeleteMessage(PlanetSideGUID(40288), 2) + //ProjectileStateMessage(PlanetSideGUID(40288),Vector3(3561.0f, 2854.0f, 92.859375f),Vector3(-38.814934f,-2.578959f,-9.313708f),Vector3(0,348.75f,267.1875f),false,0) + ) + } player.Position = pos player.Velocity = vel player.Orientation = Vector3(player.Orientation.x, pitch, yaw) @@ -4001,19 +4028,16 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) => log.info(s"ProjectileState: $msg") - projectiles - .collect { - case Some(projectile) if projectile.HasGUID => - projectile - } - .find(_.GUID == projectile_guid) match { - case Some(projectile) => + projectiles(projectile_guid.guid - Projectile.BaseUID) match { + case Some(projectile) if projectile.HasGUID => + val projectileGlobalUID = projectile.GUID + log.info(s"ProjectileState: mapped local uid ${projectile_guid.guid} to global uid ${projectileGlobalUID.guid}; updating object ...") projectile.Position = shot_pos projectile.Orientation = shot_orient projectile.Velocity = shot_vel - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive)) - case None => - log.info(s"ProjectileState: the projectile GUID#${projectile_guid.guid} can not be found") + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectile.GUID, shot_pos, shot_vel, shot_orient, unk, time_alive)) + case _ => + log.error(s"ProjectileState: the projectile@${projectile_guid.guid} can not be found") } case msg @ ReleaseAvatarRequestMessage() => @@ -5454,6 +5478,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val projectile = Projectile(projectile_info, tool.Definition, tool.FireMode, player, attribution, shot_origin, angle) projectiles(projectileIndex) = Some(projectile) if(projectile_info.ExistsOnRemoteClients) { + log.trace(s"WeaponFireMessage: ${projectile_info.Name} is a remote projectile") taskResolver ! ReregisterProjectile(projectile) } } @@ -5491,26 +5516,34 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ SplashHitMessage(seq_time, projectile_guid, explosion_pos, direct_victim_uid, unk3, projectile_vel, unk4, targets) => log.info(s"Splash: $msg") - continent.GUID(direct_victim_uid) match { - case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => - ResolveProjectileEntry(projectile_guid, ProjectileResolution.Splash, target, target.Position) match { - case Some(projectile) => - HandleDealingDamage(target, projectile) - case None => ; + FindProjectileEntry(projectile_guid) match { + case Some(projectile) => + continent.GUID(direct_victim_uid) match { + case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => + ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match { + case Some(projectile) => + HandleDealingDamage(target, projectile) + case None => ; + } + case _ => ; } - case _ => ; - } - targets.foreach(elem => { - continent.GUID(elem.uid) match { - case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => - ResolveProjectileEntry(projectile_guid, ProjectileResolution.Splash, target, explosion_pos) match { - case Some(projectile) => - HandleDealingDamage(target, projectile) - case None => ; + targets.foreach(elem => { + continent.GUID(elem.uid) match { + case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => + ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, explosion_pos) match { + case Some(projectile) => + HandleDealingDamage(target, projectile) + case None => ; + } + case _ => ; } - case _ => ; - } - }) + }) + if(projectile.profile.ExistsOnRemoteClients) { + //cleanup + taskResolver ! UnregisterProjectile(projectile) + } + case None => ; + } case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, pos, unk1) => log.info(s"Lash: $msg") @@ -6077,6 +6110,13 @@ class WorldSessionActor extends Actor with MDCContextAware { }, List(GUIDTask.RegisterAvatar(driver)(continent.GUID), GUIDTask.RegisterVehicle(obj)(continent.GUID))) } + /** + * Construct tasking that adds a completed but unregistered projectile into the scene. + * After the projectile is registered to the curent zone's global unique identifier system, + * all connected clients save for the one that registered it will be informed about the projectile's "creation." + * @param obj the projectile to be registered + * @return a `TaskResolver.GiveTask` message + */ def RegisterProjectile(obj : Projectile) : TaskResolver.GiveTask = { val definition = obj.Definition TaskResolver.GiveTask( @@ -6126,12 +6166,19 @@ class WorldSessionActor extends Actor with MDCContextAware { }, List(GUIDTask.UnregisterAvatar(driver)(continent.GUID), GUIDTask.UnregisterVehicle(obj)(continent.GUID))) } + /** + * Construct tasking that removes a formerly complete and currently registered projectile from the scene. + * After the projectile is unregistered from the curent zone's global unique identifier system, + * all connected clients save for the one that registered it will be informed about the projectile's "destruction." + * @param obj the projectile to be unregistered + * @return a `TaskResolver.GiveTask` message + */ def UnregisterProjectile(obj : Projectile) : TaskResolver.GiveTask = { TaskResolver.GiveTask( new Task() { private val globalProjectile = obj private val localAnnounce = avatarService - private val localMsg = AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, obj.GUID)) + private val localMsg = AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, obj.GUID, 2)) override def isComplete : Task.Resolution.Value = { if(!globalProjectile.HasGUID) { @@ -6192,6 +6239,14 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } + /** + * If the projectile object is unregistered, register it. + * If the projectile object is already registered, unregister it and then register it again. + * @see `RegisterProjectile(Projectile)` + * @see `UnregisterProjectile(Projectile)` + * @param obj the projectile to be registered (a second time?) + * @return a `TaskResolver.GiveTask` message + */ def ReregisterProjectile(obj : Projectile) : TaskResolver.GiveTask = { val reg = RegisterProjectile(obj) if(obj.HasGUID) { @@ -8406,9 +8461,6 @@ class WorldSessionActor extends Actor with MDCContextAware { /** * Find a projectile with the given globally unique identifier and mark it as a resolved shot. - * A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle. - * The internal copy of the projectile is retained as merely `Resolved` - * while the observed projectile is promoted to the suggested resolution status. * @param projectile the projectile object * @param index where the projectile was found * @param resolution the resolution status to promote the projectile @@ -8419,8 +8471,23 @@ class WorldSessionActor extends Actor with MDCContextAware { log.error(s"expected projectile could not be found at $index; can not resolve") None } - else if(projectile.isMiss) { - log.error(s"expected projectile at $index was already counted as a missed shot; can not resolve any further") + else { + ResolveProjectileEntry(projectile, resolution, target, pos) + } + } + + /** + * Find a projectile with the given globally unique identifier and mark it as a resolved shot. + * A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle. + * The internal copy of the projectile is retained as merely `Resolved` + * while the observed projectile is promoted to the suggested resolution status. + * @param projectile the projectile object + * @param resolution the resolution status to promote the projectile + * @return a copy of the projectile + */ + def ResolveProjectileEntry(projectile : Projectile, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = { + if(projectile.isMiss) { + log.error("expected projectile was already counted as a missed shot; can not resolve any further") None } else { From d1ba353cadd8c5a5bb03489de4b82e3fc5b87a5a Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 1 Nov 2019 11:11:47 -0400 Subject: [PATCH 05/12] 'don't knock it if it works' controlled projectiles --- build.sbt | 2 +- .../psforever/objects/GlobalDefinitions.scala | 72 ++----------------- .../converter/ProjectileConverter.scala | 2 +- .../packet/game/ProjectileStateMessage.scala | 20 +++--- .../scala/services/avatar/AvatarService.scala | 4 ++ .../avatar/AvatarServiceMessage.scala | 5 +- .../avatar/AvatarServiceResponse.scala | 7 +- project/plugins.sbt | 2 +- .../src/main/scala/WorldSessionActor.scala | 69 +++++++++--------- 9 files changed, 63 insertions(+), 120 deletions(-) diff --git a/build.sbt b/build.sbt index a5e89431..02c49142 100644 --- a/build.sbt +++ b/build.sbt @@ -54,7 +54,7 @@ lazy val psloginPackSettings = packAutoSettings ++ Seq( lazy val root = (project in file(".")). settings(commonSettings: _*). - enablePlugins(ScalaUnidocPlugin). + //enablePlugins(ScalaUnidocPlugin). settings(psloginPackSettings: _*). aggregate(pslogin, common) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index ab8f93d2..5e1eaeeb 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -31,70 +31,18 @@ object GlobalDefinitions { */ val avatar = new AvatarDefinition(121) /* - Exo-suits + exo-suits */ val Standard = ExoSuitDefinition(ExoSuitType.Standard) - Standard.Name = "standard" - Standard.MaxArmor = 50 - Standard.InventoryScale = InventoryTile.Tile96 - Standard.InventoryOffset = 6 - Standard.Holster(0, EquipmentSize.Pistol) - Standard.Holster(2, EquipmentSize.Rifle) - Standard.Holster(4, EquipmentSize.Melee) - Standard.ResistanceDirectHit = 4 - Standard.ResistanceSplash = 15 - Standard.ResistanceAggravated = 8 val Agile = ExoSuitDefinition(ExoSuitType.Agile) - Agile.Name = "agile" - Agile.MaxArmor = 100 - Agile.InventoryScale = InventoryTile.Tile99 - Agile.InventoryOffset = 6 - Agile.Holster(0, EquipmentSize.Pistol) - Agile.Holster(1, EquipmentSize.Pistol) - Agile.Holster(2, EquipmentSize.Rifle) - Agile.Holster(4, EquipmentSize.Melee) - Agile.ResistanceDirectHit = 6 - Agile.ResistanceSplash = 25 - Agile.ResistanceAggravated = 10 val Reinforced = ExoSuitDefinition(ExoSuitType.Reinforced) - Reinforced.Name = "reinforced" - Reinforced.Permissions = List(CertificationType.ReinforcedExoSuit) - Reinforced.MaxArmor = 200 - Reinforced.InventoryScale = InventoryTile.Tile1209 - Reinforced.InventoryOffset = 6 - Reinforced.Holster(0, EquipmentSize.Pistol) - Reinforced.Holster(1, EquipmentSize.Pistol) - Reinforced.Holster(2, EquipmentSize.Rifle) - Reinforced.Holster(3, EquipmentSize.Rifle) - Reinforced.Holster(4, EquipmentSize.Melee) - Reinforced.ResistanceDirectHit = 10 - Reinforced.ResistanceSplash = 35 - Reinforced.ResistanceAggravated = 12 val Infiltration = ExoSuitDefinition(ExoSuitType.Infiltration) - Infiltration.Name = "infiltration_suit" - Infiltration.Permissions = List(CertificationType.InfiltrationSuit) - Infiltration.MaxArmor = 0 - Infiltration.InventoryScale = InventoryTile.Tile66 - Infiltration.InventoryOffset = 6 - Infiltration.Holster(0, EquipmentSize.Pistol) - Infiltration.Holster(4, EquipmentSize.Melee) val MAX = SpecialExoSuitDefinition(ExoSuitType.MAX) - MAX.Permissions = List(CertificationType.AIMAX,CertificationType.AVMAX, CertificationType.AAMAX, CertificationType.UniMAX) - MAX.MaxArmor = 650 - MAX.InventoryScale = InventoryTile.Tile1612 - MAX.InventoryOffset = 6 - MAX.Holster(0, EquipmentSize.Max) - MAX.Holster(4, EquipmentSize.Melee) - MAX.Subtract.Damage1 = -2 - MAX.ResistanceDirectHit = 6 - MAX.ResistanceSplash = 35 - MAX.ResistanceAggravated = 10 - MAX.Damage = StandardMaxDamage - MAX.Model = StandardResolutions.Max + init_exosuit() /* Implants */ @@ -632,15 +580,9 @@ object GlobalDefinitions { val phoenix = ToolDefinition(ObjectClass.phoenix) //decimator - val striker = new ToolDefinition(ObjectClass.striker) { - override def NextFireModeIndex(index : Int) : Int = index - DefaultFireModeIndex = 1 - } + val striker = ToolDefinition(ObjectClass.striker) - val hunterseeker = new ToolDefinition(ObjectClass.hunterseeker) { - override def NextFireModeIndex(index : Int) : Int = index - DefaultFireModeIndex = 1 - } //phoenix + val hunterseeker = ToolDefinition(ObjectClass.hunterseeker) val lancer = ToolDefinition(ObjectClass.lancer) @@ -807,9 +749,7 @@ object GlobalDefinitions { val lightgunship_weapon_system = ToolDefinition(ObjectClass.lightgunship_weapon_system) - val wasp_weapon_system = new ToolDefinition(ObjectClass.wasp_weapon_system) { - override def NextFireModeIndex(index : Int) : Int = index - } + val wasp_weapon_system = new ToolDefinition(ObjectClass.wasp_weapon_system) val liberator_weapon_system = ToolDefinition(ObjectClass.liberator_weapon_system) @@ -955,13 +895,13 @@ object GlobalDefinitions { val router_telepad_deployable = DeployableDefinition(DeployedItem.router_telepad_deployable) + //this is only treated like a deployable val internal_router_telepad_deployable = DeployableDefinition(DeployedItem.router_telepad_deployable) init_deployables() /* Miscellaneous */ - val ams_respawn_tube = new SpawnTubeDefinition(49) val matrix_terminala = new MatrixTerminalDefinition(517) diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala index fb60a576..b9941887 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala @@ -15,7 +15,7 @@ class ProjectileConverter extends ObjectCreateConverter[Projectile]() { PlacementData( obj.Position, obj.Orientation, - None + obj.Velocity ), CommonFieldData( obj.owner.Faction, diff --git a/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala b/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala index 4f949b66..8c1ffd30 100644 --- a/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala @@ -37,22 +37,22 @@ import shapeless.{::, HNil} * A minor loss of lifespan may be levied. * @see `ProjectileDefinition` * @see `TrackedProjectileData` - * @param projectile_guid the client-specific local unique identifier of the projectile; - * this is __not__ the global unique identifier for the synchronized projectile object + * @param projectile_guid when dispatched by the client, the client-specific local unique identifier of the projectile; + * when dispatched by the server, the global unique identifier for the synchronized projectile object * @param shot_pos the position of the projectile * @param shot_vel the velocity of the projectile * @param shot_orient the orientation of the projectile * @param unk na; * usually `false` - * @param time_alive how long the projectile has been in the air; - * often expressed in multiples of 2 + * @param progress a measure of how long the projectile has been in the air; + * often expressed in multiples of 2 */ final case class ProjectileStateMessage(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk : Boolean, - time_alive : Int) + progress : Int) extends PlanetSideGamePacket { type Packet = ProjectileStateMessage def opcode = GamePacketOpcode.ProjectileStateMessage @@ -68,15 +68,15 @@ object ProjectileStateMessage extends Marshallable[ProjectileStateMessage] { ("pitch" | Angular.codec_pitch) :: ("yaw" | Angular.codec_yaw()) :: ("unk" | bool) :: - ("time_alive" | uint16L) + ("progress" | uint16L) ).xmap[ProjectileStateMessage] ( { - case guid :: pos :: vel :: roll :: pitch :: yaw :: unk :: time :: HNil => - ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), unk, time) + case guid :: pos :: vel :: roll :: pitch :: yaw :: unk :: progress :: HNil => + ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), unk, progress) }, { - case ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), unk, time) => - guid :: pos :: vel :: roll :: pitch :: yaw :: unk :: time :: HNil + case ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), unk, progress) => + guid :: pos :: vel :: roll :: pitch :: yaw :: unk :: progress :: HNil } ) } diff --git a/common/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala index 56af1513..d47627b6 100644 --- a/common/src/main/scala/services/avatar/AvatarService.scala +++ b/common/src/main/scala/services/avatar/AvatarService.scala @@ -161,6 +161,10 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(pos, vel, yaw, pitch, yaw_upper, seq_time, is_crouching, is_jumping, jump_thrust, is_cloaking, spectating, weaponInHand)) ) + case AvatarAction.ProjectileExplodes(player_guid, projectile_guid, projectile) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileExplodes(projectile_guid, projectile)) + ) case AvatarAction.ProjectileState(player_guid, projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive)) diff --git a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala index 2d8ca712..40d2eeb5 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala @@ -1,12 +1,8 @@ // Copyright (c) 2017 PSForever package services.avatar -<<<<<<< fa7365e8af5d0b21c3934ed745895edd46f741a6:common/src/main/scala/services/avatar/AvatarServiceMessage.scala import net.psforever.objects.{PlanetSideGameObject, Player} -import net.psforever.objects.ballistics.SourceEntry -======= import net.psforever.objects.ballistics.{Projectile, SourceEntry} ->>>>>>> removed fire mode override for the Phoenix; added events for loading and manipulation of remote projectiles to AvatarService; ProjectileStateMessage handles projectile data in a simple way; remote projectiles can now be registered and unregistered:common/src/main/scala/services/avatar/AvatarAction.scala import net.psforever.objects.ce.Deployable import net.psforever.objects.equipment.Equipment import net.psforever.objects.inventory.Container @@ -53,6 +49,7 @@ object AvatarAction { final case class PlanetsideAttributeSelf(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action final case class PlayerState(player_guid : PlanetSideGUID, pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Action final case class PickupItem(player_guid : PlanetSideGUID, zone : Zone, target : PlanetSideGameObject with Container, slot : Int, item : Equipment, unk : Int = 0) extends Action + final case class ProjectileExplodes(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, projectile : Projectile) extends Action final case class ProjectileState(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk : Boolean, time_alive : Int) extends Action final case class PutDownFDU(player_guid : PlanetSideGUID) extends Action final case class Release(player : Player, zone : Zone, time : Option[FiniteDuration] = None) extends Action diff --git a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala index 2b163547..b4450865 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala @@ -2,7 +2,7 @@ package services.avatar import net.psforever.objects.Player -import net.psforever.objects.ballistics.SourceEntry +import net.psforever.objects.ballistics.{Projectile, SourceEntry} import net.psforever.objects.equipment.Equipment import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.game.objectcreate.ConstructorData @@ -38,14 +38,11 @@ object AvatarResponse { final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response final case class ObjectHeld(slot : Int) extends Response final case class PlanetsideAttribute(attribute_type : Int, attribute_value : Long) extends Response -<<<<<<< fa7365e8af5d0b21c3934ed745895edd46f741a6:common/src/main/scala/services/avatar/AvatarServiceResponse.scala final case class PlanetsideAttributeToAll(attribute_type : Int, attribute_value : Long) extends Response final case class PlanetsideAttributeSelf(attribute_type : Int, attribute_value : Long) extends Response final case class PlayerState(pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Response -======= - final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response + final case class ProjectileExplodes(projectile_guid : PlanetSideGUID, projectile : Projectile) extends Response final case class ProjectileState(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk : Boolean, time_alive : Int) extends Response ->>>>>>> removed fire mode override for the Phoenix; added events for loading and manipulation of remote projectiles to AvatarService; ProjectileStateMessage handles projectile data in a simple way; remote projectiles can now be registered and unregistered:common/src/main/scala/services/avatar/AvatarResponse.scala final case class PutDownFDU(target_guid : PlanetSideGUID) extends Response final case class Release(player : Player) extends Response final case class Reload(weapon_guid : PlanetSideGUID) extends Response diff --git a/project/plugins.sbt b/project/plugins.sbt index 84abecfe..4e23b078 100644 --- a/project/plugins.sbt +++ b/project/plugins.sbt @@ -3,4 +3,4 @@ logLevel := Level.Warn addSbtPlugin("org.xerial.sbt" % "sbt-pack" % "0.7.9") addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.3.5") // https://github.com/sbt/sbt-unidoc -addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.2") +//addSbtPlugin("com.eed3si9n" % "sbt-unidoc" % "0.4.2") diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 94b47aa5..86f6de1d 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1473,9 +1473,31 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + case AvatarResponse.ProjectileExplodes(projectile_guid, projectile) => + //turn the projectile into a boomer + sendResponse( + ObjectCreateMessage( + GlobalDefinitions.boomer.ObjectId, + projectile_guid, + CommonFieldDataWithPlacement( + PlacementData(projectile.Position, projectile.Orientation), + CommonFieldData(projectile.owner.Faction,false,false,false,None,false,Some(false),None, PlanetSideGUID(0)) + ) + ) + ) + //detonate the boomer, then clean it up + sendResponse(TriggerEffectMessage(projectile_guid, "detonate_boomer")) + sendResponse(ObjectDeleteMessage(projectile_guid, 2)) + case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) => if(tplayer_guid != guid) { - sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive)) + //sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive)) + continent.GUID(projectile_guid) match { + case Some(obj) => + val definition = obj.Definition + sendResponse(ObjectCreateMessage(definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get)) + case _ => ; + } } case AvatarResponse.PutDownFDU(target) => @@ -3509,14 +3531,15 @@ class WorldSessionActor extends Actor with MDCContextAware { //player.Orientation = Vector3(0f, 0f, 132.1875f) // player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction)) - player.Slot(2).Equipment = Tool(suppressor) + player.Slot(2).Equipment = Tool(hunterseeker) //Tool(suppressor) player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction)) - player.Slot(6).Equipment = AmmoBox(bullet_9mm) - player.Slot(9).Equipment = AmmoBox(bullet_9mm) - player.Slot(12).Equipment = AmmoBox(bullet_9mm) - player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) - player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) - player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) + player.Slot(6).Equipment = AmmoBox(hunter_seeker_missile, 65535) + //player.Slot(6).Equipment = AmmoBox(bullet_9mm) + //player.Slot(9).Equipment = AmmoBox(bullet_9mm) + //player.Slot(12).Equipment = AmmoBox(bullet_9mm) + //player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) + //player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) + //player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit) player.Inventory.Items.foreach { _.obj.Faction = faction } //TODO end temp player character auto-loading @@ -3900,25 +3923,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } if(!player.Crouching && is_crouching) { - sendResponse( - ObjectCreateMessage( - 405, - PlanetSideGUID(40288), - TrackedProjectileData( - CommonFieldDataWithPlacement( - PlacementData(Vector3(3561.0f, 2854.0f, 92.859375f), Vector3(0f, 348.75f, 267.1875f), None), - CommonFieldData(PlanetSideEmpire.NC, false, false, true, None, false, None, None, PlanetSideGUID(0)) - ), - 39577, 201, FlightPhysics.State4, 0, 0 - ) - ) - ) - } - else if(player.Crouching && !is_crouching) { - sendResponse( - ObjectDeleteMessage(PlanetSideGUID(40288), 2) - //ProjectileStateMessage(PlanetSideGUID(40288),Vector3(3561.0f, 2854.0f, 92.859375f),Vector3(-38.814934f,-2.578959f,-9.313708f),Vector3(0,348.75f,267.1875f),false,0) - ) + //... } player.Position = pos player.Velocity = vel @@ -4026,16 +4031,15 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) => //log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2") - case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) => - log.info(s"ProjectileState: $msg") + case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk, progress) => + //log.info(s"ProjectileState: $msg") projectiles(projectile_guid.guid - Projectile.BaseUID) match { case Some(projectile) if projectile.HasGUID => val projectileGlobalUID = projectile.GUID - log.info(s"ProjectileState: mapped local uid ${projectile_guid.guid} to global uid ${projectileGlobalUID.guid}; updating object ...") projectile.Position = shot_pos projectile.Orientation = shot_orient projectile.Velocity = shot_vel - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectile.GUID, shot_pos, shot_vel, shot_orient, unk, time_alive)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectileGlobalUID, shot_pos, shot_vel, shot_orient, unk, progress)) case _ => log.error(s"ProjectileState: the projectile@${projectile_guid.guid} can not be found") } @@ -5540,6 +5544,7 @@ class WorldSessionActor extends Actor with MDCContextAware { }) if(projectile.profile.ExistsOnRemoteClients) { //cleanup + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile_guid, projectile)) taskResolver ! UnregisterProjectile(projectile) } case None => ; @@ -8108,7 +8113,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } /** - * If the corpse has been well-looted, it has no items in its primary holsters nor any items in its inventory. + * If the corpse has been well-lootedP, it has no items in its primary holsters nor any items in its inventory. * @param obj the corpse * @return `true`, if the `obj` is actually a corpse and has no objects in its holsters or backpack; * `false`, otherwise From 043512d6a33760ca63724663880de1fa788f29d5 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 4 Nov 2019 09:21:36 -0500 Subject: [PATCH 06/12] proper PSM encoding/decoding; normal and alternate projectile flight handling; auto lock field on projectiles, to indicate whether that projectile should produce a warning message on the target's side when weapon is pointed at the target; business logic in WSA --- .../psforever/objects/GlobalDefinitions.scala | 7 ++ .../definition/ProjectileDefinition.scala | 8 ++ .../packet/game/ProjectileStateMessage.scala | 36 +++---- .../scala/services/avatar/AvatarService.scala | 14 ++- .../avatar/AvatarServiceMessage.scala | 4 +- .../avatar/AvatarServiceResponse.scala | 4 +- .../game/ProjectileStateMessageTest.scala | 16 +-- .../src/main/scala/WorldSessionActor.scala | 97 +++++++++++++------ 8 files changed, 129 insertions(+), 57 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 5e1eaeeb..934c1ca2 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2160,6 +2160,7 @@ object GlobalDefinitions { aphelion_starfire_projectile.ProjectileDamageType = DamageType.Aggravated aphelion_starfire_projectile.ExistsOnRemoteClients = true aphelion_starfire_projectile.RemoteClientData = (39577, 249) //starfire_projectile data + aphelion_starfire_projectile.AutoLock = true aphelion_starfire_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(aphelion_starfire_projectile) @@ -3022,6 +3023,7 @@ object GlobalDefinitions { peregrine_sparrow_projectile.Lifespan = 7.5f peregrine_sparrow_projectile.ExistsOnRemoteClients = true peregrine_sparrow_projectile.RemoteClientData = (13107, 187) //sparrow_projectile data + peregrine_sparrow_projectile.AutoLock = true peregrine_sparrow_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(peregrine_sparrow_projectile) @@ -3355,6 +3357,7 @@ object GlobalDefinitions { sparrow_projectile.Lifespan = 5.85f sparrow_projectile.ExistsOnRemoteClients = true sparrow_projectile.RemoteClientData = (13107, 187) + sparrow_projectile.AutoLock = true sparrow_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(sparrow_projectile) @@ -3372,6 +3375,7 @@ object GlobalDefinitions { sparrow_secondary_projectile.Lifespan = 5.85f sparrow_secondary_projectile.ExistsOnRemoteClients = true sparrow_secondary_projectile.RemoteClientData = (13107, 187) + sparrow_secondary_projectile.AutoLock = true sparrow_secondary_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(sparrow_secondary_projectile) @@ -3425,6 +3429,7 @@ object GlobalDefinitions { starfire_projectile.Lifespan = 7.8f starfire_projectile.ExistsOnRemoteClients = true starfire_projectile.RemoteClientData = (39577, 249) + starfire_projectile.AutoLock = true starfire_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(starfire_projectile) @@ -3459,6 +3464,7 @@ object GlobalDefinitions { striker_missile_targeting_projectile.Lifespan = 4.2f striker_missile_targeting_projectile.ExistsOnRemoteClients = true striker_missile_targeting_projectile.RemoteClientData = (26214, 134) + striker_missile_targeting_projectile.AutoLock = true striker_missile_targeting_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(striker_missile_targeting_projectile) @@ -3549,6 +3555,7 @@ object GlobalDefinitions { wasp_rocket_projectile.Lifespan = 6.5f wasp_rocket_projectile.ExistsOnRemoteClients = true wasp_rocket_projectile.RemoteClientData = (0, 208) + wasp_rocket_projectile.AutoLock = true wasp_rocket_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(wasp_rocket_projectile) diff --git a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index 9d221d63..e1069612 100644 --- a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -25,6 +25,7 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) private var useDamage1Subtract : Boolean = false private var existsOnRemoteClients : Boolean = false //`true` spawns a server-managed object private var remoteClientData : (Int, Int) = (0, 0) //artificial values; for ObjectCreateMessage packet (oicw_little_buddy is undefined) + private var autoLock : Boolean = false //derived calculations private var distanceMax : Float = 0f private var distanceFromAcceleration : Float = 0f @@ -125,6 +126,13 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) RemoteClientData } + def AutoLock : Boolean = autoLock + + def AutoLock_=(lockState : Boolean) : Boolean = { + autoLock = lockState + AutoLock + } + def DistanceMax : Float = distanceMax //accessor only def DistanceFromAcceleration : Float = distanceFromAcceleration //accessor only diff --git a/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala b/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala index 8c1ffd30..10a097b1 100644 --- a/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala @@ -22,17 +22,15 @@ import shapeless.{::, HNil} * This packet will continue to be dispatched by the client for as long as the projectile being tracked is in the air. * All projectiles have a maximum lifespan before they will lose control and either despawn and/or explode. * This number is tracked in the packet for simplicity. - * If the projectile strikes a valid target, the count will jump to a significantly enormous value beyond its normal lifespan. - * This ensures that the projectile - locally and the shared model - will despawn. *
- * This control can not be exerted until that projectile is physically constructed on the other clients + * This control can not be demonstrated until that projectile is physically constructed on the other clients * in the same way that a player or a vehicle is constructed. * A projectile that exhibits intentional construction behavior is flagged using the property `exists_on_remote_client`. * The model comes with a number of caveats, * some that originate from the object construction process itself, * but also some from this packet. * For example, - * as indicated by the static `shot_orient` values reported by this packet. + * as indicated by the static `shot_original_orient` values reported by this packet. * a discharged controlled projectile will not normally rotate. * A minor loss of lifespan may be levied. * @see `ProjectileDefinition` @@ -41,18 +39,19 @@ import shapeless.{::, HNil} * when dispatched by the server, the global unique identifier for the synchronized projectile object * @param shot_pos the position of the projectile * @param shot_vel the velocity of the projectile - * @param shot_orient the orientation of the projectile - * @param unk na; - * usually `false` - * @param progress a measure of how long the projectile has been in the air; - * often expressed in multiples of 2 + * @param shot_original_orient the orientation of the projectile when it was discharged + * @param sequence_num an incrementing index of the packet in this projectile's lifetime; + * suggests the "time alive" and indicates a place in packet ordering + * @param explode indicates the projectile should explode + * @param unk na */ final case class ProjectileStateMessage(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, - shot_orient : Vector3, - unk : Boolean, - progress : Int) + shot_original_orient : Vector3, + sequence_num : Int, + explode : Boolean, + unk : Int) extends PlanetSideGamePacket { type Packet = ProjectileStateMessage def opcode = GamePacketOpcode.ProjectileStateMessage @@ -67,16 +66,17 @@ object ProjectileStateMessage extends Marshallable[ProjectileStateMessage] { ("roll" | Angular.codec_roll) :: ("pitch" | Angular.codec_pitch) :: ("yaw" | Angular.codec_yaw()) :: - ("unk" | bool) :: - ("progress" | uint16L) + ("sequence_num" | uint8) :: + ("explode" | bool) :: + ("unk" | uint16L) ).xmap[ProjectileStateMessage] ( { - case guid :: pos :: vel :: roll :: pitch :: yaw :: unk :: progress :: HNil => - ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), unk, progress) + case guid :: pos :: vel :: roll :: pitch :: yaw :: sequence_num :: explode :: unk :: HNil => + ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), sequence_num , explode, unk) }, { - case ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), unk, progress) => - guid :: pos :: vel :: roll :: pitch :: yaw :: unk :: progress :: HNil + case ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), sequence_num, explode, unk) => + guid :: pos :: vel :: roll :: pitch :: yaw :: sequence_num :: explode :: unk :: HNil } ) } diff --git a/common/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala index d47627b6..98c9705d 100644 --- a/common/src/main/scala/services/avatar/AvatarService.scala +++ b/common/src/main/scala/services/avatar/AvatarService.scala @@ -2,7 +2,7 @@ package services.avatar import akka.actor.{Actor, ActorRef, Props} -import net.psforever.packet.game.ObjectCreateMessage +import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID} import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectCreateMessageParent, PlacementData} import services.avatar.support.{CorpseRemovalActor, DroppedItemRemover} import services.{GenericEventBus, RemoverActor, Service} @@ -161,13 +161,21 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(pos, vel, yaw, pitch, yaw_upper, seq_time, is_crouching, is_jumping, jump_thrust, is_cloaking, spectating, weaponInHand)) ) + case AvatarAction.ProjectileAutoLockAwareness(mode) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", PlanetSideGUID(0), AvatarResponse.ProjectileAutoLockAwareness(mode)) + ) + case AvatarAction.ProjectileTrackingAwareness(guid) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", PlanetSideGUID(0), AvatarResponse.ProjectileTrackingAwareness(guid)) + ) case AvatarAction.ProjectileExplodes(player_guid, projectile_guid, projectile) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileExplodes(projectile_guid, projectile)) ) - case AvatarAction.ProjectileState(player_guid, projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) => + case AvatarAction.ProjectileState(player_guid, projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3)) ) case AvatarAction.PickupItem(player_guid, zone, target, slot, item, unk) => janitor forward RemoverActor.ClearSpecific(List(item), zone) diff --git a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala index 40d2eeb5..ade58d68 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala @@ -49,8 +49,10 @@ object AvatarAction { final case class PlanetsideAttributeSelf(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action final case class PlayerState(player_guid : PlanetSideGUID, pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Action final case class PickupItem(player_guid : PlanetSideGUID, zone : Zone, target : PlanetSideGameObject with Container, slot : Int, item : Equipment, unk : Int = 0) extends Action + final case class ProjectileAutoLockAwareness(mode : Int) extends Action + final case class ProjectileTrackingAwareness(projectile_guid : PlanetSideGUID) extends Action final case class ProjectileExplodes(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, projectile : Projectile) extends Action - final case class ProjectileState(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk : Boolean, time_alive : Int) extends Action + final case class ProjectileState(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk1 : Int, unk2 : Boolean, unk3 : Int) extends Action final case class PutDownFDU(player_guid : PlanetSideGUID) extends Action final case class Release(player : Player, zone : Zone, time : Option[FiniteDuration] = None) extends Action final case class Revive(target_guid: PlanetSideGUID) extends Action diff --git a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala index b4450865..2f60e87a 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala @@ -41,8 +41,10 @@ object AvatarResponse { final case class PlanetsideAttributeToAll(attribute_type : Int, attribute_value : Long) extends Response final case class PlanetsideAttributeSelf(attribute_type : Int, attribute_value : Long) extends Response final case class PlayerState(pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Response + final case class ProjectileAutoLockAwareness(mode : Int) extends Response + final case class ProjectileTrackingAwareness(projectile_guid : PlanetSideGUID) extends Response final case class ProjectileExplodes(projectile_guid : PlanetSideGUID, projectile : Projectile) extends Response - final case class ProjectileState(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk : Boolean, time_alive : Int) extends Response + final case class ProjectileState(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk1 : Int, unk2 : Boolean, unk3 : Int) extends Response final case class PutDownFDU(target_guid : PlanetSideGUID) extends Response final case class Release(player : Player) extends Response final case class Reload(weapon_guid : PlanetSideGUID) extends Response diff --git a/common/src/test/scala/game/ProjectileStateMessageTest.scala b/common/src/test/scala/game/ProjectileStateMessageTest.scala index 80891f88..6c0afc7d 100644 --- a/common/src/test/scala/game/ProjectileStateMessageTest.scala +++ b/common/src/test/scala/game/ProjectileStateMessageTest.scala @@ -8,17 +8,18 @@ import net.psforever.types.Vector3 import scodec.bits._ class ProjectileStateMessageTest extends Specification { - val string = hex"3f 259d c5019 30e4a 9514 c52c9541 d9ba05c2 c5973941 00 f8 ec 020000" + val string = hex"3f 259d c5019 30e4a 9514 c52c9541 d9ba05c2 c5973941 00 f8 ec 02000000" "decode" in { PacketCoding.DecodePacket(string).require match { - case ProjectileStateMessage(projectile, pos, vel, orient, unk, time_alive) => + case ProjectileStateMessage(projectile, pos, vel, orient, sequence, explode, unk) => projectile mustEqual PlanetSideGUID(40229) pos mustEqual Vector3(4611.539f, 5576.375f, 82.328125f) vel mustEqual Vector3(18.64686f, -33.43247f, 11.599553f) - orient mustEqual Vector3(0, 22.5f, 56.25f) - unk mustEqual false - time_alive mustEqual 4 + orient mustEqual Vector3(0, 22.5f, 146.25f) + sequence mustEqual 2 + explode mustEqual false + unk mustEqual 0 case _ => ko } @@ -29,9 +30,10 @@ class ProjectileStateMessageTest extends Specification { PlanetSideGUID(40229), Vector3(4611.539f, 5576.375f, 82.328125f), Vector3(18.64686f, -33.43247f, 11.599553f), - Vector3(0, 22.5f, 56.25f), + Vector3(0, 22.5f, 146.25f), + 2, false, - 4 + 0 ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 86f6de1d..9be7c388 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -153,6 +153,9 @@ class WorldSessionActor extends Actor with MDCContextAware { lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L var serverTime : Long = 0 + var projectileAutoLockTargets : List[PlanetSideGUID] = Nil + var projectileAutoLocksActive : Set[PlanetSideGUID] = Set.empty + var amsSpawnPoints : List[SpawnPoint] = Nil var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj @@ -1473,31 +1476,42 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + case AvatarResponse.ProjectileTrackingAwareness(projectile_guid) => + continent.GUID(projectile_guid) match { + case Some(projectile : Projectile) => + val mode = 7 + (projectile.profile == GlobalDefinitions.wasp_rocket_projectile) + sendResponse(GenericActionMessage(mode)) + case _ => ; + } + case AvatarResponse.ProjectileExplodes(projectile_guid, projectile) => - //turn the projectile into a boomer - sendResponse( - ObjectCreateMessage( - GlobalDefinitions.boomer.ObjectId, - projectile_guid, - CommonFieldDataWithPlacement( - PlacementData(projectile.Position, projectile.Orientation), - CommonFieldData(projectile.owner.Faction,false,false,false,None,false,Some(false),None, PlanetSideGUID(0)) - ) - ) - ) - //detonate the boomer, then clean it up - sendResponse(TriggerEffectMessage(projectile_guid, "detonate_boomer")) +// //turn the projectile into a boomer +// sendResponse( +// ObjectCreateMessage( +// GlobalDefinitions.boomer.ObjectId, +// projectile_guid, +// CommonFieldDataWithPlacement( +// PlacementData(projectile.Position, projectile.Orientation), +// CommonFieldData(projectile.owner.Faction,false,false,false,None,false,Some(false),None, PlanetSideGUID(0)) +// ) +// ) +// ) +// //detonate the boomer, then clean it up +// sendResponse(TriggerEffectMessage(projectile_guid, "detonate_boomer")) sendResponse(ObjectDeleteMessage(projectile_guid, 2)) - case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) => + case AvatarResponse.ProjectileAutoLockAwareness(mode) => + sendResponse(GenericActionMessage(mode)) + + case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3) => if(tplayer_guid != guid) { - //sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive)) - continent.GUID(projectile_guid) match { - case Some(obj) => - val definition = obj.Definition - sendResponse(ObjectCreateMessage(definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get)) - case _ => ; - } + sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3)) +// continent.GUID(projectile_guid) match { +// case Some(obj) => +// val definition = obj.Definition +// sendResponse(ObjectCreateMessage(definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get)) +// case _ => ; +// } } case AvatarResponse.PutDownFDU(target) => @@ -3481,7 +3495,10 @@ class WorldSessionActor extends Actor with MDCContextAware { import net.psforever.objects.GlobalDefinitions._ import net.psforever.types.CertificationType._ - val faction = PlanetSideEmpire.VS + val faction = + if(sessionId % 5 == 0) PlanetSideEmpire.TR + else if(sessionId % 3 == 0) PlanetSideEmpire.NC + else PlanetSideEmpire.VS val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1) avatar.Certifications += StandardAssault avatar.Certifications += MediumAssault @@ -3539,7 +3556,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //player.Slot(12).Equipment = AmmoBox(bullet_9mm) //player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) //player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) - //player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) + player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit) player.Inventory.Items.foreach { _.obj.Faction = faction } //TODO end temp player character auto-loading @@ -4031,15 +4048,15 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) => //log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2") - case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk, progress) => - //log.info(s"ProjectileState: $msg") + case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3) => + log.info(s"ProjectileState: $msg") projectiles(projectile_guid.guid - Projectile.BaseUID) match { case Some(projectile) if projectile.HasGUID => val projectileGlobalUID = projectile.GUID projectile.Position = shot_pos projectile.Orientation = shot_orient projectile.Velocity = shot_vel - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectileGlobalUID, shot_pos, shot_vel, shot_orient, unk, progress)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectileGlobalUID, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3)) case _ => log.error(s"ProjectileState: the projectile@${projectile_guid.guid} can not be found") } @@ -5496,6 +5513,32 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ WeaponLazeTargetPositionMessage(weapon, pos1, pos2) => log.info("Lazing position: " + pos2.toString) + case msg @ ObjectDetectedMessage(guid1, guid2, unk, targets) => + FindWeapon match { + case Some(weapon) if weapon.Projectile.AutoLock => + //projectile with auto-lock instigates a warning on the target + val mode = 7 + (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile) + projectileAutoLockTargets = targets + targets + .map { continent.GUID } + .collect { + case Some(obj : Vehicle) if !obj.Cloaked => + //TODO vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.ProjectileAutoLockAwareness(mode)) + obj.Seats.values + .collect { case seat if seat.isOccupied => + avatarService ! AvatarServiceMessage(s"${seat.Occupant.get.Name}", AvatarAction.ProjectileAutoLockAwareness(mode)) + } + case Some(obj : Mountable) => + obj.Seats.values + .collect { case seat if seat.isOccupied => + avatarService ! AvatarServiceMessage(s"${seat.Occupant.get.Name}", AvatarAction.ProjectileAutoLockAwareness(mode)) + } + case Some(obj : Player) if obj.ExoSuit == ExoSuitType.MAX => + avatarService ! AvatarServiceMessage(s"${obj.Name}", AvatarAction.ProjectileAutoLockAwareness(mode)) + } + case _ => ; + } + case msg @ HitMessage(seq_time, projectile_guid, unk1, hit_info, unk2, unk3, unk4) => log.info(s"Hit: $msg") (hit_info match { @@ -5544,7 +5587,7 @@ class WorldSessionActor extends Actor with MDCContextAware { }) if(projectile.profile.ExistsOnRemoteClients) { //cleanup - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile_guid, projectile)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile)) taskResolver ! UnregisterProjectile(projectile) } case None => ; From 693a3a5d781c80ce01af4ac17205a9b4e2d43a91 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 14 Nov 2019 13:06:58 -0500 Subject: [PATCH 07/12] proper remote projectile handling; allowances for the Decimator alt-fire mode; initial DamageMessage packet and tests; initial DamageFeedbackMessage packet and tests --- .../psforever/objects/GlobalDefinitions.scala | 4 +- .../converter/ProjectileConverter.scala | 8 +- .../psforever/packet/GamePacketOpcode.scala | 4 +- .../packet/game/DamageFeedbackMessage.scala | 72 +++++++ .../psforever/packet/game/DamageMessage.scala | 26 +++ .../packet/game/ObjectDetectedMessage.scala | 5 +- .../packet/game/ProjectileStateMessage.scala | 16 +- .../game/objectcreate/ObjectClass.scala | 30 +-- .../objectcreate/RemoteProjectileData.scala | 84 ++++++++ .../objectcreate/TrackedProjectileData.scala | 110 ----------- .../scala/services/avatar/AvatarService.scala | 8 +- .../avatar/AvatarServiceMessage.scala | 3 +- .../avatar/AvatarServiceResponse.scala | 3 +- .../game/DamageFeedbackMessageTest.scala | 68 +++++++ .../test/scala/game/DamageMessageTest.scala | 30 +++ .../game/ProjectileStateMessageTest.scala | 4 +- ...t.scala => RemoteProjectileDataTest.scala} | 12 +- .../src/main/scala/WorldSessionActor.scala | 179 +++++++++--------- 18 files changed, 421 insertions(+), 245 deletions(-) create mode 100644 common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/DamageMessage.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/RemoteProjectileData.scala delete mode 100644 common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala create mode 100644 common/src/test/scala/game/DamageFeedbackMessageTest.scala create mode 100644 common/src/test/scala/game/DamageMessageTest.scala rename common/src/test/scala/game/objectcreate/{TrackedProjectileDataTest.scala => RemoteProjectileDataTest.scala} (92%) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 934c1ca2..b6dad339 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -3076,9 +3076,11 @@ object GlobalDefinitions { phoenix_missile_guided_projectile.ProjectileDamageType = DamageType.Splash phoenix_missile_guided_projectile.InitialVelocity = 0 phoenix_missile_guided_projectile.Lifespan = 3f + //not naturally a remote projectile, but being governed as one for convenience phoenix_missile_guided_projectile.ExistsOnRemoteClients = true - phoenix_missile_guided_projectile.RemoteClientData = (39577, 201) //hunter_seeker_missile_projectile data + phoenix_missile_guided_projectile.RemoteClientData = (0,63) phoenix_missile_guided_projectile.Packet = projectileConverter + // ProjectileDefinition.CalculateDerivedFields(phoenix_missile_guided_projectile) phoenix_missile_projectile.Name = "phoenix_missile_projectile" diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala index b9941887..19dba8cf 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/ProjectileConverter.scala @@ -3,14 +3,14 @@ package net.psforever.objects.definition.converter import net.psforever.objects.ballistics.Projectile import net.psforever.packet.game.PlanetSideGUID -import net.psforever.packet.game.objectcreate.{CommonFieldData, CommonFieldDataWithPlacement, FlightPhysics, PlacementData, TrackedProjectileData} +import net.psforever.packet.game.objectcreate.{CommonFieldData, CommonFieldDataWithPlacement, FlightPhysics, PlacementData, RemoteProjectileData} import scala.util.{Failure, Success, Try} class ProjectileConverter extends ObjectCreateConverter[Projectile]() { - override def ConstructorData(obj : Projectile) : Try[TrackedProjectileData] = { + override def ConstructorData(obj : Projectile) : Try[RemoteProjectileData] = { Success( - TrackedProjectileData( + RemoteProjectileData( CommonFieldDataWithPlacement( PlacementData( obj.Position, @@ -38,6 +38,6 @@ class ProjectileConverter extends ObjectCreateConverter[Projectile]() { ) } - override def DetailedConstructorData(obj : Projectile) : Try[TrackedProjectileData] = + override def DetailedConstructorData(obj : Projectile) : Try[RemoteProjectileData] = Failure(new Exception("ProjectileConverter should not be used to generate detailed projectile data (nothing should)")) } diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 2d10e152..23bb91de 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -329,7 +329,7 @@ object GamePacketOpcode extends Enumeration { case 0x08 => game.PlayerStateMessage.decode case 0x09 => game.HitMessage.decode case 0x0a => game.HitHint.decode - case 0x0b => noDecoder(DamageMessage) + case 0x0b => game.DamageMessage.decode case 0x0c => game.DestroyMessage.decode case 0x0d => game.ReloadMessage.decode case 0x0e => game.MountVehicleMsg.decode @@ -462,7 +462,7 @@ object GamePacketOpcode extends Enumeration { case 0x78 => game.OxygenStateMessage.decode case 0x79 => noDecoder(TradeMessage) case 0x7a => noDecoder(UnknownMessage122) - case 0x7b => noDecoder(DamageFeedbackMessage) + case 0x7b => game.DamageFeedbackMessage.decode case 0x7c => game.DismountBuildingMsg.decode case 0x7d => noDecoder(UnknownMessage125) case 0x7e => noDecoder(UnknownMessage126) diff --git a/common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala b/common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala new file mode 100644 index 00000000..beaffd21 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala @@ -0,0 +1,72 @@ +// Copyright (c) 2019 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ +import shapeless.{::, HNil} + +final case class DamageFeedbackMessage(unk1 : Int, + unk2 : Boolean, + unk2a : Option[PlanetSideGUID], + unk2b : Option[String], + unk2c : Option[Int], + unk3 : Boolean, + unk3a : Option[PlanetSideGUID], + unk3b : Option[String], + unk3c : Option[Int], + unk3d : Option[Int], + unk4 : Int, + unk5 : Long, + unk6 : Int) + extends PlanetSideGamePacket { + assert( + if(unk2a.nonEmpty) unk2b.isEmpty && unk2c.isEmpty + else if(unk2b.nonEmpty) unk2 && unk2a.isEmpty && unk2c.isEmpty + else unk2a.isEmpty && !unk2 && unk2b.isEmpty && unk2c.nonEmpty + ) + assert( + if(unk3a.nonEmpty) unk3b.isEmpty && unk3c.isEmpty + else if(unk3b.nonEmpty) unk3 && unk3a.isEmpty && unk3c.isEmpty + else unk3a.isEmpty && !unk3 && unk3b.isEmpty && unk3c.nonEmpty + ) + assert(unk3a.isEmpty == unk3d.nonEmpty) + + type Packet = DamageFeedbackMessage + def opcode = GamePacketOpcode.DamageFeedbackMessage + def encode = DamageFeedbackMessage.encode(this) +} + +object DamageFeedbackMessage extends Marshallable[DamageFeedbackMessage] { + implicit val codec : Codec[DamageFeedbackMessage] = ( + ("unk1" | uint4) :: + (bool >>:~ { u2 => + bool >>:~ { u3 => + ("unk2a" | conditional(u2, PlanetSideGUID.codec)) :: + (("unk2b" | conditional(!u2 && u3, PacketHelpers.encodedWideStringAligned(6))) >>:~ { u2b => + ("unk2c" | conditional(!(u2 && u3), uintL(11))) :: + (bool >>:~ { u5 => + bool >>:~ { u6 => + ("unk3a" | conditional(u5, PlanetSideGUID.codec)) :: + ("unk3b" | conditional(!u5 && u6, PacketHelpers.encodedWideStringAligned( if(u2b.nonEmpty) 3 else 1 ))) :: + ("unk3c" | conditional(!(u5 && u6), uintL(11))) :: + ("unk3d" | conditional(!u5, uint2)) :: + ("unk4" | uint(3)) :: + ("unk5" | uint32L) :: + ("unk6" | uint2) + } + }) + }) + } + }) + ).xmap[DamageFeedbackMessage] ( + { + case u1 :: _ :: u2 :: u2a :: u2b :: u2c :: _ :: u3 :: u3a :: u3b :: u3c :: u3d :: u4 :: u5 :: u6 :: HNil => + DamageFeedbackMessage(u1, u2, u2a, u2b, u2c, u3, u3a, u3b, u3c, u3d, u4, u5, u6) + }, + { + case DamageFeedbackMessage(u1, u2, u2a, u2b, u2c, u3, u3a, u3b, u3c, u3d, u4, u5, u6) => + u1 :: u2a.nonEmpty :: u2 :: u2a :: u2b :: u2c :: u3a.nonEmpty :: u3 :: u3a :: u3b :: u3c :: u3d :: u4 :: u5 :: u6 :: HNil + } + ) +} diff --git a/common/src/main/scala/net/psforever/packet/game/DamageMessage.scala b/common/src/main/scala/net/psforever/packet/game/DamageMessage.scala new file mode 100644 index 00000000..59e64d5f --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/DamageMessage.scala @@ -0,0 +1,26 @@ +// Copyright (c) 2019 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import net.psforever.types.Angular +import scodec.Codec +import scodec.codecs._ + +final case class DamageMessage(guid1 : PlanetSideGUID, + unk1 : Int, + guid2 : PlanetSideGUID, + unk2 : Boolean) + extends PlanetSideGamePacket { + type Packet = DamageMessage + def opcode = GamePacketOpcode.DamageMessage + def encode = DamageMessage.encode(this) +} + +object DamageMessage extends Marshallable[DamageMessage] { + implicit val codec : Codec[DamageMessage] = ( + ("guid1" | PlanetSideGUID.codec) :: + ("unk1" | uint8) :: + ("guid1" | PlanetSideGUID.codec) :: + ("unk2" | bool) + ).as[DamageMessage] +} diff --git a/common/src/main/scala/net/psforever/packet/game/ObjectDetectedMessage.scala b/common/src/main/scala/net/psforever/packet/game/ObjectDetectedMessage.scala index 184a9d15..d9b4cd53 100644 --- a/common/src/main/scala/net/psforever/packet/game/ObjectDetectedMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ObjectDetectedMessage.scala @@ -11,7 +11,8 @@ import shapeless.{::, HNil} * @param player_guid1 the player * @param player_guid2 the player(?); * often matches with `player_guid1` - * @param unk na + * @param unk na; + * commonly, zero * @param list list of detected objects; * normally contains at least one element */ @@ -20,7 +21,7 @@ import shapeless.{::, HNil} BETA CLIENT DEBUG INFO: Detector Sender - Object Count + Object Count (not really) Detected Object[] */ final case class ObjectDetectedMessage(player_guid1 : PlanetSideGUID, diff --git a/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala b/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala index 10a097b1..401b32e3 100644 --- a/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/ProjectileStateMessage.scala @@ -34,7 +34,7 @@ import shapeless.{::, HNil} * a discharged controlled projectile will not normally rotate. * A minor loss of lifespan may be levied. * @see `ProjectileDefinition` - * @see `TrackedProjectileData` + * @see `RemoteProjectileData` * @param projectile_guid when dispatched by the client, the client-specific local unique identifier of the projectile; * when dispatched by the server, the global unique identifier for the synchronized projectile object * @param shot_pos the position of the projectile @@ -42,16 +42,18 @@ import shapeless.{::, HNil} * @param shot_original_orient the orientation of the projectile when it was discharged * @param sequence_num an incrementing index of the packet in this projectile's lifetime; * suggests the "time alive" and indicates a place in packet ordering - * @param explode indicates the projectile should explode - * @param unk na + * @param end indicates the projectile has reached the end of its lifespan; + * usually, it should explode + * @param hit_target_guid the global unique identifier of the object the projwectile collided with; + * will be 0 if it reached the end of its life naturally, without colliding with anything */ final case class ProjectileStateMessage(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_original_orient : Vector3, sequence_num : Int, - explode : Boolean, - unk : Int) + end : Boolean, + hit_target_guid : PlanetSideGUID) extends PlanetSideGamePacket { type Packet = ProjectileStateMessage def opcode = GamePacketOpcode.ProjectileStateMessage @@ -67,8 +69,8 @@ object ProjectileStateMessage extends Marshallable[ProjectileStateMessage] { ("pitch" | Angular.codec_pitch) :: ("yaw" | Angular.codec_yaw()) :: ("sequence_num" | uint8) :: - ("explode" | bool) :: - ("unk" | uint16L) + ("end" | bool) :: + ("hit_target" | PlanetSideGUID.codec) ).xmap[ProjectileStateMessage] ( { case guid :: pos :: vel :: roll :: pitch :: yaw :: sequence_num :: explode :: unk :: HNil => diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala index e3318427..c55e8a3a 100644 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala @@ -1223,21 +1223,21 @@ object ObjectClass { case ObjectClass.portable_manned_turret_vs => ConstructorData(OneMannedFieldTurretData.codec, "field turret") case ObjectClass.router_telepad_deployable => DroppedItemData(TelepadDeployableData.codec, "telepad deployable") //projectiles - case ObjectClass.hunter_seeker_missile_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") - case ObjectClass.meteor_common => ConstructorData(TrackedProjectileData.codec, "meteor") - case ObjectClass.meteor_projectile_b_large => ConstructorData(TrackedProjectileData.codec, "meteor") - case ObjectClass.meteor_projectile_b_medium => ConstructorData(TrackedProjectileData.codec, "meteor") - case ObjectClass.meteor_projectile_b_small => ConstructorData(TrackedProjectileData.codec, "meteor") - case ObjectClass.meteor_projectile_large => ConstructorData(TrackedProjectileData.codec, "meteor") - case ObjectClass.meteor_projectile_medium => ConstructorData(TrackedProjectileData.codec, "meteor") - case ObjectClass.meteor_projectile_small => ConstructorData(TrackedProjectileData.codec, "meteor") - case ObjectClass.phoenix_missile_guided_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") - case ObjectClass.oicw_little_buddy => ConstructorData(TrackedProjectileData.codec, "projectile") - case ObjectClass.oicw_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") - case ObjectClass.sparrow_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") - case ObjectClass.starfire_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") - case ObjectClass.striker_missile_targeting_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") - case ObjectClass.wasp_rocket_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") + case ObjectClass.hunter_seeker_missile_projectile => ConstructorData(RemoteProjectileData.codec, "projectile") + case ObjectClass.meteor_common => ConstructorData(RemoteProjectileData.codec, "meteor") + case ObjectClass.meteor_projectile_b_large => ConstructorData(RemoteProjectileData.codec, "meteor") + case ObjectClass.meteor_projectile_b_medium => ConstructorData(RemoteProjectileData.codec, "meteor") + case ObjectClass.meteor_projectile_b_small => ConstructorData(RemoteProjectileData.codec, "meteor") + case ObjectClass.meteor_projectile_large => ConstructorData(RemoteProjectileData.codec, "meteor") + case ObjectClass.meteor_projectile_medium => ConstructorData(RemoteProjectileData.codec, "meteor") + case ObjectClass.meteor_projectile_small => ConstructorData(RemoteProjectileData.codec, "meteor") + case ObjectClass.phoenix_missile_guided_projectile => ConstructorData(RemoteProjectileData.codec, "projectile") + case ObjectClass.oicw_little_buddy => ConstructorData(RemoteProjectileData.codec, "projectile") + case ObjectClass.oicw_projectile => ConstructorData(RemoteProjectileData.codec, "projectile") + case ObjectClass.sparrow_projectile => ConstructorData(RemoteProjectileData.codec, "projectile") + case ObjectClass.starfire_projectile => ConstructorData(RemoteProjectileData.codec, "projectile") + case ObjectClass.striker_missile_targeting_projectile => ConstructorData(RemoteProjectileData.codec, "projectile") + case ObjectClass.wasp_rocket_projectile => ConstructorData(RemoteProjectileData.codec, "projectile") //vehicles case ObjectClass.ams => ConstructorData(VehicleData.codec(VehicleFormat.Utility), "ams") case ObjectClass.ams_destroyed => ConstructorData(DestroyedVehicleData.codec, "wreckage") diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/RemoteProjectileData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/RemoteProjectileData.scala new file mode 100644 index 00000000..39fe8cc5 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/objectcreate/RemoteProjectileData.scala @@ -0,0 +1,84 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game.objectcreate + +import net.psforever.packet.{Marshallable, PacketHelpers} +import scodec.{Attempt, Codec} +import scodec.codecs._ +import shapeless.{::, HNil} + +object RemoteProjectiles { + abstract class Data(val a : Int, val b : Int) + + final case object Meteor extends Data(0, 32) + final case object Wasp extends Data(0, 208) + final case object Sparrow extends Data(13107, 187) + final case object OICW extends Data(13107, 195) + final case object Striker extends Data(26214, 134) + final case object HunterSeeker extends Data(39577, 201) + final case object Starfire extends Data(39577, 249) + class OICWLittleBuddy(x : Int, y : Int) extends Data(x, y) +} + +object FlightPhysics extends Enumeration { + type Type = Value + + //valid (extremely small distance) (requires non-zero unk4, unk5) + val State3 = Value(3) + //valid (infinite) (if unk4 == 0 unk5 == 0, minimum distance + time) + val State4 = Value(4) + //valid(infinite) + val State5 = Value(5) + //valid (uses velocity) (infinite) + val State6 = Value(6) + //valid (uses velocity) (infinite) + val State7 = Value(7) + //valid (uses velocity) (time > 0 is infinite) (unk5 == 2) + val State15 = Value(15) + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L) +} + +/** + * A representation of a projectile that the server must intentionally convey to players other than the shooter. + * @param common_data common game object information + * @param u1 na; + * first part of the canned remote projectile data + * @param u2 na; + * second part of the canned remote projectile data + * @param unk3 na; + * does something to how the projectile flies + * @param unk4 na + * @param unk5 na + */ +final case class RemoteProjectileData(common_data : CommonFieldDataWithPlacement, + u1 : Int, + u2 : Int, + unk3 : FlightPhysics.Value, + unk4 : Int, + unk5 : Int + ) extends ConstructorData { + override def bitsize : Long = 33L + common_data.bitsize +} + +object RemoteProjectileData extends Marshallable[RemoteProjectileData] { + implicit val codec : Codec[RemoteProjectileData] = ( + ("data" | CommonFieldDataWithPlacement.codec) :: + ("u1" | uint16) :: + ("u2" | uint8) :: + ("unk3" | FlightPhysics.codec) :: + ("unk4" | uint(3)) :: + ("unk5" | uint2) + ).exmap[RemoteProjectileData] ( + { + case data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil => + Attempt.successful(RemoteProjectileData(data, u1, u2, unk3, unk4, unk5)) + +// case data => +// Attempt.failure(Err(s"invalid projectile data format - $data")) + }, + { + case RemoteProjectileData(data, u1, u2, unk3, unk4, unk5) => + Attempt.successful(data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil) + } + ) +} diff --git a/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala b/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala deleted file mode 100644 index 2a659762..00000000 --- a/common/src/main/scala/net/psforever/packet/game/objectcreate/TrackedProjectileData.scala +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.packet.game.objectcreate - -import net.psforever.packet.{Marshallable, PacketHelpers} -import scodec.{Attempt, Codec} -import scodec.codecs._ -import shapeless.{::, HNil} - -object TrackedProjectile extends Enumeration { - type Type = Value - - val OICWLittleBuddy = Value(-1) //?, ? - val Meteor = Value(32) //0, 32 - val Wasp = Value(208) //0, 208 - val Sparrow = Value(3355579) //13107, 187 - val OICW = Value(3355587) //13107, 195 - val Striker = Value(6710918) //26214, 134 - val HunterSeeker = Value(10131913) //39577, 201 - val Starfire = Value(10131961) //39577, 249 - - implicit val codec = PacketHelpers.createEnumerationCodec(this, uint24) -} - -object TrackedProjectiles { - abstract class Data(val is : TrackedProjectile.Value, val a : Int, val b : Int) - - final case object Meteor extends Data(TrackedProjectile.Meteor, 0, 32) - final case object Wasp extends Data(TrackedProjectile.Wasp, 0, 208) - final case object Sparrow extends Data(TrackedProjectile.Sparrow, 13107, 187) - final case object OICW extends Data(TrackedProjectile.OICW, 13107, 195) - final case object Striker extends Data(TrackedProjectile.Striker, 26214, 134) - final case object HunterSeeker extends Data(TrackedProjectile.HunterSeeker, 39577, 201) - final case object Starfire extends Data(TrackedProjectile.Starfire, 39577, 249) - class OICWLittleBuddy(x : Int, y : Int) extends Data(TrackedProjectile.OICWLittleBuddy, x, y) - - val values: Seq[TrackedProjectiles.Data] = Seq(Meteor, Wasp, Sparrow, OICW, Striker, HunterSeeker, Starfire) - - def apply(x : Int, y : Int) : TrackedProjectiles.Data = { - values.find(p => p.a == x && p.b == y) match { - case Some(projectileData) => projectileData - case None => - throw new IllegalArgumentException("no combination of projectile data equates to a defined projectile type") - } - } - - def apply(is : TrackedProjectile.Value) : TrackedProjectiles.Data = { - values.find(p => p.is == is) match { - case Some(projectileData) => projectileData - case None => - throw new IllegalArgumentException("unknown projectile type") - } - } -} - -object FlightPhysics extends Enumeration { - type Type = Value - - //valid (extremely small distance) (requires non-zero unk4, unk5) - val State3 = Value(3) - //valid (infinite) (if unk4 == 0 unk5 == 0, minimum distance + time) - val State4 = Value(4) - //valid(infinite) - val State5 = Value(5) - //valid (uses velocity) (infinite) - val State6 = Value(6) - //valid (uses velocity) (infinite) - val State7 = Value(7) - //valid (uses velocity) (time > 0 is infinite) (unk5 == 2) - val State15 = Value(15) - - implicit val codec = PacketHelpers.createEnumerationCodec(this, uint4L) -} - -/** - * A representation of a projectile that the server must intentionally convey to players other than the shooter. - * @param common_data common game object information - * @param unk3 na - */ -final case class TrackedProjectileData(common_data : CommonFieldDataWithPlacement, - u1 : Int, - u2 : Int, - unk3 : FlightPhysics.Value, - unk4 : Int, - unk5 : Int - ) extends ConstructorData { - override def bitsize : Long = 33L + common_data.bitsize -} - -object TrackedProjectileData extends Marshallable[TrackedProjectileData] { - implicit val codec : Codec[TrackedProjectileData] = ( - ("data" | CommonFieldDataWithPlacement.codec) :: - ("u1" | uint16) :: - ("u2" | uint8) :: - ("unk3" | FlightPhysics.codec) :: - ("unk4" | uint(3)) :: - ("unk5" | uint2) - ).exmap[TrackedProjectileData] ( - { - case data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil => - Attempt.successful(TrackedProjectileData(data, u1, u2, unk3, unk4, unk5)) - -// case data => -// Attempt.failure(Err(s"invalid projectile data format - $data")) - }, - { - case TrackedProjectileData(data, u1, u2, unk3, unk4, unk5) => - Attempt.successful(data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil) - } - ) -} diff --git a/common/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala index 98c9705d..8317ec63 100644 --- a/common/src/main/scala/services/avatar/AvatarService.scala +++ b/common/src/main/scala/services/avatar/AvatarService.scala @@ -165,17 +165,13 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", PlanetSideGUID(0), AvatarResponse.ProjectileAutoLockAwareness(mode)) ) - case AvatarAction.ProjectileTrackingAwareness(guid) => - AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", PlanetSideGUID(0), AvatarResponse.ProjectileTrackingAwareness(guid)) - ) case AvatarAction.ProjectileExplodes(player_guid, projectile_guid, projectile) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileExplodes(projectile_guid, projectile)) ) - case AvatarAction.ProjectileState(player_guid, projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3) => + case AvatarAction.ProjectileState(player_guid, projectile_guid, shot_pos, shot_vel, shot_orient, sequence, end, target) => AvatarEvents.publish( - AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3)) + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, sequence, end, target)) ) case AvatarAction.PickupItem(player_guid, zone, target, slot, item, unk) => janitor forward RemoverActor.ClearSpecific(List(item), zone) diff --git a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala index ade58d68..9399620f 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala @@ -50,9 +50,8 @@ object AvatarAction { final case class PlayerState(player_guid : PlanetSideGUID, pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Action final case class PickupItem(player_guid : PlanetSideGUID, zone : Zone, target : PlanetSideGameObject with Container, slot : Int, item : Equipment, unk : Int = 0) extends Action final case class ProjectileAutoLockAwareness(mode : Int) extends Action - final case class ProjectileTrackingAwareness(projectile_guid : PlanetSideGUID) extends Action final case class ProjectileExplodes(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, projectile : Projectile) extends Action - final case class ProjectileState(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk1 : Int, unk2 : Boolean, unk3 : Int) extends Action + final case class ProjectileState(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, sequence : Int, end : Boolean, hit_target : PlanetSideGUID) extends Action final case class PutDownFDU(player_guid : PlanetSideGUID) extends Action final case class Release(player : Player, zone : Zone, time : Option[FiniteDuration] = None) extends Action final case class Revive(target_guid: PlanetSideGUID) extends Action diff --git a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala index 2f60e87a..9ee2e90a 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceResponse.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceResponse.scala @@ -42,9 +42,8 @@ object AvatarResponse { final case class PlanetsideAttributeSelf(attribute_type : Int, attribute_value : Long) extends Response final case class PlayerState(pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Response final case class ProjectileAutoLockAwareness(mode : Int) extends Response - final case class ProjectileTrackingAwareness(projectile_guid : PlanetSideGUID) extends Response final case class ProjectileExplodes(projectile_guid : PlanetSideGUID, projectile : Projectile) extends Response - final case class ProjectileState(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk1 : Int, unk2 : Boolean, unk3 : Int) extends Response + final case class ProjectileState(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, sequence : Int, end : Boolean, hit_target : PlanetSideGUID) extends Response final case class PutDownFDU(target_guid : PlanetSideGUID) extends Response final case class Release(player : Player) extends Response final case class Reload(weapon_guid : PlanetSideGUID) extends Response diff --git a/common/src/test/scala/game/DamageFeedbackMessageTest.scala b/common/src/test/scala/game/DamageFeedbackMessageTest.scala new file mode 100644 index 00000000..e2f1cebf --- /dev/null +++ b/common/src/test/scala/game/DamageFeedbackMessageTest.scala @@ -0,0 +1,68 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class DamageFeedbackMessageTest extends Specification { + val string = hex"7b 3d842f610b2040000000" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case DamageFeedbackMessage(unk1, unk2, unk2a, unk2b, unk2c, unk3, unk3a, unk3b, unk3c, unk3d, unk4, unk5, unk6) => + unk1 mustEqual 3 + unk2 mustEqual true + unk2a.contains(PlanetSideGUID(2913)) mustEqual true + unk2b.isEmpty mustEqual true + unk2c.isEmpty mustEqual true + unk3 mustEqual true + unk3a.contains(PlanetSideGUID(2913)) mustEqual true + unk3b.isEmpty mustEqual true + unk3c.isEmpty mustEqual true + unk3d.isEmpty mustEqual true + unk4 mustEqual 1 + unk5 mustEqual 2 + unk6 mustEqual 0 + case _ => + ko + } + } + + "encode" in { + val msg = DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + + "failures" in { + //unk2: no parameters + DamageFeedbackMessage(3, true, None, None, None, true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError] + //unk2: two exclusive parameters + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), Some("error"), None, true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError] + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, Some(5), true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError] + DamageFeedbackMessage(3, true, None, Some("error"), Some(5), true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError] + //unk2: all parameters + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), Some("error"), Some(5), true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError] + //unk2: mismatched flag for strings + DamageFeedbackMessage(3, true, None, None, Some(5), true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError] + DamageFeedbackMessage(3, false, None, Some("error"), None, true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError] + + //unk3: no parameters + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, None, None, None, None, 1, 2, 0) must throwA[AssertionError] + //unk3: two exclusive parameters + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, Some(PlanetSideGUID(2913)), Some("error"), None, None, 1, 2, 0) must throwA[AssertionError] + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, Some(PlanetSideGUID(2913)), None, Some(5), None, 1, 2, 0) must throwA[AssertionError] + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, None, Some("error"), Some(5), Some(1), 1, 2, 0) must throwA[AssertionError] + //unk3: all parameters + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, Some(PlanetSideGUID(2913)), Some("error"), Some(5), None, 1, 2, 0) must throwA[AssertionError] + //unk3: mismatched fields + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, Some(PlanetSideGUID(2913)), None, None, Some(5), 1, 2, 0) must throwA[AssertionError] + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, None, Some("Error"), None, None, 1, 2, 0) must throwA[AssertionError] + //unk3: mismatched flag for strings + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, true, None, None, Some(5), None, 1, 2, 0) must throwA[AssertionError] + DamageFeedbackMessage(3, true, Some(PlanetSideGUID(2913)), None, None, false, None, Some("error"), None, None, 1, 2, 0) must throwA[AssertionError] + } +} diff --git a/common/src/test/scala/game/DamageMessageTest.scala b/common/src/test/scala/game/DamageMessageTest.scala new file mode 100644 index 00000000..1eb44a33 --- /dev/null +++ b/common/src/test/scala/game/DamageMessageTest.scala @@ -0,0 +1,30 @@ +// Copyright (c) 2019 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class DamageMessageTest extends Specification { + val string = hex"0b610b02610b00" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case DamageMessage(guid1, unk1, guid2, unk2) => + guid1 mustEqual PlanetSideGUID(2913) + unk1 mustEqual 2 + guid2 mustEqual PlanetSideGUID(2913) + unk2 mustEqual false + case _ => + ko + } + } + + "encode" in { + val msg = DamageMessage(PlanetSideGUID(2913), 2, PlanetSideGUID(2913), false) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } +} diff --git a/common/src/test/scala/game/ProjectileStateMessageTest.scala b/common/src/test/scala/game/ProjectileStateMessageTest.scala index 6c0afc7d..b8ff8f84 100644 --- a/common/src/test/scala/game/ProjectileStateMessageTest.scala +++ b/common/src/test/scala/game/ProjectileStateMessageTest.scala @@ -19,7 +19,7 @@ class ProjectileStateMessageTest extends Specification { orient mustEqual Vector3(0, 22.5f, 146.25f) sequence mustEqual 2 explode mustEqual false - unk mustEqual 0 + unk mustEqual PlanetSideGUID(0) case _ => ko } @@ -33,7 +33,7 @@ class ProjectileStateMessageTest extends Specification { Vector3(0, 22.5f, 146.25f), 2, false, - 0 + PlanetSideGUID(0) ) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector diff --git a/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala b/common/src/test/scala/game/objectcreate/RemoteProjectileDataTest.scala similarity index 92% rename from common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala rename to common/src/test/scala/game/objectcreate/RemoteProjectileDataTest.scala index d1d4d9c1..8e09c859 100644 --- a/common/src/test/scala/game/objectcreate/TrackedProjectileDataTest.scala +++ b/common/src/test/scala/game/objectcreate/RemoteProjectileDataTest.scala @@ -8,11 +8,11 @@ import net.psforever.types.{PlanetSideEmpire, Vector3} import org.specs2.mutable._ import scodec.bits._ -class TrackedProjectileDataTest extends Specification { +class RemoteProjectileDataTest extends Specification { val string_striker_projectile = hex"17 C5000000 A4B 009D 4C129 0CB0A 9814 00 F5 E3 040000666686400" val string_hunter_seeker_missile_projectile = hex"17 c5000000 ca9 ab9e af127 ec465 3723 00 15 c4 2400009a99c9400" - "TrackedProjectileData" should { + "RemoteProjectileData" should { "decode (striker_missile_targeting_projectile)" in { PacketCoding.DecodePacket(string_striker_projectile).require match { case ObjectCreateMessage(len, cls, guid, parent, data) => @@ -21,7 +21,7 @@ class TrackedProjectileDataTest extends Specification { guid mustEqual PlanetSideGUID(40192) parent.isDefined mustEqual false data match { - case TrackedProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, lim, unk3, unk4, unk5) => + case RemoteProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, lim, unk3, unk4, unk5) => pos.coord mustEqual Vector3(4644.5938f, 5472.0938f, 82.375f) pos.orient mustEqual Vector3(0, 30.9375f, 171.5625f) deploy.faction mustEqual PlanetSideEmpire.TR @@ -55,7 +55,7 @@ class TrackedProjectileDataTest extends Specification { guid mustEqual PlanetSideGUID(40619) parent.isDefined mustEqual false data match { - case TrackedProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, lim, unk3, unk4, unk5) => + case RemoteProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, lim, unk3, unk4, unk5) => pos.coord mustEqual Vector3(3621.3672f, 2701.8438f, 140.85938f) pos.orient mustEqual Vector3(0, 300.9375f, 258.75f) deploy.faction mustEqual PlanetSideEmpire.NC @@ -82,7 +82,7 @@ class TrackedProjectileDataTest extends Specification { } "encode (striker_missile_targeting_projectile)" in { - val obj = TrackedProjectileData( + val obj = RemoteProjectileData( CommonFieldDataWithPlacement( PlacementData(4644.5938f, 5472.0938f, 82.375f, 0f, 30.9375f, 171.5625f), CommonFieldData(PlanetSideEmpire.TR, false, false, true, None, false, None, None, PlanetSideGUID(0)) @@ -100,7 +100,7 @@ class TrackedProjectileDataTest extends Specification { } "encode (hunter_seeker_missile_projectile)" in { - val obj = TrackedProjectileData( + val obj = RemoteProjectileData( CommonFieldDataWithPlacement( PlacementData(3621.3672f, 2701.8438f, 140.85938f, 0, 300.9375f, 258.75f), CommonFieldData(PlanetSideEmpire.NC, false, false, true, None, false, None, None, PlanetSideGUID(0)) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 9be7c388..b0e6f5a5 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -54,8 +54,9 @@ import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalSer import services.chat._ import services.vehicle.support.TurretUpgrader import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} -import services.teamwork.{SquadAction => SquadServiceAction, SquadServiceMessage, SquadServiceResponse, SquadResponse, SquadService} +import services.teamwork.{SquadResponse, SquadService, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction} +import scala.collection.concurrent.TrieMap import scala.collection.mutable.LongMap import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global @@ -68,6 +69,8 @@ import net.psforever.objects.vehicles.Utility.InternalTelepad import services.local.support.{HackCaptureActor, RouterTelepadActivation} import services.support.SupportActor +import scala.collection.mutable + class WorldSessionActor extends Actor with MDCContextAware { import WorldSessionActor._ @@ -153,9 +156,6 @@ class WorldSessionActor extends Actor with MDCContextAware { lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L var serverTime : Long = 0 - var projectileAutoLockTargets : List[PlanetSideGUID] = Nil - var projectileAutoLocksActive : Set[PlanetSideGUID] = Set.empty - var amsSpawnPoints : List[SpawnPoint] = Nil var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj @@ -165,6 +165,8 @@ class WorldSessionActor extends Actor with MDCContextAware { var cargoDismountTimer : Cancellable = DefaultCancellable.obj var antChargingTick : Cancellable = DefaultCancellable.obj var antDischargingTick : Cancellable = DefaultCancellable.obj + var weaponAutoLockDecay : Cancellable = DefaultCancellable.obj + /** * Convert a boolean value into an integer value. @@ -1220,6 +1222,19 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"Received a direct message: $pkt") sendResponse(pkt) + case LoadedRemoteProjectile(projectile_guid) => + continent.GUID(projectile_guid) match { + case Some(obj : Projectile) if obj.profile.ExistsOnRemoteClients => + //spawn projectile on other clients + val projectileGlobalUID = obj.GUID + val definition = obj.Definition + avatarService ! AvatarServiceMessage( + continent.Id, + AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, obj, definition.Packet.ConstructorData(obj).get) + ) + case _ => ; + } + case default => log.warn(s"Invalid packet class received: $default from $sender") } @@ -1476,43 +1491,23 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - case AvatarResponse.ProjectileTrackingAwareness(projectile_guid) => - continent.GUID(projectile_guid) match { - case Some(projectile : Projectile) => - val mode = 7 + (projectile.profile == GlobalDefinitions.wasp_rocket_projectile) - sendResponse(GenericActionMessage(mode)) - case _ => ; - } - case AvatarResponse.ProjectileExplodes(projectile_guid, projectile) => -// //turn the projectile into a boomer -// sendResponse( -// ObjectCreateMessage( -// GlobalDefinitions.boomer.ObjectId, -// projectile_guid, -// CommonFieldDataWithPlacement( -// PlacementData(projectile.Position, projectile.Orientation), -// CommonFieldData(projectile.owner.Faction,false,false,false,None,false,Some(false),None, PlanetSideGUID(0)) -// ) -// ) -// ) -// //detonate the boomer, then clean it up -// sendResponse(TriggerEffectMessage(projectile_guid, "detonate_boomer")) + sendResponse(ProjectileStateMessage(projectile_guid, projectile.Position, Vector3.Zero, projectile.Orientation, 0, true, PlanetSideGUID(0))) sendResponse(ObjectDeleteMessage(projectile_guid, 2)) case AvatarResponse.ProjectileAutoLockAwareness(mode) => sendResponse(GenericActionMessage(mode)) - case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3) => + case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) => if(tplayer_guid != guid) { - sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3)) + sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid)) // continent.GUID(projectile_guid) match { // case Some(obj) => // val definition = obj.Definition // sendResponse(ObjectCreateMessage(definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get)) // case _ => ; // } - } + } case AvatarResponse.PutDownFDU(target) => if(tplayer_guid != guid) { @@ -3495,11 +3490,8 @@ class WorldSessionActor extends Actor with MDCContextAware { import net.psforever.objects.GlobalDefinitions._ import net.psforever.types.CertificationType._ - val faction = - if(sessionId % 5 == 0) PlanetSideEmpire.TR - else if(sessionId % 3 == 0) PlanetSideEmpire.NC - else PlanetSideEmpire.VS - val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1) + val faction = PlanetSideEmpire.VS + val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1) avatar.Certifications += StandardAssault avatar.Certifications += MediumAssault avatar.Certifications += StandardExoSuit @@ -3548,14 +3540,13 @@ class WorldSessionActor extends Actor with MDCContextAware { //player.Orientation = Vector3(0f, 0f, 132.1875f) // player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction)) - player.Slot(2).Equipment = Tool(hunterseeker) //Tool(suppressor) + player.Slot(2).Equipment = Tool(suppressor) player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction)) - player.Slot(6).Equipment = AmmoBox(hunter_seeker_missile, 65535) - //player.Slot(6).Equipment = AmmoBox(bullet_9mm) - //player.Slot(9).Equipment = AmmoBox(bullet_9mm) - //player.Slot(12).Equipment = AmmoBox(bullet_9mm) - //player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) - //player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) + player.Slot(6).Equipment = AmmoBox(bullet_9mm) + player.Slot(9).Equipment = AmmoBox(bullet_9mm) + player.Slot(12).Equipment = AmmoBox(bullet_9mm) + player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) + player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit) player.Inventory.Items.foreach { _.obj.Faction = faction } @@ -3896,28 +3887,29 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, jump_thrust, is_cloaking, unk5, unk6) => if(deadState == DeadState.Alive) { + val time = System.currentTimeMillis() if (timeDL != 0) { - if (System.currentTimeMillis() - timeDL > 500) { + if (time - timeDL > 500) { player.Stamina = player.Stamina - 1 avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) - timeDL = System.currentTimeMillis() + timeDL = time } } if (timeSurge != 0) { - if (System.currentTimeMillis() - timeSurge > 500 && player.ExoSuit == ExoSuitType.Agile) { + if (time - timeSurge > 500 && player.ExoSuit == ExoSuitType.Agile) { player.Stamina = player.Stamina - 1 avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) - timeSurge = System.currentTimeMillis() + timeSurge = time } - else if (System.currentTimeMillis() - timeSurge > 333 && player.ExoSuit == ExoSuitType.Reinforced) { + else if (time - timeSurge > 333 && player.ExoSuit == ExoSuitType.Reinforced) { player.Stamina = player.Stamina - 1 avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) - timeSurge = System.currentTimeMillis() + timeSurge = time } - else if (System.currentTimeMillis() - timeSurge > 1000 && ( player.ExoSuit == ExoSuitType.Infiltration || player.ExoSuit == ExoSuitType.Standard )) { + else if (time - timeSurge > 1000 && ( player.ExoSuit == ExoSuitType.Infiltration || player.ExoSuit == ExoSuitType.Standard )) { player.Stamina = player.Stamina - 1 avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) - timeSurge = System.currentTimeMillis() + timeSurge = time } } if (player.Stamina == 0) { @@ -3938,10 +3930,6 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Stamina = player.Stamina + 1 avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) } - - if(!player.Crouching && is_crouching) { - //... - } player.Position = pos player.Velocity = vel player.Orientation = Vector3(player.Orientation.x, pitch, yaw) @@ -4048,7 +4036,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) => //log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2") - case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3) => + case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) => log.info(s"ProjectileState: $msg") projectiles(projectile_guid.guid - Projectile.BaseUID) match { case Some(projectile) if projectile.HasGUID => @@ -4056,9 +4044,10 @@ class WorldSessionActor extends Actor with MDCContextAware { projectile.Position = shot_pos projectile.Orientation = shot_orient projectile.Velocity = shot_vel - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectileGlobalUID, shot_pos, shot_vel, shot_orient, unk1, unk2, unk3)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectileGlobalUID, shot_pos, shot_vel, shot_orient, seq, end, target_guid)) + case _ => - log.error(s"ProjectileState: the projectile@${projectile_guid.guid} can not be found") + log.error(s"ProjectileState: constructed projectile ${projectile_guid.guid} can not be found") } case msg @ ReleaseAvatarRequestMessage() => @@ -4334,9 +4323,15 @@ class WorldSessionActor extends Actor with MDCContextAware { } case msg @ ChangeFireStateMessage_Start(item_guid) => - log.info("ChangeFireState_Start: " + msg) + log.trace("ChangeFireState_Start: " + msg) if(shooting.isEmpty) { FindEquipment match { + //special case - suppress the decimator's alternate fire mode + case Some(tool : Tool) + if tool.Projectile == GlobalDefinitions.phoenix_missile_guided_projectile && + (tool.Magazine > 0 || prefire.contains(item_guid)) => + prefire = None + shooting = Some(item_guid) case Some(tool : Tool) => if(tool.Magazine > 0 || prefire.contains(item_guid)) { prefire = None @@ -4357,7 +4352,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case msg @ ChangeFireStateMessage_Stop(item_guid) => - log.info("ChangeFireState_Stop: " + msg) + log.trace("ChangeFireState_Stop: " + msg) prefire = None val weapon : Option[Equipment] = if(shooting.contains(item_guid)) { shooting = None @@ -4365,13 +4360,18 @@ class WorldSessionActor extends Actor with MDCContextAware { FindEquipment } else { - //some weapons, e.g., the decimator, do not send a ChangeFireState_Start on the last shot FindEquipment match { - case Some(tool) => - if(tool.Definition == GlobalDefinitions.phoenix) { + case Some(tool : Tool) => + //the decimator does not send a ChangeFireState_Start on the last shot + if(tool.Definition == GlobalDefinitions.phoenix && + tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) { + //suppress the decimator's alternate fire mode, however avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid)) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid)) } + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid)) + Some(tool) + case Some(tool) => //permissible, for now + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid)) Some(tool) case _ => log.warn(s"ChangeFireState_Stop: received an unexpected message about $item_guid") @@ -4589,6 +4589,10 @@ class WorldSessionActor extends Actor with MDCContextAware { } else { projectile.Miss() + if(projectile.profile.ExistsOnRemoteClients && projectile.HasGUID) { + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile)) + taskResolver ! UnregisterProjectile(projectile) + } } case None => log.warn(s"RequestDestroy: projectile ${object_guid.guid} has never been fired") @@ -5485,7 +5489,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } val (angle, attribution, acceptableDistanceToOwner) = obj match { case p : Player => - (p.Orientation, tool.Definition.ObjectId, 10f) //TODO upper body facing + (p.Orientation, tool.Definition.ObjectId, 10f + (if(p.Velocity.nonEmpty) { 5f } else { 0f })) //TODO upper body facing case v : Vehicle if v.Definition.CanFly => (tool.Orientation, obj.Definition.ObjectId, 1000f) //TODO this is too simplistic to find proper angle case _ : Vehicle => @@ -5514,28 +5518,17 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("Lazing position: " + pos2.toString) case msg @ ObjectDetectedMessage(guid1, guid2, unk, targets) => + //log.info(s"Detection: $msg") FindWeapon match { case Some(weapon) if weapon.Projectile.AutoLock => //projectile with auto-lock instigates a warning on the target - val mode = 7 + (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile) - projectileAutoLockTargets = targets - targets - .map { continent.GUID } - .collect { - case Some(obj : Vehicle) if !obj.Cloaked => - //TODO vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.ProjectileAutoLockAwareness(mode)) - obj.Seats.values - .collect { case seat if seat.isOccupied => - avatarService ! AvatarServiceMessage(s"${seat.Occupant.get.Name}", AvatarAction.ProjectileAutoLockAwareness(mode)) - } - case Some(obj : Mountable) => - obj.Seats.values - .collect { case seat if seat.isOccupied => - avatarService ! AvatarServiceMessage(s"${seat.Occupant.get.Name}", AvatarAction.ProjectileAutoLockAwareness(mode)) - } - case Some(obj : Player) if obj.ExoSuit == ExoSuitType.MAX => - avatarService ! AvatarServiceMessage(s"${obj.Name}", AvatarAction.ProjectileAutoLockAwareness(mode)) + val detectedTargets = FindDetectedProjectileTargets(targets) + if(detectedTargets.nonEmpty) { + val mode = 7 + (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile) + detectedTargets.foreach { target => + avatarService ! AvatarServiceMessage(target, AvatarAction.ProjectileAutoLockAwareness(mode)) } + } case _ => ; } @@ -5565,6 +5558,8 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"Splash: $msg") FindProjectileEntry(projectile_guid) match { case Some(projectile) => + projectile.Position = explosion_pos + projectile.Velocity = projectile_vel continent.GUID(direct_victim_uid) match { case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match { @@ -6170,11 +6165,7 @@ class WorldSessionActor extends Actor with MDCContextAware { TaskResolver.GiveTask( new Task() { private val globalProjectile = obj - private val localAnnounce = avatarService - private val localMsg = AvatarServiceMessage( - continent.Id, - AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, obj, definition.Packet.ConstructorData(obj).get) - ) + private val localAnnounce = self override def isComplete : Task.Resolution.Value = { if(globalProjectile.HasGUID) { @@ -6186,7 +6177,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } def Execute(resolver : ActorRef) : Unit = { - localAnnounce ! localMsg + localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID) resolver ! scala.util.Success(this) } }, List(GUIDTask.RegisterObjectTask(obj)(continent.GUID)) @@ -10093,6 +10084,20 @@ class WorldSessionActor extends Actor with MDCContextAware { squadUpdateCounter = (squadUpdateCounter + 1) % queuedSquadActions.length } + def FindDetectedProjectileTargets(targets : Iterable[PlanetSideGUID]) : Iterable[String] = { + targets + .map { continent.GUID } + .flatMap { + case Some(obj : Vehicle) if !obj.Cloaked => + //TODO hint: vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.ProjectileAutoLockAwareness(mode)) + obj.Seats.values.collect { case seat if seat.isOccupied => seat.Occupant.get.Name } + case Some(obj : Mountable) => + obj.Seats.values.collect { case seat if seat.isOccupied => seat.Occupant.get.Name } + case Some(obj : Player) if obj.ExoSuit == ExoSuitType.MAX => + Seq(obj.Name) + } + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) @@ -10256,4 +10261,6 @@ object WorldSessionActor { private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID) private final case class FinalizeDeployable(obj : PlanetSideGameObject with Deployable, tool : ConstructionItem, index : Int) + + private final case class LoadedRemoteProjectile(projectile_guid : PlanetSideGUID) } From ed7406f25f61fb0756e5215559b5fe5b87c09638 Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 14 Nov 2019 13:51:08 -0500 Subject: [PATCH 08/12] last minute minor corrections --- .../packet/game/DamageFeedbackMessage.scala | 22 ++++++++++++++----- .../psforever/packet/game/DamageMessage.scala | 7 ++++++ .../game/DamageFeedbackMessageTest.scala | 2 +- .../src/main/scala/WorldSessionActor.scala | 3 +-- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala b/common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala index beaffd21..0fe3cb82 100644 --- a/common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala @@ -21,14 +21,24 @@ final case class DamageFeedbackMessage(unk1 : Int, unk6 : Int) extends PlanetSideGamePacket { assert( - if(unk2a.nonEmpty) unk2b.isEmpty && unk2c.isEmpty - else if(unk2b.nonEmpty) unk2 && unk2a.isEmpty && unk2c.isEmpty - else unk2a.isEmpty && !unk2 && unk2b.isEmpty && unk2c.nonEmpty + { + val unk2aEmpty = unk2a.isEmpty + val unk2bEmpty = unk2b.isEmpty + val unk2cEmpty = unk2c.isEmpty + if(unk2a.nonEmpty) unk2bEmpty && unk2cEmpty + else if(unk2b.nonEmpty) unk2 && unk2aEmpty && unk2cEmpty + else unk2aEmpty && !unk2 && unk2bEmpty && unk2c.nonEmpty + } ) assert( - if(unk3a.nonEmpty) unk3b.isEmpty && unk3c.isEmpty - else if(unk3b.nonEmpty) unk3 && unk3a.isEmpty && unk3c.isEmpty - else unk3a.isEmpty && !unk3 && unk3b.isEmpty && unk3c.nonEmpty + { + val unk3aEmpty = unk3a.isEmpty + val unk3bEmpty = unk3b.isEmpty + val unk3cEmpty = unk3c.isEmpty + if(unk3a.nonEmpty) unk3bEmpty && unk3cEmpty + else if(unk3b.nonEmpty) unk3 && unk3aEmpty && unk3cEmpty + else unk3aEmpty && !unk3 && unk3bEmpty && unk3c.nonEmpty + } ) assert(unk3a.isEmpty == unk3d.nonEmpty) diff --git a/common/src/main/scala/net/psforever/packet/game/DamageMessage.scala b/common/src/main/scala/net/psforever/packet/game/DamageMessage.scala index 59e64d5f..08f78e9e 100644 --- a/common/src/main/scala/net/psforever/packet/game/DamageMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/DamageMessage.scala @@ -6,6 +6,13 @@ import net.psforever.types.Angular import scodec.Codec import scodec.codecs._ +/** + * na + * @param guid1 na + * @param unk1 na + * @param guid2 na + * @param unk2 na + */ final case class DamageMessage(guid1 : PlanetSideGUID, unk1 : Int, guid2 : PlanetSideGUID, diff --git a/common/src/test/scala/game/DamageFeedbackMessageTest.scala b/common/src/test/scala/game/DamageFeedbackMessageTest.scala index e2f1cebf..983a6f7d 100644 --- a/common/src/test/scala/game/DamageFeedbackMessageTest.scala +++ b/common/src/test/scala/game/DamageFeedbackMessageTest.scala @@ -37,7 +37,7 @@ class DamageFeedbackMessageTest extends Specification { pkt mustEqual string } - "failures" in { + "assert catches" in { //unk2: no parameters DamageFeedbackMessage(3, true, None, None, None, true, Some(PlanetSideGUID(2913)), None, None, None, 1, 2, 0) must throwA[AssertionError] //unk2: two exclusive parameters diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index b0e6f5a5..400952c6 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -165,7 +165,6 @@ class WorldSessionActor extends Actor with MDCContextAware { var cargoDismountTimer : Cancellable = DefaultCancellable.obj var antChargingTick : Cancellable = DefaultCancellable.obj var antDischargingTick : Cancellable = DefaultCancellable.obj - var weaponAutoLockDecay : Cancellable = DefaultCancellable.obj /** @@ -3491,7 +3490,7 @@ class WorldSessionActor extends Actor with MDCContextAware { import net.psforever.types.CertificationType._ val faction = PlanetSideEmpire.VS - val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1) + val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1) avatar.Certifications += StandardAssault avatar.Certifications += MediumAssault avatar.Certifications += StandardExoSuit From f8836f1cc4eef0a6eb92ca48a690786ee6b011b3 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 15 Nov 2019 16:21:35 -0500 Subject: [PATCH 09/12] no_projectile for Tools that have no projectile; condensing cases in WSA --- .../net/psforever/objects/GlobalDefinitions.scala | 8 ++++++++ .../psforever/objects/ballistics/Projectiles.scala | 2 ++ pslogin/src/main/scala/WorldSessionActor.scala | 11 ++++------- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index b6dad339..f6521995 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -69,6 +69,8 @@ object GlobalDefinitions { /* Projectiles */ + val no_projectile = new ProjectileDefinition(0) //also called none in ADB + val bullet_105mm_projectile = ProjectileDefinition(Projectiles.bullet_105mm_projectile) val bullet_12mm_projectile = ProjectileDefinition(Projectiles.bullet_12mm_projectile) @@ -1889,6 +1891,9 @@ object GlobalDefinitions { private def init_projectile() : Unit = { val projectileConverter : ProjectileConverter = new ProjectileConverter + no_projectile.Name = "none" + ProjectileDefinition.CalculateDerivedFields(no_projectile) + bullet_105mm_projectile.Name = "105mmbullet_projectile" bullet_105mm_projectile.Damage0 = 150 bullet_105mm_projectile.Damage1 = 300 @@ -4331,6 +4336,7 @@ object GlobalDefinitions { medicalapplicator.Name = "medicalapplicator" medicalapplicator.Size = EquipmentSize.Pistol medicalapplicator.AmmoTypes += health_canister + medicalapplicator.ProjectileTypes += no_projectile medicalapplicator.FireModes += new FireModeDefinition medicalapplicator.FireModes.head.AmmoTypeIndices += 0 medicalapplicator.FireModes.head.AmmoSlotIndex = 0 @@ -4345,6 +4351,7 @@ object GlobalDefinitions { nano_dispenser.Size = EquipmentSize.Rifle nano_dispenser.AmmoTypes += armor_canister nano_dispenser.AmmoTypes += upgrade_canister + nano_dispenser.ProjectileTypes += no_projectile nano_dispenser.FireModes += new FireModeDefinition nano_dispenser.FireModes.head.AmmoTypeIndices += 0 nano_dispenser.FireModes.head.AmmoTypeIndices += 1 @@ -4358,6 +4365,7 @@ object GlobalDefinitions { bank.Name = "bank" bank.Size = EquipmentSize.Pistol bank.AmmoTypes += armor_canister + bank.ProjectileTypes += no_projectile bank.FireModes += new FireModeDefinition bank.FireModes.head.AmmoTypeIndices += 0 bank.FireModes.head.AmmoSlotIndex = 0 diff --git a/common/src/main/scala/net/psforever/objects/ballistics/Projectiles.scala b/common/src/main/scala/net/psforever/objects/ballistics/Projectiles.scala index cb305609..dc275395 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/Projectiles.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/Projectiles.scala @@ -5,6 +5,8 @@ package net.psforever.objects.ballistics * An `Enumeration` of all the projectile types in the game, paired with their object id as the `Value`. */ object Projectiles extends Enumeration { + final val no_projectile = Value(0) + final val bullet_105mm_projectile = Value(1) final val bullet_12mm_projectile = Value(4) final val bullet_12mm_projectileb = Value(5) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 400952c6..e46b167b 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -4325,17 +4325,14 @@ class WorldSessionActor extends Actor with MDCContextAware { log.trace("ChangeFireState_Start: " + msg) if(shooting.isEmpty) { FindEquipment match { - //special case - suppress the decimator's alternate fire mode - case Some(tool : Tool) - if tool.Projectile == GlobalDefinitions.phoenix_missile_guided_projectile && - (tool.Magazine > 0 || prefire.contains(item_guid)) => - prefire = None - shooting = Some(item_guid) case Some(tool : Tool) => if(tool.Magazine > 0 || prefire.contains(item_guid)) { prefire = None shooting = Some(item_guid) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid)) + //special case - suppress the decimator's alternate fire mode, by projectile + if(tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) { + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid)) + } } else { log.warn(s"ChangeFireState_Start: ${tool.Definition.Name} magazine is empty before trying to shoot bullet") From 111facc070e27f3a9fe4a2ab6683c0cc1a4b4a59 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 18 Nov 2019 08:22:32 -0500 Subject: [PATCH 10/12] added projectile index overrides for gluegun --- .../main/scala/net/psforever/objects/GlobalDefinitions.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index f6521995..cbe37685 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -4355,6 +4355,8 @@ object GlobalDefinitions { nano_dispenser.FireModes += new FireModeDefinition nano_dispenser.FireModes.head.AmmoTypeIndices += 0 nano_dispenser.FireModes.head.AmmoTypeIndices += 1 + nano_dispenser.FireModes.head.ProjectileTypeIndices += 0 //armor_canister + nano_dispenser.FireModes.head.ProjectileTypeIndices += 0 //upgrade_canister nano_dispenser.FireModes.head.AmmoSlotIndex = 0 nano_dispenser.FireModes.head.Magazine = 100 nano_dispenser.FireModes.head.CustomMagazine = Ammo.upgrade_canister -> 1 From 63d7fbefa0164ec42b1e93685950fd39906ed025 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 27 Nov 2019 08:38:30 -0500 Subject: [PATCH 11/12] accommodating short-lived projectiles, as well as out-of-order and late-to-register projectiles --- .../scala/services/avatar/AvatarService.scala | 4 +- .../avatar/AvatarServiceMessage.scala | 2 +- .../src/main/scala/WorldSessionActor.scala | 110 +++++++++++++++--- 3 files changed, 97 insertions(+), 19 deletions(-) diff --git a/common/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala index 8317ec63..f2c45843 100644 --- a/common/src/main/scala/services/avatar/AvatarService.scala +++ b/common/src/main/scala/services/avatar/AvatarService.scala @@ -131,10 +131,10 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(pkt)) ) - case AvatarAction.LoadProjectile(player_guid, object_id, obj, cdata) => + case AvatarAction.LoadProjectile(player_guid, object_id, object_guid, cdata) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadProjectile( - ObjectCreateMessage(object_id, obj.GUID, cdata) + ObjectCreateMessage(object_id, object_guid, cdata) )) ) case AvatarAction.ObjectDelete(player_guid, item_guid, unk) => diff --git a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala index 9399620f..0396a4c3 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala @@ -41,7 +41,7 @@ object AvatarAction { final case class HitHint(source_guid : PlanetSideGUID, player_guid : PlanetSideGUID) extends Action final case class KilledWhileInVehicle(player_guid : PlanetSideGUID) extends Action final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action - final case class LoadProjectile(player_guid : PlanetSideGUID, object_id : Int, obj : Projectile, cdata : ConstructorData) extends Action + final case class LoadProjectile(player_guid : PlanetSideGUID, object_id : Int, projectile_guid : PlanetSideGUID, cdata : ConstructorData) extends Action final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action final case class ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index e46b167b..a288fb6e 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -117,6 +117,7 @@ class WorldSessionActor extends Actor with MDCContextAware { var whenUsedLastSAKit : Long = 0 var whenUsedLastSSKit : Long = 0 val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None) + val projectilesToCleanUp : Array[Boolean] = Array.fill[Boolean](Projectile.RangeUID - Projectile.BaseUID)(false) var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons var updateSquad : () => Unit = NoSquadUpdates var recentTeleportAttempt : Long = 0 @@ -1221,15 +1222,26 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"Received a direct message: $pkt") sendResponse(pkt) - case LoadedRemoteProjectile(projectile_guid) => + case LoadedRemoteProjectile(projectile_guid, Some(projectile)) => + if(projectile.profile.ExistsOnRemoteClients) { + //spawn projectile on other clients + val definition = projectile.Definition + avatarService ! AvatarServiceMessage( + continent.Id, + AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(projectile).get) + ) + } + //immediately slated for deletion? + CleanUpRemoteProjectile(projectile.GUID, projectile) + + case LoadedRemoteProjectile(projectile_guid, None) => continent.GUID(projectile_guid) match { case Some(obj : Projectile) if obj.profile.ExistsOnRemoteClients => //spawn projectile on other clients - val projectileGlobalUID = obj.GUID val definition = obj.Definition avatarService ! AvatarServiceMessage( continent.Id, - AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, obj, definition.Packet.ConstructorData(obj).get) + AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get) ) case _ => ; } @@ -4036,17 +4048,19 @@ class WorldSessionActor extends Actor with MDCContextAware { //log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2") case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) => - log.info(s"ProjectileState: $msg") - projectiles(projectile_guid.guid - Projectile.BaseUID) match { + //log.trace(s"ProjectileState: $msg") + val index = projectile_guid.guid - Projectile.BaseUID + projectiles(index) match { case Some(projectile) if projectile.HasGUID => val projectileGlobalUID = projectile.GUID projectile.Position = shot_pos projectile.Orientation = shot_orient projectile.Velocity = shot_vel avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectileGlobalUID, shot_pos, shot_vel, shot_orient, seq, end, target_guid)) - + case _ if seq == 0 => + /* missing the first packet in the sequence is permissible */ case _ => - log.error(s"ProjectileState: constructed projectile ${projectile_guid.guid} can not be found") + log.warn(s"ProjectileState: constructed projectile ${projectile_guid.guid} can not be found") } case msg @ ReleaseAvatarRequestMessage() => @@ -5500,8 +5514,15 @@ class WorldSessionActor extends Actor with MDCContextAware { projectiles(projectileIndex) = Some(projectile) if(projectile_info.ExistsOnRemoteClients) { log.trace(s"WeaponFireMessage: ${projectile_info.Name} is a remote projectile") - taskResolver ! ReregisterProjectile(projectile) + taskResolver ! (if(projectile.HasGUID) { + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile)) + ReregisterProjectile(projectile) + } + else { + RegisterProjectile(projectile) + }) } + projectilesToCleanUp(projectileIndex) = false } else { log.warn(s"WeaponFireMessage: $player's ${tool.Definition.Name} projectile is too far from owner position at time of discharge ($distanceToOwner > $acceptableDistanceToOwner); suspect") @@ -5576,10 +5597,15 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => ; } }) - if(projectile.profile.ExistsOnRemoteClients) { + if(projectile.profile.ExistsOnRemoteClients && projectile.HasGUID) { //cleanup - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile)) - taskResolver ! UnregisterProjectile(projectile) + val localIndex = projectile_guid.guid - Projectile.BaseUID + if(projectile.HasGUID) { + CleanUpRemoteProjectile(projectile.GUID, projectile, localIndex) + } + else { + projectilesToCleanUp(localIndex) = true + } } case None => ; } @@ -6173,7 +6199,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } def Execute(resolver : ActorRef) : Unit = { - localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID) + localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID, Some(globalProjectile)) resolver ! scala.util.Success(this) } }, List(GUIDTask.RegisterObjectTask(obj)(continent.GUID)) @@ -7959,7 +7985,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => ; } } - log.info(s"AvatarCreate (vehicle): $guid -> $data") + //log.info(s"AvatarCreate (vehicle): $guid -> $data") //player, passenger AvatarCreateInVehicle(player, vehicle, seat) @@ -7970,7 +7996,8 @@ class WorldSessionActor extends Actor with MDCContextAware { val guid = player.GUID sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, guid, data)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.LoadPlayer(guid, ObjectClass.avatar, guid, packet.ConstructorData(player).get, None)) - log.info(s"AvatarCreate: $guid -> $data") + //log.info(s"AvatarCreate: $guid -> $data") + log.trace(s"AvatarCreate: ${player.Name}") } continent.Population ! Zone.Population.Spawn(avatar, player) //cautious redundancy @@ -8066,7 +8093,8 @@ class WorldSessionActor extends Actor with MDCContextAware { avatarService ! AvatarServiceMessage(vehicle.Continent, AvatarAction.LoadPlayer(guid, pdef.ObjectId, guid, pdef.Packet.ConstructorData(player).get, Some(parent))) AccessContents(vehicle) UpdateWeaponAtSeatPosition(vehicle, seat) - log.info(s"AvatarCreateInVehicle: $guid -> $data") + //log.info(s"AvatarCreateInVehicle: $guid -> $data") + log.trace(s"AvatarCreateInVehicle: ${player.Name} in ${vehicle.Definition.Name}") } /** @@ -10080,6 +10108,14 @@ class WorldSessionActor extends Actor with MDCContextAware { squadUpdateCounter = (squadUpdateCounter + 1) % queuedSquadActions.length } + /** + * The main purpose of this method is to determine which targets will receive "locked on" warnings from remote projectiles. + * For a given series of globally unique identifiers, indicating targets, + * and that may include mounted elements (players), + * estimate a series of channel names for communication with the vulnerable targets. + * @param targets the globally unique identifiers of the immediate detected targets + * @return channels names that allow direct communication to specific realized targets + */ def FindDetectedProjectileTargets(targets : Iterable[PlanetSideGUID]) : Iterable[String] = { targets .map { continent.GUID } @@ -10094,6 +10130,48 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * For a given registered remote projectile, perform all the actions necessary to properly dispose of it. + * Those actions involve: + * informing that the projectile should explode, + * unregistering the projectile's globally unique identifier, + * and managing the projectiles's local status information. + * @see `CleanUpRemoteProjectile(PlanetSideGUID, Projectile, Int)` + * @param projectile_guid the globally unique identifier of the projectile + * @param projectile the projectile + */ + def CleanUpRemoteProjectile(projectile_guid : PlanetSideGUID, projectile : Projectile) : Unit = { + projectiles.indexWhere({ + case Some(p) => p eq projectile + case None => false + }) match { + case -1 => ; //required catch + case index if projectilesToCleanUp(index) => + CleanUpRemoteProjectile(projectile_guid, projectile, index) + case _ => ; + } + } + + /** + * For a given registered remote projectile, perform all the actions necessary to properly dispose of it. + * Those actions involve: + * informing that the projectile should explode, + * unregistering the projectile's globally unique identifier, + * and managing the projectiles's local status information. + * @param projectile_guid the globally unique identifier of the projectile + * @param projectile the projectile + * @param local_index an index of the absolute sequence of the projectile, for internal lists + */ + def CleanUpRemoteProjectile(projectile_guid : PlanetSideGUID, projectile : Projectile, local_index : Int) : Unit = { + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile_guid, projectile)) + taskResolver ! UnregisterProjectile(projectile) + projectiles(local_index) match { + case Some(obj) if !obj.isResolved => obj.Miss + case None => ; + } + projectilesToCleanUp(local_index) = false + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose()) @@ -10258,5 +10336,5 @@ object WorldSessionActor { private final case class FinalizeDeployable(obj : PlanetSideGameObject with Deployable, tool : ConstructionItem, index : Int) - private final case class LoadedRemoteProjectile(projectile_guid : PlanetSideGUID) + private final case class LoadedRemoteProjectile(projectile_guid : PlanetSideGUID, projectile : Option[Projectile]) } From d8c9e01d3653272350d9c4a3bc5c8ab5052afa73 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 29 Nov 2019 16:43:22 -0500 Subject: [PATCH 12/12] my mistake - a merge made a mess of the code for implants; I tinkered with it --- .../src/main/scala/WorldSessionActor.scala | 87 +++++++++++-------- 1 file changed, 53 insertions(+), 34 deletions(-) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 8038a2f6..df3b5fdd 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -3913,51 +3913,70 @@ class WorldSessionActor extends Actor with MDCContextAware { self ! SetCurrentAvatar(player) case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, jump_thrust, is_cloaking, unk5, unk6) => - //if(deadState == DeadState.Alive) { - val time = System.currentTimeMillis() - if (timeDL != 0) { - if (time - timeDL > 500) { - player.Stamina = player.Stamina - 1 - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) - timeDL = time + val isMoving = WorldEntity.isMoving(vel) + //implants and stamina management start + val implantsAreActive = avatar.Implants(0).Active || avatar.Implants(1).Active + val staminaBefore = player.Stamina + val hadStaminaBefore = staminaBefore > 0 + val hasStaminaAfter = if(deadState == DeadState.Alive) { + if(implantsAreActive && hadStaminaBefore) { + val time = System.currentTimeMillis() + if(timeDL != 0) { + val duration = time - timeSurge + if(duration > 500) { + val units = (duration / 500).toInt + player.Stamina = player.Stamina - units + timeDL += units * 500 + } + } + if(timeSurge != 0) { + val duration = time - timeSurge + val period = player.ExoSuit match { + case ExoSuitType.Agile => 500 + case ExoSuitType.Reinforced => 333 + case ExoSuitType.Infiltration => 1000 + case ExoSuitType.Standard => 1000 + case _ => 1 + } + if(duration > period) { + val units = (duration / period).toInt + player.Stamina = player.Stamina - units + timeSurge += period * units + } + } + } + //if the player lost all stamina this turn (had stamina at the start), do not renew 1 stamina + if(!isMoving && (if(player.Stamina > 0) player.Stamina < player.MaxStamina else !hadStaminaBefore)) { + player.Stamina = player.Stamina + 1 + true + } + else { + player.Stamina > 0 } } - if (timeSurge != 0) { - if (time - timeSurge > 500 && player.ExoSuit == ExoSuitType.Agile) { - player.Stamina = player.Stamina - 1 - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) - timeSurge = time - } - else if (time - timeSurge > 333 && player.ExoSuit == ExoSuitType.Reinforced) { - player.Stamina = player.Stamina - 1 - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) - timeSurge = time - } - else if (time - timeSurge > 1000 && ( player.ExoSuit == ExoSuitType.Infiltration || player.ExoSuit == ExoSuitType.Standard )) { - player.Stamina = player.Stamina - 1 - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) - timeSurge = time - } + else { + timeDL = 0 + timeSurge = 0 + false } - if (player.Stamina == 0) { - if (avatar.Implants(0).Active) { + if(staminaBefore != player.Stamina) { //stamina changed + sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina)) + } + if(implantsAreActive && !hasStaminaAfter) { //implants deactivated at 0 stamina + if(avatar.Implants(0).Active) { avatar.Implants(0).Active = false avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(0).id * 2)) sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 0, 0)) timeDL = 0 } - if (avatar.Implants(1).Active) { + if(avatar.Implants(1).Active) { avatar.Implants(1).Active = false avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(1).id * 2)) sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 1, 0)) timeSurge = 0 } } - val isMoving = WorldEntity.isMoving(vel) - if (!isMoving && player.Stamina < player.MaxStamina) { - player.Stamina = player.Stamina + 1 - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) - } + //implants and stamina management finish player.Position = pos player.Velocity = vel player.Orientation = Vector3(player.Orientation.x, pitch, yaw) @@ -4552,7 +4571,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //log.info("AvatarJump: " + msg) player.Stamina = player.Stamina - 10 if(player.Stamina < 0) player.Stamina = 0 - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) + sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina)) case msg @ ZipLineMessage(player_guid,origin_side,action,id,pos) => log.info("ZipLineMessage: " + msg) @@ -4764,7 +4783,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if (avatar.Implant(slot).id == 3) { timeDL = System.currentTimeMillis() player.Stamina = player.Stamina - 3 - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) + sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina)) } if (avatar.Implant(slot).id == 9) timeSurge = System.currentTimeMillis() } else if(action == ImplantAction.Activation && status == 0) { //desactive @@ -5502,7 +5521,7 @@ class WorldSessionActor extends Actor with MDCContextAware { else { //shooting if (tool.FireModeIndex == 1 && (tool.Definition.Name == "anniversary_guna" || tool.Definition.Name == "anniversary_gun" || tool.Definition.Name == "anniversary_gunb")) { player.Stamina = 0 - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) + sendResponse(PlanetsideAttributeMessage(player.GUID, 2, 0)) } prefire = shooting.orElse(Some(weapon_guid))