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.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.Service
import net.psforever.services.vehicle.{VehicleResponse, VehicleServiceResponse} 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 { object VehicleHandlerLogic {
def apply(ops: SessionVehicleHandlers): VehicleHandlerLogic = { def apply(ops: SessionVehicleHandlers): VehicleHandlerLogic = {
@ -329,13 +329,13 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle) sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle)
case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) => case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) =>
sendResponse(ChatMsg( val str = s"${data.getOrElse("The vehicle spawn pad where you placed your order is blocked.")}"
ChatMessageType.CMT_OPEN, val msg = if (str.contains("@")) {
wideContents=true, ChatMsg(ChatMessageType.UNK_229, str)
recipient="", } else {
s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}", ChatMsg(ChatMessageType.CMT_OPEN, wideContents = true, recipient = "", str, note = None)
note=None }
)) sendResponse(msg)
case VehicleResponse.PeriodicReminder(_, data) => case VehicleResponse.PeriodicReminder(_, data) =>
val (isType, flag, msg): (ChatMessageType, Boolean, String) = data match { 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 */ /** how to process either the first order or every subsequent order */
private var handleOrderFunc: VehicleSpawnPad.VehicleOrder => Unit = NewTasking private var handleOrderFunc: VehicleSpawnPad.VehicleOrder => Unit = NewTasking
/** ... */
private var reminderSeq: Seq[Int] = Seq()
def LogId = "" def LogId = ""
/** /**
@ -113,33 +116,8 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
case VehicleSpawnControl.ProcessControl.QueueManagement => case VehicleSpawnControl.ProcessControl.QueueManagement =>
queueManagementTask() 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 => case VehicleSpawnControl.ProcessControl.Reminder =>
trackedOrder evaluateBlockedReminder()
.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
}
case VehicleSpawnControl.ProcessControl.Flush => case VehicleSpawnControl.ProcessControl.Flush =>
periodicReminder.cancel() 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. * Cancel this vehicle order and inform the person who made it, if possible.
* @param entry the order being cancelled * @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( @tailrec private final def recursiveBlockedReminder(
iter: Iterator[VehicleSpawnPad.VehicleOrder], iter: Iterator[VehicleSpawnPad.VehicleOrder],
cause: Option[Any] cause: Option[Any]
@ -414,19 +460,16 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
} }
object VehicleSpawnControl { object VehicleSpawnControl {
private final val periodicReminderTestDelay: FiniteDuration = 10 seconds
/** /**
* Control messages for the vehicle spawn process. * Control messages for the vehicle spawn process.
*/ */
sealed trait ProcessControlOperation
object ProcessControl { object ProcessControl {
sealed trait ProcessControl case object Flush extends ProcessControlOperation
case object OrderCancelled extends ProcessControlOperation
case object Flush extends ProcessControl case object GetNewOrder extends ProcessControlOperation
case object OrderCancelled extends ProcessControl case object Reminder extends ProcessControlOperation
case object GetNewOrder extends ProcessControl case object QueueManagement extends ProcessControlOperation
case object Reminder extends ProcessControl
case object QueueManagement extends ProcessControl
} }
/** /**

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 // 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 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 // 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 // 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) // 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` */ * @see `net.psforever.objects.serverobject.pad.process.VehicleSpawnControlRailJack` */
var killBox: (VehicleSpawnPad, Boolean)=>(PlanetSideGameObject, PlanetSideGameObject, Float)=> Boolean = var killBox: (VehicleSpawnPad, Boolean)=>(PlanetSideGameObject, PlanetSideGameObject, Float)=> Boolean =
VehicleSpawnPadDefinition.prepareKillBox(forwardLimit = 0, backLimit = 0, sideLimit = 0, aboveLimit = 0) 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 { object VehicleSpawnPadDefinition {

View file

@ -50,26 +50,35 @@ class VehicleSpawnControlFinalClearance(pad: VehicleSpawnPad) extends VehicleSpa
self ! VehicleSpawnControlFinalClearance.Test(order) self ! VehicleSpawnControlFinalClearance.Test(order)
case test @ VehicleSpawnControlFinalClearance.Test(entry) => 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 //once mounted, it will complain to the current driver that it is blocking the spawn pad
//no time limit exists for that state //no time limit exists for that state
val vehicle = entry.vehicle val vehicle = entry.vehicle
if (Vector3.DistanceSquared(vehicle.Position, pad.Position) > 144) { //12m away from pad val distanceTest = Vector3.DistanceSquared(vehicle.Position, pad.Position) > 144 //12m away from pad
trace("pad cleared") if (vehicle.Destroyed) {
pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad) trace("pad cleared of vehicle wreckage")
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder val delay = if (distanceTest) { 2000 } else { 5000 }
} else if (vehicle.Destroyed) {
trace("clearing the pad of vehicle wreckage")
VehicleSpawnControl.DisposeVehicle(vehicle, pad.Zone) 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 { } 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 { 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 // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad.process 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.{Default, Vehicle}
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
@ -25,7 +25,7 @@ import scala.concurrent.duration._
class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) { class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
def LogId = "-usher" def LogId = "-usher"
val vehicleOverride = context.actorOf( val vehicleOverride: ActorRef = context.actorOf(
Props(classOf[VehicleSpawnControlServerVehicleOverride], pad), Props(classOf[VehicleSpawnControlServerVehicleOverride], pad),
s"${context.parent.path.name}-override" s"${context.parent.path.name}-override"
) )
@ -43,7 +43,7 @@ class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnCo
val driver = entry.driver val driver = entry.driver
val vehicle = entry.vehicle val vehicle = entry.vehicle
//avoid unattended vehicle blocking the pad; user should mount (and does so normally) to reset decon timer //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) { if (VehicleSpawnControl.validateOrderCredentials(pad, driver, vehicle).isEmpty) {
trace("driver to be made seated in vehicle") trace("driver to be made seated in vehicle")
pad.Zone.VehicleEvents ! VehicleSpawnPad.StartPlayerSeatedInVehicle(driver.Name, vehicle, pad) 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) => case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
context.parent ! msg context.parent ! msg
case _ => ; case _ => ()
} }
} }