From 3869785591f1546e447dc210337c35da15cf8f9d Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Fri, 17 Jan 2020 13:25:35 -0500 Subject: [PATCH] Destruction (#330) * perform no damage if the target is already destroyed; correct destruction timer override on sensor-type deployables * framing vehicle remover tasks differently; screening for valid objects at some places in WSA; fixed router's internal telepad utility * corrected issue with unique control agent Actor names for Players; propagated change to all dynamic objects (not those instantiated at server time) * test fix * another test fix; this time, Travis is having trouble with it --- .../objects/ExplosiveDeployable.scala | 2 +- .../psforever/objects/SensorDeployable.scala | 4 +- .../objects/ShieldGeneratorDeployable.scala | 2 +- .../psforever/objects/TurretDeployable.scala | 2 +- .../psforever/objects/ce/TelepadLike.scala | 18 ++++ .../serverobject/PlanetSideServerObject.scala | 24 +++++ .../terminals/MatrixTerminalDefinition.scala | 3 +- .../terminals/OrderTerminalDefinition.scala | 3 +- .../terminals/ProximityTerminal.scala | 4 +- .../tube/SpawnTubeDefinition.scala | 3 +- .../objects/teamwork/SquadFeatures.scala | 2 +- .../objects/vehicles/VehicleControl.scala | 59 +++++++------ .../objects/zones/ZonePopulationActor.scala | 16 ++-- .../objects/zones/ZoneVehicleActor.scala | 3 +- .../vehicle/support/VehicleRemover.scala | 87 ++++++++++--------- .../src/test/scala/objects/UtilityTest.scala | 2 +- .../number/UniqueNumberSystemTest.scala | 4 +- .../src/main/scala/WorldSessionActor.scala | 59 ++++++++----- 18 files changed, 188 insertions(+), 109 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala index cfa37ead..48f62514 100644 --- a/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala @@ -45,7 +45,7 @@ class ExplosiveDeployableDefinition(private val objectId : Int) extends ComplexD } override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = { - obj.Actor = context.actorOf(Props(classOf[ExplosiveDeployableControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}") + obj.Actor = context.actorOf(Props(classOf[ExplosiveDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj)) } override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = { diff --git a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala index 18d9d075..22b35ab2 100644 --- a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala @@ -30,7 +30,7 @@ class SensorDeployableDefinition(private val objectId : Int) extends ComplexDepl Packet = new SmallDeployableConverter override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = { - obj.Actor = context.actorOf(Props(classOf[SensorDeployableControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}") + obj.Actor = context.actorOf(Props(classOf[SensorDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj)) } override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = { @@ -126,7 +126,7 @@ object SensorDeployableControl { target.Actor ! JammableUnit.ClearJammeredSound() target.Actor ! JammableUnit.ClearJammeredStatus() val zone = target.Zone - Deployables.AnnounceDestroyDeployable(target, Some(0 seconds)) + Deployables.AnnounceDestroyDeployable(target, None) zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", target.GUID, false, 1000)) zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position)) } diff --git a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala index 15bbc408..ff32b419 100644 --- a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala @@ -25,7 +25,7 @@ class ShieldGeneratorDefinition extends ComplexDeployableDefinition(240) { DeployCategory = DeployableCategory.ShieldGenerators override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = { - obj.Actor = context.actorOf(Props(classOf[ShieldGeneratorControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}") + obj.Actor = context.actorOf(Props(classOf[ShieldGeneratorControl], obj), PlanetSideServerObject.UniqueActorName(obj)) } override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = { diff --git a/common/src/main/scala/net/psforever/objects/TurretDeployable.scala b/common/src/main/scala/net/psforever/objects/TurretDeployable.scala index f7c379f3..273acf40 100644 --- a/common/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/common/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -49,7 +49,7 @@ class TurretDeployableDefinition(private val objectId : Int) extends ComplexDepl override def MaxHealth_=(max : Int) : Int = super[ComplexDeployableDefinition].MaxHealth_=(max) override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = { - obj.Actor = context.actorOf(Props(classOf[TurretControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}") + obj.Actor = context.actorOf(Props(classOf[TurretControl], obj), PlanetSideServerObject.UniqueActorName(obj)) } override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = { diff --git a/common/src/main/scala/net/psforever/objects/ce/TelepadLike.scala b/common/src/main/scala/net/psforever/objects/ce/TelepadLike.scala index 8ce79ee8..f0566a12 100644 --- a/common/src/main/scala/net/psforever/objects/ce/TelepadLike.scala +++ b/common/src/main/scala/net/psforever/objects/ce/TelepadLike.scala @@ -2,6 +2,7 @@ package net.psforever.objects.ce import akka.actor.ActorContext +import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.{PlanetSideGameObject, TelepadDeployable, Vehicle} import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.vehicles.Utility @@ -49,6 +50,10 @@ object TelepadLike { */ def Setup(obj : Amenity, context : ActorContext) : Unit = { obj.asInstanceOf[TelepadLike].Router = obj.Owner.GUID + import akka.actor.{ActorRef, Props} + if(obj.Actor == ActorRef.noSender) { + obj.Actor = context.actorOf(Props(classOf[TelepadControl], obj), PlanetSideServerObject.UniqueActorName(obj)) + } } /** @@ -83,3 +88,16 @@ object TelepadLike { } } } + +/** + * Telepad-like components don't actually use control agents right now, but, + * since the `trait` is used for a `Vehicle` `Utility` entity as well as a `Deployable` entity, + * and all utilities are supposed to have control agents with which to interface, + * a placeholder like this is easy to reason around. + * @param obj an entity that extends `TelepadLike` + */ +class TelepadControl(obj : TelepadLike) extends akka.actor.Actor { + def receive : akka.actor.Actor.Receive = { + case _ => ; + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala b/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala index ab968aad..01085a38 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala @@ -3,6 +3,7 @@ package net.psforever.objects.serverobject import akka.actor.ActorRef import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.entity.NoGUIDException import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.zones.ZoneAware @@ -34,3 +35,26 @@ abstract class PlanetSideServerObject extends PlanetSideGameObject actor } } + +object PlanetSideServerObject { + /** + * `Actor` entities require unique names over the course of the lifetime of the `ActorSystem` object. + * To produce this name, a composition of three strings separated by underscores is assembled.
+ * - the entity's object name; + * uniqueness is very low, less than 50, but helps to identify the object
+ * - the entity's globally unique identifier number; + * its uniqueness is much greater but still falls within less than 66535 possible integers; + * useful for locating that entity; + * an `Exception` can be thrown if this field was never set, + * but that is a condition for which it is worth throwing the `Exception`
+ * - the current POSIX time in milliseconds; + * results will remain reasonably unique; + * useful for taking a rough estimation of how long the entity has existed + * @throws `NoGUIDException` if the entity has never been registered to a unique identifier system + * @param obj the entity for whom the `Actor` object will be created + * @return the unique name + */ + def UniqueActorName(obj : PlanetSideGameObject) : String = { + s"${obj.Definition.Name}_${obj.GUID.guid}_${System.currentTimeMillis}" + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala index a669de7f..b815ce2a 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MatrixTerminalDefinition.scala @@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.terminals import akka.actor.ActorContext import net.psforever.objects.Player +import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.structures.Amenity /** @@ -29,7 +30,7 @@ object MatrixTerminalDefinition { def Setup(obj : Amenity, context : ActorContext) : Unit = { import akka.actor.{ActorRef, Props} if(obj.Actor == ActorRef.noSender) { - obj.Actor = context.actorOf(Props(classOf[TerminalControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}") + obj.Actor = context.actorOf(Props(classOf[TerminalControl], obj), PlanetSideServerObject.UniqueActorName(obj)) } } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala index 9c4e34d7..5ab86e44 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala @@ -7,6 +7,7 @@ import net.psforever.objects.{Player, Vehicle} import net.psforever.objects.equipment.Equipment import net.psforever.objects.loadouts.{InfantryLoadout, VehicleLoadout} import net.psforever.objects.inventory.InventoryItem +import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.structures.Amenity import net.psforever.packet.game.ItemTransactionMessage import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition._ @@ -341,7 +342,7 @@ object OrderTerminalDefinition { def Setup(obj : Amenity, context : ActorContext) : Unit = { import akka.actor.{ActorRef, Props} if(obj.Actor == ActorRef.noSender) { - obj.Actor = context.actorOf(Props(classOf[TerminalControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}") + obj.Actor = context.actorOf(Props(classOf[TerminalControl], obj), PlanetSideServerObject.UniqueActorName(obj)) } } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala index fd5db2af..65ba0fd0 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala @@ -2,7 +2,7 @@ package net.psforever.objects.serverobject.terminals import net.psforever.objects.Player -import net.psforever.objects.serverobject.CommonMessages +import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.structures.Amenity import net.psforever.types.Vector3 import services.Service @@ -76,7 +76,7 @@ object ProximityTerminal { def Setup(obj : Amenity, context : ActorContext) : Unit = { import akka.actor.{ActorRef, Props} if(obj.Actor == ActorRef.noSender) { - obj.Actor = context.actorOf(Props(classOf[ProximityTerminalControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}") + obj.Actor = context.actorOf(Props(classOf[ProximityTerminalControl], obj), PlanetSideServerObject.UniqueActorName(obj)) obj.Actor ! Service.Startup() } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala index 755f64a1..a78fb8dc 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala @@ -5,6 +5,7 @@ import akka.actor.ActorContext import net.psforever.objects.SpawnPointDefinition import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.definition.converter.SpawnTubeConverter +import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.structures.Amenity /** @@ -25,7 +26,7 @@ object SpawnTubeDefinition { def Setup(obj : Amenity, context : ActorContext) : Unit = { import akka.actor.{ActorRef, Props} if(obj.Actor == ActorRef.noSender) { - obj.Actor = context.actorOf(Props(classOf[SpawnTubeControl], obj), s"${obj.Definition.Name}_${obj.GUID.guid}") + obj.Actor = context.actorOf(Props(classOf[SpawnTubeControl], obj), PlanetSideServerObject.UniqueActorName(obj)) } } } diff --git a/common/src/main/scala/net/psforever/objects/teamwork/SquadFeatures.scala b/common/src/main/scala/net/psforever/objects/teamwork/SquadFeatures.scala index 2e1a5f05..aa6c05be 100644 --- a/common/src/main/scala/net/psforever/objects/teamwork/SquadFeatures.scala +++ b/common/src/main/scala/net/psforever/objects/teamwork/SquadFeatures.scala @@ -67,7 +67,7 @@ class SquadFeatures(val Squad : Squad) { private lazy val channel : String = s"${Squad.Faction}-Squad${Squad.GUID.guid}" def Start(implicit context : ActorContext) : SquadFeatures = { - switchboard = context.actorOf(Props[SquadSwitchboard], s"squad${Squad.GUID.guid}") + switchboard = context.actorOf(Props[SquadSwitchboard], s"squad_${Squad.GUID.guid}_${System.currentTimeMillis}") waypoints = Array.fill[WaypointData](SquadWaypoints.values.size)(new WaypointData()) this } diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index 1b0800f0..4451b8c5 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -53,32 +53,13 @@ class VehicleControl(vehicle : Vehicle) extends Actor def Enabled : Receive = checkBehavior .orElse(deployBehavior) - .orElse(dismountBehavior) .orElse(jammableBehavior) .orElse { - case Mountable.TryMount(user, seat_num) => - val exosuit = user.ExoSuit - val restriction = vehicle.Seats(seat_num).ArmorRestriction - val seatGroup = vehicle.SeatPermissionGroup(seat_num).getOrElse(AccessPermissionGroup.Passenger) - val permission = vehicle.PermissionGroup(seatGroup.id).getOrElse(VehicleLockState.Empire) - if( - (if(seatGroup == AccessPermissionGroup.Driver) { - vehicle.Owner.contains(user.GUID) || vehicle.Owner.isEmpty || permission != VehicleLockState.Locked - } - else { - permission != VehicleLockState.Locked - }) && - (exosuit match { - case ExoSuitType.MAX => restriction == SeatArmorRestriction.MaxOnly - case ExoSuitType.Reinforced => restriction == SeatArmorRestriction.NoMax - case _ => restriction != SeatArmorRestriction.MaxOnly - }) - ) { - mountBehavior.apply(Mountable.TryMount(user, seat_num)) - } - else { - sender ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, seat_num)) - } + case msg : Mountable.TryMount => + tryMountBehavior.apply(msg) + + case msg : Mountable.TryDismount => + dismountBehavior.apply(msg) case Vitality.Damage(damage_func) => if(vehicle.Health > 0) { @@ -122,9 +103,37 @@ class VehicleControl(vehicle : Vehicle) extends Actor case _ => ; } + val tryMountBehavior : Receive = { + case msg @ Mountable.TryMount(user, seat_num) => + val exosuit = user.ExoSuit + val restriction = vehicle.Seats(seat_num).ArmorRestriction + val seatGroup = vehicle.SeatPermissionGroup(seat_num).getOrElse(AccessPermissionGroup.Passenger) + val permission = vehicle.PermissionGroup(seatGroup.id).getOrElse(VehicleLockState.Empire) + if( + (if(seatGroup == AccessPermissionGroup.Driver) { + vehicle.Owner.contains(user.GUID) || vehicle.Owner.isEmpty || permission != VehicleLockState.Locked + } + else { + permission != VehicleLockState.Locked + }) && + (exosuit match { + case ExoSuitType.MAX => restriction == SeatArmorRestriction.MaxOnly + case ExoSuitType.Reinforced => restriction == SeatArmorRestriction.NoMax + case _ => restriction != SeatArmorRestriction.MaxOnly + }) + ) { + mountBehavior.apply(msg) + } + else { + sender ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, seat_num)) + } + } + def Disabled : Receive = checkBehavior - .orElse(dismountBehavior) .orElse { + case msg : Mountable.TryDismount => + dismountBehavior.apply(msg) + case Vehicle.Reactivate() => context.become(Enabled) diff --git a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala index a77911b7..1b5364c8 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZonePopulationActor.scala @@ -39,13 +39,13 @@ class ZonePopulationActor(zone : Zone, playerMap : TrieMap[Avatar, Option[Player case Zone.Population.Spawn(avatar, player) => PopulationSpawn(avatar, player, playerMap) match { - case Some(tplayer) => + case Some((tplayer, newToZone)) => tplayer.Zone = zone if(tplayer ne player) { sender ! Zone.Population.PlayerAlreadySpawned(zone, player) } - else { - player.Actor = context.actorOf(Props(classOf[PlayerControl], player), s"${player.Name}_${player.GUID.guid}") + else if(newToZone) { + player.Actor = context.actorOf(Props(classOf[PlayerControl], player), s"${player.Name}_${player.GUID.guid}_${System.currentTimeMillis}") player.Zone = zone } case None => @@ -110,19 +110,21 @@ object ZonePopulationActor { * @param avatar an `Avatar` object * @param player a `Player` object * @param playerMap the mapping of `Avatar` objects to `Player` objects - * @return the `Player` object that is associated with the `Avatar` key + * @return a `Tuple` object of the `Player` object that is associated with the `Avatar` key + * and whether that player was added to the zone for the first time; + * `None`, if the player should not be introduced to this zone at this time */ - def PopulationSpawn(avatar : Avatar, player : Player, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = { + def PopulationSpawn(avatar : Avatar, player : Player, playerMap : TrieMap[Avatar, Option[Player]]) : Option[(Player, Boolean)] = { playerMap.get(avatar) match { case None => None case Some(tplayer) => tplayer match { case Some(aplayer) => - Some(aplayer) + Some(aplayer, false) case None => playerMap(avatar) = Some(player) - Some(player) + Some(player, true) } } } diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala index 265de52b..efc2d01d 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala @@ -3,6 +3,7 @@ package net.psforever.objects.zones import akka.actor.{Actor, ActorRef, Props} import net.psforever.objects.Vehicle +import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.vehicles.VehicleControl import scala.annotation.tailrec @@ -43,7 +44,7 @@ class ZoneVehicleActor(zone : Zone, vehicleList : ListBuffer[Vehicle]) extends A else { vehicleList += vehicle vehicle.Zone = zone - vehicle.Actor = context.actorOf(Props(classOf[VehicleControl], vehicle), s"${vehicle.Definition.Name}_${vehicle.GUID.guid}") + vehicle.Actor = context.actorOf(Props(classOf[VehicleControl], vehicle), PlanetSideServerObject.UniqueActorName(vehicle)) } case Zone.Vehicle.Despawn(vehicle) => diff --git a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala index 17c7584b..131eaabb 100644 --- a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala +++ b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala @@ -22,52 +22,59 @@ class VehicleRemover extends RemoverActor { def InitialJob(entry : RemoverActor.Entry) : Unit = { } def FirstJob(entry : RemoverActor.Entry) : Unit = { - val vehicle = entry.obj.asInstanceOf[Vehicle] - val vehicleGUID = vehicle.GUID - val zoneId = entry.zone.Id - vehicle.Actor ! Vehicle.PrepareForDeletion() - //escape being someone else's cargo - (vehicle.MountedIn match { - case Some(carrierGUID) => - entry.zone.Vehicles.find(v => v.GUID == carrierGUID) - case None => - None - }) match { - case Some(carrier : Vehicle) => - val driverName = carrier.Seats(0).Occupant match { - case Some(driver) => driver.Name - case _ => zoneId + val vehicleGUID = entry.obj.GUID + entry.zone.GUID(vehicleGUID) match { + case Some(vehicle : Vehicle) if vehicle.HasGUID => + val zoneId = entry.zone.Id + vehicle.Actor ! Vehicle.PrepareForDeletion() + //escape being someone else's cargo + (vehicle.MountedIn match { + case Some(carrierGUID) => + entry.zone.Vehicles.find(v => v.GUID == carrierGUID) + case None => + None + }) match { + case Some(carrier : Vehicle) => + val driverName = carrier.Seats(0).Occupant match { + case Some(driver) => driver.Name + case _ => zoneId + } + context.parent ! VehicleServiceMessage(s"$driverName", VehicleAction.ForceDismountVehicleCargo(PlanetSideGUID(0), vehicleGUID, true, false, false)) + case _ => ; } - context.parent ! VehicleServiceMessage(s"$driverName", VehicleAction.ForceDismountVehicleCargo(PlanetSideGUID(0), vehicleGUID, true, false, false)) + //kick out all passengers + vehicle.Seats.values.foreach(seat => { + seat.Occupant match { + case Some(tplayer) => + seat.Occupant = None + tplayer.VehicleSeated = None + if(tplayer.HasGUID) { + context.parent ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicleGUID)) + } + case None => ; + } + //abandon all cargo + vehicle.CargoHolds.values + .collect { case hold if hold.isOccupied => + val cargo = hold.Occupant.get + context.parent ! VehicleServiceMessage(zoneId, VehicleAction.ForceDismountVehicleCargo(PlanetSideGUID(0), cargo.GUID, true, false, false)) + } + }) case _ => ; } - //kick out all passengers - vehicle.Seats.values.foreach(seat => { - seat.Occupant match { - case Some(tplayer) => - seat.Occupant = None - tplayer.VehicleSeated = None - if(tplayer.HasGUID) { - context.parent ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicleGUID)) - } - case None => ; - } - //abandon all cargo - vehicle.CargoHolds.values - .collect { case hold if hold.isOccupied => - val cargo = hold.Occupant.get - context.parent ! VehicleServiceMessage(zoneId, VehicleAction.ForceDismountVehicleCargo(PlanetSideGUID(0), cargo.GUID, true, false, false)) - } - }) } override def SecondJob(entry : RemoverActor.Entry) : Unit = { - val vehicle = entry.obj.asInstanceOf[Vehicle] - val zone = entry.zone - vehicle.DeploymentState = DriveState.Mobile - zone.Transport ! Zone.Vehicle.Despawn(vehicle) - context.parent ! VehicleServiceMessage(zone.Id, VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, zone, vehicle, vehicle.GUID)) - super.SecondJob(entry) + val vehicleGUID = entry.obj.GUID + entry.zone.GUID(vehicleGUID) match { + case Some(vehicle : Vehicle) if vehicle.HasGUID => + val zone = entry.zone + vehicle.DeploymentState = DriveState.Mobile + zone.Transport ! Zone.Vehicle.Despawn(vehicle) + context.parent ! VehicleServiceMessage(zone.Id, VehicleAction.UnloadVehicle(Service.defaultPlayerGUID, zone, vehicle, vehicleGUID)) + super.SecondJob(entry) + case _ => ; + } } def ClearanceTest(entry : RemoverActor.Entry) : Boolean = entry.obj.asInstanceOf[Vehicle].Seats.values.count(_.isOccupied) == 0 diff --git a/common/src/test/scala/objects/UtilityTest.scala b/common/src/test/scala/objects/UtilityTest.scala index ecb8d376..dcdd50dd 100644 --- a/common/src/test/scala/objects/UtilityTest.scala +++ b/common/src/test/scala/objects/UtilityTest.scala @@ -217,7 +217,7 @@ class UtilityInternalTelepadTest extends ActorTest { system.actorOf(Props(classOf[UtilityTest.SetupControl], obj), "test") ! "" receiveOne(Duration.create(100, "ms")) //consume and discard - assert(obj().Actor == ActorRef.noSender) + assert(obj().Actor != ActorRef.noSender) assert(obj().asInstanceOf[Utility.InternalTelepad].Router.contains(veh.GUID)) } } diff --git a/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala b/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala index 04107de8..b8629890 100644 --- a/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala +++ b/common/src/test/scala/objects/number/UniqueNumberSystemTest.scala @@ -187,14 +187,14 @@ class UniqueNumberSystemTest5 extends ActorTest() { assert(src.CountUsed == 0) uns ! Register(testObj, "pool2") - val msg1 = receiveOne(Duration.create(500, "ms")) + val msg1 = receiveOne(Duration.create(2000, "ms")) assert(msg1.isInstanceOf[Success[_]]) assert(testObj.HasGUID) assert(pool2.contains(testObj.GUID.guid)) assert(src.CountUsed == 1) uns ! Unregister(testObj) - val msg2 = receiveOne(Duration.create(500, "ms")) + val msg2 = receiveOne(Duration.create(2000, "ms")) assert(msg2.isInstanceOf[Success[_]]) assert(!testObj.HasGUID) assert(src.CountUsed == 0) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index fb549bf2..fd603030 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -330,6 +330,21 @@ class WorldSessionActor extends Actor context.stop(self) } + def ValidObject(id : Int) : Option[PlanetSideGameObject] = ValidObject(Some(PlanetSideGUID(id))) + + def ValidObject(id : PlanetSideGUID) : Option[PlanetSideGameObject] = ValidObject(Some(id)) + + def ValidObject(id : Option[PlanetSideGUID]) : Option[PlanetSideGameObject] = continent.GUID(id) match { + case out @ Some(obj) if obj.HasGUID => + out + case None if id.nonEmpty => + //delete stale entity reference from client + sendResponse(ObjectDeleteMessage(id.get, 0)) + None + case _ => + None + } + def Started : Receive = { case ServiceManager.LookupResult("accountIntermediary", endpoint) => accountIntermediary = endpoint @@ -3556,7 +3571,7 @@ class WorldSessionActor extends Actor whenUsedLastMAXName(1) = faction+"hev_antiaircraft" (0 until 4).foreach( index => { - if (player.Slot(index).Equipment.isDefined) player.Slot(index).Equipment = None + player.Slot(index).Equipment = None }) player.Inventory.Clear() player.ExoSuit = ExoSuitType.Standard @@ -4400,7 +4415,7 @@ class WorldSessionActor extends Actor case msg @ DropItemMessage(item_guid) => log.info(s"DropItem: $msg") - continent.GUID(item_guid) match { + ValidObject(item_guid) match { case Some(anItem : Equipment) => player.FreeHand.Equipment match { case Some(item) => @@ -4419,7 +4434,7 @@ class WorldSessionActor extends Actor case msg @ PickupItemMessage(item_guid, player_guid, unk1, unk2) => log.info(s"PickupItem: $msg") - continent.GUID(item_guid) match { + ValidObject(item_guid) match { case Some(item : Equipment) => player.Fit(item) match { case Some(_) => @@ -4541,7 +4556,7 @@ class WorldSessionActor extends Actor case msg @ RequestDestroyMessage(object_guid) => // TODO: Make sure this is the correct response for all cases - continent.GUID(object_guid) match { + ValidObject(object_guid) match { case Some(vehicle : Vehicle) => if((player.VehicleOwned.contains(object_guid) && vehicle.Owner.contains(player.GUID)) || (player.Faction == vehicle.Faction @@ -4684,7 +4699,7 @@ class WorldSessionActor extends Actor case msg @ LootItemMessage(item_guid, target_guid) => log.info(s"LootItem: $msg") - (continent.GUID(item_guid), continent.GUID(target_guid)) match { + (ValidObject(item_guid), ValidObject(target_guid)) match { case (Some(item : Equipment), Some(target : Container)) => //figure out the source ( @@ -4747,7 +4762,7 @@ class WorldSessionActor extends Actor //log.info("UseItem: " + msg) // TODO: Not all fields in the response are identical to source in real packet logs (but seems to be ok) // TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg) - continent.GUID(object_guid) match { + ValidObject(object_guid) match { case Some(door : Door) => if(player.Faction == door.Faction || (continent.Map.DoorToLock.get(object_guid.guid) match { case Some(lock_guid) => @@ -4821,7 +4836,7 @@ class WorldSessionActor extends Actor accessedContainer = Some(obj) } else if(!unk3 && player.isAlive) { //potential kit use - continent.GUID(item_used_guid) match { + ValidObject(item_used_guid) match { case Some(kit : Kit) => player.Find(kit) match { case Some(index) => @@ -4936,7 +4951,7 @@ class WorldSessionActor extends Actor FindWeapon match { case Some(tool: Tool) => if (tool.Definition == GlobalDefinitions.bank) { - continent.GUID(object_guid) match { + ValidObject(object_guid) match { case Some(tplayer: Player) => if (player.GUID != tplayer.GUID && Vector3.Distance(player.Position, tplayer.Position) < 5 && player.Faction == tplayer.Faction && !player.isMoving && tplayer.MaxArmor > 0 && tplayer.Armor < tplayer.MaxArmor) { tplayer.Armor += 15 @@ -5262,7 +5277,7 @@ class WorldSessionActor extends Actor case msg @ UnuseItemMessage(player_guid, object_guid) => log.info(s"UnuseItem: $msg") //TODO check for existing accessedContainer value? - continent.GUID(object_guid) match { + ValidObject(object_guid) match { case Some(obj : Vehicle) => if(obj.AccessingTrunk.contains(player.GUID)) { obj.AccessingTrunk = None @@ -5562,7 +5577,7 @@ class WorldSessionActor extends Actor log.info(s"Hit: $msg") (hit_info match { case Some(hitInfo) => - continent.GUID(hitInfo.hitobject_guid) match { + ValidObject(hitInfo.hitobject_guid) match { case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => Some((target, hitInfo.shot_origin, hitInfo.hit_pos)) case _ => @@ -5587,7 +5602,7 @@ class WorldSessionActor extends Actor projectile.Position = explosion_pos projectile.Velocity = projectile_vel //direct_victim_uid - continent.GUID(direct_victim_uid) match { + ValidObject(direct_victim_uid) match { case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, target.Position) match { case Some(projectile) => @@ -5598,7 +5613,7 @@ class WorldSessionActor extends Actor } //other victims targets.foreach(elem => { - continent.GUID(elem.uid) match { + ValidObject(elem.uid) match { case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => ResolveProjectileEntry(projectile, ProjectileResolution.Splash, target, explosion_pos) match { case Some(projectile) => @@ -5623,7 +5638,7 @@ class WorldSessionActor extends Actor case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, pos, unk1) => log.info(s"Lash: $msg") - continent.GUID(victim_guid) match { + ValidObject(victim_guid) match { case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => ResolveProjectileEntry(projectile_guid, ProjectileResolution.Lash, target, pos) match { case Some(projectile) => @@ -5663,7 +5678,7 @@ class WorldSessionActor extends Actor case msg @ MountVehicleMsg(player_guid, mountable_guid, entry_point) => log.info("MountVehicleMsg: "+msg) - continent.GUID(mountable_guid) match { + ValidObject(mountable_guid) match { case Some(obj : Mountable) => obj.GetSeatFromMountPoint(entry_point) match { case Some(seat_num) => @@ -5723,7 +5738,7 @@ class WorldSessionActor extends Actor //kicking someone else out of a seat; need to own that seat/mountable player.VehicleOwned match { case Some(obj_guid) => - (continent.GUID(obj_guid), continent.GUID(player_guid)) match { + (ValidObject(obj_guid), ValidObject(player_guid)) match { case (Some(obj : Mountable), Some(tplayer : Player)) => obj.PassengerInSeat(tplayer) match { case Some(seat_num : Int) => @@ -5785,7 +5800,7 @@ class WorldSessionActor extends Actor case msg @ PlanetsideAttributeMessage(object_guid, attribute_type, attribute_value) => log.info("PlanetsideAttributeMessage: "+msg) - continent.GUID(object_guid) match { + ValidObject(object_guid) match { case Some(vehicle : Vehicle) => if(player.VehicleOwned.contains(vehicle.GUID)) { if(9 < attribute_type && attribute_type < 14) { @@ -8546,12 +8561,12 @@ class WorldSessionActor extends Actor def HandleDealingDamage(target : PlanetSideGameObject with Vitality, data : ResolvedProjectile) : Unit = { val func = data.damage_model.Calculate(data) target match { - case obj : Player => obj.Actor ! Vitality.Damage(func) - case obj : Vehicle => obj.Actor ! Vitality.Damage(func) - case obj : FacilityTurret => obj.Actor ! Vitality.Damage(func) - case obj : ComplexDeployable => obj.Actor ! Vitality.Damage(func) + case obj : Player if obj.Health > 0 => obj.Actor ! Vitality.Damage(func) + case obj : Vehicle if obj.Health > 0 => obj.Actor ! Vitality.Damage(func) + case obj : FacilityTurret if obj.Health > 0 => obj.Actor ! Vitality.Damage(func) + case obj : ComplexDeployable if obj.Health > 0 => obj.Actor ! Vitality.Damage(func) - case obj : SimpleDeployable => + case obj : SimpleDeployable if obj.Health > 0 => //damage is synchronized on `LSA` (results returned to and distributed from this `WSA`) continent.LocalEvents ! Vitality.DamageOn(obj, func) case _ => ; @@ -10754,7 +10769,7 @@ class WorldSessionActor extends Actor */ def FindDetectedProjectileTargets(targets : Iterable[PlanetSideGUID]) : Iterable[String] = { targets - .map { continent.GUID } + .map { ValidObject } .flatMap { case Some(obj : Vehicle) if !obj.Cloaked => //TODO hint: vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.ProjectileAutoLockAwareness(mode))