removed fire mode override for the Phoenix; added events for loading and manipulation of remote projectiles to AvatarService; ProjectileStateMessage handles projectile data in a simple way; remote projectiles can now be registered and unregistered

This commit is contained in:
FateJH 2019-05-14 16:20:24 -04:00
parent fa7365e8af
commit f1c73688f7
7 changed files with 181 additions and 46 deletions

View file

@ -1,7 +1,6 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.GlobalDefinitions.sparrow_projectile
import net.psforever.objects.ballistics.Projectiles
import net.psforever.objects.ce.{DeployableCategory, DeployedItem}
import net.psforever.objects.definition._
@ -641,8 +640,8 @@ object GlobalDefinitions {
}
val hunterseeker = new ToolDefinition(ObjectClass.hunterseeker) {
override def NextFireModeIndex(index : Int) : Int = index
DefaultFireModeIndex = 1
// override def NextFireModeIndex(index : Int) : Int = index
// DefaultFireModeIndex = 1
} //phoenix
val lancer = ToolDefinition(ObjectClass.lancer)

View file

@ -2,48 +2,56 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.Vector3
import net.psforever.types.{Angular, Vector3}
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
/**
* Dispatched to deliberately render certain projectiles of a weapon on other players' clients.<br>
* Dispatched to deliberately control certain projectiles of a weapon on other players' clients.<br>
* <br>
* This packet is generated by firing specific weapons in specific fire modes.
* This packet should be generated by firing specific weapons in specific fire modes.
* For example, the Phoenix (`hunterseeker`) discharged in its primary fire mode generates this packet;
* but, the Phoenix in secondary fire mode does not.
* The Striker (`striker`) discharged in its primary fire mode generates this packet;
* but, the Striker in secondary fire mode does not.
* The chosen fire mode(s) are not a straight-fire projectile but one that has special control asserted over it.
* For the Phoenix, it is user-operated.
* For the Striker, it tracks towards a target while the weapon's reticle hovers over that target.<br>
* For the Phoenix, it is user operated (camera-guided).
* For the Striker, it tracks towards a valid target while the weapon's reticle hovers over that target.<br>
* <br>
* This packet will continue to be dispatched by the client for as long as the projectile being tracked is in the air.
* All projectiles have a maximum lifespan before they will lose control and either despawn and/or explode.
* This number is tracked in the packet for simplicity.
* If the projectile strikes a valid target, the count will jump to a significantly enormous value beyond its normal lifespan.
* This ensures that the projectile - locally and the shared model - will despawn.
* @param projectile_guid the projectile
* <br>
* This control can not be exerted until that projectile is physically constructed on the other clients
* in the same way that a player or a vehicle is constructed.
* A projectile that exhibits intentional construction behavior is flagged using the property `exists_on_remote_client`.
* The model comes with a number of caveats,
* some that originate from the object construction process itself,
* but also some from this packet.
* For example,
* as indicated by the static `shot_orient` values reported by this packet.
* a discharged controlled projectile will not normally rotate.
* A minor loss of lifespan may be levied.
* @see `ProjectileDefinition`
* @see `TrackedProjectileData`
* @param projectile_guid the client-specific local unique identifier of the projectile;
* this is __not__ the global unique identifier for the synchronized projectile object
* @param shot_pos the position of the projectile
* @param shot_vel the velocity of the projectile
* @param unk1 na;
* usually 0
* @param unk2 na;
* will remain consistent for the lifespan of a given projectile in most cases
* @param unk3 na;
* will remain consistent for the lifespan of a given projectile in most cases
* @param unk4 na;
* usually false
* @param shot_orient the orientation of the projectile
* @param unk na;
* usually `false`
* @param time_alive how long the projectile has been in the air;
* often expressed in multiples of 2
*/
final case class ProjectileStateMessage(projectile_guid : PlanetSideGUID,
shot_pos : Vector3,
shot_vel : Vector3,
unk1 : Int,
unk2 : Int,
unk3 : Int,
unk4 : Boolean,
shot_orient : Vector3,
unk : Boolean,
time_alive : Int)
extends PlanetSideGamePacket {
type Packet = ProjectileStateMessage
@ -56,10 +64,19 @@ object ProjectileStateMessage extends Marshallable[ProjectileStateMessage] {
("projectile_guid" | PlanetSideGUID.codec) ::
("shot_pos" | Vector3.codec_pos) ::
("shot_vel" | Vector3.codec_float) ::
("unk1" | uint8L) ::
("unk2" | uint8L) ::
("unk3" | uint8L) ::
("unk4" | bool) ::
("roll" | Angular.codec_roll) ::
("pitch" | Angular.codec_pitch) ::
("yaw" | Angular.codec_yaw()) ::
("unk" | bool) ::
("time_alive" | uint16L)
).as[ProjectileStateMessage]
).xmap[ProjectileStateMessage] (
{
case guid :: pos :: vel :: roll :: pitch :: yaw :: unk :: time :: HNil =>
ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), unk, time)
},
{
case ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), unk, time) =>
guid :: pos :: vel :: roll :: pitch :: yaw :: unk :: time :: HNil
}
)
}

