From 1bb21ba79c0cf0c40098af655b41296d3231f38a Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 17 Apr 2023 01:16:06 -0400 Subject: [PATCH] Vehicle Gating with Cargo (#1060) * restore vehicle cargoing after vehicle cargo gate transfers * fixed this test, maybe? --- .../actors/session/SessionActor.scala | 4 +- .../actors/session/support/SessionData.scala | 214 +----------------- .../support/SessionGalaxyHandlers.scala | 53 +---- .../session/support/VehicleOperations.scala | 8 +- .../session/support/ZoningOperations.scala | 10 +- .../scala/net/psforever/objects/Vehicle.scala | 7 - .../objects/vehicles/CargoBehavior.scala | 36 +-- .../scala/objects/VehicleControlTest.scala | 12 +- 8 files changed, 41 insertions(+), 303 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index c8350c7aa..36d860e17 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -3,7 +3,7 @@ package net.psforever.actors.session import akka.actor.typed.receptionist.Receptionist import akka.actor.typed.scaladsl.adapter._ -import akka.actor.{Actor, MDCContextAware, SupervisorStrategy, typed} +import akka.actor.{Actor, MDCContextAware, typed} import org.joda.time.LocalDateTime import org.log4s.MDC import scala.collection.mutable @@ -116,8 +116,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con private[this] val buffer: mutable.ListBuffer[Any] = new mutable.ListBuffer[Any]() private[this] val sessionFuncs = new SessionData(middlewareActor, context) - override val supervisorStrategy: SupervisorStrategy = sessionFuncs.sessionSupervisorStrategy - ServiceManager.serviceManager ! Lookup("accountIntermediary") ServiceManager.serviceManager ! Lookup("accountPersistence") ServiceManager.serviceManager ! Lookup("galaxy") diff --git a/src/main/scala/net/psforever/actors/session/support/SessionData.scala b/src/main/scala/net/psforever/actors/session/support/SessionData.scala index 37913f07d..aef627f4f 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionData.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionData.scala @@ -2,7 +2,7 @@ package net.psforever.actors.session.support import akka.actor.typed.scaladsl.adapter._ -import akka.actor.{ActorContext, ActorRef, Cancellable, OneForOneStrategy, SupervisorStrategy, typed} +import akka.actor.{ActorContext, ActorRef, Cancellable, typed} import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} import scala.collection.mutable @@ -19,11 +19,10 @@ import net.psforever.objects.avatar._ import net.psforever.objects.ballistics._ import net.psforever.objects.ce._ import net.psforever.objects.definition._ -import net.psforever.objects.entity.{NoGUIDException,WorldEntity} +import net.psforever.objects.entity.WorldEntity import net.psforever.objects.equipment._ import net.psforever.objects.guid._ -import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} -import net.psforever.objects.loadouts.InfantryLoadout +import net.psforever.objects.inventory.{Container, InventoryItem} import net.psforever.objects.locker.LockerContainer import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.deploy.Deployment @@ -65,6 +64,7 @@ import net.psforever.types._ import net.psforever.util.Config object SessionData { + //noinspection ScalaUnusedSymbol private def NoTurnCounterYet(guid: PlanetSideGUID): Unit = { } } @@ -1122,212 +1122,6 @@ class SessionData( /* supporting functions */ - val sessionSupervisorStrategy: SupervisorStrategy = { - import net.psforever.objects.inventory.InventoryDisarrayException - OneForOneStrategy(maxNrOfRetries = -1, withinTimeRange = 1 minute) { - case nge: NoGUIDException => - nge.getEntity match { - case p: Player => - continent.GUID(p.VehicleSeated) match { - case Some(v: Vehicle) => - attemptRecoveryFromNoGuidExceptionAsVehicle(v, nge) - case _ => - attemptRecoveryFromNoGuidExceptionAsPlayer(p, nge) - } - - case v: Vehicle => - attemptRecoveryFromNoGuidExceptionAsVehicle(v, nge) - - case e: Equipment => - ( - player.Holsters().zipWithIndex.flatMap { case (o, i) => - o.Equipment.flatMap { item => Some((player, InventoryItem(item, i))) } - }.toList ++ - player.Inventory.Items.map { o => (player, o) } ++ - player.FreeHand.Equipment.flatMap { item => Some((player, InventoryItem(item, Player.FreeHandSlot))) }.toList ++ - (validObject(player.VehicleSeated) match { - case Some(v: Vehicle) => v.Trunk.Items.map { o => (v, o) } - case _ => Nil - }) - ) - .find { case (_, InventoryItem(o, _)) => o eq e } match { - case Some((p: Player, InventoryItem(obj, index))) if !obj.HasGUID => - p.Slot(index).Equipment = None - attemptRecoveryFromNoGuidExceptionAsPlayer(p, nge) - case Some((p: Player, _)) => - attemptRecoveryFromNoGuidExceptionAsPlayer(p, nge) - - case Some((v: Vehicle, InventoryItem(obj, index))) if !obj.HasGUID && v.PassengerInSeat(player).contains(0) => - v.Slot(index).Equipment = None - attemptRecoveryFromNoGuidExceptionAsPlayer(player, nge) - SupervisorStrategy.resume - case Some((v: Vehicle, InventoryItem(obj, index))) if !obj.HasGUID => - v.Slot(index).Equipment = None - SupervisorStrategy.resume - case Some((v: Vehicle, _)) if v.PassengerInSeat(player).contains(0) => - attemptRecoveryFromNoGuidExceptionAsPlayer(player, nge) - SupervisorStrategy.resume - case Some((_: Vehicle, _)) => - SupervisorStrategy.resume - - case _ => - //did not discover or resolve the situation - writeLogExceptionAndStop(nge) - } - case _ => - SupervisorStrategy.resume - } - - case ide: InventoryDisarrayException => - attemptRecoveryFromInventoryDisarrayException(ide.inventory) - //re-evaluate results - if (ide.inventory.ElementsOnGridMatchList() > 0) { - writeLogExceptionAndStop(ide) - } else { - SupervisorStrategy.resume - } - - case e => - writeLogExceptionAndStop(e) - } - } - - def attemptRecoveryFromNoGuidExceptionAsVehicle(v: Vehicle, e: Throwable): SupervisorStrategy.Directive = { - val entry = v.Seats.find { case (_, s) => s.occupants.contains(player) } - entry match { - case Some(_) => - player.VehicleSeated = None - v.Seats(0).unmount(player) - player.Position = v.Position - player.Orientation = v.Orientation - zoning.interstellarFerry = None - zoning.interstellarFerryTopLevelGUID = None - attemptRecoveryFromNoGuidExceptionAsPlayer(player, e) - case None => - writeLogException(e) - } - } - - def attemptRecoveryFromNoGuidExceptionAsPlayer(p: Player, e: Throwable): SupervisorStrategy.Directive = { - if (p eq player) { - val hasGUID = p.HasGUID - zoning.zoneLoaded match { - case Some(true) if hasGUID => - zoning.spawn.AvatarCreate() //this will probably work? - SupervisorStrategy.resume - case Some(false) => - zoning.RequestSanctuaryZoneSpawn(p, continent.Number) - SupervisorStrategy.resume - case None if player.Zone eq Zone.Nowhere => - zoning.RequestSanctuaryZoneSpawn(p, continent.Number) - SupervisorStrategy.resume - case None => - zoning.zoneReload = true - zoning.LoadZoneAsPlayer(player, player.Zone.id) - SupervisorStrategy.resume - case _ => - writeLogExceptionAndStop(e) - } - } else { - SupervisorStrategy.resume - } - } - - def attemptRecoveryFromInventoryDisarrayException(inv: GridInventory): Unit = { - inv.ElementsInListCollideInGrid() match { - case Nil => () - case list if list.isEmpty => () - case overlaps => - val previousItems = inv.Clear() - val allOverlaps = overlaps.flatten.sortBy { entry => - val tile = entry.obj.Definition.Tile - tile.Width * tile.Height - }.toSet - val notCollidingRemainder = previousItems.filterNot(allOverlaps.contains) - notCollidingRemainder.foreach { entry => - inv.InsertQuickly(entry.start, entry.obj) - } - var didNotFit: List[Equipment] = Nil - allOverlaps.foreach { entry => - inv.Fit(entry.obj.Definition.Tile) match { - case Some(newStart) => - inv.InsertQuickly(newStart, entry.obj) - case None => - didNotFit = didNotFit :+ entry.obj - } - } - //completely clear the inventory - val pguid = player.GUID - val equipmentInHand = player.Slot(player.DrawnSlot).Equipment - //redraw suit - sendResponse(ArmorChangedMessage( - pguid, - player.ExoSuit, - InfantryLoadout.DetermineSubtypeA(player.ExoSuit, equipmentInHand) - )) - //redraw item in free hand (if) - player.FreeHand.Equipment.foreach { item => - sendResponse(ObjectCreateDetailedMessage( - item.Definition.ObjectId, - item.GUID, - ObjectCreateMessageParent(pguid, Player.FreeHandSlot), - item.Definition.Packet.DetailedConstructorData(item).get - )) - } - //redraw items in holsters - player.Holsters().zipWithIndex.foreach { case (slot, _) => - slot.Equipment.foreach { item => - sendResponse(ObjectCreateDetailedMessage( - item.Definition.ObjectId, - item.GUID, - item.Definition.Packet.DetailedConstructorData(item).get - )) - } - } - //redraw raised hand (if) - equipmentInHand.foreach { _ => - sendResponse(ObjectHeldMessage(pguid, player.DrawnSlot, unk1 = true)) - } - //redraw inventory items - val recoveredItems = inv.Items - recoveredItems.foreach { entry => - val item = entry.obj - sendResponse(ObjectCreateDetailedMessage( - item.Definition.ObjectId, - item.GUID, - ObjectCreateMessageParent(pguid, entry.start), - item.Definition.Packet.DetailedConstructorData(item).get - )) - } - //drop items that did not fit - val placementData = PlacementData(player.Position, Vector3.z(player.Orientation.z)) - didNotFit.foreach { item => - sendResponse(ObjectCreateMessage( - item.Definition.ObjectId, - item.GUID, - DroppedItemData( - placementData, - item.Definition.Packet.ConstructorData(item).get - ) - )) - } - } - } - - def writeLogException(e: Throwable): SupervisorStrategy.Directive = { - import java.io.{PrintWriter, StringWriter} - val sw = new StringWriter - e.printStackTrace(new PrintWriter(sw)) - log.error(sw.toString) - SupervisorStrategy.Resume - } - - def writeLogExceptionAndStop(e: Throwable): SupervisorStrategy.Directive = { - writeLogException(e) - immediateDisconnect() - SupervisorStrategy.stop - } - def buildDependentOperationsForGalaxy(galaxyActor: ActorRef): Unit = { if (vehicleResponseOpt.isEmpty && galaxyActor != Default.Actor) { galaxyResponseOpt = Some(new SessionGalaxyHandlers(sessionData=this, avatarActor, galaxyActor, context)) diff --git a/src/main/scala/net/psforever/actors/session/support/SessionGalaxyHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionGalaxyHandlers.scala index dc9507092..9a0db54a0 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionGalaxyHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionGalaxyHandlers.scala @@ -45,52 +45,7 @@ class SessionGalaxyHandlers( sendResponse(msg) case GalaxyResponse.TransferPassenger(temp_channel, vehicle, _, manifest) => - val playerName = player.Name - log.debug(s"TransferPassenger: $playerName received the summons to transfer to ${vehicle.Zone.id} ...") - (manifest.passengers.find { _.name.equals(playerName) } match { - case Some(entry) if vehicle.Seats(entry.mount).occupant.isEmpty => - player.VehicleSeated = None - vehicle.Seats(entry.mount).mount(player) - player.VehicleSeated = vehicle.GUID - Some(vehicle) - case Some(entry) if vehicle.Seats(entry.mount).occupant.contains(player) => - Some(vehicle) - case Some(entry) => - log.warn( - s"TransferPassenger: $playerName tried to mount seat ${entry.mount} during summoning, but it was already occupied, and ${player.Sex.pronounSubject} was rebuked" - ) - None - case None => - //log.warn(s"TransferPassenger: $playerName is missing from the manifest of a summoning ${vehicle.Definition.Name} from ${vehicle.Zone.id}") - None - }).orElse { - manifest.cargo.find { _.name.equals(playerName) } match { - case Some(entry) => - vehicle.CargoHolds(entry.mount).occupant match { - case out @ Some(cargo) if cargo.Seats(0).occupants.exists(_.Name.equals(playerName)) => - out - case _ => - None - } - case None => - None - } - } match { - case Some(v: Vehicle) => - galaxyService ! Service.Leave(Some(temp_channel)) //temporary vehicle-specific channel (see above) - sessionData.zoning.spawn.deadState = DeadState.Release - sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, unk5=true)) - sessionData.zoning.interstellarFerry = Some(v) //on the other continent and registered to that continent's GUID system - sessionData.zoning.spawn.LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds, None) - case _ => - sessionData.zoning.interstellarFerry match { - case None => - galaxyService ! Service.Leave(Some(temp_channel)) //no longer being transferred between zones - sessionData.zoning.interstellarFerryTopLevelGUID = None - case Some(_) => ; - //wait patiently - } - } + sessionData.zoning.handleTransferPassenger(temp_channel, vehicle, manifest) case GalaxyResponse.LockedZoneUpdate(zone, time) => sendResponse(ZoneInfoMessage(zone.Number, empire_status=false, lock_time=time)) @@ -103,10 +58,8 @@ class SessionGalaxyHandlers( val popVS = zone.Players.count(_.faction == PlanetSideEmpire.VS) sendResponse(ZonePopulationUpdateMessage(zone.Number, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) - case GalaxyResponse.LogStatusChange(name) => - if (avatar.people.friend.exists { _.name.equals(name) }) { - avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name) - } + case GalaxyResponse.LogStatusChange(name) if (avatar.people.friend.exists { _.name.equals(name) }) => + avatarActor ! AvatarActor.MemberListRequest(MemberAction.UpdateFriend, name) case GalaxyResponse.SendResponse(msg) => sendResponse(msg) diff --git a/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala b/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala index 66847c567..ecf6dc8bf 100644 --- a/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala @@ -309,11 +309,8 @@ class VehicleOperations( obj.PassengerInSeat(player) match { case Some(seat_num) => obj.Actor ! Mountable.TryDismount(player, seat_num, bailType) - if (sessionData.zoning.interstellarFerry.isDefined) { - //short-circuit the temporary channel for transferring between zones, the player is no longer doing that - //see above in VehicleResponse.TransferPassenger case - sessionData.zoning.interstellarFerry = None - } + //short-circuit the temporary channel for transferring between zones, the player is no longer doing that + sessionData.zoning.interstellarFerry = None // Deconstruct the vehicle if the driver has bailed out and the vehicle is capable of flight //todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle //todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct. @@ -626,6 +623,7 @@ class VehicleOperations( } } + //noinspection ScalaUnusedSymbol def TotalDriverVehicleControl(vehicle: Vehicle): Unit = { serverVehicleControlVelocity = None sendResponse(ServerVehicleOverrideMsg(lock_accelerator=false, lock_wheel=false, reverse=false, unk4=false, 0, 0, 0, None)) diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala index ac0685b87..fa576f638 100644 --- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala @@ -762,19 +762,19 @@ class ZoningOperations( None } } match { - case Some(v: Vehicle) => + case Some(cargo: Vehicle) => galaxyService ! Service.Leave(Some(temp_channel)) //temporary vehicle-specific channel (see above) spawn.deadState = DeadState.Release sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, unk5=true)) - interstellarFerry = Some(v) //on the other continent and registered to that continent's GUID system - spawn.LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds, None) + interstellarFerry = Some(cargo) //on the other continent and registered to that continent's GUID system + cargo.MountedIn = vehicle.GUID + spawn.LoadZonePhysicalSpawnPoint(cargo.Continent, cargo.Position, cargo.Orientation, respawnTime = 1 seconds, None) case _ => interstellarFerry match { case None => galaxyService ! Service.Leave(Some(temp_channel)) //no longer being transferred between zones interstellarFerryTopLevelGUID = None - case Some(_) => ; - //wait patiently + case Some(_) => () //wait patiently } } } diff --git a/src/main/scala/net/psforever/objects/Vehicle.scala b/src/main/scala/net/psforever/objects/Vehicle.scala index ee0650be8..b2e09fcf9 100644 --- a/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/src/main/scala/net/psforever/objects/Vehicle.scala @@ -113,13 +113,6 @@ class Vehicle(private val vehicleDef: VehicleDefinition) private var utilities: Map[Int, Utility] = Map.empty private var subsystems: List[VehicleSubsystem] = Nil private val trunk: GridInventory = GridInventory() - - /* - * Records the GUID of the cargo vehicle (galaxy/lodestar) this vehicle is stored in for DismountVehicleCargoMsg use - * DismountVehicleCargoMsg only passes the player_guid and this vehicle's guid - */ - //private var mountedIn: Option[PlanetSideGUID] = None - private var vehicleGatingManifest: Option[VehicleManifest] = None private var previousVehicleGatingManifest: Option[VehicleManifest] = None diff --git a/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala b/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala index 47ea7cdca..dec138367 100644 --- a/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala +++ b/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala @@ -27,7 +27,7 @@ trait CargoBehavior { case _ => ; } isDismounting = None - startCargoDismounting(bailed = false) + startCargoDismountingNoCleanup(bailed = false) } val cargoBehavior: Receive = { @@ -64,24 +64,26 @@ trait CargoBehavior { } def startCargoDismounting(bailed: Boolean): Unit = { - val obj = CargoObject - obj.Zone.GUID(obj.MountedIn) match { - case Some(carrier: Vehicle) => - carrier.CargoHolds.find { case (_, hold) => hold.occupant.contains(obj) } match { - case Some((mountPoint, _)) - if isDismounting.isEmpty && isMounting.isEmpty => - isDismounting = obj.MountedIn - carrier.Actor ! CarrierBehavior.CheckCargoDismount(obj.GUID, mountPoint, 0, bailed) - - case _ => - obj.MountedIn = None - isDismounting = None - } - case _ => - obj.MountedIn = None - isDismounting = None + if (!startCargoDismountingNoCleanup(bailed)) { + isDismounting = None + CargoObject.MountedIn = None } } + + def startCargoDismountingNoCleanup(bailed: Boolean): Boolean = { + val obj = CargoObject + obj.Zone.GUID(obj.MountedIn) + .collect { case carrier: Vehicle => + (carrier, carrier.CargoHolds.find { case (_, hold) => hold.occupant.contains(obj) }) + } + .collect { case (carrier, Some((mountPoint, _))) + if isDismounting.isEmpty && isMounting.isEmpty => + isDismounting = obj.MountedIn + carrier.Actor ! CarrierBehavior.CheckCargoDismount(obj.GUID, mountPoint, 0, bailed) + true + } + .nonEmpty + } } object CargoBehavior { diff --git a/src/test/scala/objects/VehicleControlTest.scala b/src/test/scala/objects/VehicleControlTest.scala index e22f33e21..e65bb2d11 100644 --- a/src/test/scala/objects/VehicleControlTest.scala +++ b/src/test/scala/objects/VehicleControlTest.scala @@ -218,7 +218,7 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor lodestar.Actor ! Vehicle.Deconstruct() val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds) - vehicle_msg(5) match { + vehicle_msg.head match { case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(4), 4, true, PlanetSideGUID(2))) => ; case _ => assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-1: ${vehicle_msg(5)}") @@ -226,27 +226,27 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor assert(player2.VehicleSeated.isEmpty) assert(lodestar.Seats(0).occupant.isEmpty) //cargo dismounting messages - vehicle_msg.head match { + vehicle_msg(1) match { case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ; case _ => assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-2: ${vehicle_msg.head}") } - vehicle_msg(1) match { + vehicle_msg(2) match { case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ; case _ => assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-3: ${vehicle_msg(1)}") } - vehicle_msg(2) match { + vehicle_msg(3) match { case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0))) => ; case _ => assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-4: ${vehicle_msg(2)}") } - vehicle_msg(3) match { + vehicle_msg(4) match { case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ; case _ => assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-5: ${vehicle_msg(3)}") } - vehicle_msg(4) match { + vehicle_msg(5) match { case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ; case _ => assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-6: ${vehicle_msg(4)}")