diff --git a/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala b/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
index 142eb0df4..f2f00f6dd 100644
--- a/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
+++ b/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
@@ -13,6 +13,7 @@ import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.Avatar
+import net.psforever.objects.serverobject.terminals.Terminal
import scala.concurrent.duration._
@@ -29,11 +30,11 @@ class VehicleSpawnControl1Test extends ActorTest {
class VehicleSpawnControl2Test extends ActorTest {
"VehicleSpawnControl" should {
"complete a vehicle order" in {
- val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ val (vehicle, player, pad, terminal, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
val probe = new TestProbe(system, "zone-events")
- zone.VehicleEvents = probe.ref //zone events
- pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) //order
+ zone.VehicleEvents = probe.ref //zone events
+ pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle, terminal) //order
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.ConcealPlayer])
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
@@ -55,7 +56,7 @@ class VehicleSpawnControl2Test extends ActorTest {
class VehicleSpawnControl3Test extends ActorTest {
"VehicleSpawnControl" should {
"block the second vehicle order until the first is completed" in {
- val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ val (vehicle, player, pad, terminal, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
//we can recycle the vehicle and the player for each order
val probe = new TestProbe(system, "zone-events")
val player2 = Player(Avatar(0, "test2", player.Faction, CharacterSex.Male, 0, CharacterVoice.Mute))
@@ -63,9 +64,9 @@ class VehicleSpawnControl3Test extends ActorTest {
player2.Continent = zone.id
player2.Spawn()
- zone.VehicleEvents = probe.ref //zone events
- pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) //first order
- pad.Actor ! VehicleSpawnPad.VehicleOrder(player2, vehicle) //second order (vehicle shared)
+ zone.VehicleEvents = probe.ref //zone events
+ pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle, terminal) //first order
+ pad.Actor ! VehicleSpawnPad.VehicleOrder(player2, vehicle, terminal) //second order (vehicle shared)
assert(probe.receiveOne(1 seconds) match {
case VehicleSpawnPad.PeriodicReminder(_, VehicleSpawnPad.Reminders.Queue, _) => true
@@ -103,12 +104,12 @@ class VehicleSpawnControl3Test extends ActorTest {
class VehicleSpawnControl4Test extends ActorTest {
"VehicleSpawnControl" should {
"clean up the vehicle if the driver-to-be is on the wrong continent" in {
- val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ val (vehicle, player, pad, terminal, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
val probe = new TestProbe(system, "zone-events")
zone.VehicleEvents = probe.ref
player.Continent = "problem" //problem
- pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) //order
+ pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle, terminal) //order
val msg = probe.receiveOne(1 minute)
// assert(
@@ -125,11 +126,11 @@ class VehicleSpawnControl4Test extends ActorTest {
class VehicleSpawnControl5Test extends ActorTest() {
"VehicleSpawnControl" should {
"abandon a destroyed vehicle on the spawn pad (blocking)" in {
- val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ val (vehicle, player, pad, terminal, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
val probe = new TestProbe(system, "zone-events")
zone.VehicleEvents = probe.ref
- pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) //order
+ pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle, terminal) //order
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.ConcealPlayer])
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
@@ -152,11 +153,11 @@ class VehicleSpawnControl5Test extends ActorTest() {
class VehicleSpawnControl6Test extends ActorTest() {
"VehicleSpawnControl" should {
"abandon a vehicle on the spawn pad if driver is unfit to drive (blocking)" in {
- val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ val (vehicle, player, pad, terminal, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
val probe = new TestProbe(system, "zone-events")
zone.VehicleEvents = probe.ref
- pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) //order
+ pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle, terminal) //order
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.ConcealPlayer])
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
@@ -179,12 +180,12 @@ class VehicleSpawnControl6Test extends ActorTest() {
class VehicleSpawnControl7Test extends ActorTest {
"VehicleSpawnControl" should {
"abandon a vehicle on the spawn pad if driver is unfit to drive (blocking)" in {
- val (vehicle, player, pad, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
+ val (vehicle, player, pad, terminal, zone) = VehicleSpawnPadControlTest.SetUpAgents(PlanetSideEmpire.TR)
val probe = new TestProbe(system, "zone-events")
player.ExoSuit = ExoSuitType.MAX
- zone.VehicleEvents = probe.ref //zone events
- pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) //order
+ zone.VehicleEvents = probe.ref //zone events
+ pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle, terminal) //order
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.ConcealPlayer])
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
@@ -210,7 +211,7 @@ object VehicleSpawnPadControlTest {
def SetUpAgents(
faction: PlanetSideEmpire.Value
- )(implicit system: ActorSystem): (Vehicle, Player, VehicleSpawnPad, Zone) = {
+ )(implicit system: ActorSystem): (Vehicle, Player, VehicleSpawnPad, Terminal, Zone) = {
import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.structures.Building
@@ -218,6 +219,7 @@ object VehicleSpawnPadControlTest {
import net.psforever.objects.Tool
import net.psforever.types.CharacterSex
+ val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined)
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
val weapon = vehicle.WeaponControlledFromSeat(1).get.asInstanceOf[Tool]
val guid: NumberPoolHub = new NumberPoolHub(MaxNumberSource(5))
@@ -252,6 +254,6 @@ object VehicleSpawnPadControlTest {
//note: pad and vehicle are both at Vector3(1,0,0) so they count as blocking
pad.Position = Vector3(1, 0, 0)
vehicle.Position = Vector3(1, 0, 0)
- (vehicle, player, pad, zone)
+ (vehicle, player, pad, terminal, zone)
}
}
diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala
index 154d34a39..626f649aa 100644
--- a/src/main/scala/net/psforever/actors/session/SessionActor.scala
+++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala
@@ -44,6 +44,7 @@ import net.psforever.objects.vehicles.Utility.InternalTelepad
import net.psforever.objects.vehicles._
import net.psforever.objects.vital._
import net.psforever.objects.vital.base._
+import net.psforever.objects.vital.etc.ExplodingEntityReason
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.projectile.ProjectileReason
import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning}
@@ -60,12 +61,7 @@ import net.psforever.services.local.support.{CaptureFlagManager, HackCaptureActo
import net.psforever.services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
import net.psforever.services.properties.PropertyOverrideManager
import net.psforever.services.support.SupportActor
-import net.psforever.services.teamwork.{
- SquadResponse,
- SquadServiceMessage,
- SquadServiceResponse,
- SquadAction => SquadServiceAction
-}
+import net.psforever.services.teamwork.{SquadResponse, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction}
import net.psforever.services.hart.HartTimer
import net.psforever.services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
import net.psforever.services.{RemoverActor, Service, ServiceManager, InterstellarClusterService => ICS}
@@ -443,6 +439,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
session.player.spectator = spectator
case Recall() =>
+ player.ZoningRequest = Zoning.Method.Recall
zoningType = Zoning.Method.Recall
zoningChatMessageType = ChatMessageType.CMT_RECALL
zoningStatus = Zoning.Status.Request
@@ -456,6 +453,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
})
case InstantAction() =>
+ player.ZoningRequest = Zoning.Method.InstantAction
zoningType = Zoning.Method.InstantAction
zoningChatMessageType = ChatMessageType.CMT_INSTANTACTION
zoningStatus = Zoning.Status.Request
@@ -482,10 +480,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
cluster ! ICS.GetInstantActionSpawnPoint(player.Faction, context.self)
case Quit() =>
- //priority to quitting is given to quit over other zoning methods
+ //priority is given to quit over other zoning methods
if (session.zoningType == Zoning.Method.InstantAction || session.zoningType == Zoning.Method.Recall) {
CancelZoningProcessWithDescriptiveReason("cancel")
}
+ player.ZoningRequest = Zoning.Method.Quit
zoningType = Zoning.Method.Quit
zoningChatMessageType = ChatMessageType.CMT_QUIT
zoningStatus = Zoning.Status.Request
@@ -1726,6 +1725,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
*/
def CancelZoningProcess(): Unit = {
zoningTimer.cancel()
+ player.ZoningRequest = Zoning.Method.None
zoningType = Zoning.Method.None
zoningStatus = Zoning.Status.None
zoningCounter = 0
@@ -1859,6 +1859,16 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
DropSpecialSlotItem()
ToggleMaxSpecialState(enable = false)
+ if (player.LastDamage match {
+ case Some(damage) => damage.interaction.cause match {
+ case cause: ExplodingEntityReason => cause.entity.isInstanceOf[VehicleSpawnPad]
+ case _ => false
+ }
+ case None => false
+ }) {
+ //also, @SVCP_Killed_TooCloseToPadOnCreate^n~ or "... within n meters of pad ..."
+ sendResponse(ChatMsg(ChatMessageType.UNK_227, false, "", "@SVCP_Killed_OnPadOnCreate", None))
+ }
keepAliveFunc = NormalKeepAlive
zoningStatus = Zoning.Status.None
@@ -1866,7 +1876,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
continent.GUID(mount) match {
case Some(obj: Vehicle) =>
- TotalDriverVehicleControl(obj)
+ ConditionalDriverVehicleControl(obj)
UnaccessContainer(obj)
case _ => ;
}
@@ -2190,6 +2200,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
specialItemSlotGuid match {
case Some(guid: PlanetSideGUID) =>
specialItemSlotGuid = None
+ player.Carrying = None
continent.GUID(guid) match {
case Some(llu: CaptureFlag) =>
llu.Carrier match {
@@ -2398,6 +2409,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(guid) =>
if (guid == llu.GUID) {
specialItemSlotGuid = None
+ player.Carrying = None
}
case _ => ;
}
@@ -2622,7 +2634,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val player_guid: PlanetSideGUID = tplayer.GUID
if (player_guid == player.GUID) {
//disembarking self
- TotalDriverVehicleControl(obj)
+ ConditionalDriverVehicleControl(obj)
UnaccessContainer(obj)
DismountAction(tplayer, obj, seat_num)
} else {
@@ -2675,7 +2687,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case None =>
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
continent.tasks ! BuyNewEquipmentPutInInventory(
- continent.GUID(tplayer.VehicleSeated) match { case Some(v: Vehicle) => v; case _ => player },
+ continent.GUID(tplayer.VehicleSeated) match { case Some(v : Vehicle) => v; case _ => player },
tplayer,
msg.terminal_guid
)(item)
@@ -2701,13 +2713,18 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
lastTerminalOrderFulfillment = true
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
- continent.map.terminalToSpawnPad.get(msg.terminal_guid.guid) match {
- case Some(padGuid) =>
- tplayer.avatar.purchaseCooldown(vehicle.Definition) match {
- case Some(_) =>
- sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
- case None =>
- val pad = continent.GUID(padGuid).get.asInstanceOf[VehicleSpawnPad]
+ tplayer.avatar.purchaseCooldown(vehicle.Definition) match {
+ case Some(_) =>
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
+ case None =>
+ continent.map.terminalToSpawnPad
+ .find { case (termid, _) => termid == msg.terminal_guid.guid }
+ .collect {
+ case (a: Int, b: Int) => (continent.GUID(a), continent.GUID(b))
+ case _ => (None, None)
+ }
+ .get match {
+ case (Some(term: Terminal), Some(pad: VehicleSpawnPad)) =>
vehicle.Faction = tplayer.Faction
vehicle.Position = pad.Position
vehicle.Orientation = pad.Orientation + Vector3.z(pad.Definition.VehicleCreationZOrientOffset)
@@ -2732,13 +2749,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
entry.obj.Faction = tplayer.Faction
vTrunk.InsertQuickly(entry.start, entry.obj)
})
- continent.tasks ! RegisterVehicleFromSpawnPad(vehicle, pad)
+ continent.tasks ! RegisterVehicleFromSpawnPad(vehicle, pad, term)
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
+ case _ =>
+ log.error(
+ s"${tplayer.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it"
+ )
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
}
- case None =>
- log.error(
- s"${tplayer.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it"
- )
}
lastTerminalOrderFulfillment = true
@@ -2793,7 +2811,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
ObjectDetachMessage(
pad_guid,
vehicle_guid,
- pad_position + Vector3(0, 0, pad.VehicleCreationZOffset),
+ pad_position + Vector3.z(pad.VehicleCreationZOffset),
pad_orientation_z + pad.VehicleCreationZOrientOffset
)
)
@@ -2958,6 +2976,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, pad) =>
val vehicle_guid = vehicle.GUID
PlayerActionsToCancel()
+ serverVehicleControlVelocity = Some(0)
CancelAllProximityUnits()
if (player.VisibleSlots.contains(player.DrawnSlot)) {
player.DrawnSlot = Player.HandsDownSlot
@@ -2988,16 +3007,23 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case VehicleResponse.ServerVehicleOverrideEnd(vehicle, pad) =>
DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2)
- case VehicleResponse.PeriodicReminder(cause, data) =>
- val msg: String = cause match {
- case VehicleSpawnPad.Reminders.Blocked =>
- s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}"
- case VehicleSpawnPad.Reminders.Queue =>
- s"Your position in the vehicle spawn queue is ${data.getOrElse("dead last")}."
- case VehicleSpawnPad.Reminders.Cancelled =>
- "Your vehicle order has been cancelled."
+ case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) =>
+ sendResponse(ChatMsg(
+ ChatMessageType.CMT_OPEN,
+ true,
+ "",
+ s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}",
+ None
+ ))
+
+ case VehicleResponse.PeriodicReminder(_, data) =>
+ val (isType, flag, msg): (ChatMessageType, Boolean, String) = data match {
+ case Some(msg: String)
+ if msg.startsWith("@") => (ChatMessageType.UNK_227, false, msg)
+ case Some(msg: String) => (ChatMessageType.CMT_OPEN, true, msg)
+ case _ => (ChatMessageType.CMT_OPEN, true, "Your vehicle order has been cancelled.")
}
- sendResponse(ChatMsg(ChatMessageType.CMT_OPEN, true, "", msg, None))
+ sendResponse(ChatMsg(isType, flag, "", msg, None))
case VehicleResponse.ChangeLoadout(target, old_weapons, added_weapons, old_inventory, new_inventory) =>
//TODO when vehicle weapons can be changed without visual glitches, rewrite this
@@ -4869,6 +4895,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
case _ => log.warn("Item in specialItemSlotGuid is not registered with continent or is not a LLU")
}
+ case _ => ;
}
case Some(obj: FacilityTurret) =>
@@ -5101,6 +5128,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (specialItemSlotGuid.isEmpty) {
if (obj.Faction == player.Faction) {
specialItemSlotGuid = Some(obj.GUID)
+ player.Carrying = SpecialCarry.CaptureFlag
continent.LocalEvents ! CaptureFlagManager.PickupFlag(obj, player)
} else {
log.warn(
@@ -5504,11 +5532,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
projectile.profile.JammerProjectile ||
projectile.profile.SympatheticExplosion
) {
- Zone.causeSpecialEmp(
+ //can also substitute 'projectile.profile' for 'SpecialEmp.emp'
+ Zone.serverSideDamage(
continent,
player,
- explosion_pos,
- GlobalDefinitions.special_emp.innateDamage.get
+ SpecialEmp.emp,
+ SpecialEmp.createEmpInteraction(SpecialEmp.emp, explosion_pos),
+ SpecialEmp.prepareDistanceCheck(player, explosion_pos, player.Faction),
+ SpecialEmp.findAllBoomers
)
}
if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
@@ -6033,12 +6064,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* @see `RegisterVehicle`
* @return a `TaskResolver.GiveTask` message
*/
- def RegisterVehicleFromSpawnPad(obj: Vehicle, pad: VehicleSpawnPad): TaskResolver.GiveTask = {
+ def RegisterVehicleFromSpawnPad(obj: Vehicle, pad: VehicleSpawnPad, terminal: Terminal): TaskResolver.GiveTask = {
TaskResolver.GiveTask(
new Task() {
- private val localVehicle = obj
- private val localPad = pad.Actor
- private val localPlayer = player
+ private val localVehicle = obj
+ private val localPad = pad.Actor
+ private val localTerminal = terminal
+ private val localPlayer = player
override def Description: String = s"register a ${localVehicle.Definition.Name} for spawn pad"
@@ -6051,7 +6083,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
def Execute(resolver: ActorRef): Unit = {
- localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle)
+ localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle, localTerminal)
resolver ! Success(this)
}
},
@@ -7027,10 +7059,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
progressBarUpdate.cancel()
progressBarValue = None
lastTerminalOrderFulfillment = true
- serverVehicleControlVelocity = None
accessedContainer match {
case Some(v: Vehicle) =>
val vguid = v.GUID
+ ConditionalDriverVehicleControl(v)
if (v.AccessingTrunk.contains(player.GUID)) {
if (player.VehicleSeated.contains(vguid)) {
v.AccessingTrunk = None //player is seated; just stop accessing trunk
@@ -7749,7 +7781,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* Set the vehicle to move in reverse
*/
def ServerVehicleLockReverse(): Unit = {
- serverVehicleControlVelocity = Some(0)
+ serverVehicleControlVelocity = Some(-1)
sendResponse(
ServerVehicleOverrideMsg(
lock_accelerator = true,
@@ -7770,7 +7802,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* Set the vehicle to strafe right
*/
def ServerVehicleLockStrafeRight(): Unit = {
- serverVehicleControlVelocity = Some(0)
+ serverVehicleControlVelocity = Some(-1)
sendResponse(
ServerVehicleOverrideMsg(
lock_accelerator = true,
@@ -7791,7 +7823,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* Set the vehicle to strafe left
*/
def ServerVehicleLockStrafeLeft(): Unit = {
- serverVehicleControlVelocity = Some(0)
+ serverVehicleControlVelocity = Some(-1)
sendResponse(
ServerVehicleOverrideMsg(
lock_accelerator = true,
@@ -7812,7 +7844,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* @param vehicle the vehicle being controlled
*/
def ServerVehicleLock(vehicle: Vehicle): Unit = {
- serverVehicleControlVelocity = Some(0)
+ serverVehicleControlVelocity = Some(-1)
sendResponse(ServerVehicleOverrideMsg(true, true, false, false, 0, 1, 0, Some(0)))
}
@@ -7847,13 +7879,17 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* Stop all movement entirely.
* @param vehicle the vehicle
*/
- def TotalDriverVehicleControl(vehicle: Vehicle): Unit = {
- if (serverVehicleControlVelocity.nonEmpty) {
- serverVehicleControlVelocity = None
- sendResponse(ServerVehicleOverrideMsg(false, false, false, false, 0, 0, 0, None))
+ def ConditionalDriverVehicleControl(vehicle: Vehicle): Unit = {
+ if (serverVehicleControlVelocity.nonEmpty && !serverVehicleControlVelocity.contains(0)) {
+ TotalDriverVehicleControl(vehicle)
}
}
+ def TotalDriverVehicleControl(vehicle: Vehicle): Unit = {
+ serverVehicleControlVelocity = None
+ sendResponse(ServerVehicleOverrideMsg(false, false, false, false, 0, 0, 0, None))
+ }
+
/**
* Given a globally unique identifier in the 40100 to 40124 range
* (with an optional 25 as buffer),
@@ -8883,6 +8919,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (player.avatar.vehicle.nonEmpty && player.VehicleSeated != player.avatar.vehicle) {
continent.GUID(player.avatar.vehicle) match {
case Some(vehicle: Vehicle) if vehicle.Actor != Default.Actor =>
+ TotalDriverVehicleControl(vehicle)
vehicle.Actor ! Vehicle.Ownership(None)
case _ => ;
}
diff --git a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
index fc3c00a35..381ffd5fc 100644
--- a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
+++ b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
@@ -170,7 +170,12 @@ object ExplosiveDeployableControl {
val zone = target.Zone
zone.Activity ! Zone.HotSpot.Activity(cause)
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.Detonate(target.GUID, target))
- Zone.causeExplosion(zone, target, Some(cause), ExplosiveDeployableControl.detectionForExplosiveSource(target))
+ Zone.serverSideDamage(
+ zone,
+ target,
+ Zone.explosionDamage(Some(cause)),
+ ExplosiveDeployableControl.detectionForExplosiveSource(target)
+ )
}
/**
diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index 0899ba585..aa1885c49 100644
--- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -20,12 +20,7 @@ import net.psforever.objects.serverobject.painbox.PainboxDefinition
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
-import net.psforever.objects.serverobject.structures.{
- AmenityDefinition,
- AutoRepairStats,
- BuildingDefinition,
- WarpGateDefinition
-}
+import net.psforever.objects.serverobject.structures.{AmenityDefinition, AutoRepairStats, BuildingDefinition, WarpGateDefinition}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalDefinition
import net.psforever.objects.serverobject.terminals.implant.{ImplantTerminalDefinition, ImplantTerminalMechDefinition}
import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade}
@@ -982,8 +977,6 @@ object GlobalDefinitions {
val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
- val special_emp = ExplosiveDeployableDefinition(DeployedItem.jammer_mine)
-
//this is only treated like a deployable
val internal_router_telepad_deployable = InternalTelepadDefinition() //objectId: 744
init_deployables()
@@ -6788,9 +6781,7 @@ object GlobalDefinitions {
dropship.MaxShields = 1000
dropship.CanFly = true
dropship.Seats += 0 -> new SeatDefinition()
- dropship.Seats += 1 -> new SeatDefinition() {
- bailable = true
- }
+ dropship.Seats += 1 -> bailableSeat
dropship.Seats += 2 -> bailableSeat
dropship.Seats += 3 -> bailableSeat
dropship.Seats += 4 -> bailableSeat
@@ -7039,11 +7030,10 @@ object GlobalDefinitions {
* Initialize `Deployable` globals.
*/
private def init_deployables(): Unit = {
- val mine = GeometryForm.representByCylinder(radius = 0.1914f, height = 0.0957f) _
+ val mine = GeometryForm.representByCylinder(radius = 0.1914f, height = 0.0957f) _
val smallTurret = GeometryForm.representByCylinder(radius = 0.48435f, height = 1.23438f) _
- val sensor = GeometryForm.representByCylinder(radius = 0.1914f, height = 1.21875f) _
+ val sensor = GeometryForm.representByCylinder(radius = 0.1914f, height = 1.21875f) _
val largeTurret = GeometryForm.representByCylinder(radius = 0.8437f, height = 2.29687f) _
-
boomer.Name = "boomer"
boomer.Descriptor = "Boomers"
boomer.MaxHealth = 100
@@ -7066,7 +7056,6 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
boomer.Geometry = mine
-
he_mine.Name = "he_mine"
he_mine.Descriptor = "Mines"
he_mine.MaxHealth = 100
@@ -7088,7 +7077,6 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
he_mine.Geometry = mine
-
jammer_mine.Name = "jammer_mine"
jammer_mine.Descriptor = "JammerMines"
jammer_mine.MaxHealth = 100
@@ -7098,14 +7086,13 @@ object GlobalDefinitions {
jammer_mine.DeployTime = Duration.create(1000, "ms")
jammer_mine.DetonateOnJamming = false
jammer_mine.Geometry = mine
-
spitfire_turret.Name = "spitfire_turret"
spitfire_turret.Descriptor = "Spitfires"
spitfire_turret.MaxHealth = 100
spitfire_turret.Damageable = true
spitfire_turret.Repairable = true
spitfire_turret.RepairIfDestroyed = false
- spitfire_turret.WeaponPaths += 1 -> new mutable.HashMap()
+ spitfire_turret.WeaponPaths += 1 -> new mutable.HashMap()
spitfire_turret.WeaponPaths(1) += TurretUpgrade.None -> spitfire_weapon
spitfire_turret.ReserveAmmunition = false
spitfire_turret.DeployCategory = DeployableCategory.SmallTurrets
@@ -7122,14 +7109,13 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
spitfire_turret.Geometry = smallTurret
-
spitfire_cloaked.Name = "spitfire_cloaked"
spitfire_cloaked.Descriptor = "CloakingSpitfires"
spitfire_cloaked.MaxHealth = 100
spitfire_cloaked.Damageable = true
spitfire_cloaked.Repairable = true
spitfire_cloaked.RepairIfDestroyed = false
- spitfire_cloaked.WeaponPaths += 1 -> new mutable.HashMap()
+ spitfire_cloaked.WeaponPaths += 1 -> new mutable.HashMap()
spitfire_cloaked.WeaponPaths(1) += TurretUpgrade.None -> spitfire_weapon
spitfire_cloaked.ReserveAmmunition = false
spitfire_cloaked.DeployCategory = DeployableCategory.SmallTurrets
@@ -7145,14 +7131,13 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
spitfire_cloaked.Geometry = smallTurret
-
spitfire_aa.Name = "spitfire_aa"
spitfire_aa.Descriptor = "FlakSpitfires"
spitfire_aa.MaxHealth = 100
spitfire_aa.Damageable = true
spitfire_aa.Repairable = true
spitfire_aa.RepairIfDestroyed = false
- spitfire_aa.WeaponPaths += 1 -> new mutable.HashMap()
+ spitfire_aa.WeaponPaths += 1 -> new mutable.HashMap()
spitfire_aa.WeaponPaths(1) += TurretUpgrade.None -> spitfire_aa_weapon
spitfire_aa.ReserveAmmunition = false
spitfire_aa.DeployCategory = DeployableCategory.SmallTurrets
@@ -7168,7 +7153,6 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
spitfire_aa.Geometry = smallTurret
-
motionalarmsensor.Name = "motionalarmsensor"
motionalarmsensor.Descriptor = "MotionSensors"
motionalarmsensor.MaxHealth = 100
@@ -7177,7 +7161,6 @@ object GlobalDefinitions {
motionalarmsensor.RepairIfDestroyed = false
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
motionalarmsensor.Geometry = sensor
-
sensor_shield.Name = "sensor_shield"
sensor_shield.Descriptor = "SensorShields"
sensor_shield.MaxHealth = 100
@@ -7186,7 +7169,6 @@ object GlobalDefinitions {
sensor_shield.RepairIfDestroyed = false
sensor_shield.DeployTime = Duration.create(5000, "ms")
sensor_shield.Geometry = sensor
-
tank_traps.Name = "tank_traps"
tank_traps.Descriptor = "TankTraps"
tank_traps.MaxHealth = 5000
@@ -7205,7 +7187,6 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
tank_traps.Geometry = GeometryForm.representByCylinder(radius = 2.89680997f, height = 3.57812f)
-
val fieldTurretConverter = new FieldTurretConverter
portable_manned_turret.Name = "portable_manned_turret"
portable_manned_turret.Descriptor = "FieldTurrets"
@@ -7213,11 +7194,11 @@ object GlobalDefinitions {
portable_manned_turret.Damageable = true
portable_manned_turret.Repairable = true
portable_manned_turret.RepairIfDestroyed = false
- portable_manned_turret.controlledWeapons += 0 -> 1
- portable_manned_turret.WeaponPaths += 1 -> new mutable.HashMap()
+ portable_manned_turret.controlledWeapons += 0 -> 1
+ portable_manned_turret.WeaponPaths += 1 -> new mutable.HashMap()
portable_manned_turret.WeaponPaths(1) += TurretUpgrade.None -> energy_gun
- portable_manned_turret.MountPoints += 1 -> MountInfo(0)
- portable_manned_turret.MountPoints += 2 -> MountInfo(0)
+ portable_manned_turret.MountPoints += 1 -> MountInfo(0)
+ portable_manned_turret.MountPoints += 2 -> MountInfo(0)
portable_manned_turret.ReserveAmmunition = true
portable_manned_turret.FactionLocked = true
portable_manned_turret.Packet = fieldTurretConverter
@@ -7234,18 +7215,17 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret.Geometry = largeTurret
-
portable_manned_turret_nc.Name = "portable_manned_turret_nc"
portable_manned_turret_nc.Descriptor = "FieldTurrets"
portable_manned_turret_nc.MaxHealth = 1000
portable_manned_turret_nc.Damageable = true
portable_manned_turret_nc.Repairable = true
portable_manned_turret_nc.RepairIfDestroyed = false
- portable_manned_turret_nc.WeaponPaths += 1 -> new mutable.HashMap()
+ portable_manned_turret_nc.WeaponPaths += 1 -> new mutable.HashMap()
portable_manned_turret_nc.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_nc
- portable_manned_turret_nc.controlledWeapons += 0 -> 1
- portable_manned_turret_nc.MountPoints += 1 -> MountInfo(0)
- portable_manned_turret_nc.MountPoints += 2 -> MountInfo(0)
+ portable_manned_turret_nc.controlledWeapons += 0 -> 1
+ portable_manned_turret_nc.MountPoints += 1 -> MountInfo(0)
+ portable_manned_turret_nc.MountPoints += 2 -> MountInfo(0)
portable_manned_turret_nc.ReserveAmmunition = true
portable_manned_turret_nc.FactionLocked = true
portable_manned_turret_nc.Packet = fieldTurretConverter
@@ -7262,18 +7242,17 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret_nc.Geometry = largeTurret
-
portable_manned_turret_tr.Name = "portable_manned_turret_tr"
portable_manned_turret_tr.Descriptor = "FieldTurrets"
portable_manned_turret_tr.MaxHealth = 1000
portable_manned_turret_tr.Damageable = true
portable_manned_turret_tr.Repairable = true
portable_manned_turret_tr.RepairIfDestroyed = false
- portable_manned_turret_tr.WeaponPaths += 1 -> new mutable.HashMap()
+ portable_manned_turret_tr.WeaponPaths += 1 -> new mutable.HashMap()
portable_manned_turret_tr.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_tr
- portable_manned_turret_tr.controlledWeapons += 0 -> 1
- portable_manned_turret_tr.MountPoints += 1 -> MountInfo(0)
- portable_manned_turret_tr.MountPoints += 2 -> MountInfo(0)
+ portable_manned_turret_tr.controlledWeapons += 0 -> 1
+ portable_manned_turret_tr.MountPoints += 1 -> MountInfo(0)
+ portable_manned_turret_tr.MountPoints += 2 -> MountInfo(0)
portable_manned_turret_tr.ReserveAmmunition = true
portable_manned_turret_tr.FactionLocked = true
portable_manned_turret_tr.Packet = fieldTurretConverter
@@ -7290,18 +7269,17 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret_tr.Geometry = largeTurret
-
portable_manned_turret_vs.Name = "portable_manned_turret_vs"
portable_manned_turret_vs.Descriptor = "FieldTurrets"
portable_manned_turret_vs.MaxHealth = 1000
portable_manned_turret_vs.Damageable = true
portable_manned_turret_vs.Repairable = true
portable_manned_turret_vs.RepairIfDestroyed = false
- portable_manned_turret_vs.WeaponPaths += 1 -> new mutable.HashMap()
+ portable_manned_turret_vs.WeaponPaths += 1 -> new mutable.HashMap()
portable_manned_turret_vs.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_vs
- portable_manned_turret_vs.controlledWeapons += 0 -> 1
- portable_manned_turret_vs.MountPoints += 1 -> MountInfo(0)
- portable_manned_turret_vs.MountPoints += 2 -> MountInfo(0)
+ portable_manned_turret_vs.controlledWeapons += 0 -> 1
+ portable_manned_turret_vs.MountPoints += 1 -> MountInfo(0)
+ portable_manned_turret_vs.MountPoints += 2 -> MountInfo(0)
portable_manned_turret_vs.ReserveAmmunition = true
portable_manned_turret_vs.FactionLocked = true
portable_manned_turret_vs.Packet = fieldTurretConverter
@@ -7318,7 +7296,6 @@ object GlobalDefinitions {
Modifiers = ExplodingRadialDegrade
}
portable_manned_turret_vs.Geometry = largeTurret
-
deployable_shield_generator.Name = "deployable_shield_generator"
deployable_shield_generator.Descriptor = "ShieldGenerators"
deployable_shield_generator.MaxHealth = 1700
@@ -7328,7 +7305,6 @@ object GlobalDefinitions {
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
deployable_shield_generator.Model = ComplexDeployableResolutions.calculate
deployable_shield_generator.Geometry = GeometryForm.representByCylinder(radius = 0.6562f, height = 2.17188f)
-
router_telepad_deployable.Name = "router_telepad_deployable"
router_telepad_deployable.MaxHealth = 100
router_telepad_deployable.Damageable = true
@@ -7338,7 +7314,6 @@ object GlobalDefinitions {
router_telepad_deployable.Packet = new TelepadDeployableConverter
router_telepad_deployable.Model = SimpleResolutions.calculate
router_telepad_deployable.Geometry = GeometryForm.representByRaisedSphere(radius = 1.2344f)
-
internal_router_telepad_deployable.Name = "router_telepad_deployable"
internal_router_telepad_deployable.MaxHealth = 1
internal_router_telepad_deployable.Damageable = false
@@ -7346,22 +7321,6 @@ object GlobalDefinitions {
internal_router_telepad_deployable.DeployTime = Duration.create(1, "ms")
internal_router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
internal_router_telepad_deployable.Packet = new InternalTelepadDeployableConverter
-
- special_emp.Name = "emp"
- special_emp.MaxHealth = 1
- special_emp.Damageable = false
- special_emp.Repairable = false
- special_emp.DeployCategory = DeployableCategory.Mines
- special_emp.explodes = true
- special_emp.innateDamage = new DamageWithPosition {
- CausesDamageType = DamageType.Splash
- SympatheticExplosion = true
- Damage0 = 0
- DamageAtEdge = 1.0f
- DamageRadius = 5f
- AdditionalEffect = true
- Modifiers = MaxDistanceCutoff
- }
}
/**
@@ -7713,14 +7672,54 @@ object GlobalDefinitions {
mb_pad_creation.Name = "mb_pad_creation"
mb_pad_creation.Damageable = false
mb_pad_creation.Repairable = false
+ mb_pad_creation.killBox = VehicleSpawnPadDefinition.prepareKillBox(
+ forwardLimit = 14,
+ backLimit = 10,
+ sideLimit = 7.5f,
+ aboveLimit = 5 //double to 10 when spawning a flying vehicle
+ )
+ mb_pad_creation.innateDamage = new DamageWithPosition {
+ CausesDamageType = DamageType.One
+ Damage0 = 99999
+ DamageRadiusMin = 14
+ DamageRadius = 14.5f
+ DamageAtEdge = 0.00002f
+ //damage is 99999 at 14m, dropping rapidly to ~1 at 14.5m
+ }
dropship_pad_doors.Name = "dropship_pad_doors"
dropship_pad_doors.Damageable = false
dropship_pad_doors.Repairable = false
+ dropship_pad_doors.killBox = VehicleSpawnPadDefinition.prepareKillBox(
+ forwardLimit = 14,
+ backLimit = 14,
+ sideLimit = 13.5f,
+ aboveLimit = 5 //doubles to 10
+ )
+ dropship_pad_doors.innateDamage = new DamageWithPosition {
+ CausesDamageType = DamageType.One
+ Damage0 = 99999
+ DamageRadiusMin = 14
+ DamageRadius = 14.5f
+ DamageAtEdge = 0.00002f
+ //damage is 99999 at 14m, dropping rapidly to ~1 at 14.5m
+ }
vanu_vehicle_creation_pad.Name = "vanu_vehicle_creation_pad"
vanu_vehicle_creation_pad.Damageable = false
vanu_vehicle_creation_pad.Repairable = false
+ vanu_vehicle_creation_pad.killBox = VehicleSpawnPadDefinition.prepareVanuKillBox(
+ radius = 8.5f,
+ aboveLimit = 5
+ )
+ vanu_vehicle_creation_pad.innateDamage = new DamageWithPosition {
+ CausesDamageType = DamageType.One
+ Damage0 = 99999
+ DamageRadiusMin = 14
+ DamageRadius = 14.5f
+ DamageAtEdge = 0.00002f
+ //damage is 99999 at 14m, dropping rapidly to ~1 at 14.5m
+ }
mb_locker.Name = "mb_locker"
mb_locker.Damageable = false
diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala
index 4f1f8b33b..3f4a71f53 100644
--- a/src/main/scala/net/psforever/objects/Player.scala
+++ b/src/main/scala/net/psforever/objects/Player.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
-import net.psforever.objects.avatar.{Avatar, LoadoutManager}
+import net.psforever.objects.avatar.{Avatar, LoadoutManager, SpecialCarry}
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
@@ -13,7 +13,7 @@ import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.resolution.DamageResistanceModel
-import net.psforever.objects.zones.ZoneAware
+import net.psforever.objects.zones.{ZoneAware, Zoning}
import net.psforever.types.{PlanetSideGUID, _}
import scala.annotation.tailrec
@@ -30,6 +30,7 @@ class Player(var avatar: Avatar)
with ZoneAware
with AuraContainer {
private var backpack: Boolean = false
+ private var released: Boolean = false
private var armor: Int = 0
private var capacitor: Float = 0f
@@ -44,12 +45,14 @@ class Player(var avatar: Avatar)
private var drawnSlot: Int = Player.HandsDownSlot
private var lastDrawnSlot: Int = Player.HandsDownSlot
private var backpackAccess: Option[PlanetSideGUID] = None
+ private var carrying: Option[SpecialCarry] = None
- private var facingYawUpper: Float = 0f
- private var crouching: Boolean = false
- private var jumping: Boolean = false
- private var cloaked: Boolean = false
- private var afk: Boolean = false
+ private var facingYawUpper: Float = 0f
+ private var crouching: Boolean = false
+ private var jumping: Boolean = false
+ private var cloaked: Boolean = false
+ private var afk: Boolean = false
+ private var zoning: Zoning.Method.Value = Zoning.Method.None
private var vehicleSeated: Option[PlanetSideGUID] = None
@@ -95,6 +98,7 @@ class Player(var avatar: Avatar)
Health = Definition.DefaultHealth
Armor = MaxArmor
Capacitor = 0
+ released = false
}
isAlive
}
@@ -108,18 +112,18 @@ class Player(var avatar: Avatar)
def Revive: Boolean = {
Destroyed = false
Health = Definition.DefaultHealth
+ released = false
true
}
def Release: Boolean = {
- if (!isAlive) {
- backpack = true
- true
- } else {
- false
- }
+ released = true
+ backpack = !isAlive
+ true
}
+ def isReleased: Boolean = released
+
def Armor: Int = armor
def Armor_=(assignArmor: Int): Int = {
@@ -498,6 +502,23 @@ class Player(var avatar: Avatar)
VehicleSeated
}
+ def Carrying: Option[SpecialCarry] = carrying
+
+ def Carrying_=(item: SpecialCarry): Option[SpecialCarry] = {
+ Carrying
+ }
+
+ def Carrying_=(item: Option[SpecialCarry]): Option[SpecialCarry] = {
+ Carrying
+ }
+
+ def ZoningRequest: Zoning.Method.Value = zoning
+
+ def ZoningRequest_=(request: Zoning.Method.Value): Zoning.Method.Value = {
+ zoning = request
+ ZoningRequest
+ }
+
def DamageModel = exosuit.asInstanceOf[DamageResistanceModel]
def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
diff --git a/src/main/scala/net/psforever/objects/SpecialEmp.scala b/src/main/scala/net/psforever/objects/SpecialEmp.scala
new file mode 100644
index 000000000..c0b8a1a24
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/SpecialEmp.scala
@@ -0,0 +1,144 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects
+
+import net.psforever.objects.ballistics.SourceEntry
+import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.affinity.FactionAffinity
+import net.psforever.objects.vital.{Vitality, VitalityDefinition}
+import net.psforever.objects.vital.base.DamageType
+import net.psforever.objects.vital.etc.EmpReason
+import net.psforever.objects.vital.interaction.DamageInteraction
+import net.psforever.objects.vital.projectile.MaxDistanceCutoff
+import net.psforever.objects.vital.prop.DamageWithPosition
+import net.psforever.objects.zones.Zone
+import net.psforever.types.{PlanetSideEmpire, Vector3}
+
+/**
+ * Information and functions useful for the construction of a server-side electromagnetic pulse
+ * (not intigated by any specific thing the client does).
+ */
+object SpecialEmp {
+ /** A defaulted emp definition.
+ * Any projectile definition can be used. */
+ final val emp = new DamageWithPosition {
+ CausesDamageType = DamageType.Splash
+ SympatheticExplosion = true
+ Damage0 = 0
+ DamageAtEdge = 1.0f
+ DamageRadius = 5f
+ AdditionalEffect = true
+ Modifiers = MaxDistanceCutoff
+ }
+
+ /** The definition for a proxy object that represents a physical component of the source of the electromagnetic pulse. */
+ private val proxy_definition = new ObjectDefinition(objectId = 420) with VitalityDefinition {
+ Name = "emp"
+ MaxHealth = 1
+ Damageable = false
+ Repairable = false
+ explodes = true
+ innateDamage = emp
+ }
+
+ /**
+ * The damage interaction for an electromagnetic pulse effect.
+ * @param empEffect information about the effect
+ * @param position where the effect occurs
+ * @param source a game object that represents the source of the EMP
+ * @param target a game object that is affected by the EMP
+ * @return a `DamageInteraction` object
+ */
+ def createEmpInteraction(
+ empEffect: DamageWithPosition,
+ position: Vector3
+ )
+ (
+ source: PlanetSideGameObject with FactionAffinity with Vitality,
+ target: PlanetSideGameObject with FactionAffinity with Vitality
+ ): DamageInteraction = {
+ DamageInteraction(
+ SourceEntry(target),
+ EmpReason(source, empEffect, target),
+ position
+ )
+ }
+
+ /**
+ * The "within affected distance" test for special electromagnetic pulses
+ * is not necessarily centered around a game object as the source of that EMP.
+ * A proxy entity is generated to perform the measurements and
+ * is given token information that connects it with another object for the proper attribution.
+ * @see `OwnableByPlayer`
+ * @see `PlanetSideServerObject`
+ * @see `SpecialEmp.distanceCheck`
+ * @param owner the formal entity to which the EMP is attributed
+ * @param position the coordinate location of the EMP
+ * @param faction the affinity of the EMP
+ * @return a function that determines if two game entities are near enough to each other
+ */
+ def prepareDistanceCheck(
+ owner: PlanetSideGameObject,
+ position: Vector3,
+ faction: PlanetSideEmpire.Value
+ ): (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = {
+ distanceCheck(new PlanetSideServerObject with OwnableByPlayer {
+ Owner = Some(owner.GUID)
+ OwnerName = owner match {
+ case p: Player => p.Name
+ case o: OwnableByPlayer => o.OwnerName.getOrElse("")
+ case _ => ""
+ }
+ Position = position
+ def Faction = faction
+ def Definition = proxy_definition
+ })
+ }
+
+ /**
+ * The "within affected distance" test for special electromagnetic pulses
+ * is not necessarily centered around a game object as the source of that EMP.
+ * A proxy entity is provided to perform the measurements and
+ * is given token information that connects it with another object for the proper attribution.
+ * @see `Zone.distanceCheck`
+ * @param obj1 a game entity, should be the source of the damage
+ * @param obj2 a game entity, should be the target of the damage
+ * @param maxDistance the square of the maximum distance permissible between game entities
+ * before they are no longer considered "near"
+ * @return `true`, if the two entities are near enough to each other;
+ * `false`, otherwise
+ */
+ def distanceCheck(
+ proxy: PlanetSideGameObject
+ )
+ (
+ obj1: PlanetSideGameObject,
+ obj2: PlanetSideGameObject,
+ maxDistance: Float
+ ): Boolean = {
+ Zone.distanceCheck(proxy, obj2, maxDistance)
+ }
+
+ /**
+ * A sort of `SpecialEmp` that only affects deployed boomer explosives
+ * must find affected deployed boomer explosive entities.
+ * @see `BoomerDeployable`
+ * @param zone the zone in which to search
+ * @param obj a game entity that is excluded from results
+ * @param properties information about the effect/damage
+ * @return two lists of objects with different characteristics;
+ * the first list is `PlanetSideServerObject` entities with `Vitality`;
+ * since only boomer explosives are returned, this second list can be ignored
+ */
+ def findAllBoomers(
+ zone: Zone,
+ obj: PlanetSideGameObject with FactionAffinity with Vitality,
+ properties: DamageWithPosition
+ ): (List[PlanetSideServerObject with Vitality], List[PlanetSideGameObject with FactionAffinity with Vitality]) = {
+ (
+ zone.DeployableList
+ .collect { case o: BoomerDeployable if !o.Destroyed && (o ne obj) => o },
+ Nil
+ )
+ }
+}
\ No newline at end of file
diff --git a/src/main/scala/net/psforever/objects/TrapDeployable.scala b/src/main/scala/net/psforever/objects/TrapDeployable.scala
index 4dc3a591f..6a9ec3880 100644
--- a/src/main/scala/net/psforever/objects/TrapDeployable.scala
+++ b/src/main/scala/net/psforever/objects/TrapDeployable.scala
@@ -47,6 +47,6 @@ class TrapDeployableControl(trap: TrapDeployable) extends Actor with DamageableE
override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = {
super.DestructionAwareness(target, cause)
Deployables.AnnounceDestroyDeployable(trap, None)
- Zone.causeExplosion(target.Zone, target, Some(cause))
+ Zone.serverSideDamage(target.Zone, target, Zone.explosionDamage(Some(cause)))
}
}
diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
index f7107c22d..2380f9a26 100644
--- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
+++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
@@ -711,6 +711,9 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
//uninitialize implants
avatarActor ! AvatarActor.DeinitializeImplants()
+ //log historical event
+ target.History(cause)
+ //log message
cause.adversarial match {
case Some(a) =>
damageLog.info(s"DisplayDestroy: ${a.defender} was killed by ${a.attacker}")
diff --git a/src/main/scala/net/psforever/objects/avatar/SpecialCarry.scala b/src/main/scala/net/psforever/objects/avatar/SpecialCarry.scala
new file mode 100644
index 000000000..c4526a004
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/avatar/SpecialCarry.scala
@@ -0,0 +1,22 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.avatar
+
+import enumeratum.values.{StringEnum, StringEnumEntry}
+
+sealed abstract class SpecialCarry(override val value: String) extends StringEnumEntry
+
+/**
+ * Things that the player can carry that are not stored in the inventory or in holsters.
+ */
+object SpecialCarry extends StringEnum[SpecialCarry] {
+ val values = findValues
+
+ /** The lattice logic unit (LLU). Not actually a flag. */
+ case object CaptureFlag extends SpecialCarry(value = "CaptureFlag")
+ /** Special enhancement modules generated in cavern facilities to be installed into above ground facilities. */
+ case object VanuModule extends SpecialCarry(value = "VanuModule")
+ /** Mysterious MacGuffins tied to the Bending. */
+ case object MonolithUnit extends SpecialCarry(value = "MonolithUnit")
+ /** Pyon~~ */
+ case object RabbitBall extends SpecialCarry(value = "RabbitBall")
+}
diff --git a/src/main/scala/net/psforever/objects/geometry/PrimitiveShape.scala b/src/main/scala/net/psforever/objects/geometry/PrimitiveShape.scala
index 738e45964..7d7c72f5d 100644
--- a/src/main/scala/net/psforever/objects/geometry/PrimitiveShape.scala
+++ b/src/main/scala/net/psforever/objects/geometry/PrimitiveShape.scala
@@ -431,3 +431,73 @@ object Cylinder {
*/
def apply(p: Vector3, v: Vector3, radius: Float, height: Float): Cylinder = Cylinder(Point3D(p), v, radius, height)
}
+
+/**
+ * Untested geometry.
+ * @param p na
+ * @param relativeForward na
+ * @param relativeUp na
+ * @param length na
+ * @param width na
+ * @param height na
+ */
+final case class Cuboid(
+ p: Point3D,
+ relativeForward: Vector3,
+ relativeUp: Vector3,
+ length: Float,
+ width: Float,
+ height: Float,
+ ) extends Geometry3D {
+ def center: Point3D = Point3D(p.asVector3 + relativeUp * height * 0.5f)
+
+ override def pointOnOutside(v: Vector3): Point3D = {
+ import net.psforever.types.Vector3.{CrossProduct, DotProduct, neg}
+ val height2 = height * 0.5f
+ val relativeSide = CrossProduct(relativeForward, relativeUp)
+ //val forwardVector = relativeForward * length
+ //val sideVector = relativeSide * width
+ //val upVector = relativeUp * height2
+ val closestVector: Vector3 = Seq(
+ relativeForward, relativeSide, relativeUp,
+ neg(relativeForward), neg(relativeSide), neg(relativeUp)
+ ).maxBy { dir => DotProduct(dir, v) }
+ def dz(): Float = {
+ if (Geometry.closeToInsignificance(v.z) != 0) {
+ closestVector.z / v.z
+ } else {
+ 0f
+ }
+ }
+ def dy(): Float = {
+ if (Geometry.closeToInsignificance(v.y) != 0) {
+ val fyfactor = closestVector.y / v.y
+ if (v.z * fyfactor <= height2) {
+ fyfactor
+ } else {
+ dz()
+ }
+ } else {
+ dz()
+ }
+ }
+
+ val scaleFactor: Float = {
+ if (Geometry.closeToInsignificance(v.x) != 0) {
+ val fxfactor = closestVector.x / v.x
+ if (v.y * fxfactor <= length) {
+ if (v.z * fxfactor <= height2) {
+ fxfactor
+ } else {
+ dy()
+ }
+ } else {
+ dy()
+ }
+ } else {
+ dy()
+ }
+ }
+ Point3D(center.asVector3 + (v * scaleFactor))
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
index 455fa0267..ebbeb7918 100644
--- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
@@ -205,7 +205,7 @@ trait DamageableVehicle
}
})
//things positioned around us can get hurt from us
- Zone.causeExplosion(obj.Zone, obj, Some(cause))
+ Zone.serverSideDamage(obj.Zone, target, Zone.explosionDamage(Some(cause)))
//special considerations for certain vehicles
Vehicles.BeforeUnloadVehicle(obj, zone)
//shields
diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala
index 6ba61f483..1abd1608f 100644
--- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala
@@ -93,7 +93,7 @@ trait DamageableWeaponTurret
EndAllAggravation()
DamageableWeaponTurret.DestructionAwareness(obj, cause)
DamageableMountable.DestructionAwareness(obj, cause)
- Zone.causeExplosion(target.Zone, target, Some(cause))
+ Zone.serverSideDamage(target.Zone, target, Zone.explosionDamage(Some(cause)))
}
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala
index 60286181d..677474606 100644
--- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala
@@ -122,7 +122,7 @@ class GeneratorControl(gen: Generator)
queuedExplosion = Default.Cancellable
imminentExplosion = false
//hate on everything nearby
- Zone.causeExplosion(gen.Zone, gen, gen.LastDamage, explosionFunc)
+ Zone.serverSideDamage(gen.Zone, gen, Zone.explosionDamage(gen.LastDamage), explosionFunc)
gen.ClearHistory()
case GeneratorControl.Restored() =>
@@ -338,8 +338,8 @@ object GeneratorControl {
* As a consequence, different measurements must be performed to determine that the target is "within" and
* that the target is not "outside" of the detection radius of the room.
* Magic numbers for the room dimensions are employed.
- * @see `Zone.causeExplosion`
* @see `Zone.distanceCheck`
+ * @see `Zone.serverSideDamage`
* @param g1ctrXY the center of the generator on the xy-axis
* @param ufront a `Vector3` entity that points to the "front" direction of the generator;
* the `u` prefix indicates a "unit vector"
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 8d3dfdb9b..4cde293a3 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
@@ -1,12 +1,15 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad
-import akka.actor.{ActorContext, Cancellable, Props}
+import akka.actor.{Cancellable, Props}
+import net.psforever.objects.avatar.SpecialCarry
+import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.guid.GUIDTask.UnregisterVehicle
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer}
-import net.psforever.objects.zones.Zone
-import net.psforever.objects.{Default, Player, Vehicle}
+import net.psforever.objects.zones.{Zone, ZoneAware, Zoning}
+import net.psforever.objects.{Default, PlanetSideGameObject, Player, Vehicle}
+import net.psforever.types.Vector3
import scala.annotation.tailrec
import scala.concurrent.ExecutionContext.Implicits.global
@@ -37,8 +40,11 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
/** a reminder sent to future customers */
var periodicReminder: Cancellable = Default.Cancellable
+ /** repeatedly test whether queued orders are valid */
+ var queueManagement: Cancellable = Default.Cancellable
+
/** a list of vehicle orders that have been submitted for this spawn pad */
- var orders: List[VehicleSpawnControl.Order] = List.empty[VehicleSpawnControl.Order]
+ var orders: List[VehicleSpawnPad.VehicleOrder] = List.empty[VehicleSpawnPad.VehicleOrder]
/** the current vehicle order being acted upon;
* used as a guard condition to control order processing rate
@@ -46,7 +52,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
var trackedOrder: Option[VehicleSpawnControl.Order] = None
/** how to process either the first order or every subsequent order */
- var handleOrderFunc: VehicleSpawnControl.Order => Unit = NewTasking
+ var handleOrderFunc: VehicleSpawnPad.VehicleOrder => Unit = NewTasking
def LogId = ""
@@ -67,39 +73,60 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
}
}
+ override def postStop() : Unit = {
+ periodicReminder.cancel()
+ queueManagement.cancel()
+ }
+
def receive: Receive =
checkBehavior.orElse {
- case VehicleSpawnPad.VehicleOrder(player, vehicle) =>
+ case msg @ VehicleSpawnPad.VehicleOrder(player, vehicle, _) =>
trace(s"order from ${player.Name} for a ${vehicle.Definition.Name} received")
try {
- handleOrderFunc(VehicleSpawnControl.Order(player, vehicle))
+ handleOrderFunc(msg)
} catch {
case _: AssertionError => ; //ehhh
case e: Exception => //something unexpected
e.printStackTrace()
}
+ case VehicleSpawnControl.ProcessControl.OrderCancelled =>
+ trackedOrder match {
+ case Some(entry)
+ if sender() == concealPlayer =>
+ CancelOrder(
+ entry,
+ VehicleSpawnControl.validateOrderCredentials(pad, entry.driver, entry.vehicle)
+ .orElse(Some("@SVCP_RemovedFromVehicleQueue_Generic"))
+ )
+ case _ => ;
+ }
+ trackedOrder = None //guard off
+ SelectOrder()
+
case VehicleSpawnControl.ProcessControl.GetNewOrder =>
if (sender() == concealPlayer) {
trackedOrder = None //guard off
SelectOrder()
}
+ 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 all current customers in the order queue.
- */
+ 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 match {
case Some(entry) =>
if (periodicReminder.isCancelled) {
- trace(s"the pad has become blocked by ${entry.vehicle.Definition.Name}")
+ trace(s"the pad has become blocked by a ${entry.vehicle.Definition.Name} in its current order")
periodicReminder = context.system.scheduler.scheduleWithFixedDelay(
- VehicleSpawnControl.initialReminderDelay,
- VehicleSpawnControl.periodicReminderDelay,
+ VehicleSpawnControl.periodicReminderTestDelay,
+ VehicleSpawnControl.periodicReminderTestDelay,
self,
VehicleSpawnControl.ProcessControl.Reminder
)
@@ -112,19 +139,16 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
case VehicleSpawnControl.ProcessControl.Flush =>
periodicReminder.cancel()
- orders.foreach {
- CancelOrder
- }
+ orders.foreach { CancelOrder(_, Some("@SVCP_RemovedFromVehicleQueue_Generic")) }
orders = Nil
trackedOrder match {
- case Some(entry) =>
- CancelOrder(entry)
+ case Some(entry) => CancelOrder(entry, Some("@SVCP_RemovedFromVehicleQueue_Generic"))
case None => ;
}
trackedOrder = None
handleOrderFunc = NewTasking
pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad) //cautious animation reset
- concealPlayer ! akka.actor.Kill //should cause the actor to restart, which will abort any trapped messages
+ concealPlayer ! akka.actor.Kill //should cause the actor to restart, which will abort any trapped messages
case _ => ;
}
@@ -134,7 +158,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* All orders accepted in the meantime will be queued and a note about priority will be issued.
* @param order the order being accepted
*/
- def NewTasking(order: VehicleSpawnControl.Order): Unit = {
+ def NewTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
handleOrderFunc = QueuedTasking
ProcessOrder(Some(order))
}
@@ -144,23 +168,51 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* all orders accepted in the meantime will be queued and a note about priority will be issued.
* @param order the order being accepted
*/
- def QueuedTasking(order: VehicleSpawnControl.Order): Unit = {
- val name = order.driver.Name
- if (
- (trackedOrder match {
- case Some(tracked) => !tracked.driver.Name.equals(name)
- case None => true
- }) && orders.forall { !_.driver.Name.equals(name) }
- ) {
- //not a second order from an existing order's player
- orders = orders :+ order
- pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
- name,
- VehicleSpawnPad.Reminders.Queue,
- Some(orders.length + 1)
- )
- } else {
- VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
+ def QueuedTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
+ val name = order.player.Name
+ if (trackedOrder match {
+ case Some(tracked) =>
+ !tracked.driver.Name.equals(name)
+ case None =>
+ handleOrderFunc = NewTasking
+ NewTasking(order)
+ false
+ }) {
+ orders.indexWhere { _.player.Name.equals(name) } match {
+ case -1 if orders.isEmpty =>
+ //first queued order
+ orders = List(order)
+ queueManagementTask()
+ pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
+ name,
+ VehicleSpawnPad.Reminders.Queue,
+ Some(s"@SVCP_PositionInQueue^2~^2~")
+ )
+ case -1 =>
+ //new order
+ orders = orders :+ order
+ val size = orders.size + 1
+ pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
+ name,
+ VehicleSpawnPad.Reminders.Queue,
+ Some(s"@SVCP_PositionInQueue^$size~^$size~")
+ )
+ case n if orders(n).vehicle.Definition ne order.vehicle.Definition =>
+ //replace existing order with new order
+ val zone = pad.Zone
+ val originalOrder = orders(n)
+ val originalVehicle = originalOrder.vehicle.Definition.Name
+ orders = (orders.take(n) :+ order) ++ orders.drop(n+1)
+ VehicleSpawnControl.DisposeVehicle(originalOrder.vehicle, zone)
+ zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
+ name,
+ VehicleSpawnPad.Reminders.Queue,
+ Some(s"@SVCP_ReplacedVehicleWithVehicle^@$originalVehicle~^@${order.vehicle.Definition.Name}~")
+ )
+ case _ =>
+ //order is the duplicate of an existing order; do nothing to the queue
+ CancelOrder(order, None)
+ }
}
}
@@ -174,17 +226,19 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* If the queue has been exhausted, set functionality to prepare to accept the next order as a "first order."
* @return the next-available order
*/
- def SelectFirstOrder(): Option[VehicleSpawnControl.Order] = {
+ def SelectFirstOrder(): Option[VehicleSpawnPad.VehicleOrder] = {
trackedOrder match {
case None =>
- val (completeOrder, remainingOrders): (Option[VehicleSpawnControl.Order], List[VehicleSpawnControl.Order]) =
- orders match {
+ val (completeOrder, remainingOrders): (Option[VehicleSpawnPad.VehicleOrder], List[VehicleSpawnPad.VehicleOrder]) =
+ orderCredentialsCheck(orders) match {
case x :: Nil =>
+ queueManagement.cancel()
(Some(x), Nil)
case x :: b =>
(Some(x), b)
case Nil =>
handleOrderFunc = NewTasking
+ queueManagement.cancel()
(None, Nil)
}
orders = remainingOrders
@@ -201,35 +255,93 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* @param order the order being accepted;
* `None`, if no order found or submitted
*/
- def ProcessOrder(order: Option[VehicleSpawnControl.Order]): Unit = {
+ def ProcessOrder(order: Option[VehicleSpawnPad.VehicleOrder]): Unit = {
periodicReminder.cancel()
order match {
case Some(_order) =>
- recursiveOrderReminder(orders.iterator)
- trace(s"processing next order - a ${_order.vehicle.Definition.Name} for ${_order.driver.Name}")
- trackedOrder = order //guard on
- context.system.scheduler.scheduleOnce(2000 milliseconds, concealPlayer, _order)
+ val size = orders.size + 1
+ val driver = _order.player
+ val name = driver.Name
+ val vehicle = _order.vehicle
+ val newOrder = VehicleSpawnControl.Order(driver, vehicle)
+ recursiveOrderReminder(orders.iterator, size)
+ trace(s"processing next order - a ${vehicle.Definition.Name} for $name")
+ pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
+ name,
+ VehicleSpawnPad.Reminders.Queue,
+ Some(s"@SVCP_PositionInQueue^1~^$size~")
+ )
+ trackedOrder = Some(newOrder) //guard on
+ context.system.scheduler.scheduleOnce(2000 milliseconds, concealPlayer, newOrder)
case None => ;
}
}
+ /**
+ * One-stop shop to test queued vehicle spawn pad orders for valid credentials and
+ * either start a periodic examination of those credentials until the queue has been emptied or
+ * cancel a running periodic examination if the queue is already empty.
+ */
+ def queueManagementTask(): Unit = {
+ if (orders.nonEmpty) {
+ orders = orderCredentialsCheck(orders).toList
+ if (queueManagement.isCancelled) {
+ queueManagement = context.system.scheduler.scheduleWithFixedDelay(
+ 1.second,
+ 1.second,
+ self,
+ VehicleSpawnControl.ProcessControl.QueueManagement
+ )
+ }
+ } else {
+ queueManagement.cancel()
+ }
+ }
+
+ /**
+ * For all orders, ensure that that order's details match acceptable specifications
+ * and partition all orders that should be cancelled for one reason or another.
+ * Generate informative error messages for the failing orders, cancel those partitioned orders,
+ * and only return all orders that are still valid.
+ * @param recipients the original list of orders
+ * @return the list of still-acceptable orders
+ */
+ def orderCredentialsCheck(recipients: Iterable[VehicleSpawnPad.VehicleOrder]): Iterable[VehicleSpawnPad.VehicleOrder] = {
+ recipients
+ .map { order =>
+ (order, VehicleSpawnControl.validateOrderCredentials(order.terminal, order.player, order.vehicle))
+ }
+ .foldRight(List.empty[VehicleSpawnPad.VehicleOrder]) {
+ case (f, list) =>
+ f match {
+ case (order, msg @ Some(_)) =>
+ CancelOrder(order, msg)
+ list
+ case (order, None) =>
+ list :+ order
+ }
+ }
+ }
+
/**
* 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
*/
- def BlockedReminder(blockedOrder: VehicleSpawnControl.Order, recipients: Seq[VehicleSpawnControl.Order]): Unit = {
+ def BlockedReminder(blockedOrder: VehicleSpawnControl.Order, recipients: Seq[VehicleSpawnPad.VehicleOrder]): Unit = {
val user = blockedOrder.vehicle
.Seats(0).occupant
.orElse(pad.Zone.GUID(blockedOrder.vehicle.Owner))
.orElse(pad.Zone.GUID(blockedOrder.DriverGUID))
- val relevantRecipients = user match {
+ val relevantRecipients: Iterator[VehicleSpawnPad.VehicleOrder] = user match {
case Some(p: Player) if !p.HasGUID =>
recipients.iterator
- case Some(p: Player) if blockedOrder.driver == p =>
- (blockedOrder +: recipients).iterator
- case Some(p: Player) =>
- (VehicleSpawnControl.Order(p, blockedOrder.vehicle) +: recipients).iterator //who took possession of the vehicle
+ case Some(_: Player) =>
+ (VehicleSpawnPad.VehicleOrder(
+ blockedOrder.driver,
+ blockedOrder.vehicle,
+ null //permissible
+ ) +: recipients).iterator //one who took possession of the vehicle
case _ =>
recipients.iterator
}
@@ -245,24 +357,37 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
/**
* Cancel this vehicle order and inform the person who made it, if possible.
* @param entry the order being cancelled
- * @param context an `ActorContext` object for which to create the `TaskResolver` object
*/
- def CancelOrder(entry: VehicleSpawnControl.Order)(implicit context: ActorContext): Unit = {
- val vehicle = entry.vehicle
+ def CancelOrder(entry: VehicleSpawnControl.Order, msg: Option[String]): Unit = {
+ CancelOrder(entry.vehicle, entry.driver, msg)
+ }
+ /**
+ * Cancel this vehicle order and inform the person who made it, if possible.
+ * @param entry the order being cancelled
+ */
+ def CancelOrder(entry: VehicleSpawnPad.VehicleOrder, msg: Option[String]): Unit = {
+ CancelOrder(entry.vehicle, entry.player, msg)
+ }
+ /**
+ * Cancel this vehicle order and inform the person who made it, if possible.
+ * @param vehicle the vehicle from the order being cancelled
+ * @param player the player who would driver the vehicle from the order being cancelled
+ */
+ def CancelOrder(vehicle: Vehicle, player: Player, msg: Option[String]): Unit = {
if (vehicle.Seats.values.count(_.isOccupied) == 0) {
- VehicleSpawnControl.DisposeSpawnedVehicle(entry, pad.Zone)
- pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(entry.driver.Name, VehicleSpawnPad.Reminders.Cancelled)
+ VehicleSpawnControl.DisposeSpawnedVehicle(vehicle, player, pad.Zone)
+ pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(player.Name, VehicleSpawnPad.Reminders.Cancelled, msg)
}
}
@tailrec private final def recursiveBlockedReminder(
- iter: Iterator[VehicleSpawnControl.Order],
+ iter: Iterator[VehicleSpawnPad.VehicleOrder],
cause: Option[Any]
): Unit = {
if (iter.hasNext) {
val recipient = iter.next()
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
- recipient.driver.Name,
+ recipient.player.Name,
VehicleSpawnPad.Reminders.Blocked,
cause
)
@@ -271,30 +396,36 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
}
@tailrec private final def recursiveOrderReminder(
- iter: Iterator[VehicleSpawnControl.Order],
+ iter: Iterator[VehicleSpawnPad.VehicleOrder],
+ size: Int,
position: Int = 2
): Unit = {
if (iter.hasNext) {
val recipient = iter.next()
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
- recipient.driver.Name,
+ recipient.player.Name,
VehicleSpawnPad.Reminders.Queue,
- Some(position)
+ Some(s"@SVCP_PositionInQueue^$position~^$size~")
)
- recursiveOrderReminder(iter, position + 1)
+ recursiveOrderReminder(iter, size, position + 1)
}
}
}
object VehicleSpawnControl {
- private final val initialReminderDelay: FiniteDuration = 10000 milliseconds
- private final val periodicReminderDelay: FiniteDuration = 10000 milliseconds
+ private final val periodicReminderTestDelay: FiniteDuration = 10 seconds
/**
- * An `Enumeration` of non-data control messages for the vehicle spawn process.
+ * Control messages for the vehicle spawn process.
*/
- object ProcessControl extends Enumeration {
- val Reminder, GetNewOrder, Flush = Value
+ 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
}
/**
@@ -306,17 +437,74 @@ object VehicleSpawnControl {
assert(driver.HasGUID, s"when ordering a vehicle, the prospective driver ${driver.Name} does not have a GUID")
assert(vehicle.HasGUID, s"when ordering a vehicle, the ${vehicle.Definition.Name} does not have a GUID")
val DriverGUID = driver.GUID
+ val time = System.currentTimeMillis()
+ }
+
+ /**
+ * Assess the applicable details of an order that is being processed (is usually enqueued)
+ * and determine whether it is is still valid based on the current situation of those details.
+ * @param inZoneThing some physical aspect of this system through which the order will be processed;
+ * either the vehicle spawn pad or the vehicle spawn terminal are useful;
+ * this entity and the player are subject to a distance check
+ * @param player the player who would be the driver of the vehicle filed in the order
+ * @param vehicle the vehicle filed in the order
+ * @param tooFarDistance the distance check;
+ * defaults to 1225 (35m squared) relative to the anticipation of a `Terminal` entity
+ * @return whether or not a cancellation message is associated with these entry details,
+ * explaining why the order should be cancelled
+ */
+ def validateOrderCredentials(
+ inZoneThing: PlanetSideGameObject with WorldEntity with ZoneAware,
+ player: Player,
+ vehicle: Vehicle,
+ tooFarDistance: Float = 1225
+ ): Option[String] = {
+ if (!player.HasGUID || player.Zone != inZoneThing.Zone || !vehicle.HasGUID || vehicle.Destroyed) {
+ Some("@SVCP_RemovedFromVehicleQueue_Generic")
+ } else if (!player.isAlive || player.isReleased) {
+ Some("@SVCP_RemovedFromVehicleQueue_Destroyed")
+ } else if (vehicle.PassengerInSeat(player).isEmpty) {
+ //once seated, these are not a concern anymore
+ if (inZoneThing.Destroyed) {
+ Some("@SVCP_RemovedFromQueue_TerminalDestroyed")
+ } else if (Vector3.DistanceSquared(inZoneThing.Position, player.Position) > tooFarDistance) {
+ Some("@SVCP_RemovedFromVehicleQueue_MovedTooFar")
+ } else if (player.VehicleSeated.nonEmpty) {
+ Some("@SVCP_RemovedFromVehicleQueue_ParentChanged")
+ } else if (!vehicle.Seats(0).definition.restriction.test(player)) {
+ Some("@SVCP_RemovedFromVehicleQueue_ArmorChanged")
+ } else if (player.Carrying.contains(SpecialCarry.CaptureFlag)) {
+ Some("@SVCP_RemovedFromVehicleQueue_CaptureFlag")
+ } else if (player.Carrying.contains(SpecialCarry.VanuModule)) {
+ Some("@SVCP_RemovedFromVehicleQueue_VanuModule")
+ } else if (player.Carrying.contains(SpecialCarry.MonolithUnit)) {
+ Some("@SVCP_RemovedFromVehicleQueue_MonolithUnit")
+ } else if ( player.ZoningRequest == Zoning.Method.Quit) {
+ Some("@SVCP_RemovedFromVehicleQueue_Quit")
+ } else if ( player.ZoningRequest == Zoning.Method.InstantAction) {
+ Some("@SVCP_RemovedFromVehicleQueue_InstantAction")
+ } else if ( player.ZoningRequest == Zoning.Method.Recall) {
+ Some("@SVCP_RemovedFromVehicleQueue_Recall")
+ } else if ( player.ZoningRequest == Zoning.Method.OutfitRecall) {
+ Some("@SVCP_RemovedFromVehicleQueue_OutfitRecall")
+ } else {
+ None
+ }
+ } else {
+ None
+ }
}
/**
* Properly clean up a vehicle that has been registered and spawned into the game world.
* Call this downstream of "`ConcealPlayer`".
- * @param entry the order being cancelled
+ * @param vehicle the vehicle being disposed
+ * @param player the player who would own the vehicle being disposed
* @param zone the zone in which the vehicle is registered (should be located)
*/
- def DisposeSpawnedVehicle(entry: VehicleSpawnControl.Order, zone: Zone): Unit = {
- DisposeVehicle(entry.vehicle, zone)
- zone.VehicleEvents ! VehicleSpawnPad.RevealPlayer(entry.DriverGUID)
+ def DisposeSpawnedVehicle(vehicle: Vehicle, player: Player, zone: Zone): Unit = {
+ DisposeVehicle(vehicle, zone)
+ zone.VehicleEvents ! VehicleSpawnPad.RevealPlayer(player.GUID)
}
/**
@@ -325,8 +513,8 @@ object VehicleSpawnControl {
* @param zone the zone in which the vehicle is registered (should be located)
*/
def DisposeVehicle(vehicle: Vehicle, zone: Zone): Unit = {
- if (zone.Vehicles.exists(_.GUID == vehicle.GUID)) { //already added to zone
- vehicle.Actor ! Vehicle.Deconstruct()
+ if (zone.Vehicles.contains(vehicle)) { //already added to zone
+ vehicle.Actor ! Vehicle.Deconstruct(Some(0.seconds))
} else { //just registered to zone
zone.tasks ! UnregisterVehicle(vehicle)(zone.GUID)
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala
index 544cf117c..c125da0c1 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala
@@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.pad
import net.psforever.objects.{Player, Vehicle}
import net.psforever.objects.serverobject.structures.Amenity
+import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.types.PlanetSideGUID
/**
@@ -28,7 +29,7 @@ object VehicleSpawnPad {
* @param player the player who submitted the order (the "owner")
* @param vehicle the vehicle produced from the order
*/
- final case class VehicleOrder(player: Player, vehicle: Vehicle)
+ final case class VehicleOrder(player: Player, vehicle: Vehicle, terminal: Terminal)
/**
* Message to indicate that a certain player should be made transparent.
@@ -130,9 +131,11 @@ object VehicleSpawnPad {
* An `Enumeration` of reasons for sending a periodic reminder to the user.
*/
object Reminders extends Enumeration {
- val Queue, //optional data is the numeric position in the queue
- Blocked, //optional data is a message regarding the blockage
- Cancelled = Value
+ val
+ Queue, //optional data is the numeric position in the queue
+ Blocked, //optional data is a message regarding the blockage
+ Cancelled //optional data is the message
+ = Value
}
/**
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 670e89e27..447af1e08 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala
@@ -1,7 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad
+import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.serverobject.structures.AmenityDefinition
+import net.psforever.types.Vector3
/**
* The definition for any `VehicleSpawnPad`.
@@ -39,4 +41,166 @@ class VehicleSpawnPadDefinition(objectId: Int) extends AmenityDefinition(objectI
case 947 => Name = "vanu_vehicle_creation_pad"
case _ => throw new IllegalArgumentException("Not a valid object id with the type vehicle_creation_pad")
}
+
+ /** The region surrounding a vehicle spawn pad that is cleared of damageable targets prior to a vehicle being spawned.
+ * I mean to say that, if it can die, that target will die.
+ * @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)
+}
+
+object VehicleSpawnPadDefinition {
+ /**
+ * A function that sets up the region around a vehicle spawn pad
+ * to be cleared of damageable targets upon spawning of a vehicle.
+ * All measurements are provided in terms of distance from the center of the pad.
+ * These generic pads are rectangular in bounds and the kill box is cuboid in shape.
+ * @param forwardLimit how far in front of the spawn pad is to be cleared
+ * @param backLimit how far behind the spawn pad to be cleared;
+ * "back" is a squared direction usually in that direction of the corresponding terminal
+ * @param sideLimit how far to either side of the spawn pad is to be cleared
+ * @param aboveLimit how far above the spawn pad is to be cleared
+ * @param pad the vehicle spawn pad in question
+ * @param flightVehicle whether the current vehicle being ordered is a flying craft
+ * @return a function that describes a region around the vehicle spawn pad
+ */
+ def prepareKillBox(
+ forwardLimit: Float,
+ backLimit: Float,
+ sideLimit: Float,
+ aboveLimit: Float
+ )
+ (
+ pad: VehicleSpawnPad,
+ flightVehicle: Boolean
+ ): (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = {
+ val forward = Vector3(0,1,0).Rz(pad.Orientation.z + pad.Definition.VehicleCreationZOrientOffset)
+ val side = Vector3.CrossProduct(forward, Vector3(0,0,1))
+ vehicleSpawnKillBox(
+ forward,
+ side,
+ pad.Position,
+ if (flightVehicle) backLimit else forwardLimit,
+ backLimit,
+ sideLimit,
+ if (flightVehicle) aboveLimit * 2 else aboveLimit,
+ )
+ }
+
+ /**
+ * A function that finalizes the detection for the region around a vehicle spawn pad
+ * to be cleared of damageable targets upon spawning of a vehicle.
+ * All measurements are provided in terms of distance from the center of the pad.
+ * These generic pads are rectangular in bounds and the kill box is cuboid in shape.
+ * @param forward a direction in a "forwards" direction relative to the orientation of the spawn pad
+ * @param side a direction in a "side-wards" direction relative to the orientation of the spawn pad
+ * @param origin the center of the spawn pad
+ * @param forwardLimit how far in front of the spawn pad is to be cleared
+ * @param backLimit how far behind the spawn pad to be cleared
+ * @param sideLimit how far to either side of the spawn pad is to be cleared
+ * @param aboveLimit how far above the spawn pad is to be cleared
+ * @param obj1 a game entity, should be the source
+ * @param obj2 a game entity, should be the target
+ * @param maxDistance the square of the maximum distance permissible between game entities
+ * before they are no longer considered "near"
+ * @return `true`, if the two entities are near enough to each other;
+ * `false`, otherwise
+ */
+ protected def vehicleSpawnKillBox(
+ forward: Vector3,
+ side: Vector3,
+ origin: Vector3,
+ forwardLimit: Float,
+ backLimit: Float,
+ sideLimit: Float,
+ aboveLimit: Float
+ )
+ (
+ obj1: PlanetSideGameObject,
+ obj2: PlanetSideGameObject,
+ maxDistance: Float
+ ): Boolean = {
+ val dir: Vector3 = {
+ val g2 = obj2.Definition.Geometry(obj2)
+ val cdir = Vector3.Unit(origin - g2.center.asVector3)
+ val point = g2.pointOnOutside(cdir).asVector3
+ point - origin
+ }
+ val originZ = origin.z
+ val obj2Z = obj2.Position.z
+ originZ - 1 <= obj2Z && originZ + aboveLimit > obj2Z &&
+ {
+ val calculatedForwardDistance = Vector3.ScalarProjection(dir, forward)
+ if (calculatedForwardDistance >= 0) {
+ calculatedForwardDistance < forwardLimit
+ }
+ else {
+ -calculatedForwardDistance < backLimit
+ }
+ } &&
+ math.abs(Vector3.ScalarProjection(dir, side)) < sideLimit
+ }
+
+ /**
+ * A function that sets up the region around a vehicle spawn pad
+ * to be cleared of damageable targets upon spawning of a vehicle.
+ * All measurements are provided in terms of distance from the center of the pad.
+ * These pads are only found in the cavern zones and are cylindrical in shape.
+ * @param radius the distance from the middle of the spawn pad
+ * @param aboveLimit how far above the spawn pad is to be cleared
+ * @param pad he vehicle spawn pad in question
+ * @param flightVehicle whether the current vehicle being ordered is a flying craft
+ * @return a function that describes a region around the vehicle spawn pad
+ */
+ def prepareVanuKillBox(
+ radius: Float,
+ aboveLimit: Float
+ )
+ (
+ pad: VehicleSpawnPad,
+ flightVehicle: Boolean
+ ): (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = {
+ if (flightVehicle) {
+ vanuKillBox(pad.Position, radius, aboveLimit * 2)
+ } else {
+ vanuKillBox(pad.Position, radius * 1.2f, aboveLimit)
+ }
+ }
+
+ /**
+ * A function that finalizes the detection for the region around a vehicle spawn pad
+ * to be cleared of damageable targets upon spawning of a vehicle.
+ * All measurements are provided in terms of distance from the center of the pad.
+ * These pads are only found in the cavern zones and are cylindrical in shape.
+ * @param origin the center of the spawn pad
+ * @param radius the distance from the middle of the spawn pad
+ * @param aboveLimit how far above the spawn pad is to be cleared
+ * @param obj1 a game entity, should be the source
+ * @param obj2 a game entity, should be the target
+ * @param maxDistance the square of the maximum distance permissible between game entities
+ * before they are no longer considered "near"
+ * @return `true`, if the two entities are near enough to each other;
+ * `false`, otherwise
+ */
+ def vanuKillBox(
+ origin: Vector3,
+ radius: Float,
+ aboveLimit: Float
+ )
+ (
+ obj1: PlanetSideGameObject,
+ obj2: PlanetSideGameObject,
+ maxDistance: Float
+ ): Boolean = {
+ val dir: Vector3 = {
+ val g2 = obj2.Definition.Geometry(obj2)
+ val cdir = Vector3.Unit(origin - g2.center.asVector3)
+ val point = g2.pointOnOutside(cdir).asVector3
+ point - origin
+ }
+ val originZ = origin.z
+ val obj2Z = obj2.Position.z
+ originZ - 1 <= obj2Z && originZ + aboveLimit > obj2Z &&
+ Vector3.MagnitudeSquared(dir.xy) < radius * radius
+ }
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala
index 2667d785b..b035a7849 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala
@@ -25,19 +25,19 @@ class VehicleSpawnControlConcealPlayer(pad: VehicleSpawnPad) extends VehicleSpaw
context.actorOf(Props(classOf[VehicleSpawnControlLoadVehicle], pad), s"${context.parent.path.name}-load")
def receive: Receive = {
- case order @ VehicleSpawnControl.Order(driver, _) =>
- //TODO how far can the driver stray from the Terminal before his order is cancelled?
- if (driver.Continent == pad.Continent && driver.VehicleSeated.isEmpty && driver.isAlive) {
+ case order @ VehicleSpawnControl.Order(driver, vehicle) =>
+ if (VehicleSpawnControl.validateOrderCredentials(pad, driver, vehicle).isEmpty) {
trace(s"hiding ${driver.Name}")
pad.Zone.VehicleEvents ! VehicleSpawnPad.ConcealPlayer(driver.GUID)
context.system.scheduler.scheduleOnce(2000 milliseconds, loadVehicle, order)
} else {
trace(s"integral component lost; abort order fulfillment")
- VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
- context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
+ context.parent ! VehicleSpawnControl.ProcessControl.OrderCancelled
}
- case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
+ case msg @ (VehicleSpawnControl.ProcessControl.Reminder |
+ VehicleSpawnControl.ProcessControl.GetNewOrder |
+ VehicleSpawnControl.ProcessControl.OrderCancelled) =>
context.parent ! msg
case _ => ;
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala
index 047e9c928..dbef9a3c8 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlDriverControl.scala
@@ -22,13 +22,10 @@ class VehicleSpawnControlDriverControl(pad: VehicleSpawnPad) extends VehicleSpaw
def receive: Receive = {
case order @ VehicleSpawnControl.Order(driver, vehicle) =>
- if (vehicle.Health > 0 && vehicle.PassengerInSeat(driver).contains(0)) {
- trace(s"returning control of ${vehicle.Definition.Name} to ${driver.Name}")
+ trace(s"returning control of ${vehicle.Definition.Name} to its current driver")
+ if (vehicle.PassengerInSeat(driver).nonEmpty) {
pad.Zone.VehicleEvents ! VehicleSpawnPad.ServerVehicleOverrideEnd(driver.Name, vehicle, pad)
- } else {
- trace(s"${driver.Name} is not seated in ${vehicle.Definition.Name}; vehicle controls might have been locked")
}
- vehicle.MountedIn = None
finalClear ! order
case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
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 0010cf22c..516b904b2 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
@@ -1,6 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad.process
+import akka.actor.Cancellable
+import net.psforever.objects.Default
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
import net.psforever.types.{PlanetSideGUID, Vector3}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
@@ -13,8 +15,7 @@ import scala.concurrent.duration._
* The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other.
* Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
*
- * There is nothing left to do
- * except make certain that the vehicle has moved far enough away from the spawn pad
+ * There is nothing left to do except make certain that the vehicle has moved far enough away from the spawn pad
* to not block the next order that may be queued.
* A long call is made to the root of this `Actor` object chain to start work on any subsequent vehicle order.
* @param pad the `VehicleSpawnPad` object being governed
@@ -22,10 +23,16 @@ import scala.concurrent.duration._
class VehicleSpawnControlFinalClearance(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
def LogId = "-clearer"
+ var temp: Cancellable = Default.Cancellable
+
+ override def postStop() : Unit = {
+ temp.cancel()
+ }
+
def receive: Receive = {
- case order @ VehicleSpawnControl.Order(driver, vehicle) =>
- if (vehicle.PassengerInSeat(driver).isEmpty) {
- //ensure the vacant vehicle is above the trench and doors
+ case order @ VehicleSpawnControl.Order(_, vehicle) =>
+ if (!vehicle.Seats(0).isOccupied) {
+ //ensure the vacant vehicle is above the trench and the doors
vehicle.Position = pad.Position + Vector3.z(pad.Definition.VehicleCreationZOffset)
val definition = vehicle.Definition
pad.Zone.VehicleEvents ! VehicleServiceMessage(
@@ -43,12 +50,20 @@ class VehicleSpawnControlFinalClearance(pad: VehicleSpawnPad) extends VehicleSpa
self ! VehicleSpawnControlFinalClearance.Test(order)
case test @ VehicleSpawnControlFinalClearance.Test(entry) =>
- if (Vector3.DistanceSquared(entry.vehicle.Position, pad.Position) > 100.0f) { //10m away from pad
+ //the vehicle has an initial decay of 30s 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")
+ VehicleSpawnControl.DisposeVehicle(vehicle, pad.Zone)
+ context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
} else {
- context.system.scheduler.scheduleOnce(2000 milliseconds, self, test)
+ temp = context.system.scheduler.scheduleOnce(2000 milliseconds, self, test)
}
case _ => ;
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala
index 1ad844721..d9bdd3d94 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlLoadVehicle.scala
@@ -1,8 +1,10 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad.process
-import akka.actor.{Cancellable, Props}
-import net.psforever.objects.{Default, GlobalDefinitions}
+import akka.actor.Props
+import akka.pattern.ask
+import akka.util.Timeout
+import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
import net.psforever.objects.zones.Zone
import net.psforever.services.Service
@@ -11,6 +13,7 @@ import net.psforever.types.Vector3
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
+import scala.util.Success
/**
* An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
@@ -28,63 +31,64 @@ class VehicleSpawnControlLoadVehicle(pad: VehicleSpawnPad) extends VehicleSpawnC
val railJack = context.actorOf(Props(classOf[VehicleSpawnControlRailJack], pad), s"${context.parent.path.name}-rails")
- var temp: Cancellable = Default.Cancellable
+ var temp: Option[VehicleSpawnControl.Order] = None
- override def postStop() : Unit = {
- temp.cancel()
- super.postStop()
- }
+ implicit val timeout = Timeout(3.seconds)
def receive: Receive = {
case order @ VehicleSpawnControl.Order(driver, vehicle) =>
- if (driver.Continent == pad.Continent && vehicle.Health > 0 && driver.isAlive) {
+ if (VehicleSpawnControl.validateOrderCredentials(pad, driver, vehicle).isEmpty) {
trace(s"loading the ${vehicle.Definition.Name}")
vehicle.Position = vehicle.Position - Vector3.z(
if (GlobalDefinitions.isFlightVehicle(vehicle.Definition)) 9 else 5
) //appear below the trench and doors
vehicle.Cloaked = vehicle.Definition.CanCloak && driver.Cloaked
- pad.Zone.Transport.tell(Zone.Vehicle.Spawn(vehicle), self)
- temp = context.system.scheduler.scheduleOnce(
- delay = 100 milliseconds,
- self,
- VehicleSpawnControlLoadVehicle.WaitOnSpawn(order)
- )
+
+ temp = Some(order)
+ val result = ask(pad.Zone.Transport, Zone.Vehicle.Spawn(vehicle))
+ //if too long, or something goes wrong
+ result.recover {
+ case _ =>
+ temp = None
+ context.parent ! VehicleSpawnControl.ProcessControl.OrderCancelled
+ }
+ //resolution
+ result.onComplete {
+ case Success(Zone.Vehicle.HasSpawned(zone, v))
+ if (temp match { case Some(_order) => _order.vehicle eq v; case _ => false }) =>
+ val definition = v.Definition
+ val vtype = definition.ObjectId
+ val vguid = v.GUID
+ val vdata = definition.Packet.ConstructorData(v).get
+ zone.VehicleEvents ! VehicleServiceMessage(
+ zone.id,
+ VehicleAction.LoadVehicle(Service.defaultPlayerGUID, v, vtype, vguid, vdata)
+ )
+ railJack ! temp.get
+ temp = None
+
+ case Success(Zone.Vehicle.CanNotSpawn(_, _, reason)) =>
+ trace(s"vehicle can not spawn - $reason; abort order fulfillment")
+ temp = None
+ context.parent ! VehicleSpawnControl.ProcessControl.OrderCancelled
+
+ case _ =>
+ temp match {
+ case Some(_) =>
+ trace(s"abort order fulfillment")
+ context.parent ! VehicleSpawnControl.ProcessControl.OrderCancelled
+ case None => ; //should we have gotten this message?
+ }
+ temp = None
+ }
} else {
trace("owner lost or vehicle in poor condition; abort order fulfillment")
- VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
- context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
+ context.parent ! VehicleSpawnControl.ProcessControl.OrderCancelled
}
- case Zone.Vehicle.HasSpawned(zone, vehicle) =>
- val definition = vehicle.Definition
- val vtype = definition.ObjectId
- val vguid = vehicle.GUID
- val vdata = definition.Packet.ConstructorData(vehicle).get
- zone.VehicleEvents ! VehicleServiceMessage(
- zone.id,
- VehicleAction.LoadVehicle(Service.defaultPlayerGUID, vehicle, vtype, vguid, vdata)
- )
-
- case VehicleSpawnControlLoadVehicle.WaitOnSpawn(order) =>
- if (pad.Zone.Vehicles.contains(order.vehicle)) {
- railJack ! order
- } else {
- VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
- context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
- }
-
- case Zone.Vehicle.CanNotSpawn(_, _, reason) =>
- trace(s"vehicle $reason; abort order fulfillment")
- temp.cancel()
- context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
-
case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
context.parent ! msg
case _ => ;
}
}
-
-object VehicleSpawnControlLoadVehicle {
- private case class WaitOnSpawn(order: VehicleSpawnControl.Order)
-}
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala
index d589bf0df..0ea6dfb00 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala
@@ -2,7 +2,15 @@
package net.psforever.objects.serverobject.pad.process
import akka.actor.Props
+import net.psforever.objects.PlanetSideGameObject
+import net.psforever.objects.ballistics.SourceEntry
+import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
+import net.psforever.objects.vital.Vitality
+import net.psforever.objects.vital.etc.{ExplodingEntityReason, VehicleSpawnReason}
+import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
+import net.psforever.objects.vital.prop.DamageProperties
+import net.psforever.objects.zones.Zone
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
@@ -13,10 +21,7 @@ import scala.concurrent.duration._
* Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
*
* When the vehicle is added into the environment, it is attached to the spawn pad platform.
- * On cue, the trapdoor of the platform will open, and the vehicle will be raised up into plain sight on a group of rails.
- * These actions are actually integrated into previous stages and into later stages of the process.
- * The primary objective to be completed is a specific place to start a frequent message to the other customers.
- * It has failure cases should the driver be in an incorrect state.
+ * On cue, the trapdoor of the platform will open, and the vehicle will be raised on a railed platform.
* @param pad the `VehicleSpawnPad` object being governed
*/
class VehicleSpawnControlRailJack(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
@@ -26,8 +31,14 @@ class VehicleSpawnControlRailJack(pad: VehicleSpawnPad) extends VehicleSpawnCont
context.actorOf(Props(classOf[VehicleSpawnControlSeatDriver], pad), s"${context.parent.path.name}-mount")
def receive: Receive = {
- case order @ VehicleSpawnControl.Order(_, vehicle) =>
+ case order @ VehicleSpawnControl.Order(driver, vehicle) =>
vehicle.MountedIn = pad.GUID
+ Zone.serverSideDamage(
+ pad.Zone,
+ pad,
+ VehicleSpawnControlRailJack.prepareSpawnExplosion(pad, SourceEntry(driver), SourceEntry(vehicle)),
+ pad.Definition.killBox(pad, vehicle.Definition.CanFly)
+ )
pad.Zone.VehicleEvents ! VehicleSpawnPad.AttachToRails(vehicle, pad)
context.system.scheduler.scheduleOnce(10 milliseconds, seatDriver, order)
@@ -37,3 +48,46 @@ class VehicleSpawnControlRailJack(pad: VehicleSpawnPad) extends VehicleSpawnCont
case _ => ;
}
}
+
+object VehicleSpawnControlRailJack {
+ def prepareSpawnExplosion(
+ pad: VehicleSpawnPad,
+ driver: SourceEntry,
+ vehicle: SourceEntry
+ ):
+ (
+ PlanetSideGameObject with FactionAffinity with Vitality,
+ PlanetSideGameObject with FactionAffinity with Vitality
+ ) => DamageInteraction = {
+ vehicleSpawnExplosion(
+ vehicle,
+ pad.Definition.innateDamage.get,
+ Some(DamageInteraction(
+ SourceEntry(pad),
+ VehicleSpawnReason(driver, vehicle),
+ pad.Position
+ ).calculate()(pad))
+ )
+ }
+
+ def vehicleSpawnExplosion(
+ vehicle: SourceEntry,
+ properties: DamageProperties,
+ cause: Option[DamageResult]
+ )
+ (
+ source: PlanetSideGameObject with FactionAffinity with Vitality,
+ target: PlanetSideGameObject with FactionAffinity with Vitality
+ ): DamageInteraction = {
+ DamageInteraction(
+ SourceEntry(target),
+ ExplodingEntityReason(
+ vehicle,
+ properties,
+ target.DamageModel,
+ cause
+ ),
+ target.Position
+ )
+ }
+}
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 4235cf554..bf14a8cb3 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
@@ -44,7 +44,7 @@ class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnCo
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))
- if (vehicle.Health > 0 && driver.isAlive && driver.Continent == pad.Continent && driver.VehicleSeated.isEmpty) {
+ if (VehicleSpawnControl.validateOrderCredentials(pad, driver, vehicle).isEmpty) {
trace("driver to be made seated in vehicle")
pad.Zone.VehicleEvents ! VehicleSpawnPad.StartPlayerSeatedInVehicle(driver.Name, vehicle, pad)
} else {
@@ -53,7 +53,10 @@ class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnCo
context.system.scheduler.scheduleOnce(2500 milliseconds, self, VehicleSpawnControlSeatDriver.DriverInSeat(entry))
case VehicleSpawnControlSeatDriver.DriverInSeat(entry) =>
- if (entry.vehicle.Health > 0 && entry.driver.isAlive && entry.vehicle.PassengerInSeat(entry.driver).contains(0)) {
+ val driver = entry.driver
+ val vehicle = entry.vehicle
+ if (VehicleSpawnControl.validateOrderCredentials(pad, driver, vehicle).isEmpty &&
+ entry.vehicle.PassengerInSeat(entry.driver).contains(0)) {
trace(s"driver ${entry.driver.Name} has taken the wheel")
pad.Zone.VehicleEvents ! VehicleSpawnPad.PlayerSeatedInVehicle(entry.driver.Name, entry.vehicle, pad)
} else {
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala
index 6389c5c58..bbec75af4 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlServerVehicleOverride.scala
@@ -30,6 +30,7 @@ class VehicleSpawnControlServerVehicleOverride(pad: VehicleSpawnPad) extends Veh
val vehicleFailState = vehicle.Health == 0 || vehicle.Position == Vector3.Zero
val driverFailState =
!driver.isAlive || driver.Continent != pad.Continent || !vehicle.PassengerInSeat(driver).contains(0)
+ vehicle.MountedIn = None
pad.Zone.VehicleEvents ! VehicleSpawnPad.DetachFromRails(vehicle, pad)
if (vehicleFailState || driverFailState) {
if (vehicleFailState) {
diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
index 62049f764..368fa0622 100644
--- a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
@@ -698,7 +698,10 @@ class VehicleControl(vehicle: Vehicle)
percentage,
body,
vehicle.Seats.values
- .flatMap { case seat if seat.isOccupied => seat.occupants }
+ .flatMap {
+ case seat if seat.isOccupied => seat.occupants
+ case _ => Nil
+ }
.filter { p => p.isAlive && (p.Zone eq vehicle.Zone) }
)
}
diff --git a/src/main/scala/net/psforever/objects/vital/etc/EmpReason.scala b/src/main/scala/net/psforever/objects/vital/etc/EmpReason.scala
index 3693b9e16..e3eb107d4 100644
--- a/src/main/scala/net/psforever/objects/vital/etc/EmpReason.scala
+++ b/src/main/scala/net/psforever/objects/vital/etc/EmpReason.scala
@@ -3,7 +3,6 @@ package net.psforever.objects.vital.etc
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ballistics.SourceEntry
-import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
@@ -13,9 +12,8 @@ import net.psforever.objects.vital.resolution.DamageAndResistance
/**
* A wrapper for a "damage source" in damage calculations
* that parameterizes information necessary to explain a server-driven electromagnetic pulse occurring.
- * @see `VitalityDefinition.explodes`
+ * @see `SpecialEmp.createEmpInteraction`
* @see `VitalityDefinition.innateDamage`
- * @see `Zone.causesSpecialEmp`
* @param entity the source of the explosive yield
* @param damageModel the model to be utilized in these calculations;
* typically, but not always, defined by the target
@@ -41,7 +39,7 @@ object EmpReason {
def apply(
owner: PlanetSideGameObject with FactionAffinity,
source: DamageWithPosition,
- target: PlanetSideServerObject with Vitality
+ target: PlanetSideGameObject with Vitality
): EmpReason = {
EmpReason(SourceEntry(owner), source, target.DamageModel, owner.Definition.ObjectId)
}
diff --git a/src/main/scala/net/psforever/objects/vital/etc/ExplodingEntityReason.scala b/src/main/scala/net/psforever/objects/vital/etc/ExplodingEntityReason.scala
index ed1096ab9..1f310c279 100644
--- a/src/main/scala/net/psforever/objects/vital/etc/ExplodingEntityReason.scala
+++ b/src/main/scala/net/psforever/objects/vital/etc/ExplodingEntityReason.scala
@@ -4,10 +4,11 @@ package net.psforever.objects.vital.etc
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.{Vitality, VitalityDefinition}
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
-import net.psforever.objects.vital.prop.DamageWithPosition
+import net.psforever.objects.vital.prop.{DamageProperties, DamageWithPosition}
import net.psforever.objects.vital.resolution.DamageAndResistance
import net.psforever.objects.zones.Zone
@@ -18,21 +19,18 @@ import net.psforever.objects.zones.Zone
* @see `VitalityDefinition.explodes`
* @see `VitalityDefinition.innateDamage`
* @see `Zone.causesExplosion`
- * @param entity the source of the explosive yield
+ * @param entity what is accredited as the source of the explosive yield
+ * @param source information about the explosive yield
* @param damageModel the model to be utilized in these calculations;
* typically, but not always, defined by the target
* @param instigation what previous event happened, if any, that caused this explosion
*/
final case class ExplodingEntityReason(
- entity: PlanetSideGameObject with Vitality,
+ entity: SourceEntry,
+ source: DamageProperties,
damageModel: DamageAndResistance,
instigation: Option[DamageResult]
) extends DamageReason {
- private val definition = entity.Definition.asInstanceOf[ObjectDefinition with VitalityDefinition]
- assert(definition.explodes && definition.innateDamage.nonEmpty, "causal entity does not explode")
-
- def source: DamageWithPosition = definition.innateDamage.get
-
def resolution: DamageResolution.Value = DamageResolution.Explosion
def same(test: DamageReason): Boolean = test match {
@@ -43,11 +41,29 @@ final case class ExplodingEntityReason(
/** lay the blame on that which caused this explosion to occur */
def adversary: Option[SourceEntry] = instigation match {
case Some(prior) => prior.interaction.cause.adversary
- case None => None
+ case None => Some(entity)
}
- /** the entity that exploded is the source of the damage */
- override def attribution: Int = definition.ObjectId
+ override def attribution: Int = entity.Definition.ObjectId
+}
+
+object ExplodingEntityReason {
+ /**
+ * An overloaded constructor for a wrapper for a "damage source" in damage calculations.
+ * @param entity the source of the explosive yield
+ * @param damageModel the model to be utilized in these calculations
+ * @param instigation what previous event happened, if any, that caused this explosion
+ * @return an `ExplodingEntityReason` entity
+ */
+ def apply(
+ entity: PlanetSideGameObject with FactionAffinity with Vitality,
+ damageModel: DamageAndResistance,
+ instigation: Option[DamageResult]
+ ): ExplodingEntityReason = {
+ val definition = entity.Definition.asInstanceOf[ObjectDefinition with VitalityDefinition]
+ assert(definition.explodes && definition.innateDamage.nonEmpty, "causal entity does not explode")
+ ExplodingEntityReason(SourceEntry(entity), definition.innateDamage.get, damageModel, instigation)
+ }
}
object ExplodingDamageModifiers {
diff --git a/src/main/scala/net/psforever/objects/vital/etc/VehicleSpawnReason.scala b/src/main/scala/net/psforever/objects/vital/etc/VehicleSpawnReason.scala
new file mode 100644
index 000000000..62f06bc11
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vital/etc/VehicleSpawnReason.scala
@@ -0,0 +1,46 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.vital.etc
+
+import net.psforever.objects.ballistics.SourceEntry
+import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
+import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
+import net.psforever.objects.vital.damage.DamageCalculations.AgainstNothing
+import net.psforever.objects.vital.prop.DamageProperties
+import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel}
+
+/**
+ * The instigating cause of dying on an operational vehicle spawn pad.
+ * @param driver the driver whose vehicle was being created
+ * @param vehicle the vehicle being created
+ */
+final case class VehicleSpawnReason(driver: SourceEntry, vehicle: SourceEntry)
+ extends DamageReason {
+ def resolution: DamageResolution.Value = DamageResolution.Resolved
+
+ def same(test: DamageReason): Boolean = test match {
+ case cause: VehicleSpawnReason =>
+ driver.Name.equals(cause.driver.Name) &&
+ (vehicle.Definition eq cause.vehicle.Definition)
+ case _ =>
+ false
+ }
+
+ def source: DamageProperties = VehicleSpawnReason.source
+
+ def damageModel: DamageAndResistance = VehicleSpawnReason.drm
+
+ override def adversary : Option[SourceEntry] = Some(driver)
+
+ override def attribution : Int = vehicle.Definition.ObjectId
+}
+
+object VehicleSpawnReason {
+ private val source = new DamageProperties { /*intentional blank*/ }
+
+ /** basic damage, no resisting, quick and simple */
+ private val drm = new DamageResistanceModel {
+ DamageUsing = AgainstNothing
+ ResistUsing = NoResistanceSelection
+ Model = SimpleResolutions.calculate
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala
index 1a3843cf9..3feab1236 100644
--- a/src/main/scala/net/psforever/objects/zones/Zone.scala
+++ b/src/main/scala/net/psforever/objects/zones/Zone.scala
@@ -4,7 +4,7 @@ package net.psforever.objects.zones
import akka.actor.{ActorContext, ActorRef, Props}
import akka.routing.RandomPool
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
-import net.psforever.objects._
+import net.psforever.objects.{PlanetSideGameObject, _}
import net.psforever.objects.ce.{ComplexDeployable, Deployable, SimpleDeployable}
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment.Equipment
@@ -40,13 +40,14 @@ import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.geometry.Geometry3D
import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.vehicles.UtilityType
-import net.psforever.objects.vital.etc.{EmpReason, ExplodingEntityReason}
+import net.psforever.objects.vital.etc.ExplodingEntityReason
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.prop.DamageWithPosition
import net.psforever.objects.vital.Vitality
@@ -1092,177 +1093,169 @@ object Zone {
}
/**
- * Allocates `Damageable` targets within the radius of a server-prepared explosion
- * and informs those entities that they have affected by the aforementioned explosion.
- * @see `Amenity.Owner`
- * @see `ComplexDeployable`
- * @see `DamageInteraction`
- * @see `DamageResult`
- * @see `DamageWithPosition`
- * @see `ExplodingEntityReason`
- * @see `SimpleDeployable`
- * @see `VitalityDefinition`
- * @see `VitalityDefinition.innateDamage`
- * @see `Zone.Buildings`
- * @see `Zone.DeployableList`
- * @see `Zone.LivePlayers`
- * @see `Zone.LocalEvents`
- * @see `Zone.Vehicles`
- * @param zone the zone in which the explosion should occur
- * @param obj the entity that embodies the explosion (information)
- * @param instigation whatever prior action triggered the entity to explode, if anything
- * @param detectionTest a custom test to determine if any given target is affected;
- * defaults to an internal test for simple radial proximity
+ * Allocates `Damageable` targets within the vicinity of server-prepared damage dealing
+ * and informs those entities that they have affected by the aforementioned damage.
+ * Usually, this is considered an "explosion;" but, the application can be utilized for a variety of unbound damage.
+ * @param zone the zone in which the damage should occur
+ * @param source the entity that embodies the damage (information)
+ * @param createInteraction how the interaction for this damage is to prepared
+ * @param testTargetsFromZone a custom test for determining whether the allocated targets are affected by the damage
+ * @param acquireTargetsFromZone the main target-collecting algorithm
* @return a list of affected entities;
* only mostly complete due to the exclusion of objects whose damage resolution is different than usual
*/
- def causeExplosion(
- zone: Zone,
- obj: PlanetSideGameObject with Vitality,
- instigation: Option[DamageResult],
- detectionTest: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = distanceCheck
+ def serverSideDamage(
+ zone: Zone,
+ source: PlanetSideGameObject with FactionAffinity with Vitality,
+ createInteraction: (PlanetSideGameObject with FactionAffinity with Vitality, PlanetSideGameObject with FactionAffinity with Vitality) => DamageInteraction,
+ testTargetsFromZone: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = distanceCheck,
+ acquireTargetsFromZone: (Zone, PlanetSideGameObject with FactionAffinity with Vitality, DamageWithPosition) => (List[PlanetSideServerObject with Vitality], List[PlanetSideGameObject with FactionAffinity with Vitality]) = findAllTargets
): List[PlanetSideServerObject] = {
- obj.Definition.innateDamage match {
- case Some(explosion) if obj.Definition.explodes =>
- //useful in this form
- val sourcePosition = obj.Position
- val sourcePositionXY = sourcePosition.xy
- val radius = explosion.DamageRadius * explosion.DamageRadius
- //collect all targets that can be damaged
- //players
- val playerTargets = zone.LivePlayers.filterNot { _.VehicleSeated.nonEmpty }
- //vehicles
- val vehicleTargets = zone.Vehicles.filterNot { v => v.Destroyed || v.MountedIn.nonEmpty }
- //deployables
- val (simpleDeployableTargets, complexDeployableTargets) =
- zone.DeployableList
- .filterNot { _.Destroyed }
- .foldRight((List.empty[SimpleDeployable], List.empty[ComplexDeployable])) { case (f, (simp, comp)) =>
- f match {
- case o: SimpleDeployable => (simp :+ o, comp)
- case o: ComplexDeployable => (simp, comp :+ o)
- case _ => (simp, comp)
- }
- }
- //amenities
- val soiTargets = obj match {
- case o: Amenity =>
- //fortunately, even where soi overlap, amenities in different buildings are never that close to each other
- o.Owner.Amenities
- case _ =>
- zone.Buildings.values
- .filter { b =>
- val soiRadius = b.Definition.SOIRadius * b.Definition.SOIRadius
- Vector3.DistanceSquared(sourcePositionXY, b.Position.xy) < soiRadius || soiRadius <= radius
- }
- .flatMap { _.Amenities }
- .filter { _.Definition.Damageable }
- }
- //restrict to targets according to the detection plan
- val allAffectedTargets = (playerTargets ++ vehicleTargets ++ complexDeployableTargets ++ soiTargets)
- .filter { target =>
- (target ne obj) && detectionTest(obj, target, radius)
- }
- //inform remaining targets that they have suffered an explosion
- allAffectedTargets
- .foreach { target =>
- target.Actor ! Vitality.Damage(
- DamageInteraction(
- SourceEntry(target),
- ExplodingEntityReason(obj, target.DamageModel, instigation),
- target.Position
- ).calculate()
- )
- }
- //important note - these are not returned as targets that were affected
- simpleDeployableTargets
- .filter { target =>
- (target ne obj) && detectionTest(obj, target, radius)
- }
- .foreach { target =>
- zone.LocalEvents ! Vitality.DamageOn(
- target,
- DamageInteraction(
- SourceEntry(target),
- ExplodingEntityReason(obj, target.DamageModel, instigation),
- target.Position
- ).calculate()
- )
- }
- allAffectedTargets
+ source.Definition.innateDamage match {
+ case Some(damage) =>
+ serverSideDamage(zone, source, damage, createInteraction, testTargetsFromZone, acquireTargetsFromZone)
case None =>
Nil
}
}
/**
- * Allocates `Damageable` targets within the radius of a server-prepared electromagnetic pulse
- * and informs those entities that they have affected by the aforementioned pulse.
- * Targets within the effect radius within other rooms are affected, unlike with normal damage.
- * The only affected target is Boomer deployables.
- * @see `Amenity.Owner`
- * @see `BoomerDeployable`
+ * Allocates `Damageable` targets within the vicinity of server-prepared damage dealing
+ * and informs those entities that they have affected by the aforementioned damage.
+ * Usually, this is considered an "explosion;" but, the application can be utilized for a variety of unbound damage.
* @see `DamageInteraction`
* @see `DamageResult`
* @see `DamageWithPosition`
- * @see `EmpReason`
- * @see `Zone.DeployableList`
- * @param zone the zone in which the emp should occur
- * @param obj the entity that triggered the emp (information)
- * @param sourcePosition where the emp physically originates
- * @param effect characteristics of the emp produced
- * @param detectionTest a custom test to determine if any given target is affected;
- * defaults to an internal test for simple radial proximity
- * @return a list of affected entities
+ * @see `Vitality.Damage`
+ * @see `Vitality.DamageOn`
+ * @see `VitalityDefinition`
+ * @see `VitalityDefinition.innateDamage`
+ * @see `Zone.LocalEvents`
+ * @param zone the zone in which the damage should occur
+ * @param source the entity that embodies the damage (information)
+ * @param createInteraction how the interaction for this damage is to prepared
+ * @param testTargetsFromZone a custom test for determining whether the allocated targets are affected by the damage
+ * @param acquireTargetsFromZone the main target-collecting algorithm
+ * @return a list of affected entities;
+ * only mostly complete due to the exclusion of objects whose damage resolution is different than usual
*/
- def causeSpecialEmp(
- zone: Zone,
- obj: PlanetSideServerObject with Vitality,
- sourcePosition: Vector3,
- effect: DamageWithPosition,
- detectionTest: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = distanceCheck
- ): List[PlanetSideServerObject] = {
- val proxy: ExplosiveDeployable = {
- //construct a proxy unit to represent the pulse
- val o = new ExplosiveDeployable(GlobalDefinitions.special_emp)
- o.Owner = Some(obj.GUID)
- o.OwnerName = obj match {
- case p: Player => p.Name
- case o: OwnableByPlayer => o.OwnerName.getOrElse("")
- case _ => ""
- }
- o.Position = sourcePosition
- o.Faction = obj.Faction
- o
- }
- val radius = effect.DamageRadius * effect.DamageRadius
- //only boomers can be affected (that's why it's special)
- val allAffectedTargets = zone.DeployableList
- .collect { case o: BoomerDeployable if !o.Destroyed && (o ne obj) && detectionTest(proxy, o, radius) => o }
- //inform targets that they have suffered the effects of the emp
+ def serverSideDamage(
+ zone: Zone,
+ source: PlanetSideGameObject with FactionAffinity with Vitality,
+ properties: DamageWithPosition,
+ createInteraction: (PlanetSideGameObject with FactionAffinity with Vitality, PlanetSideGameObject with FactionAffinity with Vitality) => DamageInteraction,
+ testTargetsFromZone: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean,
+ acquireTargetsFromZone: (Zone, PlanetSideGameObject with FactionAffinity with Vitality, DamageWithPosition) => (List[PlanetSideServerObject with Vitality], List[PlanetSideGameObject with FactionAffinity with Vitality])
+ ): List[PlanetSideServerObject] = {
+ //collect targets that can be damaged
+ val (pssos, psgos) = acquireTargetsFromZone(zone, source, properties)
+ val radius = properties.DamageRadius * properties.DamageRadius
+ //restrict to targets according to the detection plan
+ val allAffectedTargets = pssos.filter { target => testTargetsFromZone(source, target, radius) }
+ //inform remaining targets that they have suffered damage
allAffectedTargets
- .foreach { target =>
- target.Actor ! Vitality.Damage(
- DamageInteraction(
- SourceEntry(target),
- EmpReason(obj, effect, target),
- sourcePosition
- ).calculate()
- )
- }
+ .foreach { target => target.Actor ! Vitality.Damage(createInteraction(source, target).calculate()) }
+ //important note - these are not returned as targets that were affected
+ psgos
+ .filter { target => testTargetsFromZone(source, target, radius) }
+ .foreach { target => zone.LocalEvents ! Vitality.DamageOn(target, createInteraction(source, target).calculate()) }
allAffectedTargets
}
+ /**
+ * na
+ * @see `Amenity.Owner`
+ * @see `ComplexDeployable`
+ * @see `DamageWithPosition`
+ * @see `SimpleDeployable`
+ * @see `Zone.Buildings`
+ * @see `Zone.DeployableList`
+ * @see `Zone.LivePlayers`
+ * @see `Zone.Vehicles`
+ * @param zone the zone in which to search
+ * @param source a game entity that is treated as the origin and is excluded from results
+ * @param damagePropertiesBySource information about the effect/damage
+ * @return two lists of objects with different characteristics;
+ * the first list is `PlanetSideServerObject` entities with `Vitality`;
+ * the second list is `PlanetSideGameObject` entities with both `Vitality` and `FactionAffinity`
+ */
+ def findAllTargets(
+ zone: Zone,
+ source: PlanetSideGameObject with Vitality,
+ damagePropertiesBySource: DamageWithPosition
+ ): (List[PlanetSideServerObject with Vitality], List[PlanetSideGameObject with FactionAffinity with Vitality]) = {
+ val sourcePosition = source.Position
+ val sourcePositionXY = sourcePosition.xy
+ val radius = damagePropertiesBySource.DamageRadius * damagePropertiesBySource.DamageRadius
+ //collect all targets that can be damaged
+ //players
+ val playerTargets = zone.LivePlayers.filterNot { _.VehicleSeated.nonEmpty }
+ //vehicles
+ val vehicleTargets = zone.Vehicles.filterNot { v => v.Destroyed || v.MountedIn.nonEmpty }
+ //deployables
+ val (simpleDeployableTargets, complexDeployableTargets) =
+ zone.DeployableList
+ .filterNot { _.Destroyed }
+ .foldRight((List.empty[SimpleDeployable], List.empty[ComplexDeployable])) { case (f, (simp, comp)) =>
+ f match {
+ case o: SimpleDeployable => (simp :+ o, comp)
+ case o: ComplexDeployable => (simp, comp :+ o)
+ case _ => (simp, comp)
+ }
+ }
+ //amenities
+ val soiTargets = source match {
+ case o: Amenity =>
+ //fortunately, even where soi overlap, amenities in different buildings are never that close to each other
+ o.Owner.Amenities
+ case _ =>
+ zone.Buildings.values
+ .filter { b =>
+ val soiRadius = b.Definition.SOIRadius * b.Definition.SOIRadius
+ Vector3.DistanceSquared(sourcePositionXY, b.Position.xy) < soiRadius || soiRadius <= radius
+ }
+ .flatMap { _.Amenities }
+ .filter { _.Definition.Damageable }
+ }
+ (
+ (playerTargets ++ vehicleTargets ++ complexDeployableTargets ++ soiTargets)
+ .filter { target => target ne source },
+ simpleDeployableTargets
+ .filter { target => target ne source }
+ )
+ }
+
+ /**
+ * na
+ * @param instigation what previous event happened, if any, that caused this explosion
+ * @param source a game object that represents the source of the explosion
+ * @param target a game object that is affected by the explosion
+ * @return a `DamageInteraction` object
+ */
+ def explosionDamage(
+ instigation: Option[DamageResult]
+ )
+ (
+ source: PlanetSideGameObject with FactionAffinity with Vitality,
+ target: PlanetSideGameObject with FactionAffinity with Vitality
+ ): DamageInteraction = {
+ DamageInteraction(
+ SourceEntry(target),
+ ExplodingEntityReason(source, target.DamageModel, instigation),
+ target.Position
+ )
+ }
+
/**
* Two game entities are considered "near" each other if they are within a certain distance of one another.
- * A default function literal mainly used for `causesExplosion`.
- * @see `causeExplosion`
+ * A default function literal mainly used for `serverSideDamage`.
* @see `ObjectDefinition.Geometry`
- * @param obj1 a game entity, should be the source of the explosion
- * @param obj2 a game entity, should be the target of the explosion
+ * @see `serverSideDamage`
+ * @param obj1 a game entity, should be the source of the damage
+ * @param obj2 a game entity, should be the target of the damage
* @param maxDistance the square of the maximum distance permissible between game entities
* before they are no longer considered "near"
- * @return `true`, if the target entities are near enough to each other;
+ * @return `true`, if the two entities are near enough to each other;
* `false`, otherwise
*/
def distanceCheck(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float): Boolean = {
@@ -1278,7 +1271,7 @@ object Zone {
* @return `true`, if the target entities are near enough to each other;
* `false`, otherwise
*/
- def distanceCheck(g1: Geometry3D, g2: Geometry3D, maxDistance: Float): Boolean = {
+ private def distanceCheck(g1: Geometry3D, g2: Geometry3D, maxDistance: Float): Boolean = {
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3) <= maxDistance ||
distanceCheck(g1, g2) <= maxDistance
}
diff --git a/src/main/scala/net/psforever/objects/zones/Zoning.scala b/src/main/scala/net/psforever/objects/zones/Zoning.scala
index 26b064476..16848a0b9 100644
--- a/src/main/scala/net/psforever/objects/zones/Zoning.scala
+++ b/src/main/scala/net/psforever/objects/zones/Zoning.scala
@@ -7,7 +7,7 @@ object Zoning {
object Method extends Enumeration {
type Type = Value
- val None, InstantAction, Recall, Quit = Value
+ val None, InstantAction, OutfitRecall, Recall, Quit = Value
}
object Status extends Enumeration {