diff --git a/common/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala index 8317ec63..f2c45843 100644 --- a/common/src/main/scala/services/avatar/AvatarService.scala +++ b/common/src/main/scala/services/avatar/AvatarService.scala @@ -131,10 +131,10 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadPlayer(pkt)) ) - case AvatarAction.LoadProjectile(player_guid, object_id, obj, cdata) => + case AvatarAction.LoadProjectile(player_guid, object_id, object_guid, cdata) => AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.LoadProjectile( - ObjectCreateMessage(object_id, obj.GUID, cdata) + ObjectCreateMessage(object_id, object_guid, cdata) )) ) case AvatarAction.ObjectDelete(player_guid, item_guid, unk) => diff --git a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala index 9399620f..0396a4c3 100644 --- a/common/src/main/scala/services/avatar/AvatarServiceMessage.scala +++ b/common/src/main/scala/services/avatar/AvatarServiceMessage.scala @@ -41,7 +41,7 @@ object AvatarAction { final case class HitHint(source_guid : PlanetSideGUID, player_guid : PlanetSideGUID) extends Action final case class KilledWhileInVehicle(player_guid : PlanetSideGUID) extends Action final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action - final case class LoadProjectile(player_guid : PlanetSideGUID, object_id : Int, obj : Projectile, cdata : ConstructorData) extends Action + final case class 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 diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index e46b167b..a288fb6e 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -117,6 +117,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 @@ -1221,15 +1222,26 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info(s"Received a direct message: $pkt") sendResponse(pkt) - case LoadedRemoteProjectile(projectile_guid) => + 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 projectileGlobalUID = obj.GUID val definition = obj.Definition avatarService ! AvatarServiceMessage( continent.Id, - AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, obj, definition.Packet.ConstructorData(obj).get) + AvatarAction.LoadProjectile(player.GUID, definition.ObjectId, projectile_guid, definition.Packet.ConstructorData(obj).get) ) case _ => ; } @@ -4036,17 +4048,19 @@ class WorldSessionActor extends Actor with MDCContextAware { //log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2") case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) => - log.info(s"ProjectileState: $msg") - projectiles(projectile_guid.guid - Projectile.BaseUID) match { + //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.error(s"ProjectileState: constructed projectile ${projectile_guid.guid} can not be found") + log.warn(s"ProjectileState: constructed projectile ${projectile_guid.guid} can not be found") } case msg @ ReleaseAvatarRequestMessage() => @@ -5500,8 +5514,15 @@ class WorldSessionActor extends Actor with MDCContextAware { projectiles(projectileIndex) = Some(projectile) if(projectile_info.ExistsOnRemoteClients) { log.trace(s"WeaponFireMessage: ${projectile_info.Name} is a remote projectile") - taskResolver ! ReregisterProjectile(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") @@ -5576,10 +5597,15 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => ; } }) - if(projectile.profile.ExistsOnRemoteClients) { + if(projectile.profile.ExistsOnRemoteClients && projectile.HasGUID) { //cleanup - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ProjectileExplodes(player.GUID, projectile.GUID, projectile)) - taskResolver ! UnregisterProjectile(projectile) + val localIndex = projectile_guid.guid - Projectile.BaseUID + if(projectile.HasGUID) { + CleanUpRemoteProjectile(projectile.GUID, projectile, localIndex) + } + else { + projectilesToCleanUp(localIndex) = true + } } case None => ; } @@ -6173,7 +6199,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } def Execute(resolver : ActorRef) : Unit = { - localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID) + localAnnounce ! LoadedRemoteProjectile(globalProjectile.GUID, Some(globalProjectile)) resolver ! scala.util.Success(this) } }, List(GUIDTask.RegisterObjectTask(obj)(continent.GUID)) @@ -7959,7 +7985,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) @@ -7970,7 +7996,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 @@ -8066,7 +8093,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}") } /** @@ -10080,6 +10108,14 @@ 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 } @@ -10094,6 +10130,48 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * 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()) @@ -10258,5 +10336,5 @@ object WorldSessionActor { private final case class FinalizeDeployable(obj : PlanetSideGameObject with Deployable, tool : ConstructionItem, index : Int) - private final case class LoadedRemoteProjectile(projectile_guid : PlanetSideGUID) + private final case class LoadedRemoteProjectile(projectile_guid : PlanetSideGUID, projectile : Option[Projectile]) }