diff --git a/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala index 7084427ea..fa65279c8 100644 --- a/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala @@ -14,7 +14,7 @@ import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent import net.psforever.packet.game.{ChangeAmmoMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, ChildObjectStateMessage, DeadState, DeployRequestMessage, DismountVehicleMsg, FrameVehicleStateMessage, GenericObjectActionMessage, HitHint, InventoryStateMessage, ObjectAttachMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, PlanetsideAttributeMessage, ReloadMessage, ServerVehicleOverrideMsg, VehicleStateMessage, WeaponDryFireMessage} import net.psforever.services.Service import net.psforever.services.vehicle.{VehicleResponse, VehicleServiceResponse} -import net.psforever.types.{BailType, ChatMessageType, DriveState, PlanetSideGUID, Vector3} +import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3} object VehicleHandlerLogic { def apply(ops: SessionVehicleHandlers): VehicleHandlerLogic = { @@ -329,13 +329,13 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context: sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle) case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) => - sendResponse(ChatMsg( - ChatMessageType.CMT_OPEN, - wideContents=true, - recipient="", - s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}", - note=None - )) + val str = s"${data.getOrElse("The vehicle spawn pad where you placed your order is blocked.")}" + val msg = if (str.contains("@")) { + ChatMsg(ChatMessageType.UNK_229, str) + } else { + ChatMsg(ChatMessageType.CMT_OPEN, wideContents = true, recipient = "", str, note = None) + } + sendResponse(msg) case VehicleResponse.PeriodicReminder(_, data) => val (isType, flag, msg): (ChatMessageType, Boolean, String) = data match { diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala index 0fa40b9e8..06b65013c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala @@ -56,6 +56,9 @@ class VehicleSpawnControl(pad: VehicleSpawnPad) /** how to process either the first order or every subsequent order */ private var handleOrderFunc: VehicleSpawnPad.VehicleOrder => Unit = NewTasking + /** ... */ + private var reminderSeq: Seq[Int] = Seq() + def LogId = "" /** @@ -113,33 +116,8 @@ class VehicleSpawnControl(pad: VehicleSpawnPad) case VehicleSpawnControl.ProcessControl.QueueManagement => queueManagementTask() - /* - When the vehicle is spawned and added to the pad, it will "occupy" the pad and block it from further action. - Normally, the player who wanted to spawn the vehicle will be automatically put into the driver mount. - If this is blocked, the vehicle will idle on the pad and must be moved far enough away from the point of origin. - During this time, a periodic message about the spawn pad being blocked will be broadcast to the order queue. - */ case VehicleSpawnControl.ProcessControl.Reminder => - trackedOrder - .collect { - case entry => - if (periodicReminder.isCancelled) { - trace(s"the pad has become blocked by a ${entry.vehicle.Definition.Name} in its current order") - periodicReminder = context.system.scheduler.scheduleWithFixedDelay( - VehicleSpawnControl.periodicReminderTestDelay, - VehicleSpawnControl.periodicReminderTestDelay, - self, - VehicleSpawnControl.ProcessControl.Reminder - ) - } else { - BlockedReminder(entry, orders) - } - trackedOrder - } - .orElse { - periodicReminder.cancel() - None - } + evaluateBlockedReminder() case VehicleSpawnControl.ProcessControl.Flush => periodicReminder.cancel() @@ -324,37 +302,6 @@ class VehicleSpawnControl(pad: VehicleSpawnPad) } } - /** - * na - * @param blockedOrder the previous order whose vehicle is blocking the spawn pad from operating - * @param recipients all of the other customers who will be receiving the message - */ - private def BlockedReminder(blockedOrder: VehicleSpawnControl.Order, recipients: Seq[VehicleSpawnPad.VehicleOrder]): Unit = { - val user = blockedOrder.vehicle - .Seats(0).occupant - .orElse(pad.Zone.GUID(blockedOrder.vehicle.OwnerGuid)) - .orElse(pad.Zone.GUID(blockedOrder.DriverGUID)) - val relevantRecipients: Iterator[VehicleSpawnPad.VehicleOrder] = user match { - case Some(p: Player) if !p.HasGUID => - recipients.iterator - case Some(_: Player) => - (VehicleSpawnPad.VehicleOrder( - blockedOrder.driver, - blockedOrder.vehicle, - null //permissible - ) +: recipients).iterator //one who took possession of the vehicle - case _ => - recipients.iterator - } - recursiveBlockedReminder( - relevantRecipients, - if (blockedOrder.vehicle.Health == 0) - Option("Clear the wreckage.") - else - None - ) - } - /** * Cancel this vehicle order and inform the person who made it, if possible. * @param entry the order being cancelled @@ -381,6 +328,105 @@ class VehicleSpawnControl(pad: VehicleSpawnPad) } } + /** + * When the vehicle is spawned and added to the pad, it will "occupy" the pad and block it from further action. + * During this time, a periodic message about the spawn pad being blocked will be broadcast to the order queue.
+ * The vehicle is also queued to deconstruct in 30s if no one assumes the driver seat. + */ + private def evaluateBlockedReminder(): Unit = { + /* + Normally, the player who wanted to spawn the vehicle will be automatically put into the driver mount. + If this is blocked or aborted, the vehicle will idle on the pad and must be moved far enough away from the point of origin. + */ + trackedOrder + .collect { + case entry => + if (reminderSeq.isEmpty) { + //begin reminder + trace(s"the pad has become blocked by a ${entry.vehicle.Definition.Name} in its current order") + retimePeriodicReminder( + shaveOffFirstElementAndDiffSecondElement(pad.Definition.BlockedReminderMessageDelays) + ) + } else if (reminderSeq.size == 1) { + //end reminder + periodicReminder.cancel() + periodicReminder = Default.Cancellable + reminderSeq = List() + } else { + //continue reminder + BlockedReminder(entry, orders) + retimePeriodicReminder( + shaveOffFirstElementAndDiffSecondElement(reminderSeq) + ) + } + trackedOrder + } + .orElse { + periodicReminder.cancel() + periodicReminder = Default.Cancellable + reminderSeq = List() + None + } + } + + /** + * na + * @param blockedOrder the previous order whose vehicle is blocking the spawn pad from operating + * @param recipients all of the other customers who will be receiving the message + */ + private def BlockedReminder(blockedOrder: VehicleSpawnControl.Order, recipients: Seq[VehicleSpawnPad.VehicleOrder]): Unit = { + //everyone else + recursiveBlockedReminder( + recipients.iterator, + if (blockedOrder.vehicle.Health == 0) + Option("The vehicle spawn pad where you placed your order is blocked. Clearing the wreckage ...") + else + Option("The vehicle spawn pad where you placed your order is blocked.") + ) + //would-be driver + blockedOrder.vehicle + .Seats(0).occupant + .orElse(pad.Zone.GUID(blockedOrder.vehicle.OwnerGuid)) + .orElse(pad.Zone.GUID(blockedOrder.DriverGUID)) collect { + case p: Player if p.isAlive => + recursiveBlockedReminder( + Seq(VehicleSpawnPad.VehicleOrder( + blockedOrder.driver, + blockedOrder.vehicle, + null //permissible + )).iterator, + Some(s"@PadDeconstruct_secsA^${reminderSeq.head}~") + ) + } + } + + /** + * Clip the first entry in a list of numbers and + * get the difference between the clipped entry and the next entry. + * The clipped-off list will be made to be the new sequence of reminder delays. + * @param sequence reminder delay test values + * @return difference between first delay and second delay + */ + private def shaveOffFirstElementAndDiffSecondElement(sequence: Seq[Int]): Int = { + val startTime = sequence.take(1).headOption.getOrElse(0) + val restTimes = sequence.drop(1) + val headOfRestTimes = restTimes.headOption.getOrElse(startTime) + reminderSeq = restTimes + startTime - headOfRestTimes + } + + /** + * Set a single instance of the "periodic reminder" to this kind of delay. + * @param delay how long until the next reminder + */ + private def retimePeriodicReminder(delay: Int): Unit = { + periodicReminder = context.system.scheduler.scheduleOnce( + delay.seconds, + self, + VehicleSpawnControl.ProcessControl.Reminder + ) + } + @tailrec private final def recursiveBlockedReminder( iter: Iterator[VehicleSpawnPad.VehicleOrder], cause: Option[Any] @@ -414,19 +460,16 @@ class VehicleSpawnControl(pad: VehicleSpawnPad) } object VehicleSpawnControl { - private final val periodicReminderTestDelay: FiniteDuration = 10 seconds - /** * Control messages for the vehicle spawn process. */ + sealed trait ProcessControlOperation object ProcessControl { - sealed trait ProcessControl - - case object Flush extends ProcessControl - case object OrderCancelled extends ProcessControl - case object GetNewOrder extends ProcessControl - case object Reminder extends ProcessControl - case object QueueManagement extends ProcessControl + case object Flush extends ProcessControlOperation + case object OrderCancelled extends ProcessControlOperation + case object GetNewOrder extends ProcessControlOperation + case object Reminder extends ProcessControlOperation + case object QueueManagement extends ProcessControlOperation } /** diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala index 51c4c4a3b..86b072f12 100644 --- a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala @@ -12,7 +12,6 @@ class VehicleSpawnPadDefinition(objectId: Int) extends AmenityDefinition(objectI // Different pads require a Z offset to stop vehicles falling through the world after the pad rises from the floor, these values are found in game_objects.adb.lst private var vehicle_creation_z_offset = 0f - // Different pads also require an orientation offset when detaching vehicles from the rails associated with the spawn pad, again in game_objects.adb.lst // For example: 9754:add_property dropship_pad_doors vehiclecreationzorientoffset 90 // However, it seems these values need to be reversed to turn CCW to CW rotation (e.g. +90 to -90) @@ -35,6 +34,12 @@ class VehicleSpawnPadDefinition(objectId: Int) extends AmenityDefinition(objectI * @see `net.psforever.objects.serverobject.pad.process.VehicleSpawnControlRailJack` */ var killBox: (VehicleSpawnPad, Boolean)=>(PlanetSideGameObject, PlanetSideGameObject, Float)=> Boolean = VehicleSpawnPadDefinition.prepareKillBox(forwardLimit = 0, backLimit = 0, sideLimit = 0, aboveLimit = 0) + + private val blockedReminderMessageDelays: Seq[Int] = Seq(30, 23, 15, 8, 0) + + def VehicleCreationDeconstructTime: Int = blockedReminderMessageDelays.head + + def BlockedReminderMessageDelays: Seq[Int] = blockedReminderMessageDelays } object VehicleSpawnPadDefinition { diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala index 516b904b2..67c8d6e61 100644 --- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala +++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala @@ -50,26 +50,35 @@ class VehicleSpawnControlFinalClearance(pad: VehicleSpawnPad) extends VehicleSpa self ! VehicleSpawnControlFinalClearance.Test(order) case test @ VehicleSpawnControlFinalClearance.Test(entry) => - //the vehicle has an initial decay of 30s in which time it needs to be mounted + //the vehicle has an initial decay in which time it needs to be mounted //once mounted, it will complain to the current driver that it is blocking the spawn pad //no time limit exists for that state val vehicle = entry.vehicle - if (Vector3.DistanceSquared(vehicle.Position, pad.Position) > 144) { //12m away from pad - trace("pad cleared") - pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad) - context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder - } else if (vehicle.Destroyed) { - trace("clearing the pad of vehicle wreckage") + val distanceTest = Vector3.DistanceSquared(vehicle.Position, pad.Position) > 144 //12m away from pad + if (vehicle.Destroyed) { + trace("pad cleared of vehicle wreckage") + val delay = if (distanceTest) { 2000 } else { 5000 } VehicleSpawnControl.DisposeVehicle(vehicle, pad.Zone) - context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder + temp = context.system.scheduler.scheduleOnce(delay.milliseconds, self, VehicleSpawnControlFinalClearance.NextOrder) + } else if (distanceTest) { + trace("pad cleared") + val delay = if (vehicle.Seats.head._2.occupant.isEmpty) { 4500 } else { 2000 } + temp = context.system.scheduler.scheduleOnce(delay.milliseconds, self, VehicleSpawnControlFinalClearance.NextOrder) } else { - temp = context.system.scheduler.scheduleOnce(2000 milliseconds, self, test) + //retry test + temp = context.system.scheduler.scheduleOnce(delay = 2000.milliseconds, self, test) } - case _ => ; + case VehicleSpawnControlFinalClearance.NextOrder => + pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad) + context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder + + case _ => () } } object VehicleSpawnControlFinalClearance { - private final case class Test(entry: VehicleSpawnControl.Order) + private case class Test(entry: VehicleSpawnControl.Order) + + private case object NextOrder } diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala index bf14a8cb3..520d0c1ec 100644 --- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala +++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.pad.process -import akka.actor.Props +import akka.actor.{ActorRef, Props} import net.psforever.objects.{Default, Vehicle} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} @@ -25,7 +25,7 @@ import scala.concurrent.duration._ class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) { def LogId = "-usher" - val vehicleOverride = context.actorOf( + val vehicleOverride: ActorRef = context.actorOf( Props(classOf[VehicleSpawnControlServerVehicleOverride], pad), s"${context.parent.path.name}-override" ) @@ -43,7 +43,7 @@ class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnCo val driver = entry.driver val vehicle = entry.vehicle //avoid unattended vehicle blocking the pad; user should mount (and does so normally) to reset decon timer - vehicle.Actor ! Vehicle.Deconstruct(Some(30 seconds)) + vehicle.Actor ! Vehicle.Deconstruct(Some(pad.Definition.VehicleCreationDeconstructTime.seconds)) if (VehicleSpawnControl.validateOrderCredentials(pad, driver, vehicle).isEmpty) { trace("driver to be made seated in vehicle") pad.Zone.VehicleEvents ! VehicleSpawnPad.StartPlayerSeatedInVehicle(driver.Name, vehicle, pad) @@ -67,7 +67,7 @@ class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnCo case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) => context.parent ! msg - case _ => ; + case _ => () } }