mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
Vehicle Spawn Pad QoL (#802)
* preparation for redefining the checks involved in spawning a vehicle from a spawn pad * pass the terminal from which a vehicle order was issued to be used in validation tests; implemented validation tests to ensure delayed orders remain valid until being executed; various messages about orders that have failed validation, as well as order changes and queue changes * local zoning field on player for use in statusing and message composition * expiration of a vehicle order for a given reason; linking of messages for expiration of vehicle order queue; death on an active vehicle pad (during rail operation) * players that die to spawning vehicles can blame that vehicle's future driver; the calculations for server-side damage are heavily modified * definitions for vehicle spawn pad kill box, used during vehicle generation to eliminate targets on the spawn pad, per kind of vehicle spawn pad * reusing common order validation test for some stages of the vehicle order fulfillment process * adjusts when the vehicle thinks it is mounted to the spawn pad; vehicle wreckage should be cleaned up quicker * cancelling the active order
This commit is contained in:
parent
42e4db8972
commit
7fca0a5582
|
|
@ -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
|
||||
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))
|
||||
|
|
@ -64,8 +65,8 @@ class VehicleSpawnControl3Test extends ActorTest {
|
|||
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)
|
||||
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
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
@ -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]
|
||||
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 None =>
|
||||
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))
|
||||
}
|
||||
}
|
||||
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,11 +6064,12 @@ 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 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,12 +7879,16 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
|||
* Stop all movement entirely.
|
||||
* @param vehicle the vehicle
|
||||
*/
|
||||
def ConditionalDriverVehicleControl(vehicle: Vehicle): Unit = {
|
||||
if (serverVehicleControlVelocity.nonEmpty && !serverVehicleControlVelocity.contains(0)) {
|
||||
TotalDriverVehicleControl(vehicle)
|
||||
}
|
||||
}
|
||||
|
||||
def TotalDriverVehicleControl(vehicle: Vehicle): Unit = {
|
||||
if (serverVehicleControlVelocity.nonEmpty) {
|
||||
serverVehicleControlVelocity = None
|
||||
sendResponse(ServerVehicleOverrideMsg(false, false, false, false, 0, 0, 0, None))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a globally unique identifier in the 40100 to 40124 range
|
||||
|
|
@ -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 _ => ;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -7043,7 +7034,6 @@ object GlobalDefinitions {
|
|||
val smallTurret = GeometryForm.representByCylinder(radius = 0.48435f, height = 1.23438f) _
|
||||
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,7 +7086,6 @@ 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
|
||||
|
|
@ -7122,7 +7109,6 @@ object GlobalDefinitions {
|
|||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
spitfire_turret.Geometry = smallTurret
|
||||
|
||||
spitfire_cloaked.Name = "spitfire_cloaked"
|
||||
spitfire_cloaked.Descriptor = "CloakingSpitfires"
|
||||
spitfire_cloaked.MaxHealth = 100
|
||||
|
|
@ -7145,7 +7131,6 @@ object GlobalDefinitions {
|
|||
Modifiers = ExplodingRadialDegrade
|
||||
}
|
||||
spitfire_cloaked.Geometry = smallTurret
|
||||
|
||||
spitfire_aa.Name = "spitfire_aa"
|
||||
spitfire_aa.Descriptor = "FlakSpitfires"
|
||||
spitfire_aa.MaxHealth = 100
|
||||
|
|
@ -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"
|
||||
|
|
@ -7234,7 +7215,6 @@ 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
|
||||
|
|
@ -7262,7 +7242,6 @@ 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
|
||||
|
|
@ -7290,7 +7269,6 @@ 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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 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
|
||||
released = true
|
||||
backpack = !isAlive
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
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]
|
||||
|
|
|
|||
144
src/main/scala/net/psforever/objects/SpecialEmp.scala
Normal file
144
src/main/scala/net/psforever/objects/SpecialEmp.scala
Normal file
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}")
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
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,13 +139,10 @@ 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
|
||||
|
|
@ -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
|
||||
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(orders.length + 1)
|
||||
Some(s"@SVCP_PositionInQueue^2~^2~")
|
||||
)
|
||||
} else {
|
||||
VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
|
||||
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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
val
|
||||
Queue, //optional data is the numeric position in the queue
|
||||
Blocked, //optional data is a message regarding the blockage
|
||||
Cancelled = Value
|
||||
Cancelled //optional data is the message
|
||||
= Value
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 _ => ;
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
|
|
|
|||
|
|
@ -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.<br>
|
||||
* <br>
|
||||
* 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 _ => ;
|
||||
|
|
|
|||
|
|
@ -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,55 +31,60 @@ 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)
|
||||
)
|
||||
} else {
|
||||
trace("owner lost or vehicle in poor condition; abort order fulfillment")
|
||||
VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
||||
}
|
||||
|
||||
case Zone.Vehicle.HasSpawned(zone, vehicle) =>
|
||||
val definition = vehicle.Definition
|
||||
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 = vehicle.GUID
|
||||
val vdata = definition.Packet.ConstructorData(vehicle).get
|
||||
val vguid = v.GUID
|
||||
val vdata = definition.Packet.ConstructorData(v).get
|
||||
zone.VehicleEvents ! VehicleServiceMessage(
|
||||
zone.id,
|
||||
VehicleAction.LoadVehicle(Service.defaultPlayerGUID, vehicle, vtype, vguid, vdata)
|
||||
VehicleAction.LoadVehicle(Service.defaultPlayerGUID, v, vtype, vguid, vdata)
|
||||
)
|
||||
railJack ! temp.get
|
||||
temp = None
|
||||
|
||||
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 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")
|
||||
context.parent ! VehicleSpawnControl.ProcessControl.OrderCancelled
|
||||
}
|
||||
|
||||
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
|
||||
|
|
@ -84,7 +92,3 @@ class VehicleSpawnControlLoadVehicle(pad: VehicleSpawnPad) extends VehicleSpawnC
|
|||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
object VehicleSpawnControlLoadVehicle {
|
||||
private case class WaitOnSpawn(order: VehicleSpawnControl.Order)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.<br>
|
||||
* <br>
|
||||
* 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,42 +1093,100 @@ 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(
|
||||
def serverSideDamage(
|
||||
zone: Zone,
|
||||
obj: PlanetSideGameObject with Vitality,
|
||||
instigation: Option[DamageResult],
|
||||
detectionTest: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = distanceCheck
|
||||
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
|
||||
source.Definition.innateDamage match {
|
||||
case Some(damage) =>
|
||||
serverSideDamage(zone, source, damage, createInteraction, testTargetsFromZone, acquireTargetsFromZone)
|
||||
case None =>
|
||||
Nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 `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 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(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 = explosion.DamageRadius * explosion.DamageRadius
|
||||
val radius = damagePropertiesBySource.DamageRadius * damagePropertiesBySource.DamageRadius
|
||||
//collect all targets that can be damaged
|
||||
//players
|
||||
val playerTargets = zone.LivePlayers.filterNot { _.VehicleSeated.nonEmpty }
|
||||
|
|
@ -1145,7 +1204,7 @@ object Zone {
|
|||
}
|
||||
}
|
||||
//amenities
|
||||
val soiTargets = obj match {
|
||||
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
|
||||
|
|
@ -1158,111 +1217,45 @@ object Zone {
|
|||
.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
|
||||
(
|
||||
(playerTargets ++ vehicleTargets ++ complexDeployableTargets ++ soiTargets)
|
||||
.filter { target => target ne source },
|
||||
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()
|
||||
.filter { target => target ne source }
|
||||
)
|
||||
}
|
||||
allAffectedTargets
|
||||
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`
|
||||
* @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
|
||||
* 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 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
|
||||
allAffectedTargets
|
||||
.foreach { target =>
|
||||
target.Actor ! Vitality.Damage(
|
||||
def explosionDamage(
|
||||
instigation: Option[DamageResult]
|
||||
)
|
||||
(
|
||||
source: PlanetSideGameObject with FactionAffinity with Vitality,
|
||||
target: PlanetSideGameObject with FactionAffinity with Vitality
|
||||
): DamageInteraction = {
|
||||
DamageInteraction(
|
||||
SourceEntry(target),
|
||||
EmpReason(obj, effect, target),
|
||||
sourcePosition
|
||||
).calculate()
|
||||
ExplodingEntityReason(source, target.DamageModel, instigation),
|
||||
target.Position
|
||||
)
|
||||
}
|
||||
allAffectedTargets
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue