deconstruction message for would-be driver when vehicle is abandoned on spawn pad; tempo on message delivery is different than usual

This commit is contained in:
Fate-JH 2024-08-24 00:18:22 -04:00
parent 2e901ae904
commit b3b62c94cc
5 changed files with 147 additions and 90 deletions

View file

@ -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 {

View file

@ -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.<br>
* 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
}
/**

View file

@ -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 {

View file

@ -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
}

View file

@ -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 _ => ()
}
}