events for future expansion; added custom projectile remote client data for OCM packet creation; initial projectile velocity calculation; (too many) details when it comes to Enumerations for projectiles; hooked up theoretical projectile creatiuon, management, and deletion

This commit is contained in:
FateJH 2019-05-17 01:54:27 -04:00
parent f1c73688f7
commit 8952ab86f6
10 changed files with 258 additions and 65 deletions

View file

@ -630,9 +630,7 @@ 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
@ -640,8 +638,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)
@ -2221,6 +2219,7 @@ object GlobalDefinitions {
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.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(aphelion_starfire_projectile)
@ -2699,6 +2698,7 @@ object GlobalDefinitions {
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)
@ -2986,6 +2986,7 @@ object GlobalDefinitions {
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)
@ -2997,7 +2998,7 @@ object GlobalDefinitions {
oicw_little_buddy.ProjectileDamageType = DamageType.Splash
oicw_little_buddy.InitialVelocity = 40
oicw_little_buddy.Lifespan = 0.5f
oicw_little_buddy.ExistsOnRemoteClients = true
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)
@ -3080,6 +3081,7 @@ object GlobalDefinitions {
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.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(peregrine_sparrow_projectile)
@ -3133,6 +3135,7 @@ object GlobalDefinitions {
phoenix_missile_guided_projectile.InitialVelocity = 0
phoenix_missile_guided_projectile.Lifespan = 3f
phoenix_missile_guided_projectile.ExistsOnRemoteClients = true
phoenix_missile_guided_projectile.RemoteClientData = (39577, 201) //hunter_seeker_missile_projectile data
phoenix_missile_guided_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(phoenix_missile_guided_projectile)
@ -3411,6 +3414,7 @@ object GlobalDefinitions {
sparrow_projectile.InitialVelocity = 60
sparrow_projectile.Lifespan = 5.85f
sparrow_projectile.ExistsOnRemoteClients = true
sparrow_projectile.RemoteClientData = (13107, 187)
sparrow_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(sparrow_projectile)
@ -3427,6 +3431,7 @@ object GlobalDefinitions {
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.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(sparrow_secondary_projectile)
@ -3479,6 +3484,7 @@ object GlobalDefinitions {
starfire_projectile.InitialVelocity = 45
starfire_projectile.Lifespan = 7.8f
starfire_projectile.ExistsOnRemoteClients = true
starfire_projectile.RemoteClientData = (39577, 249)
starfire_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(starfire_projectile)
@ -3512,6 +3518,7 @@ object GlobalDefinitions {
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.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(striker_missile_targeting_projectile)
@ -3601,6 +3608,7 @@ object GlobalDefinitions {
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.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(wasp_rocket_projectile)

View file

@ -36,6 +36,15 @@ final case class Projectile(profile : ProjectileDefinition,
shot_origin : Vector3,
shot_angle : Vector3,
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

View file

@ -24,6 +24,7 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
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)
//derived calculations
private var distanceMax : Float = 0f
private var distanceFromAcceleration : Float = 0f
@ -117,6 +118,13 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
ExistsOnRemoteClients
}
def RemoteClientData : (Int, Int) = remoteClientData
def RemoteClientData_=(remoteClientData : (Int, Int)) : (Int, Int) = {
this.remoteClientData = remoteClientData
RemoteClientData
}
def DistanceMax : Float = distanceMax //accessor only
def DistanceFromAcceleration : Float = distanceFromAcceleration //accessor only

View file

@ -15,7 +15,7 @@ class ProjectileConverter extends ObjectCreateConverter[Projectile]() {
PlacementData(
obj.Position,
obj.Orientation,
obj.Velocity
None
),
CommonFieldData(
obj.owner.Faction,
@ -29,11 +29,11 @@ class ProjectileConverter extends ObjectCreateConverter[Projectile]() {
PlanetSideGUID(0)
)
),
obj.profile.RemoteClientData._1,
obj.profile.RemoteClientData._2,
FlightPhysics.State4,
0,
0,
FlightPhysics.State3,
7,
2
0
)
)
}

View file

@ -2,10 +2,56 @@
package net.psforever.packet.game.objectcreate
import net.psforever.packet.{Marshallable, PacketHelpers}
import scodec.{Attempt, Codec, Err}
import scodec.{Attempt, Codec}
import scodec.codecs._
import shapeless.{::, HNil}
object TrackedProjectile extends Enumeration {
type Type = Value
val OICWLittleBuddy = Value(-1) //?, ?
val Meteor = Value(32) //0, 32
val Wasp = Value(208) //0, 208
val Sparrow = Value(3355579) //13107, 187
val OICW = Value(3355587) //13107, 195
val Striker = Value(6710918) //26214, 134
val HunterSeeker = Value(10131913) //39577, 201
val Starfire = Value(10131961) //39577, 249
implicit val codec = PacketHelpers.createEnumerationCodec(this, uint24)
}
object TrackedProjectiles {
abstract class Data(val is : TrackedProjectile.Value, val a : Int, val b : Int)
final case object Meteor extends Data(TrackedProjectile.Meteor, 0, 32)
final case object Wasp extends Data(TrackedProjectile.Wasp, 0, 208)
final case object Sparrow extends Data(TrackedProjectile.Sparrow, 13107, 187)
final case object OICW extends Data(TrackedProjectile.OICW, 13107, 195)
final case object Striker extends Data(TrackedProjectile.Striker, 26214, 134)
final case object HunterSeeker extends Data(TrackedProjectile.HunterSeeker, 39577, 201)
final case object Starfire extends Data(TrackedProjectile.Starfire, 39577, 249)
class OICWLittleBuddy(x : Int, y : Int) extends Data(TrackedProjectile.OICWLittleBuddy, x, y)
val values: Seq[TrackedProjectiles.Data] = Seq(Meteor, Wasp, Sparrow, OICW, Striker, HunterSeeker, Starfire)
def apply(x : Int, y : Int) : TrackedProjectiles.Data = {
values.find(p => p.a == x && p.b == y) match {
case Some(projectileData) => projectileData
case None =>
throw new IllegalArgumentException("no combination of projectile data equates to a defined projectile type")
}
}
def apply(is : TrackedProjectile.Value) : TrackedProjectiles.Data = {
values.find(p => p.is == is) match {
case Some(projectileData) => projectileData
case None =>
throw new IllegalArgumentException("unknown projectile type")
}
}
}
object FlightPhysics extends Enumeration {
type Type = Value
@ -27,41 +73,38 @@ object FlightPhysics extends Enumeration {
/**
* A representation of a projectile that the server must intentionally convey to players other than the shooter.
* @param data common game object information
* @param unk2 na
* @param unit_distance_limit how quickly the projectile travels before naturally being destroyed
* `FlightPhysics` needs to be
* @param common_data common game object information
* @param unk3 na
*/
final case class TrackedProjectileData(data : CommonFieldDataWithPlacement,
unk2 : Int,
unit_distance_limit : Int,
final case class TrackedProjectileData(common_data : CommonFieldDataWithPlacement,
u1 : Int,
u2 : Int,
unk3 : FlightPhysics.Value,
unk4 : Int,
unk5 : Int
) extends ConstructorData {
override def bitsize : Long = 33L + data.bitsize
override def bitsize : Long = 33L + common_data.bitsize
}
object TrackedProjectileData extends Marshallable[TrackedProjectileData] {
implicit val codec : Codec[TrackedProjectileData] = (
("data" | CommonFieldDataWithPlacement.codec) ::
("unk2" | uint16) ::
("unit_distance_limit" | uint8) ::
("u1" | uint16) ::
("u2" | uint8) ::
("unk3" | FlightPhysics.codec) ::
("unk4" | uint(3)) ::
("unk5" | uint2)
).exmap[TrackedProjectileData] (
{
case data :: unk2 :: lim :: unk3 :: unk4 :: unk5 :: HNil =>
Attempt.successful(TrackedProjectileData(data, unk2, lim, unk3, unk4, unk5))
case data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil =>
Attempt.successful(TrackedProjectileData(data, u1, u2, unk3, unk4, unk5))
case data =>
Attempt.failure(Err(s"invalid projectile data format - $data"))
// case data =>
// Attempt.failure(Err(s"invalid projectile data format - $data"))
},
{
case TrackedProjectileData(data, unk2, lim, unk3, unk4, unk5) =>
Attempt.successful(data :: unk2 :: lim :: unk3 :: unk4 :: unk5 :: HNil)
case TrackedProjectileData(data, u1, u2, unk3, unk4, unk5) =>
Attempt.successful(data :: u1 :: u2 :: unk3 :: unk4 :: unk5 :: HNil)
}
)
}

View file

@ -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))
@ -129,7 +133,7 @@ class AvatarService extends Actor {
)
case AvatarAction.LoadProjectile(player_guid, object_id, obj, cdata) =>
AvatarEvents.publish(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadProjectile(
ObjectCreateMessage(object_id, obj.GUID, cdata)
))
)

View file

@ -41,6 +41,7 @@ object AvatarAction {
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

View file

@ -30,6 +30,7 @@ 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

View file

@ -10,9 +10,10 @@ import scodec.bits._
class TrackedProjectileDataTest extends Specification {
val string_striker_projectile = hex"17 C5000000 A4B 009D 4C129 0CB0A 9814 00 F5 E3 040000666686400"
val string_hunter_seeker_missile_projectile = hex"17 c5000000 ca9 ab9e af127 ec465 3723 00 15 c4 2400009a99c9400"
"TrackedProjectileData" should {
"decode" in {
"decode (striker_missile_targeting_projectile)" in {
PacketCoding.DecodePacket(string_striker_projectile).require match {
case ObjectCreateMessage(len, cls, guid, parent, data) =>
len mustEqual 197
@ -46,7 +47,41 @@ class TrackedProjectileDataTest extends Specification {
}
}
"encode" in {
"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 TrackedProjectileData(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 = TrackedProjectileData(
CommonFieldDataWithPlacement(
PlacementData(4644.5938f, 5472.0938f, 82.375f, 0f, 30.9375f, 171.5625f),
@ -63,4 +98,21 @@ class TrackedProjectileDataTest extends Specification {
pkt.toBitVector.drop(141) mustEqual string_striker_projectile.toBitVector.drop(141)
}
}
"encode (hunter_seeker_missile_projectile)" in {
val obj = TrackedProjectileData(
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

@ -1364,6 +1364,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))
@ -3453,6 +3458,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
@ -3893,6 +3899,27 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttributeSelf(player.GUID, 2, player.Stamina))
}
if(!player.Crouching && is_crouching) {
sendResponse(
ObjectCreateMessage(
405,
PlanetSideGUID(40288),
TrackedProjectileData(
CommonFieldDataWithPlacement(
PlacementData(Vector3(3561.0f, 2854.0f, 92.859375f), Vector3(0f, 348.75f, 267.1875f), None),
CommonFieldData(PlanetSideEmpire.NC, false, false, true, None, false, None, None, PlanetSideGUID(0))
),
39577, 201, FlightPhysics.State4, 0, 0
)
)
)
}
else if(player.Crouching && !is_crouching) {
sendResponse(
ObjectDeleteMessage(PlanetSideGUID(40288), 2)
//ProjectileStateMessage(PlanetSideGUID(40288),Vector3(3561.0f, 2854.0f, 92.859375f),Vector3(-38.814934f,-2.578959f,-9.313708f),Vector3(0,348.75f,267.1875f),false,0)
)
}
player.Position = pos
player.Velocity = vel
player.Orientation = Vector3(player.Orientation.x, pitch, yaw)
@ -4001,19 +4028,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
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) =>
projectiles(projectile_guid.guid - Projectile.BaseUID) match {
case Some(projectile) if projectile.HasGUID =>
val projectileGlobalUID = projectile.GUID
log.info(s"ProjectileState: mapped local uid ${projectile_guid.guid} to global uid ${projectileGlobalUID.guid}; updating object ...")
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")
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileState(player.GUID, projectile.GUID, shot_pos, shot_vel, shot_orient, unk, time_alive))
case _ =>
log.error(s"ProjectileState: the projectile@${projectile_guid.guid} can not be found")
}
case msg @ ReleaseAvatarRequestMessage() =>
@ -5454,6 +5478,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
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 ! ReregisterProjectile(projectile)
}
}
@ -5491,26 +5516,34 @@ 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) =>
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) {
//cleanup
taskResolver ! UnregisterProjectile(projectile)
}
case None => ;
}
case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, pos, unk1) =>
log.info(s"Lash: $msg")
@ -6077,6 +6110,13 @@ 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(
@ -6126,12 +6166,19 @@ 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))
private val localMsg = AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, obj.GUID, 2))
override def isComplete : Task.Resolution.Value = {
if(!globalProjectile.HasGUID) {
@ -6192,6 +6239,14 @@ 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) {
@ -8406,9 +8461,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
@ -8419,8 +8471,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 {