diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index a038eede3..ab8f93d2c 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 136c20bd8..1db6d3e4f 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 0d01ec435..9d221d639 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 a1534461b..fb60a5767 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 a70e28459..2a6597623 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 0fdca89ce..56af15133 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 5035ceba8..2d8ca712b 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 d009c160d..2b1635472 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 586345bd2..d1d4d9c10 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 fa402f3f6..94b47aa53 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 {