View file

@ -127,6 +127,12 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(pkt))
)
case AvatarAction.LoadProjectile(player_guid, object_id, obj, cdata) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(
ObjectCreateMessage(object_id, obj.GUID, cdata)
))
)
case AvatarAction.ObjectDelete(player_guid, item_guid, unk) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ObjectDelete(item_guid, unk))
@ -151,6 +157,10 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarResponse.PlayerState(pos, vel, yaw, pitch, yaw_upper, seq_time, is_crouching, is_jumping, jump_thrust, is_cloaking, spectating, weaponInHand))
)
case AvatarAction.ProjectileState(player_guid, projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive))
)
case AvatarAction.PickupItem(player_guid, zone, target, slot, item, unk) =>
janitor forward RemoverActor.ClearSpecific(List(item), zone)
AvatarEvents.publish(

View file

@ -1,8 +1,12 @@
// Copyright (c) 2017 PSForever
package services.avatar
<<<<<<< fa7365e8af5d0b21c3934ed745895edd46f741a6:common/src/main/scala/services/avatar/AvatarServiceMessage.scala
import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.objects.ballistics.SourceEntry
=======
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
>>>>>>> removed fire mode override for the Phoenix; added events for loading and manipulation of remote projectiles to AvatarService; ProjectileStateMessage handles projectile data in a simple way; remote projectiles can now be registered and unregistered:common/src/main/scala/services/avatar/AvatarAction.scala
import net.psforever.objects.ce.Deployable
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.Container
@ -30,7 +34,7 @@ object AvatarAction {
final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action
final case class EnvironmentalDamage(player_guid : PlanetSideGUID, amont: Int) extends Action
final case class EnvironmentalDamage(player_guid : PlanetSideGUID, amount: Int) extends Action
final case class Damage(player_guid : PlanetSideGUID, target : Player, resolution_function : Any=>Unit) extends Action
final case class DeployItem(player_guid : PlanetSideGUID, item : PlanetSideGameObject with Deployable) extends Action
final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Action
@ -40,6 +44,7 @@ object AvatarAction {
final case class HitHint(source_guid : PlanetSideGUID, player_guid : PlanetSideGUID) extends Action
final case class KilledWhileInVehicle(player_guid : PlanetSideGUID) extends Action
final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action
final case class LoadProjectile(player_guid : PlanetSideGUID, object_id : Int, obj : Projectile, cdata : ConstructorData) extends Action
final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action
final case class ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action
final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action
@ -47,6 +52,7 @@ object AvatarAction {
final case class PlanetsideAttributeSelf(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action
final case class PlayerState(player_guid : PlanetSideGUID, pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Action
final case class PickupItem(player_guid : PlanetSideGUID, zone : Zone, target : PlanetSideGameObject with Container, slot : Int, item : Equipment, unk : Int = 0) extends Action
final case class ProjectileState(player_guid : PlanetSideGUID, projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk : Boolean, time_alive : Int) extends Action
final case class PutDownFDU(player_guid : PlanetSideGUID) extends Action
final case class Release(player : Player, zone : Zone, time : Option[FiniteDuration] = None) extends Action
final case class Revive(target_guid: PlanetSideGUID) extends Action

View file

@ -33,12 +33,18 @@ object AvatarResponse {
final case class HitHint(source_guid : PlanetSideGUID) extends Response
final case class KilledWhileInVehicle() extends Response
final case class LoadPlayer(pkt : ObjectCreateMessage) extends Response
final case class LoadProjectile(pkt : ObjectCreateMessage) extends Response
final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response
final case class ObjectHeld(slot : Int) extends Response
final case class PlanetsideAttribute(attribute_type : Int, attribute_value : Long) extends Response
<<<<<<< fa7365e8af5d0b21c3934ed745895edd46f741a6:common/src/main/scala/services/avatar/AvatarServiceResponse.scala
final case class PlanetsideAttributeToAll(attribute_type : Int, attribute_value : Long) extends Response
final case class PlanetsideAttributeSelf(attribute_type : Int, attribute_value : Long) extends Response
final case class PlayerState(pos : Vector3, vel : Option[Vector3], facingYaw : Float, facingPitch : Float, facingYawUpper : Float, timestamp : Int, is_crouching : Boolean, is_jumping : Boolean, jump_thrust : Boolean, is_cloaked : Boolean, spectator : Boolean, weaponInHand : Boolean) extends Response
=======
final case class PlayerState(msg : PlayerStateMessageUpstream, spectator : Boolean, weaponInHand : Boolean) extends Response
final case class ProjectileState(projectile_guid : PlanetSideGUID, shot_pos : Vector3, shot_vel : Vector3, shot_orient : Vector3, unk : Boolean, time_alive : Int) extends Response
>>>>>>> removed fire mode override for the Phoenix; added events for loading and manipulation of remote projectiles to AvatarService; ProjectileStateMessage handles projectile data in a simple way; remote projectiles can now be registered and unregistered:common/src/main/scala/services/avatar/AvatarResponse.scala
final case class PutDownFDU(target_guid : PlanetSideGUID) extends Response
final case class Release(player : Player) extends Response
final case class Reload(weapon_guid : PlanetSideGUID) extends Response

View file

@ -12,18 +12,12 @@ class ProjectileStateMessageTest extends Specification {
"decode" in {
PacketCoding.DecodePacket(string).require match {
case ProjectileStateMessage(projectile, pos, vel, unk1, unk2, unk3, unk4, time_alive) =>
case ProjectileStateMessage(projectile, pos, vel, orient, unk, time_alive) =>
projectile mustEqual PlanetSideGUID(40229)
pos.x mustEqual 4611.539f
pos.y mustEqual 5576.375f
pos.z mustEqual 82.328125f
vel.x mustEqual 18.64686f
vel.y mustEqual -33.43247f
vel.z mustEqual 11.599553f
unk1 mustEqual 0
unk2 mustEqual 248
unk3 mustEqual 236
unk4 mustEqual false
pos mustEqual Vector3(4611.539f, 5576.375f, 82.328125f)
vel mustEqual Vector3(18.64686f, -33.43247f, 11.599553f)
orient mustEqual Vector3(0, 22.5f, 56.25f)
unk mustEqual false
time_alive mustEqual 4
case _ =>
ko
@ -35,10 +29,17 @@ class ProjectileStateMessageTest extends Specification {
PlanetSideGUID(40229),
Vector3(4611.539f, 5576.375f, 82.328125f),
Vector3(18.64686f, -33.43247f, 11.599553f),
0, 248, 236, false, 4
Vector3(0, 22.5f, 56.25f),
false,
4
)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
//pkt mustEqual string
val pkt_bits = pkt.toBitVector
val str_bits = string.toBitVector
pkt_bits.take(184) mustEqual str_bits.take(184) //skip 1 bit
pkt_bits.drop(185).take(7) mustEqual str_bits.drop(185).take(7) //skip 1 bit
pkt_bits.drop(193) mustEqual str_bits.drop(193)
}
}

View file

@ -1402,6 +1402,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(pkt)
}
case AvatarResponse.LoadProjectile(pkt) =>
if(tplayer_guid != guid) {
sendResponse(pkt)
}
case AvatarResponse.ObjectDelete(item_guid, unk) =>
if(tplayer_guid != guid) {
sendResponse(ObjectDeleteMessage(item_guid, unk))
@ -1463,6 +1468,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
case AvatarResponse.ProjectileState(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) =>
if(tplayer_guid != guid) {
sendResponse(ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive))
}
case AvatarResponse.PutDownFDU(target) =>
if(tplayer_guid != guid) {
sendResponse(GenericObjectActionMessage(target, 212))
@ -3989,8 +3999,22 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) =>
//log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2")
case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vector, unk1, unk2, unk3, unk4, time_alive) =>
//log.info("ProjectileState: " + msg)
case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive) =>
log.info(s"ProjectileState: $msg")
projectiles
.collect {
case Some(projectile) if projectile.HasGUID =>
projectile
}
.find(_.GUID == projectile_guid) match {
case Some(projectile) =>
projectile.Position = shot_pos
projectile.Orientation = shot_orient
projectile.Velocity = shot_vel
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectile_guid, shot_pos, shot_vel, shot_orient, unk, time_alive))
case None =>
log.info(s"ProjectileState: the projectile GUID#${projectile_guid.guid} can not be found")
}
case msg @ ReleaseAvatarRequestMessage() =>
log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released")
@ -5385,7 +5409,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
case msg @ WeaponFireMessage(seq_time, weapon_guid, projectile_guid, shot_origin, unk1, unk2, unk3, unk4, unk5, unk6, unk7) =>
log.info("WeaponFire: " + msg)
log.info(s"WeaponFire: $msg")
FindContainedWeapon match {
case (Some(obj), Some(tool : Tool)) =>
if(tool.Magazine <= 0) { //safety: enforce ammunition depletion
@ -5405,7 +5429,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
prefire = shooting.orElse(Some(weapon_guid))
tool.Discharge
tool.Discharge //always
val projectileIndex = projectile_guid.guid - Projectile.BaseUID
val projectilePlace = projectiles(projectileIndex)
if(projectilePlace match {
@ -5426,8 +5450,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
val distanceToOwner = Vector3.DistanceSquared(shot_origin, player.Position)
if(distanceToOwner <= acceptableDistanceToOwner) {
projectiles(projectileIndex) =
Some(Projectile(tool.Projectile, tool.Definition, tool.FireMode, player, attribution, shot_origin, angle))
val projectile_info = tool.Projectile
val projectile = Projectile(projectile_info, tool.Definition, tool.FireMode, player, attribution, shot_origin, angle)
projectiles(projectileIndex) = Some(projectile)
if(projectile_info.ExistsOnRemoteClients) {
taskResolver ! ReregisterProjectile(projectile)
}
}
else {
log.warn(s"WeaponFireMessage: $player's ${tool.Definition.Name} projectile is too far from owner position at time of discharge ($distanceToOwner > $acceptableDistanceToOwner); suspect")
@ -6049,6 +6077,34 @@ class WorldSessionActor extends Actor with MDCContextAware {
}, List(GUIDTask.RegisterAvatar(driver)(continent.GUID), GUIDTask.RegisterVehicle(obj)(continent.GUID)))
}
def RegisterProjectile(obj : Projectile) : TaskResolver.GiveTask = {
val definition = obj.Definition
TaskResolver.GiveTask(
new Task() {
private val globalProjectile = obj
private val localAnnounce = avatarService
private val localMsg = AvatarServiceMessage(
continent.Id,
AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, obj, definition.Packet.ConstructorData(obj).get)
)
override def isComplete : Task.Resolution.Value = {
if(globalProjectile.HasGUID) {
Task.Resolution.Success
}
else {
Task.Resolution.Incomplete
}
}
def Execute(resolver : ActorRef) : Unit = {
localAnnounce ! localMsg
resolver ! scala.util.Success(this)
}
}, List(GUIDTask.RegisterObjectTask(obj)(continent.GUID))
)
}
def UnregisterDrivenVehicle(obj : Vehicle, driver : Player) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
@ -6070,6 +6126,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
}, List(GUIDTask.UnregisterAvatar(driver)(continent.GUID), GUIDTask.UnregisterVehicle(obj)(continent.GUID)))
}
def UnregisterProjectile(obj : Projectile) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val globalProjectile = obj
private val localAnnounce = avatarService
private val localMsg = AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, obj.GUID))
override def isComplete : Task.Resolution.Value = {
if(!globalProjectile.HasGUID) {
Task.Resolution.Success
}
else {
Task.Resolution.Incomplete
}
}
def Execute(resolver : ActorRef) : Unit = {
localAnnounce ! localMsg
resolver ! scala.util.Success(this)
}
}, List(GUIDTask.UnregisterObjectTask(obj)(continent.GUID))
)
}
/**
* Construct tasking that removes the `Equipment` to `target`.
* @param target what object that contains the `Equipment`
@ -6112,6 +6192,22 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
}
def ReregisterProjectile(obj : Projectile) : TaskResolver.GiveTask = {
val reg = RegisterProjectile(obj)
if(obj.HasGUID) {
TaskResolver.GiveTask(
reg.task,
List(TaskResolver.GiveTask(
reg.subs(0).task,
List(UnregisterProjectile(obj))
))
)
}
else {
reg
}
}
/**
* After some subtasking is completed, draw a particular slot, as if an `ObjectHeldMessage` packet was sent/received.<br>
* <br>