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.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"

View file

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

View file

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

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_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,

View file

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

View file

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

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

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

View file

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

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

View file

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

View file

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