Merge pull request #291 from Fate-JH/projectiles

Remote Projectiles
This commit is contained in:
Mazo 2019-12-09 11:21:37 +00:00 committed by GitHub
commit 885387b734
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 1095 additions and 348 deletions

View file

@ -31,70 +31,18 @@ object GlobalDefinitions {
*/
val avatar = new AvatarDefinition(121)
/*
Exo-suits
exo-suits
*/
val Standard = ExoSuitDefinition(ExoSuitType.Standard)
Standard.Name = "standard"
Standard.MaxArmor = 50
Standard.InventoryScale = InventoryTile.Tile96
Standard.InventoryOffset = 6
Standard.Holster(0, EquipmentSize.Pistol)
Standard.Holster(2, EquipmentSize.Rifle)
Standard.Holster(4, EquipmentSize.Melee)
Standard.ResistanceDirectHit = 4
Standard.ResistanceSplash = 15
Standard.ResistanceAggravated = 8
val Agile = ExoSuitDefinition(ExoSuitType.Agile)
Agile.Name = "agile"
Agile.MaxArmor = 100
Agile.InventoryScale = InventoryTile.Tile99
Agile.InventoryOffset = 6
Agile.Holster(0, EquipmentSize.Pistol)
Agile.Holster(1, EquipmentSize.Pistol)
Agile.Holster(2, EquipmentSize.Rifle)
Agile.Holster(4, EquipmentSize.Melee)
Agile.ResistanceDirectHit = 6
Agile.ResistanceSplash = 25
Agile.ResistanceAggravated = 10
val Reinforced = ExoSuitDefinition(ExoSuitType.Reinforced)
Reinforced.Name = "reinforced"
Reinforced.Permissions = List(CertificationType.ReinforcedExoSuit)
Reinforced.MaxArmor = 200
Reinforced.InventoryScale = InventoryTile.Tile1209
Reinforced.InventoryOffset = 6
Reinforced.Holster(0, EquipmentSize.Pistol)
Reinforced.Holster(1, EquipmentSize.Pistol)
Reinforced.Holster(2, EquipmentSize.Rifle)
Reinforced.Holster(3, EquipmentSize.Rifle)
Reinforced.Holster(4, EquipmentSize.Melee)
Reinforced.ResistanceDirectHit = 10
Reinforced.ResistanceSplash = 35
Reinforced.ResistanceAggravated = 12
val Infiltration = ExoSuitDefinition(ExoSuitType.Infiltration)
Infiltration.Name = "infiltration_suit"
Infiltration.Permissions = List(CertificationType.InfiltrationSuit)
Infiltration.MaxArmor = 0
Infiltration.InventoryScale = InventoryTile.Tile66
Infiltration.InventoryOffset = 6
Infiltration.Holster(0, EquipmentSize.Pistol)
Infiltration.Holster(4, EquipmentSize.Melee)
val MAX = SpecialExoSuitDefinition(ExoSuitType.MAX)
MAX.Permissions = List(CertificationType.AIMAX,CertificationType.AVMAX, CertificationType.AAMAX, CertificationType.UniMAX)
MAX.MaxArmor = 650
MAX.InventoryScale = InventoryTile.Tile1612
MAX.InventoryOffset = 6
MAX.Holster(0, EquipmentSize.Max)
MAX.Holster(4, EquipmentSize.Melee)
MAX.Subtract.Damage1 = -2
MAX.ResistanceDirectHit = 6
MAX.ResistanceSplash = 35
MAX.ResistanceAggravated = 10
MAX.Damage = StandardMaxDamage
MAX.Model = StandardResolutions.Max
init_exosuit()
/*
Implants
*/
@ -121,6 +69,8 @@ object GlobalDefinitions {
/*
Projectiles
*/
val no_projectile = new ProjectileDefinition(0) //also called none in ADB
val bullet_105mm_projectile = ProjectileDefinition(Projectiles.bullet_105mm_projectile)
val bullet_12mm_projectile = ProjectileDefinition(Projectiles.bullet_12mm_projectile)
@ -297,6 +247,8 @@ object GlobalDefinitions {
val oicw_projectile = ProjectileDefinition(Projectiles.oicw_projectile)
val oicw_little_buddy = ProjectileDefinition(Projectiles.oicw_little_buddy)
val pellet_gun_projectile = ProjectileDefinition(Projectiles.pellet_gun_projectile)
val peregrine_dual_machine_gun_projectile = ProjectileDefinition(Projectiles.peregrine_dual_machine_gun_projectile)
@ -628,19 +580,11 @@ object GlobalDefinitions {
val maelstrom = ToolDefinition(ObjectClass.maelstrom)
val phoenix = new ToolDefinition(ObjectClass.phoenix) {
override def NextFireModeIndex(index : Int) : Int = index
} //decimator
val phoenix = ToolDefinition(ObjectClass.phoenix) //decimator
val striker = new ToolDefinition(ObjectClass.striker) {
override def NextFireModeIndex(index : Int) : Int = index
DefaultFireModeIndex = 1
}
val striker = ToolDefinition(ObjectClass.striker)
val hunterseeker = new ToolDefinition(ObjectClass.hunterseeker) {
override def NextFireModeIndex(index : Int) : Int = index
DefaultFireModeIndex = 1
} //phoenix
val hunterseeker = ToolDefinition(ObjectClass.hunterseeker)
val lancer = ToolDefinition(ObjectClass.lancer)
@ -807,9 +751,7 @@ object GlobalDefinitions {
val lightgunship_weapon_system = ToolDefinition(ObjectClass.lightgunship_weapon_system)
val wasp_weapon_system = new ToolDefinition(ObjectClass.wasp_weapon_system) {
override def NextFireModeIndex(index : Int) : Int = index
}
val wasp_weapon_system = new ToolDefinition(ObjectClass.wasp_weapon_system)
val liberator_weapon_system = ToolDefinition(ObjectClass.liberator_weapon_system)
@ -955,13 +897,13 @@ object GlobalDefinitions {
val router_telepad_deployable = DeployableDefinition(DeployedItem.router_telepad_deployable)
//this is only treated like a deployable
val internal_router_telepad_deployable = DeployableDefinition(DeployedItem.router_telepad_deployable)
init_deployables()
/*
Miscellaneous
*/
val ams_respawn_tube = new SpawnTubeDefinition(49)
val matrix_terminala = new MatrixTerminalDefinition(517)
@ -1947,6 +1889,11 @@ object GlobalDefinitions {
* Initialize `ProjectileDefinition` globals.
*/
private def init_projectile() : Unit = {
val projectileConverter : ProjectileConverter = new ProjectileConverter
no_projectile.Name = "none"
ProjectileDefinition.CalculateDerivedFields(no_projectile)
bullet_105mm_projectile.Name = "105mmbullet_projectile"
bullet_105mm_projectile.Damage0 = 150
bullet_105mm_projectile.Damage1 = 300
@ -2216,6 +2163,10 @@ object GlobalDefinitions {
aphelion_starfire_projectile.InitialVelocity = 45
aphelion_starfire_projectile.Lifespan = 7f
aphelion_starfire_projectile.ProjectileDamageType = DamageType.Aggravated
aphelion_starfire_projectile.ExistsOnRemoteClients = true
aphelion_starfire_projectile.RemoteClientData = (39577, 249) //starfire_projectile data
aphelion_starfire_projectile.AutoLock = true
aphelion_starfire_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(aphelion_starfire_projectile)
bolt_projectile.Name = "bolt_projectile"
@ -2692,6 +2643,9 @@ object GlobalDefinitions {
hunter_seeker_missile_projectile.ProjectileDamageType = DamageType.Splash
hunter_seeker_missile_projectile.InitialVelocity = 40
hunter_seeker_missile_projectile.Lifespan = 6.3f
hunter_seeker_missile_projectile.ExistsOnRemoteClients = true
hunter_seeker_missile_projectile.RemoteClientData = (39577, 201)
hunter_seeker_missile_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(hunter_seeker_missile_projectile)
jammer_cartridge_projectile.Name = "jammer_cartridge_projectile"
@ -2977,8 +2931,24 @@ object GlobalDefinitions {
oicw_projectile.ProjectileDamageType = DamageType.Splash
oicw_projectile.InitialVelocity = 5
oicw_projectile.Lifespan = 6.1f
oicw_projectile.ExistsOnRemoteClients = true
oicw_projectile.RemoteClientData = (13107, 195)
oicw_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(oicw_projectile)
oicw_little_buddy.Name = "oicw_projectile"
oicw_little_buddy.Damage0 = 75
oicw_little_buddy.Damage1 = 75
oicw_little_buddy.DamageAtEdge = 0.1f
oicw_little_buddy.DamageRadius = 7.5f
oicw_little_buddy.ProjectileDamageType = DamageType.Splash
oicw_little_buddy.InitialVelocity = 40
oicw_little_buddy.Lifespan = 0.5f
oicw_little_buddy.ExistsOnRemoteClients = false //TODO true
oicw_little_buddy.Packet = projectileConverter
//add_property oicw_little_buddy multi_stage_spawn_server_side true ...
ProjectileDefinition.CalculateDerivedFields(oicw_little_buddy)
pellet_gun_projectile.Name = "pellet_gun_projectile"
// TODO for later, maybe : set_resource_parent pellet_gun_projectile game_objects shotgun_shell_projectile
pellet_gun_projectile.Damage0 = 12
@ -3056,6 +3026,10 @@ object GlobalDefinitions {
peregrine_sparrow_projectile.ProjectileDamageType = DamageType.Splash
peregrine_sparrow_projectile.InitialVelocity = 45
peregrine_sparrow_projectile.Lifespan = 7.5f
peregrine_sparrow_projectile.ExistsOnRemoteClients = true
peregrine_sparrow_projectile.RemoteClientData = (13107, 187) //sparrow_projectile data
peregrine_sparrow_projectile.AutoLock = true
peregrine_sparrow_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(peregrine_sparrow_projectile)
phalanx_av_projectile.Name = "phalanx_av_projectile"
@ -3107,6 +3081,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 = (0,63)
phoenix_missile_guided_projectile.Packet = projectileConverter
//
ProjectileDefinition.CalculateDerivedFields(phoenix_missile_guided_projectile)
phoenix_missile_projectile.Name = "phoenix_missile_projectile"
@ -3383,6 +3362,10 @@ object GlobalDefinitions {
sparrow_projectile.ProjectileDamageType = DamageType.Splash
sparrow_projectile.InitialVelocity = 60
sparrow_projectile.Lifespan = 5.85f
sparrow_projectile.ExistsOnRemoteClients = true
sparrow_projectile.RemoteClientData = (13107, 187)
sparrow_projectile.AutoLock = true
sparrow_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(sparrow_projectile)
sparrow_secondary_projectile.Name = "sparrow_secondary_projectile"
@ -3397,6 +3380,10 @@ object GlobalDefinitions {
sparrow_secondary_projectile.ProjectileDamageType = DamageType.Splash
sparrow_secondary_projectile.InitialVelocity = 60
sparrow_secondary_projectile.Lifespan = 5.85f
sparrow_secondary_projectile.ExistsOnRemoteClients = true
sparrow_secondary_projectile.RemoteClientData = (13107, 187)
sparrow_secondary_projectile.AutoLock = true
sparrow_secondary_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(sparrow_secondary_projectile)
spiker_projectile.Name = "spiker_projectile"
@ -3447,6 +3434,10 @@ object GlobalDefinitions {
starfire_projectile.ProjectileDamageType = DamageType.Aggravated
starfire_projectile.InitialVelocity = 45
starfire_projectile.Lifespan = 7.8f
starfire_projectile.ExistsOnRemoteClients = true
starfire_projectile.RemoteClientData = (39577, 249)
starfire_projectile.AutoLock = true
starfire_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(starfire_projectile)
striker_missile_projectile.Name = "striker_missile_projectile"
@ -3478,6 +3469,10 @@ object GlobalDefinitions {
striker_missile_targeting_projectile.ProjectileDamageType = DamageType.Splash
striker_missile_targeting_projectile.InitialVelocity = 30
striker_missile_targeting_projectile.Lifespan = 4.2f
striker_missile_targeting_projectile.ExistsOnRemoteClients = true
striker_missile_targeting_projectile.RemoteClientData = (26214, 134)
striker_missile_targeting_projectile.AutoLock = true
striker_missile_targeting_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(striker_missile_targeting_projectile)
trek_projectile.Name = "trek_projectile"
@ -3565,6 +3560,10 @@ object GlobalDefinitions {
wasp_rocket_projectile.ProjectileDamageType = DamageType.Splash
wasp_rocket_projectile.InitialVelocity = 60
wasp_rocket_projectile.Lifespan = 6.5f
wasp_rocket_projectile.ExistsOnRemoteClients = true
wasp_rocket_projectile.RemoteClientData = (0, 208)
wasp_rocket_projectile.AutoLock = true
wasp_rocket_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(wasp_rocket_projectile)
winchester_projectile.Name = "winchester_projectile"
@ -4337,6 +4336,7 @@ object GlobalDefinitions {
medicalapplicator.Name = "medicalapplicator"
medicalapplicator.Size = EquipmentSize.Pistol
medicalapplicator.AmmoTypes += health_canister
medicalapplicator.ProjectileTypes += no_projectile
medicalapplicator.FireModes += new FireModeDefinition
medicalapplicator.FireModes.head.AmmoTypeIndices += 0
medicalapplicator.FireModes.head.AmmoSlotIndex = 0
@ -4351,9 +4351,12 @@ object GlobalDefinitions {
nano_dispenser.Size = EquipmentSize.Rifle
nano_dispenser.AmmoTypes += armor_canister
nano_dispenser.AmmoTypes += upgrade_canister
nano_dispenser.ProjectileTypes += no_projectile
nano_dispenser.FireModes += new FireModeDefinition
nano_dispenser.FireModes.head.AmmoTypeIndices += 0
nano_dispenser.FireModes.head.AmmoTypeIndices += 1
nano_dispenser.FireModes.head.ProjectileTypeIndices += 0 //armor_canister
nano_dispenser.FireModes.head.ProjectileTypeIndices += 0 //upgrade_canister
nano_dispenser.FireModes.head.AmmoSlotIndex = 0
nano_dispenser.FireModes.head.Magazine = 100
nano_dispenser.FireModes.head.CustomMagazine = Ammo.upgrade_canister -> 1
@ -4364,6 +4367,7 @@ object GlobalDefinitions {
bank.Name = "bank"
bank.Size = EquipmentSize.Pistol
bank.AmmoTypes += armor_canister
bank.ProjectileTypes += no_projectile
bank.FireModes += new FireModeDefinition
bank.FireModes.head.AmmoTypeIndices += 0
bank.FireModes.head.AmmoSlotIndex = 0

View file

@ -35,7 +35,16 @@ final case class Projectile(profile : ProjectileDefinition,
attribute_to : Int,
shot_origin : Vector3,
shot_angle : Vector3,
fire_time: Long = System.nanoTime) {
fire_time: Long = System.nanoTime) extends PlanetSideGameObject {
Position = shot_origin
Orientation = shot_angle
Velocity = {
val initVel : Int = profile.InitialVelocity //initial velocity
val radAngle : Double = math.toRadians(shot_angle.y) //angle of elevation
val rise : Float = initVel * math.sin(radAngle).toFloat //z
val ground : Float = initVel * math.cos(radAngle).toFloat //base
Vector3.Rz(Vector3(0, -ground, 0), shot_angle.z) + Vector3.z(rise)
}
/** Information about the current world coordinates and orientation of the projectile */
val current : SimpleWorldEntity = new SimpleWorldEntity()
private var resolved : ProjectileResolution.Value = ProjectileResolution.Unresolved
@ -54,6 +63,8 @@ final case class Projectile(profile : ProjectileDefinition,
def isResolved : Boolean = resolved == ProjectileResolution.Resolved || resolved == ProjectileResolution.MissedShot
def isMiss : Boolean = resolved == ProjectileResolution.MissedShot
def Definition = profile
}
object Projectile {

View file

@ -5,6 +5,8 @@ package net.psforever.objects.ballistics
* An `Enumeration` of all the projectile types in the game, paired with their object id as the `Value`.
*/
object Projectiles extends Enumeration {
final val no_projectile = Value(0)
final val bullet_105mm_projectile = Value(1)
final val bullet_12mm_projectile = Value(4)
final val bullet_12mm_projectileb = Value(5)
@ -92,6 +94,7 @@ object Projectiles extends Enumeration {
final val mine_projectile = Value(551)
final val mine_sweeper_projectile = Value(554)
final val mine_sweeper_projectile_enh = Value(555)
final val oicw_little_buddy = Value(601)
final val oicw_projectile = Value(602)
final val pellet_gun_projectile = Value(631)
final val peregrine_dual_machine_gun_projectile = Value(639)

View file

@ -23,6 +23,9 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
private var damageAtEdge : Float = 1f
private var damageRadius : Float = 1f
private var useDamage1Subtract : Boolean = false
private var existsOnRemoteClients : Boolean = false //`true` spawns a server-managed object
private var remoteClientData : (Int, Int) = (0, 0) //artificial values; for ObjectCreateMessage packet (oicw_little_buddy is undefined)
private var autoLock : Boolean = false
//derived calculations
private var distanceMax : Float = 0f
private var distanceFromAcceleration : Float = 0f
@ -109,6 +112,27 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
DamageRadius
}
def ExistsOnRemoteClients : Boolean = existsOnRemoteClients
def ExistsOnRemoteClients_=(existsOnRemoteClients : Boolean) : Boolean = {
this.existsOnRemoteClients = existsOnRemoteClients
ExistsOnRemoteClients
}
def RemoteClientData : (Int, Int) = remoteClientData
def RemoteClientData_=(remoteClientData : (Int, Int)) : (Int, Int) = {
this.remoteClientData = remoteClientData
RemoteClientData
}
def AutoLock : Boolean = autoLock
def AutoLock_=(lockState : Boolean) : Boolean = {
autoLock = lockState
AutoLock
}
def DistanceMax : Float = distanceMax //accessor only
def DistanceFromAcceleration : Float = distanceFromAcceleration //accessor only

View file

@ -0,0 +1,43 @@
// Copyright (c) 2019 PSForever
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, RemoteProjectileData}
import scala.util.{Failure, Success, Try}
class ProjectileConverter extends ObjectCreateConverter[Projectile]() {
override def ConstructorData(obj : Projectile) : Try[RemoteProjectileData] = {
Success(
RemoteProjectileData(
CommonFieldDataWithPlacement(
PlacementData(
obj.Position,
obj.Orientation,
obj.Velocity
),
CommonFieldData(
obj.owner.Faction,
false,
false,
true,
None,
false,
None,
None,
PlanetSideGUID(0)
)
),
obj.profile.RemoteClientData._1,
obj.profile.RemoteClientData._2,
FlightPhysics.State4,
0,
0
)
)
}
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,82 @@
// 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(
{
val unk2aEmpty = unk2a.isEmpty
val unk2bEmpty = unk2b.isEmpty
val unk2cEmpty = unk2c.isEmpty
if(unk2a.nonEmpty) unk2bEmpty && unk2cEmpty
else if(unk2b.nonEmpty) unk2 && unk2aEmpty && unk2cEmpty
else unk2aEmpty && !unk2 && unk2bEmpty && unk2c.nonEmpty
}
)
assert(
{
val unk3aEmpty = unk3a.isEmpty
val unk3bEmpty = unk3b.isEmpty
val unk3cEmpty = unk3c.isEmpty
if(unk3a.nonEmpty) unk3bEmpty && unk3cEmpty
else if(unk3b.nonEmpty) unk3 && unk3aEmpty && unk3cEmpty
else unk3aEmpty && !unk3 && unk3bEmpty && 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,33 @@
// 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._
/**
* na
* @param guid1 na
* @param unk1 na
* @param guid2 na
* @param unk2 na
*/
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

@ -2,49 +2,58 @@
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 demonstrated 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_original_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 `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
* @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 time_alive how long the projectile has been in the air;
* often expressed in multiples of 2
* @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 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,
unk1 : Int,
unk2 : Int,
unk3 : Int,
unk4 : Boolean,
time_alive : Int)
shot_original_orient : Vector3,
sequence_num : Int,
end : Boolean,
hit_target_guid : PlanetSideGUID)
extends PlanetSideGamePacket {
type Packet = ProjectileStateMessage
def opcode = GamePacketOpcode.ProjectileStateMessage
@ -56,10 +65,20 @@ 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) ::
("time_alive" | uint16L)
).as[ProjectileStateMessage]
("roll" | Angular.codec_roll) ::
("pitch" | Angular.codec_pitch) ::
("yaw" | Angular.codec_yaw()) ::
("sequence_num" | uint8) ::
("end" | bool) ::
("hit_target" | PlanetSideGUID.codec)
).xmap[ProjectileStateMessage] (
{
case guid :: pos :: vel :: roll :: pitch :: yaw :: sequence_num :: explode :: unk :: HNil =>
ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), sequence_num , explode, unk)
},
{
case ProjectileStateMessage(guid, pos, vel, Vector3(roll, pitch, yaw), sequence_num, explode, unk) =>
guid :: pos :: vel :: roll :: pitch :: yaw :: sequence_num :: explode :: unk :: HNil
}
)
}

View file

@ -295,7 +295,7 @@ object ObjectClass {
final val portable_manned_turret_tr = 687
final val portable_manned_turret_vs = 688
//projectiles
final val hunter_seeker_missile_projectile = 405
final val hunter_seeker_missile_projectile = 405 //phoenix projectile
final val meteor_common = 543
final val meteor_projectile_b_large = 544
final val meteor_projectile_b_medium = 545
@ -303,13 +303,14 @@ object ObjectClass {
final val meteor_projectile_large = 547
final val meteor_projectile_medium = 548
final val meteor_projectile_small = 549
final val oicw_little_buddy = 601
final val oicw_projectile = 602
final val phoenix_missile_guided_projectile = 675 //decimator projectile
final val oicw_little_buddy = 601 //scorpion projectile's projectiles
final val oicw_projectile = 602 //scorpion projectile
final val radiator_cload = 717
final val sparrow_projectile = 792
final val starfire_projectile = 831
final val striker_missile_targeting_projectile = 841
final val wasp_rocket_projectile = 1001
final val sparrow_projectile = 792 //nc aa max projectile
final val starfire_projectile = 831 //vs aa max projectile
final val striker_missile_targeting_projectile = 841 //striker projectile
final val wasp_rocket_projectile = 1001 //wasp projectile
//vehicles
final val apc_destroyed = 65
final val apc_tr = 67 //juggernaut
@ -1222,19 +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.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,57 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game.objectcreate
import net.psforever.packet.{Marshallable, PacketHelpers}
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
object TrackedProjectile extends Enumeration {
type Type = Value
val Meteor = Value(32)
val WaspRocket = Value(208)
val Sparrow = Value(3355579)
val OICW = Value(3355587)
val Striker = Value(6710918)
val HunterSeeker = Value(10131913)
val Starfire = Value(10131961)
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint24)
}
/**
* A representation of a projectile that the server must intentionally convey to players other than the shooter.
* @param data na
* @param unk2 na;
* data specific to the type of projectile(?)
* @param unk3 na
*/
final case class TrackedProjectileData(data : CommonFieldDataWithPlacement,
unk2 : TrackedProjectile.Value,
unk3 : Int = 0
) extends ConstructorData {
override def bitsize : Long = 33L + data.bitsize
}
object TrackedProjectileData extends Marshallable[TrackedProjectileData] {
implicit val codec : Codec[TrackedProjectileData] = (
("data" | CommonFieldDataWithPlacement.codec) ::
("unk2" | TrackedProjectile.codec) ::
uint4 ::
uint(3) ::
uint2
).exmap[TrackedProjectileData] (
{
case data :: unk2 :: 4 :: unk3 :: 0 :: HNil =>
Attempt.successful(TrackedProjectileData(data, unk2, unk3))
case data =>
Attempt.failure(Err(s"invalid projectile data format - $data"))
},
{
case TrackedProjectileData(data, unk2, unk3) =>
Attempt.successful(data :: unk2 :: 4 :: unk3 :: 0 :: HNil)
}
)
}

View file

@ -2,7 +2,7 @@
package services.avatar
import akka.actor.{Actor, ActorRef, Props}
import net.psforever.packet.game.ObjectCreateMessage
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectCreateMessageParent, PlacementData}
import services.avatar.support.{CorpseRemovalActor, DroppedItemRemover}
import services.{GenericEventBus, RemoverActor, Service}
@ -109,6 +109,10 @@ class AvatarService extends Actor {
AvatarResponse.EquipmentInHand(ObjectCreateMessage(definition.ObjectId, item.GUID, containerData, objectData))
)
)
case AvatarAction.GenericObjectAction(player_guid, object_guid, action_code) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.GenericObjectAction(object_guid, action_code))
)
case AvatarAction.HitHint(source_guid, player_guid) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.HitHint(source_guid))
@ -127,6 +131,12 @@ class AvatarService extends Actor {
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(pkt))
)
case AvatarAction.LoadProjectile(player_guid, object_id, object_guid, cdata) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadProjectile(
ObjectCreateMessage(object_id, object_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 +161,18 @@ 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.ProjectileAutoLockAwareness(mode) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", PlanetSideGUID(0), AvatarResponse.ProjectileAutoLockAwareness(mode))
)
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, sequence, end, target) =>
AvatarEvents.publish(
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)
AvatarEvents.publish(

View file

@ -2,7 +2,7 @@
package services.avatar
import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.inventory.Container
@ -30,16 +30,18 @@ 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
final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) extends Action
final case class DropItem(player_guid : PlanetSideGUID, item : Equipment, zone : Zone) extends Action
final case class EquipmentInHand(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
final case class GenericObjectAction(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, action_code : Int) extends Action
final case class HitHint(source_guid : PlanetSideGUID, player_guid : PlanetSideGUID) extends Action
final case class KilledWhileInVehicle(player_guid : PlanetSideGUID) extends Action
final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action
final case class LoadProjectile(player_guid : PlanetSideGUID, object_id : Int, projectile_guid : PlanetSideGUID, 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 +49,9 @@ 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 ProjectileAutoLockAwareness(mode : Int) 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, 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

@ -2,7 +2,7 @@
package services.avatar
import net.psforever.objects.Player
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.objectcreate.ConstructorData
@ -30,15 +30,20 @@ object AvatarResponse {
final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int) extends Response
final case class DropItem(pkt : ObjectCreateMessage) extends Response
final case class EquipmentInHand(pkt : ObjectCreateMessage) extends Response
final case class GenericObjectAction(object_guid : PlanetSideGUID, action_code : Int) extends Response
final case class HitHint(source_guid : PlanetSideGUID) extends Response
final case class KilledWhileInVehicle() extends Response
final case class LoadPlayer(pkt : ObjectCreateMessage) extends Response
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
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 ProjectileAutoLockAwareness(mode : Int) 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, 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
}
"assert catches" 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

@ -8,23 +8,18 @@ import net.psforever.types.Vector3
import scodec.bits._
class ProjectileStateMessageTest extends Specification {
val string = hex"3f 259d c5019 30e4a 9514 c52c9541 d9ba05c2 c5973941 00 f8 ec 020000"
val string = hex"3f 259d c5019 30e4a 9514 c52c9541 d9ba05c2 c5973941 00 f8 ec 02000000"
"decode" in {
PacketCoding.DecodePacket(string).require match {
case ProjectileStateMessage(projectile, pos, vel, unk1, unk2, unk3, unk4, time_alive) =>
case ProjectileStateMessage(projectile, pos, vel, orient, sequence, explode, unk) =>
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
time_alive mustEqual 4
pos mustEqual Vector3(4611.539f, 5576.375f, 82.328125f)
vel mustEqual Vector3(18.64686f, -33.43247f, 11.599553f)
orient mustEqual Vector3(0, 22.5f, 146.25f)
sequence mustEqual 2
explode mustEqual false
unk mustEqual PlanetSideGUID(0)
case _ =>
ko
}
@ -35,10 +30,18 @@ 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, 146.25f),
2,
false,
PlanetSideGUID(0)
)
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

@ -0,0 +1,118 @@
// Copyright (c) 2017 PSForever
package game.objectcreate
import net.psforever.packet.PacketCoding
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{PlanetSideEmpire, Vector3}
import org.specs2.mutable._
import scodec.bits._
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"
"RemoteProjectileData" should {
"decode (striker_missile_targeting_projectile)" in {
PacketCoding.DecodePacket(string_striker_projectile).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 197
cls mustEqual ObjectClass.striker_missile_targeting_projectile
guid mustEqual PlanetSideGUID(40192)
parent.isDefined mustEqual false
data match {
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
deploy.bops mustEqual false
deploy.alternate mustEqual false
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
deploy.v3 mustEqual false
deploy.v4.isEmpty mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(0)
unk2 mustEqual 26214
lim mustEqual 134
unk3 mustEqual FlightPhysics.State4
unk4 mustEqual 0
unk5 mustEqual 0
case _ =>
ko
}
case _ =>
ko
}
}
"decode (hunter_seeker_missile_projectile)" in {
PacketCoding.DecodePacket(string_hunter_seeker_missile_projectile).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 197
cls mustEqual ObjectClass.hunter_seeker_missile_projectile
guid mustEqual PlanetSideGUID(40619)
parent.isDefined mustEqual false
data match {
case 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
deploy.bops mustEqual false
deploy.alternate mustEqual false
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
deploy.v3 mustEqual false
deploy.v4.isEmpty mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(0)
unk2 mustEqual 39577
lim mustEqual 201
unk3 mustEqual FlightPhysics.State4
unk4 mustEqual 0
unk5 mustEqual 0
case _ =>
ko
}
case _ =>
ko
}
}
"encode (striker_missile_targeting_projectile)" in {
val obj = 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))
),
26214, 134, FlightPhysics.State4, 0, 0
)
val msg = ObjectCreateMessage(ObjectClass.striker_missile_targeting_projectile, PlanetSideGUID(40192), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
//pkt mustEqual string_striker_projectile
pkt.toBitVector.take(132) mustEqual string_striker_projectile.toBitVector.take(132)
pkt.toBitVector.drop(133).take(7) mustEqual string_striker_projectile.toBitVector.drop(133).take(7)
pkt.toBitVector.drop(141) mustEqual string_striker_projectile.toBitVector.drop(141)
}
}
"encode (hunter_seeker_missile_projectile)" in {
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))
),
39577, 201, FlightPhysics.State4, 0, 0
)
val msg = ObjectCreateMessage(ObjectClass.hunter_seeker_missile_projectile, PlanetSideGUID(40619), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
//pkt mustEqual string_hunter_seeker_missile_projectile
pkt.toBitVector.take(132) mustEqual string_hunter_seeker_missile_projectile.toBitVector.take(132)
pkt.toBitVector.drop(133).take(7) mustEqual string_hunter_seeker_missile_projectile.toBitVector.drop(133).take(7)
pkt.toBitVector.drop(141) mustEqual string_hunter_seeker_missile_projectile.toBitVector.drop(141)
}
}

View file

@ -1,64 +0,0 @@
// Copyright (c) 2017 PSForever
package game.objectcreate
import net.psforever.packet.PacketCoding
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID}
import net.psforever.packet.game.objectcreate._
import net.psforever.types.{PlanetSideEmpire, Vector3}
import org.specs2.mutable._
import scodec.bits._
class TrackedProjectileDataTest extends Specification {
val string_striker_projectile = hex"17 C5000000 A4B 009D 4C129 0CB0A 9814 00 F5 E3 040000666686400"
"TrackedProjectileData" should {
"decode" in {
PacketCoding.DecodePacket(string_striker_projectile).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 197
cls mustEqual ObjectClass.striker_missile_targeting_projectile
guid mustEqual PlanetSideGUID(40192)
parent.isDefined mustEqual false
data match {
case TrackedProjectileData(CommonFieldDataWithPlacement(pos, deploy), unk2, unk3) =>
pos.coord mustEqual Vector3(4644.5938f, 5472.0938f, 82.375f)
pos.orient mustEqual Vector3(0, 30.9375f, 171.5625f)
deploy.faction mustEqual PlanetSideEmpire.TR
deploy.bops mustEqual false
deploy.alternate mustEqual false
deploy.v1 mustEqual true
deploy.v2.isEmpty mustEqual true
deploy.v3 mustEqual false
deploy.v4.isEmpty mustEqual true
deploy.v5.isEmpty mustEqual true
deploy.guid mustEqual PlanetSideGUID(0)
unk2 mustEqual TrackedProjectile.Striker
unk3 mustEqual 0
case _ =>
ko
}
case _ =>
ko
}
}
"encode" in {
val obj = TrackedProjectileData(
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))
),
TrackedProjectile.Striker,
0
)
val msg = ObjectCreateMessage(ObjectClass.striker_missile_targeting_projectile, PlanetSideGUID(40192), obj)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt.toBitVector.take(132) mustEqual string_striker_projectile.toBitVector.take(132)
pkt.toBitVector.drop(133).take(7) mustEqual string_striker_projectile.toBitVector.drop(133).take(7)
pkt.toBitVector.drop(141) mustEqual string_striker_projectile.toBitVector.drop(141)
}
}
}

View file

@ -56,6 +56,7 @@ import services.vehicle.support.TurretUpgrader
import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
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
@ -69,6 +70,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._
@ -115,6 +118,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
var whenUsedLastSAKit : Long = 0
var whenUsedLastSSKit : Long = 0
val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None)
val projectilesToCleanUp : Array[Boolean] = Array.fill[Boolean](Projectile.RangeUID - Projectile.BaseUID)(false)
var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
var updateSquad : () => Unit = NoSquadUpdates
var recentTeleportAttempt : Long = 0
@ -164,6 +168,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
var antChargingTick : Cancellable = DefaultCancellable.obj
var antDischargingTick : Cancellable = DefaultCancellable.obj
/**
* Convert a boolean value into an integer value.
* Use: `true:Int` or `false:Int`
@ -1218,6 +1223,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"Received a direct message: $pkt")
sendResponse(pkt)
case LoadedRemoteProjectile(projectile_guid, Some(projectile)) =>
if(projectile.profile.ExistsOnRemoteClients) {
//spawn projectile on other clients
val definition = projectile.Definition
avatarService ! AvatarServiceMessage(
continent.Id,
AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(projectile).get)
)
}
//immediately slated for deletion?
CleanUpRemoteProjectile(projectile.GUID, projectile)
case LoadedRemoteProjectile(projectile_guid, None) =>
continent.GUID(projectile_guid) match {
case Some(obj : Projectile) if obj.profile.ExistsOnRemoteClients =>
//spawn projectile on other clients
val definition = obj.Definition
avatarService ! AvatarServiceMessage(
continent.Id,
AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get)
)
case _ => ;
}
case default =>
log.warn(s"Invalid packet class received: $default from $sender")
}
@ -1365,6 +1394,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(pkt)
}
case AvatarResponse.GenericObjectAction(object_guid, action_code) =>
if(tplayer_guid != guid) {
sendResponse(GenericObjectActionMessage(object_guid, action_code))
}
case AvatarResponse.HitHint(source_guid) =>
if(player.isAlive) {
sendResponse(HitHint(source_guid, guid))
@ -1403,6 +1437,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))
@ -1464,6 +1503,24 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
case AvatarResponse.ProjectileExplodes(projectile_guid, projectile) =>
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, seq, end, target_guid) =>
if(tplayer_guid != guid) {
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) {
sendResponse(GenericObjectActionMessage(target, 212))
@ -3457,6 +3514,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
//all head features, faction, and sex also match that test character
import net.psforever.objects.GlobalDefinitions._
import net.psforever.types.CertificationType._
val faction = PlanetSideEmpire.VS
val avatar = new Avatar(41605313L+sessionId, s"TestCharacter$sessionId", faction, CharacterGender.Female, 41, CharacterVoice.Voice1)
avatar.Certifications += StandardAssault
@ -3855,52 +3913,70 @@ class WorldSessionActor extends Actor with MDCContextAware {
self ! SetCurrentAvatar(player)
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 (timeDL != 0) {
if (System.currentTimeMillis() - timeDL > 500) {
player.Stamina = player.Stamina - 1
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
timeDL = System.currentTimeMillis()
val isMoving = WorldEntity.isMoving(vel)
//implants and stamina management start
val implantsAreActive = avatar.Implants(0).Active || avatar.Implants(1).Active
val staminaBefore = player.Stamina
val hadStaminaBefore = staminaBefore > 0
val hasStaminaAfter = if(deadState == DeadState.Alive) {
if(implantsAreActive && hadStaminaBefore) {
val time = System.currentTimeMillis()
if(timeDL != 0) {
val duration = time - timeSurge
if(duration > 500) {
val units = (duration / 500).toInt
player.Stamina = player.Stamina - units
timeDL += units * 500
}
}
if(timeSurge != 0) {
val duration = time - timeSurge
val period = player.ExoSuit match {
case ExoSuitType.Agile => 500
case ExoSuitType.Reinforced => 333
case ExoSuitType.Infiltration => 1000
case ExoSuitType.Standard => 1000
case _ => 1
}
if(duration > period) {
val units = (duration / period).toInt
player.Stamina = player.Stamina - units
timeSurge += period * units
}
}
}
if (timeSurge != 0) {
if (System.currentTimeMillis() - 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()
}
else if (System.currentTimeMillis() - 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()
}
else if (System.currentTimeMillis() - 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()
}
}
if (player.Stamina == 0) {
if (avatar.Implants(0).Active) {
avatar.Implants(0).Active = false
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(0).id * 2))
sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 0, 0))
timeDL = 0
}
if (avatar.Implants(1).Active) {
avatar.Implants(1).Active = false
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(1).id * 2))
sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 1, 0))
timeSurge = 0
}
}
val isMoving = WorldEntity.isMoving(vel)
if (!isMoving && player.Stamina < player.MaxStamina) {
//if the player lost all stamina this turn (had stamina at the start), do not renew 1 stamina
if(!isMoving && (if(player.Stamina > 0) player.Stamina < player.MaxStamina else !hadStaminaBefore)) {
player.Stamina = player.Stamina + 1
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
true
}
else {
player.Stamina > 0
}
}
else {
timeDL = 0
timeSurge = 0
false
}
if(staminaBefore != player.Stamina) { //stamina changed
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina))
}
if(implantsAreActive && !hasStaminaAfter) { //implants deactivated at 0 stamina
if(avatar.Implants(0).Active) {
avatar.Implants(0).Active = false
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(0).id * 2))
sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 0, 0))
timeDL = 0
}
if(avatar.Implants(1).Active) {
avatar.Implants(1).Active = false
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player.GUID, 28, avatar.Implant(1).id * 2))
sendResponse(AvatarImplantMessage(PlanetSideGUID(player.GUID.guid), ImplantAction.Activation, 1, 0))
timeSurge = 0
}
}
//implants and stamina management finish
player.Position = pos
player.Velocity = vel
player.Orientation = Vector3(player.Orientation.x, pitch, yaw)
@ -4002,8 +4078,21 @@ 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, seq, end, target_guid) =>
//log.trace(s"ProjectileState: $msg")
val index = projectile_guid.guid - Projectile.BaseUID
projectiles(index) match {
case Some(projectile) if projectile.HasGUID =>
val projectileGlobalUID = projectile.GUID
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, seq, end, target_guid))
case _ if seq == 0 =>
/* missing the first packet in the sequence is permissible */
case _ =>
log.warn(s"ProjectileState: constructed projectile ${projectile_guid.guid} can not be found")
}
case msg @ ReleaseAvatarRequestMessage() =>
log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released")
@ -4278,14 +4367,17 @@ 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 {
case Some(tool : Tool) =>
if(tool.Magazine > 0 || prefire.contains(item_guid)) {
prefire = None
shooting = Some(item_guid)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
//special case - suppress the decimator's alternate fire mode, by projectile
if(tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) {
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
}
}
else {
log.warn(s"ChangeFireState_Start: ${tool.Definition.Name} magazine is empty before trying to shoot bullet")
@ -4301,7 +4393,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
@ -4309,13 +4401,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")
@ -4474,7 +4571,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
//log.info("AvatarJump: " + msg)
player.Stamina = player.Stamina - 10
if(player.Stamina < 0) player.Stamina = 0
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina))
case msg @ ZipLineMessage(player_guid,origin_side,action,id,pos) =>
log.info("ZipLineMessage: " + msg)
@ -4533,6 +4630,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")
@ -4682,7 +4783,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
if (avatar.Implant(slot).id == 3) {
timeDL = System.currentTimeMillis()
player.Stamina = player.Stamina - 3
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, player.Stamina))
}
if (avatar.Implant(slot).id == 9) timeSurge = System.currentTimeMillis()
} else if(action == ImplantAction.Activation && status == 0) { //desactive
@ -5404,7 +5505,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
@ -5420,11 +5521,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
else { //shooting
if (tool.FireModeIndex == 1 && (tool.Definition.Name == "anniversary_guna" || tool.Definition.Name == "anniversary_gun" || tool.Definition.Name == "anniversary_gunb")) {
player.Stamina = 0
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
sendResponse(PlanetsideAttributeMessage(player.GUID, 2, 0))
}
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 {
@ -5435,7 +5536,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
val (angle, attribution, acceptableDistanceToOwner) = obj match {
case p : Player =>
(SimpleWorldEntity.validateOrientationEntry(p.Orientation + Vector3.z(p.FacingYawUpper)), tool.Definition.ObjectId, 10f)
(SimpleWorldEntity.validateOrientationEntry(p.Orientation + Vector3.z(p.FacingYawUpper)), tool.Definition.ObjectId, 10f + (if(p.Velocity.nonEmpty) { 5f } else { 0f }))
case v : Vehicle if v.Definition.CanFly =>
(tool.Orientation, obj.Definition.ObjectId, 1000f) //TODO this is too simplistic to find proper angle
case _ : Vehicle =>
@ -5445,8 +5546,20 @@ 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) {
log.trace(s"WeaponFireMessage: ${projectile_info.Name} is a remote projectile")
taskResolver ! (if(projectile.HasGUID) {
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile))
ReregisterProjectile(projectile)
}
else {
RegisterProjectile(projectile)
})
}
projectilesToCleanUp(projectileIndex) = false
}
else {
log.warn(s"WeaponFireMessage: $player's ${tool.Definition.Name} projectile is too far from owner position at time of discharge ($distanceToOwner > $acceptableDistanceToOwner); suspect")
@ -5458,6 +5571,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ WeaponLazeTargetPositionMessage(weapon, pos1, pos2) =>
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 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 _ => ;
}
case msg @ HitMessage(seq_time, projectile_guid, unk1, hit_info, unk2, unk3, unk4) =>
log.info(s"Hit: $msg")
(hit_info match {
@ -5482,26 +5610,42 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ SplashHitMessage(seq_time, projectile_guid, explosion_pos, direct_victim_uid, unk3, projectile_vel, unk4, targets) =>
log.info(s"Splash: $msg")
continent.GUID(direct_victim_uid) match {
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
ResolveProjectileEntry(projectile_guid, ProjectileResolution.Splash, target, target.Position) match {
case Some(projectile) =>
HandleDealingDamage(target, projectile)
case None => ;
FindProjectileEntry(projectile_guid) match {
case Some(projectile) =>
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 {
case Some(projectile) =>
HandleDealingDamage(target, projectile)
case None => ;
}
case _ => ;
}
case _ => ;
}
targets.foreach(elem => {
continent.GUID(elem.uid) match {
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
ResolveProjectileEntry(projectile_guid, ProjectileResolution.Splash, target, explosion_pos) match {
case Some(projectile) =>
HandleDealingDamage(target, projectile)
case None => ;
targets.foreach(elem => {
continent.GUID(elem.uid) match {
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, explosion_pos) match {
case Some(projectile) =>
HandleDealingDamage(target, projectile)
case None => ;
}
case _ => ;
}
case _ => ;
}
})
})
if(projectile.profile.ExistsOnRemoteClients && projectile.HasGUID) {
//cleanup
val localIndex = projectile_guid.guid - Projectile.BaseUID
if(projectile.HasGUID) {
CleanUpRemoteProjectile(projectile.GUID, projectile, localIndex)
}
else {
projectilesToCleanUp(localIndex) = true
}
}
case None => ;
}
case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, pos, unk1) =>
log.info(s"Lash: $msg")
@ -6068,6 +6212,37 @@ class WorldSessionActor extends Actor with MDCContextAware {
}, List(GUIDTask.RegisterAvatar(driver)(continent.GUID), GUIDTask.RegisterVehicle(obj)(continent.GUID)))
}
/**
* Construct tasking that adds a completed but unregistered projectile into the scene.
* After the projectile is registered to the curent zone's global unique identifier system,
* all connected clients save for the one that registered it will be informed about the projectile's "creation."
* @param obj the projectile to be registered
* @return a `TaskResolver.GiveTask` message
*/
def RegisterProjectile(obj : Projectile) : TaskResolver.GiveTask = {
val definition = obj.Definition
TaskResolver.GiveTask(
new Task() {
private val globalProjectile = obj
private val localAnnounce = self
override def isComplete : Task.Resolution.Value = {
if(globalProjectile.HasGUID) {
Task.Resolution.Success
}
else {
Task.Resolution.Incomplete
}
}
def Execute(resolver : ActorRef) : Unit = {
localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID, Some(globalProjectile))
resolver ! scala.util.Success(this)
}
}, List(GUIDTask.RegisterObjectTask(obj)(continent.GUID))
)
}
def UnregisterDrivenVehicle(obj : Vehicle, driver : Player) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
@ -6089,6 +6264,37 @@ class WorldSessionActor extends Actor with MDCContextAware {
}, List(GUIDTask.UnregisterAvatar(driver)(continent.GUID), GUIDTask.UnregisterVehicle(obj)(continent.GUID)))
}
/**
* Construct tasking that removes a formerly complete and currently registered projectile from the scene.
* After the projectile is unregistered from the curent zone's global unique identifier system,
* all connected clients save for the one that registered it will be informed about the projectile's "destruction."
* @param obj the projectile to be unregistered
* @return a `TaskResolver.GiveTask` message
*/
def UnregisterProjectile(obj : Projectile) : TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
private val globalProjectile = obj
private val localAnnounce = avatarService
private val localMsg = AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, obj.GUID, 2))
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`
@ -6131,6 +6337,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
}
/**
* If the projectile object is unregistered, register it.
* If the projectile object is already registered, unregister it and then register it again.
* @see `RegisterProjectile(Projectile)`
* @see `UnregisterProjectile(Projectile)`
* @param obj the projectile to be registered (a second time?)
* @return a `TaskResolver.GiveTask` message
*/
def ReregisterProjectile(obj : Projectile) : TaskResolver.GiveTask = {
val reg = RegisterProjectile(obj)
if(obj.HasGUID) {
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>
@ -7794,7 +8024,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case _ => ;
}
}
log.info(s"AvatarCreate (vehicle): $guid -> $data")
//log.info(s"AvatarCreate (vehicle): $guid -> $data")
//player, passenger
AvatarCreateInVehicle(player, vehicle, seat)
@ -7805,7 +8035,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
val guid = player.GUID
sendResponse(ObjectCreateDetailedMessage(ObjectClass.avatar, guid, data))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.LoadPlayer(guid, ObjectClass.avatar, guid, packet.ConstructorData(player).get, None))
log.info(s"AvatarCreate: $guid -> $data")
//log.info(s"AvatarCreate: $guid -> $data")
log.trace(s"AvatarCreate: ${player.Name}")
}
continent.Population ! Zone.Population.Spawn(avatar, player)
//cautious redundancy
@ -7901,7 +8132,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatarService ! AvatarServiceMessage(vehicle.Continent, AvatarAction.LoadPlayer(guid, pdef.ObjectId, guid, pdef.Packet.ConstructorData(player).get, Some(parent)))
AccessContents(vehicle)
UpdateWeaponAtSeatPosition(vehicle, seat)
log.info(s"AvatarCreateInVehicle: $guid -> $data")
//log.info(s"AvatarCreateInVehicle: $guid -> $data")
log.trace(s"AvatarCreateInVehicle: ${player.Name} in ${vehicle.Definition.Name}")
}
/**
@ -7978,7 +8210,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
/**
* If the corpse has been well-looted, it has no items in its primary holsters nor any items in its inventory.
* If the corpse has been well-lootedP, it has no items in its primary holsters nor any items in its inventory.
* @param obj the corpse
* @return `true`, if the `obj` is actually a corpse and has no objects in its holsters or backpack;
* `false`, otherwise
@ -8331,9 +8563,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
/**
* Find a projectile with the given globally unique identifier and mark it as a resolved shot.
* A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle.
* The internal copy of the projectile is retained as merely `Resolved`
* while the observed projectile is promoted to the suggested resolution status.
* @param projectile the projectile object
* @param index where the projectile was found
* @param resolution the resolution status to promote the projectile
@ -8344,8 +8573,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.error(s"expected projectile could not be found at $index; can not resolve")
None
}
else if(projectile.isMiss) {
log.error(s"expected projectile at $index was already counted as a missed shot; can not resolve any further")
else {
ResolveProjectileEntry(projectile, resolution, target, pos)
}
}
/**
* Find a projectile with the given globally unique identifier and mark it as a resolved shot.
* A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle.
* The internal copy of the projectile is retained as merely `Resolved`
* while the observed projectile is promoted to the suggested resolution status.
* @param projectile the projectile object
* @param resolution the resolution status to promote the projectile
* @return a copy of the projectile
*/
def ResolveProjectileEntry(projectile : Projectile, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = {
if(projectile.isMiss) {
log.error("expected projectile was already counted as a missed shot; can not resolve any further")
None
}
else {
@ -9903,6 +10147,70 @@ class WorldSessionActor extends Actor with MDCContextAware {
squadUpdateCounter = (squadUpdateCounter + 1) % queuedSquadActions.length
}
/**
* The main purpose of this method is to determine which targets will receive "locked on" warnings from remote projectiles.
* For a given series of globally unique identifiers, indicating targets,
* and that may include mounted elements (players),
* estimate a series of channel names for communication with the vulnerable targets.
* @param targets the globally unique identifiers of the immediate detected targets
* @return channels names that allow direct communication to specific realized targets
*/
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)
}
}
/**
* For a given registered remote projectile, perform all the actions necessary to properly dispose of it.
* Those actions involve:
* informing that the projectile should explode,
* unregistering the projectile's globally unique identifier,
* and managing the projectiles's local status information.
* @see `CleanUpRemoteProjectile(PlanetSideGUID, Projectile, Int)`
* @param projectile_guid the globally unique identifier of the projectile
* @param projectile the projectile
*/
def CleanUpRemoteProjectile(projectile_guid : PlanetSideGUID, projectile : Projectile) : Unit = {
projectiles.indexWhere({
case Some(p) => p eq projectile
case None => false
}) match {
case -1 => ; //required catch
case index if projectilesToCleanUp(index) =>
CleanUpRemoteProjectile(projectile_guid, projectile, index)
case _ => ;
}
}
/**
* For a given registered remote projectile, perform all the actions necessary to properly dispose of it.
* Those actions involve:
* informing that the projectile should explode,
* unregistering the projectile's globally unique identifier,
* and managing the projectiles's local status information.
* @param projectile_guid the globally unique identifier of the projectile
* @param projectile the projectile
* @param local_index an index of the absolute sequence of the projectile, for internal lists
*/
def CleanUpRemoteProjectile(projectile_guid : PlanetSideGUID, projectile : Projectile, local_index : Int) : Unit = {
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile_guid, projectile))
taskResolver ! UnregisterProjectile(projectile)
projectiles(local_index) match {
case Some(obj) if !obj.isResolved => obj.Miss
case None => ;
}
projectilesToCleanUp(local_index) = false
}
def failWithError(error : String) = {
log.error(error)
sendResponse(ConnectionClose())
@ -10066,4 +10374,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, projectile : Option[Projectile])
}