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