proper remote projectile handling; allowances for the Decimator alt-fire mode; initial DamageMessage packet and tests; initial DamageFeedbackMessage packet and tests

This commit is contained in:
FateJH 2019-11-14 13:06:58 -05:00
parent 043512d6a3
commit 693a3a5d78
18 changed files with 421 additions and 245 deletions

View file

@ -3076,9 +3076,11 @@ object GlobalDefinitions {
phoenix_missile_guided_projectile.ProjectileDamageType = DamageType.Splash phoenix_missile_guided_projectile.ProjectileDamageType = DamageType.Splash
phoenix_missile_guided_projectile.InitialVelocity = 0 phoenix_missile_guided_projectile.InitialVelocity = 0
phoenix_missile_guided_projectile.Lifespan = 3f 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.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 phoenix_missile_guided_projectile.Packet = projectileConverter
//
ProjectileDefinition.CalculateDerivedFields(phoenix_missile_guided_projectile) ProjectileDefinition.CalculateDerivedFields(phoenix_missile_guided_projectile)
phoenix_missile_projectile.Name = "phoenix_missile_projectile" phoenix_missile_projectile.Name = "phoenix_missile_projectile"

View file

@ -3,14 +3,14 @@ package net.psforever.objects.definition.converter
import net.psforever.objects.ballistics.Projectile import net.psforever.objects.ballistics.Projectile
import net.psforever.packet.game.PlanetSideGUID 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} import scala.util.{Failure, Success, Try}
class ProjectileConverter extends ObjectCreateConverter[Projectile]() { class ProjectileConverter extends ObjectCreateConverter[Projectile]() {
override def ConstructorData(obj : Projectile) : Try[TrackedProjectileData] = { override def ConstructorData(obj : Projectile) : Try[RemoteProjectileData] = {
Success( Success(
TrackedProjectileData( RemoteProjectileData(
CommonFieldDataWithPlacement( CommonFieldDataWithPlacement(
PlacementData( PlacementData(
obj.Position, 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)")) Failure(new Exception("ProjectileConverter should not be used to generate detailed projectile data (nothing should)"))
} }

View file

@ -329,7 +329,7 @@ object GamePacketOpcode extends Enumeration {
case 0x08 => game.PlayerStateMessage.decode case 0x08 => game.PlayerStateMessage.decode
case 0x09 => game.HitMessage.decode case 0x09 => game.HitMessage.decode
case 0x0a => game.HitHint.decode case 0x0a => game.HitHint.decode
case 0x0b => noDecoder(DamageMessage) case 0x0b => game.DamageMessage.decode
case 0x0c => game.DestroyMessage.decode case 0x0c => game.DestroyMessage.decode
case 0x0d => game.ReloadMessage.decode case 0x0d => game.ReloadMessage.decode
case 0x0e => game.MountVehicleMsg.decode case 0x0e => game.MountVehicleMsg.decode
@ -462,7 +462,7 @@ object GamePacketOpcode extends Enumeration {
case 0x78 => game.OxygenStateMessage.decode case 0x78 => game.OxygenStateMessage.decode
case 0x79 => noDecoder(TradeMessage) case 0x79 => noDecoder(TradeMessage)
case 0x7a => noDecoder(UnknownMessage122) case 0x7a => noDecoder(UnknownMessage122)
case 0x7b => noDecoder(DamageFeedbackMessage) case 0x7b => game.DamageFeedbackMessage.decode
case 0x7c => game.DismountBuildingMsg.decode case 0x7c => game.DismountBuildingMsg.decode
case 0x7d => noDecoder(UnknownMessage125) case 0x7d => noDecoder(UnknownMessage125)
case 0x7e => noDecoder(UnknownMessage126) case 0x7e => noDecoder(UnknownMessage126)

View file

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

View file

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

View file

@ -11,7 +11,8 @@ import shapeless.{::, HNil}
* @param player_guid1 the player * @param player_guid1 the player
* @param player_guid2 the player(?); * @param player_guid2 the player(?);
* often matches with `player_guid1` * often matches with `player_guid1`
* @param unk na * @param unk na;
* commonly, zero
* @param list list of detected objects; * @param list list of detected objects;
* normally contains at least one element * normally contains at least one element
*/ */
@ -20,7 +21,7 @@ import shapeless.{::, HNil}
BETA CLIENT DEBUG INFO: BETA CLIENT DEBUG INFO:
Detector Detector
Sender Sender
Object Count Object Count (not really)
Detected Object[] Detected Object[]
*/ */
final case class ObjectDetectedMessage(player_guid1 : PlanetSideGUID, final case class ObjectDetectedMessage(player_guid1 : PlanetSideGUID,

View file

@ -34,7 +34,7 @@ import shapeless.{::, HNil}
* a discharged controlled projectile will not normally rotate. * a discharged controlled projectile will not normally rotate.
* A minor loss of lifespan may be levied. * A minor loss of lifespan may be levied.
* @see `ProjectileDefinition` * @see `ProjectileDefinition`
* @see `TrackedProjectileData` * @see `RemoteProjectileData`
* @param projectile_guid when dispatched by the client, the client-specific local unique identifier of the projectile; * @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 * when dispatched by the server, the global unique identifier for the synchronized projectile object
* @param shot_pos the position of the projectile * @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 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; * @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 * suggests the "time alive" and indicates a place in packet ordering
* @param explode indicates the projectile should explode * @param end indicates the projectile has reached the end of its lifespan;
* @param unk na * 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, final case class ProjectileStateMessage(projectile_guid : PlanetSideGUID,
shot_pos : Vector3, shot_pos : Vector3,
shot_vel : Vector3, shot_vel : Vector3,
shot_original_orient : Vector3, shot_original_orient : Vector3,
sequence_num : Int, sequence_num : Int,
explode : Boolean, end : Boolean,
unk : Int) hit_target_guid : PlanetSideGUID)
extends PlanetSideGamePacket { extends PlanetSideGamePacket {
type Packet = ProjectileStateMessage type Packet = ProjectileStateMessage
def opcode = GamePacketOpcode.ProjectileStateMessage def opcode = GamePacketOpcode.ProjectileStateMessage
@ -67,8 +69,8 @@ object ProjectileStateMessage extends Marshallable[ProjectileStateMessage] {
("pitch" | Angular.codec_pitch) :: ("pitch" | Angular.codec_pitch) ::
("yaw" | Angular.codec_yaw()) :: ("yaw" | Angular.codec_yaw()) ::
("sequence_num" | uint8) :: ("sequence_num" | uint8) ::
("explode" | bool) :: ("end" | bool) ::
("unk" | uint16L) ("hit_target" | PlanetSideGUID.codec)
).xmap[ProjectileStateMessage] ( ).xmap[ProjectileStateMessage] (
{ {
case guid :: pos :: vel :: roll :: pitch :: yaw :: sequence_num :: explode :: unk :: HNil => case guid :: pos :: vel :: roll :: pitch :: yaw :: sequence_num :: explode :: unk :: HNil =>

View file

@ -1223,21 +1223,21 @@ object ObjectClass {
case ObjectClass.portable_manned_turret_vs => ConstructorData(OneMannedFieldTurretData.codec, "field turret") case ObjectClass.portable_manned_turret_vs => ConstructorData(OneMannedFieldTurretData.codec, "field turret")
case ObjectClass.router_telepad_deployable => DroppedItemData(TelepadDeployableData.codec, "telepad deployable") case ObjectClass.router_telepad_deployable => DroppedItemData(TelepadDeployableData.codec, "telepad deployable")
//projectiles //projectiles
case ObjectClass.hunter_seeker_missile_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.hunter_seeker_missile_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
case ObjectClass.meteor_common => ConstructorData(TrackedProjectileData.codec, "meteor") case ObjectClass.meteor_common => ConstructorData(RemoteProjectileData.codec, "meteor")
case ObjectClass.meteor_projectile_b_large => ConstructorData(TrackedProjectileData.codec, "meteor") case ObjectClass.meteor_projectile_b_large => ConstructorData(RemoteProjectileData.codec, "meteor")
case ObjectClass.meteor_projectile_b_medium => ConstructorData(TrackedProjectileData.codec, "meteor") case ObjectClass.meteor_projectile_b_medium => ConstructorData(RemoteProjectileData.codec, "meteor")
case ObjectClass.meteor_projectile_b_small => ConstructorData(TrackedProjectileData.codec, "meteor") case ObjectClass.meteor_projectile_b_small => ConstructorData(RemoteProjectileData.codec, "meteor")
case ObjectClass.meteor_projectile_large => ConstructorData(TrackedProjectileData.codec, "meteor") case ObjectClass.meteor_projectile_large => ConstructorData(RemoteProjectileData.codec, "meteor")
case ObjectClass.meteor_projectile_medium => ConstructorData(TrackedProjectileData.codec, "meteor") case ObjectClass.meteor_projectile_medium => ConstructorData(RemoteProjectileData.codec, "meteor")
case ObjectClass.meteor_projectile_small => ConstructorData(TrackedProjectileData.codec, "meteor") case ObjectClass.meteor_projectile_small => ConstructorData(RemoteProjectileData.codec, "meteor")
case ObjectClass.phoenix_missile_guided_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.phoenix_missile_guided_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
case ObjectClass.oicw_little_buddy => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.oicw_little_buddy => ConstructorData(RemoteProjectileData.codec, "projectile")
case ObjectClass.oicw_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.oicw_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
case ObjectClass.sparrow_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.sparrow_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
case ObjectClass.starfire_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.starfire_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
case ObjectClass.striker_missile_targeting_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.striker_missile_targeting_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
case ObjectClass.wasp_rocket_projectile => ConstructorData(TrackedProjectileData.codec, "projectile") case ObjectClass.wasp_rocket_projectile => ConstructorData(RemoteProjectileData.codec, "projectile")
//vehicles //vehicles
case ObjectClass.ams => ConstructorData(VehicleData.codec(VehicleFormat.Utility), "ams") case ObjectClass.ams => ConstructorData(VehicleData.codec(VehicleFormat.Utility), "ams")
case ObjectClass.ams_destroyed => ConstructorData(DestroyedVehicleData.codec, "wreckage") case ObjectClass.ams_destroyed => ConstructorData(DestroyedVehicleData.codec, "wreckage")

View file

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

View file

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

View file

@ -165,17 +165,13 @@ class AvatarService extends Actor {
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", PlanetSideGUID(0), AvatarResponse.ProjectileAutoLockAwareness(mode)) 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) => case AvatarAction.ProjectileExplodes(player_guid, projectile_guid, projectile) =>
AvatarEvents.publish( AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileExplodes(projectile_guid, projectile)) 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( 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) => case AvatarAction.PickupItem(player_guid, zone, target, slot, item, unk) =>
janitor forward RemoverActor.ClearSpecific(List(item), zone) janitor forward RemoverActor.ClearSpecific(List(item), zone)

View file

@ -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 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 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 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 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 PutDownFDU(player_guid : PlanetSideGUID) extends Action
final case class Release(player : Player, zone : Zone, time : Option[FiniteDuration] = None) 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 final case class Revive(target_guid: PlanetSideGUID) extends Action

View file

@ -42,9 +42,8 @@ object AvatarResponse {
final case class PlanetsideAttributeSelf(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(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 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 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 PutDownFDU(target_guid : PlanetSideGUID) extends Response
final case class Release(player : Player) extends Response final case class Release(player : Player) extends Response
final case class Reload(weapon_guid : PlanetSideGUID) extends Response final case class Reload(weapon_guid : PlanetSideGUID) extends Response

View file

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

View file

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

View file

@ -19,7 +19,7 @@ class ProjectileStateMessageTest extends Specification {
orient mustEqual Vector3(0, 22.5f, 146.25f) orient mustEqual Vector3(0, 22.5f, 146.25f)
sequence mustEqual 2 sequence mustEqual 2
explode mustEqual false explode mustEqual false
unk mustEqual 0 unk mustEqual PlanetSideGUID(0)
case _ => case _ =>
ko ko
} }
@ -33,7 +33,7 @@ class ProjectileStateMessageTest extends Specification {
Vector3(0, 22.5f, 146.25f), Vector3(0, 22.5f, 146.25f),
2, 2,
false, false,
0 PlanetSideGUID(0)
) )
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector val pkt = PacketCoding.EncodePacket(msg).require.toByteVector

View file

@ -8,11 +8,11 @@ import net.psforever.types.{PlanetSideEmpire, Vector3}
import org.specs2.mutable._ import org.specs2.mutable._
import scodec.bits._ 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_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" 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 { "decode (striker_missile_targeting_projectile)" in {
PacketCoding.DecodePacket(string_striker_projectile).require match { PacketCoding.DecodePacket(string_striker_projectile).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) => case ObjectCreateMessage(len, cls, guid, parent, data) =>
@ -21,7 +21,7 @@ class TrackedProjectileDataTest extends Specification {
guid mustEqual PlanetSideGUID(40192) guid mustEqual PlanetSideGUID(40192)
parent.isDefined mustEqual false parent.isDefined mustEqual false
data match { 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.coord mustEqual Vector3(4644.5938f, 5472.0938f, 82.375f)
pos.orient mustEqual Vector3(0, 30.9375f, 171.5625f) pos.orient mustEqual Vector3(0, 30.9375f, 171.5625f)
deploy.faction mustEqual PlanetSideEmpire.TR deploy.faction mustEqual PlanetSideEmpire.TR
@ -55,7 +55,7 @@ class TrackedProjectileDataTest extends Specification {
guid mustEqual PlanetSideGUID(40619) guid mustEqual PlanetSideGUID(40619)
parent.isDefined mustEqual false parent.isDefined mustEqual false
data match { 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.coord mustEqual Vector3(3621.3672f, 2701.8438f, 140.85938f)
pos.orient mustEqual Vector3(0, 300.9375f, 258.75f) pos.orient mustEqual Vector3(0, 300.9375f, 258.75f)
deploy.faction mustEqual PlanetSideEmpire.NC deploy.faction mustEqual PlanetSideEmpire.NC
@ -82,7 +82,7 @@ class TrackedProjectileDataTest extends Specification {
} }
"encode (striker_missile_targeting_projectile)" in { "encode (striker_missile_targeting_projectile)" in {
val obj = TrackedProjectileData( val obj = RemoteProjectileData(
CommonFieldDataWithPlacement( CommonFieldDataWithPlacement(
PlacementData(4644.5938f, 5472.0938f, 82.375f, 0f, 30.9375f, 171.5625f), PlacementData(4644.5938f, 5472.0938f, 82.375f, 0f, 30.9375f, 171.5625f),
CommonFieldData(PlanetSideEmpire.TR, false, false, true, None, false, None, None, PlanetSideGUID(0)) 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 { "encode (hunter_seeker_missile_projectile)" in {
val obj = TrackedProjectileData( val obj = RemoteProjectileData(
CommonFieldDataWithPlacement( CommonFieldDataWithPlacement(
PlacementData(3621.3672f, 2701.8438f, 140.85938f, 0, 300.9375f, 258.75f), PlacementData(3621.3672f, 2701.8438f, 140.85938f, 0, 300.9375f, 258.75f),
CommonFieldData(PlanetSideEmpire.NC, false, false, true, None, false, None, None, PlanetSideGUID(0)) CommonFieldData(PlanetSideEmpire.NC, false, false, true, None, false, None, None, PlanetSideGUID(0))

View file

@ -54,8 +54,9 @@ import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalSer
import services.chat._ import services.chat._
import services.vehicle.support.TurretUpgrader import services.vehicle.support.TurretUpgrader
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} 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.collection.mutable.LongMap
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global 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.local.support.{HackCaptureActor, RouterTelepadActivation}
import services.support.SupportActor import services.support.SupportActor
import scala.collection.mutable
class WorldSessionActor extends Actor with MDCContextAware { class WorldSessionActor extends Actor with MDCContextAware {
import WorldSessionActor._ import WorldSessionActor._
@ -153,9 +156,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L
var serverTime : Long = 0 var serverTime : Long = 0
var projectileAutoLockTargets : List[PlanetSideGUID] = Nil
var projectileAutoLocksActive : Set[PlanetSideGUID] = Set.empty
var amsSpawnPoints : List[SpawnPoint] = Nil var amsSpawnPoints : List[SpawnPoint] = Nil
var clientKeepAlive : Cancellable = DefaultCancellable.obj var clientKeepAlive : Cancellable = DefaultCancellable.obj
var progressBarUpdate : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj
@ -165,6 +165,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
var cargoDismountTimer : Cancellable = DefaultCancellable.obj var cargoDismountTimer : Cancellable = DefaultCancellable.obj
var antChargingTick : Cancellable = DefaultCancellable.obj var antChargingTick : Cancellable = DefaultCancellable.obj
var antDischargingTick : Cancellable = DefaultCancellable.obj var antDischargingTick : Cancellable = DefaultCancellable.obj
var weaponAutoLockDecay : Cancellable = DefaultCancellable.obj
/** /**
* Convert a boolean value into an integer value. * 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") log.info(s"Received a direct message: $pkt")
sendResponse(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 => case default =>
log.warn(s"Invalid packet class received: $default from $sender") 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) => case AvatarResponse.ProjectileExplodes(projectile_guid, projectile) =>
// //turn the projectile into a boomer sendResponse(ProjectileStateMessage(projectile_guid, projectile.Position, Vector3.Zero, projectile.Orientation, 0, true, PlanetSideGUID(0)))
// 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)) sendResponse(ObjectDeleteMessage(projectile_guid, 2))
case AvatarResponse.ProjectileAutoLockAwareness(mode) => case AvatarResponse.ProjectileAutoLockAwareness(mode) =>
sendResponse(GenericActionMessage(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) { 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 { // continent.GUID(projectile_guid) match {
// case Some(obj) => // case Some(obj) =>
// val definition = obj.Definition // val definition = obj.Definition
// sendResponse(ObjectCreateMessage(definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get)) // sendResponse(ObjectCreateMessage(definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get))
// case _ => ; // case _ => ;
// } // }
} }
case AvatarResponse.PutDownFDU(target) => case AvatarResponse.PutDownFDU(target) =>
if(tplayer_guid != guid) { if(tplayer_guid != guid) {
@ -3495,11 +3490,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects.GlobalDefinitions._
import net.psforever.types.CertificationType._ import net.psforever.types.CertificationType._
val faction = val faction = PlanetSideEmpire.VS
if(sessionId % 5 == 0) PlanetSideEmpire.TR val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1)
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 += StandardAssault
avatar.Certifications += MediumAssault avatar.Certifications += MediumAssault
avatar.Certifications += StandardExoSuit avatar.Certifications += StandardExoSuit
@ -3548,14 +3540,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
//player.Orientation = Vector3(0f, 0f, 132.1875f) //player.Orientation = Vector3(0f, 0f, 132.1875f)
// player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting // 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(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(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(6).Equipment = AmmoBox(bullet_9mm) player.Slot(9).Equipment = AmmoBox(bullet_9mm)
//player.Slot(9).Equipment = AmmoBox(bullet_9mm) player.Slot(12).Equipment = AmmoBox(bullet_9mm)
//player.Slot(12).Equipment = AmmoBox(bullet_9mm) player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP)
//player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction))
//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.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit)
player.Inventory.Items.foreach { _.obj.Faction = faction } 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) => 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) { if(deadState == DeadState.Alive) {
val time = System.currentTimeMillis()
if (timeDL != 0) { if (timeDL != 0) {
if (System.currentTimeMillis() - timeDL > 500) { if (time - timeDL > 500) {
player.Stamina = player.Stamina - 1 player.Stamina = player.Stamina - 1
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
timeDL = System.currentTimeMillis() timeDL = time
} }
} }
if (timeSurge != 0) { 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 player.Stamina = player.Stamina - 1
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) 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 player.Stamina = player.Stamina - 1
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) 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 player.Stamina = player.Stamina - 1
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
timeSurge = System.currentTimeMillis() timeSurge = time
} }
} }
if (player.Stamina == 0) { if (player.Stamina == 0) {
@ -3938,10 +3930,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.Stamina = player.Stamina + 1 player.Stamina = player.Stamina + 1
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina)) avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
} }
if(!player.Crouching && is_crouching) {
//...
}
player.Position = pos player.Position = pos
player.Velocity = vel player.Velocity = vel
player.Orientation = Vector3(player.Orientation.x, pitch, yaw) 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) => 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") //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") log.info(s"ProjectileState: $msg")
projectiles(projectile_guid.guid - Projectile.BaseUID) match { projectiles(projectile_guid.guid - Projectile.BaseUID) match {
case Some(projectile) if projectile.HasGUID => case Some(projectile) if projectile.HasGUID =>
@ -4056,9 +4044,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
projectile.Position = shot_pos projectile.Position = shot_pos
projectile.Orientation = shot_orient projectile.Orientation = shot_orient
projectile.Velocity = shot_vel 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 _ => 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() => case msg @ ReleaseAvatarRequestMessage() =>
@ -4334,9 +4323,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
case msg @ ChangeFireStateMessage_Start(item_guid) => case msg @ ChangeFireStateMessage_Start(item_guid) =>
log.info("ChangeFireState_Start: " + msg) log.trace("ChangeFireState_Start: " + msg)
if(shooting.isEmpty) { if(shooting.isEmpty) {
FindEquipment match { 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) => case Some(tool : Tool) =>
if(tool.Magazine > 0 || prefire.contains(item_guid)) { if(tool.Magazine > 0 || prefire.contains(item_guid)) {
prefire = None prefire = None
@ -4357,7 +4352,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
case msg @ ChangeFireStateMessage_Stop(item_guid) => case msg @ ChangeFireStateMessage_Stop(item_guid) =>
log.info("ChangeFireState_Stop: " + msg) log.trace("ChangeFireState_Stop: " + msg)
prefire = None prefire = None
val weapon : Option[Equipment] = if(shooting.contains(item_guid)) { val weapon : Option[Equipment] = if(shooting.contains(item_guid)) {
shooting = None shooting = None
@ -4365,13 +4360,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
FindEquipment FindEquipment
} }
else { else {
//some weapons, e.g., the decimator, do not send a ChangeFireState_Start on the last shot
FindEquipment match { FindEquipment match {
case Some(tool) => case Some(tool : Tool) =>
if(tool.Definition == GlobalDefinitions.phoenix) { //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_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) Some(tool)
case _ => case _ =>
log.warn(s"ChangeFireState_Stop: received an unexpected message about $item_guid") log.warn(s"ChangeFireState_Stop: received an unexpected message about $item_guid")
@ -4589,6 +4589,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
else { else {
projectile.Miss() projectile.Miss()
if(projectile.profile.ExistsOnRemoteClients && projectile.HasGUID) {
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile))
taskResolver ! UnregisterProjectile(projectile)
}
} }
case None => case None =>
log.warn(s"RequestDestroy: projectile ${object_guid.guid} has never been fired") 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 { val (angle, attribution, acceptableDistanceToOwner) = obj match {
case p : Player => 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 => case v : Vehicle if v.Definition.CanFly =>
(tool.Orientation, obj.Definition.ObjectId, 1000f) //TODO this is too simplistic to find proper angle (tool.Orientation, obj.Definition.ObjectId, 1000f) //TODO this is too simplistic to find proper angle
case _ : Vehicle => case _ : Vehicle =>
@ -5514,28 +5518,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info("Lazing position: " + pos2.toString) log.info("Lazing position: " + pos2.toString)
case msg @ ObjectDetectedMessage(guid1, guid2, unk, targets) => case msg @ ObjectDetectedMessage(guid1, guid2, unk, targets) =>
//log.info(s"Detection: $msg")
FindWeapon match { FindWeapon match {
case Some(weapon) if weapon.Projectile.AutoLock => case Some(weapon) if weapon.Projectile.AutoLock =>
//projectile with auto-lock instigates a warning on the target //projectile with auto-lock instigates a warning on the target
val mode = 7 + (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile) val detectedTargets = FindDetectedProjectileTargets(targets)
projectileAutoLockTargets = targets if(detectedTargets.nonEmpty) {
targets val mode = 7 + (weapon.Projectile == GlobalDefinitions.wasp_rocket_projectile)
.map { continent.GUID } detectedTargets.foreach { target =>
.collect { avatarService ! AvatarServiceMessage(target, AvatarAction.ProjectileAutoLockAwareness(mode))
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 _ => ;
} }
@ -5565,6 +5558,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"Splash: $msg") log.info(s"Splash: $msg")
FindProjectileEntry(projectile_guid) match { FindProjectileEntry(projectile_guid) match {
case Some(projectile) => case Some(projectile) =>
projectile.Position = explosion_pos
projectile.Velocity = projectile_vel
continent.GUID(direct_victim_uid) match { continent.GUID(direct_victim_uid) match {
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match { ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match {
@ -6170,11 +6165,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
TaskResolver.GiveTask( TaskResolver.GiveTask(
new Task() { new Task() {
private val globalProjectile = obj private val globalProjectile = obj
private val localAnnounce = avatarService private val localAnnounce = self
private val localMsg = AvatarServiceMessage(
continent.Id,
AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, obj, definition.Packet.ConstructorData(obj).get)
)
override def isComplete : Task.Resolution.Value = { override def isComplete : Task.Resolution.Value = {
if(globalProjectile.HasGUID) { if(globalProjectile.HasGUID) {
@ -6186,7 +6177,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
def Execute(resolver : ActorRef) : Unit = { def Execute(resolver : ActorRef) : Unit = {
localAnnounce ! localMsg localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID)
resolver ! scala.util.Success(this) resolver ! scala.util.Success(this)
} }
}, List(GUIDTask.RegisterObjectTask(obj)(continent.GUID)) }, List(GUIDTask.RegisterObjectTask(obj)(continent.GUID))
@ -10093,6 +10084,20 @@ class WorldSessionActor extends Actor with MDCContextAware {
squadUpdateCounter = (squadUpdateCounter + 1) % queuedSquadActions.length 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) = { def failWithError(error : String) = {
log.error(error) log.error(error)
sendResponse(ConnectionClose()) sendResponse(ConnectionClose())
@ -10256,4 +10261,6 @@ object WorldSessionActor {
private final case class NtuDischarging(tplayer: Player, vehicle: Vehicle, silo_guid: PlanetSideGUID) 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 FinalizeDeployable(obj : PlanetSideGameObject with Deployable, tool : ConstructionItem, index : Int)
private final case class LoadedRemoteProjectile(projectile_guid : PlanetSideGUID)
} }