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 akka.actor.typed.scaladsl.adapter._
|
||||||
import net.psforever.actors.zone.ZoneActor
|
import net.psforever.actors.zone.ZoneActor
|
||||||
import net.psforever.objects.avatar.Avatar
|
import net.psforever.objects.avatar.Avatar
|
||||||
|
import net.psforever.objects.serverobject.terminals.Terminal
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
|
|
@ -29,11 +30,11 @@ class VehicleSpawnControl1Test extends ActorTest {
|
||||||
class VehicleSpawnControl2Test extends ActorTest {
|
class VehicleSpawnControl2Test extends ActorTest {
|
||||||
"VehicleSpawnControl" should {
|
"VehicleSpawnControl" should {
|
||||||
"complete a vehicle order" in {
|
"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")
|
val probe = new TestProbe(system, "zone-events")
|
||||||
|
|
||||||
zone.VehicleEvents = probe.ref //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[VehicleSpawnPad.ConcealPlayer])
|
||||||
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
|
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
|
||||||
|
|
@ -55,7 +56,7 @@ class VehicleSpawnControl2Test extends ActorTest {
|
||||||
class VehicleSpawnControl3Test extends ActorTest {
|
class VehicleSpawnControl3Test extends ActorTest {
|
||||||
"VehicleSpawnControl" should {
|
"VehicleSpawnControl" should {
|
||||||
"block the second vehicle order until the first is completed" in {
|
"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
|
//we can recycle the vehicle and the player for each order
|
||||||
val probe = new TestProbe(system, "zone-events")
|
val probe = new TestProbe(system, "zone-events")
|
||||||
val player2 = Player(Avatar(0, "test2", player.Faction, CharacterSex.Male, 0, CharacterVoice.Mute))
|
val player2 = Player(Avatar(0, "test2", player.Faction, CharacterSex.Male, 0, CharacterVoice.Mute))
|
||||||
|
|
@ -63,9 +64,9 @@ class VehicleSpawnControl3Test extends ActorTest {
|
||||||
player2.Continent = zone.id
|
player2.Continent = zone.id
|
||||||
player2.Spawn()
|
player2.Spawn()
|
||||||
|
|
||||||
zone.VehicleEvents = probe.ref //zone events
|
zone.VehicleEvents = probe.ref //zone events
|
||||||
pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle) //first order
|
pad.Actor ! VehicleSpawnPad.VehicleOrder(player, vehicle, terminal) //first order
|
||||||
pad.Actor ! VehicleSpawnPad.VehicleOrder(player2, vehicle) //second order (vehicle shared)
|
pad.Actor ! VehicleSpawnPad.VehicleOrder(player2, vehicle, terminal) //second order (vehicle shared)
|
||||||
|
|
||||||
assert(probe.receiveOne(1 seconds) match {
|
assert(probe.receiveOne(1 seconds) match {
|
||||||
case VehicleSpawnPad.PeriodicReminder(_, VehicleSpawnPad.Reminders.Queue, _) => true
|
case VehicleSpawnPad.PeriodicReminder(_, VehicleSpawnPad.Reminders.Queue, _) => true
|
||||||
|
|
@ -103,12 +104,12 @@ class VehicleSpawnControl3Test extends ActorTest {
|
||||||
class VehicleSpawnControl4Test extends ActorTest {
|
class VehicleSpawnControl4Test extends ActorTest {
|
||||||
"VehicleSpawnControl" should {
|
"VehicleSpawnControl" should {
|
||||||
"clean up the vehicle if the driver-to-be is on the wrong continent" in {
|
"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")
|
val probe = new TestProbe(system, "zone-events")
|
||||||
zone.VehicleEvents = probe.ref
|
zone.VehicleEvents = probe.ref
|
||||||
player.Continent = "problem" //problem
|
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)
|
val msg = probe.receiveOne(1 minute)
|
||||||
// assert(
|
// assert(
|
||||||
|
|
@ -125,11 +126,11 @@ class VehicleSpawnControl4Test extends ActorTest {
|
||||||
class VehicleSpawnControl5Test extends ActorTest() {
|
class VehicleSpawnControl5Test extends ActorTest() {
|
||||||
"VehicleSpawnControl" should {
|
"VehicleSpawnControl" should {
|
||||||
"abandon a destroyed vehicle on the spawn pad (blocking)" in {
|
"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")
|
val probe = new TestProbe(system, "zone-events")
|
||||||
zone.VehicleEvents = probe.ref
|
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[VehicleSpawnPad.ConcealPlayer])
|
||||||
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
|
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
|
||||||
|
|
@ -152,11 +153,11 @@ class VehicleSpawnControl5Test extends ActorTest() {
|
||||||
class VehicleSpawnControl6Test extends ActorTest() {
|
class VehicleSpawnControl6Test extends ActorTest() {
|
||||||
"VehicleSpawnControl" should {
|
"VehicleSpawnControl" should {
|
||||||
"abandon a vehicle on the spawn pad if driver is unfit to drive (blocking)" in {
|
"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")
|
val probe = new TestProbe(system, "zone-events")
|
||||||
zone.VehicleEvents = probe.ref
|
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[VehicleSpawnPad.ConcealPlayer])
|
||||||
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
|
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
|
||||||
|
|
@ -179,12 +180,12 @@ class VehicleSpawnControl6Test extends ActorTest() {
|
||||||
class VehicleSpawnControl7Test extends ActorTest {
|
class VehicleSpawnControl7Test extends ActorTest {
|
||||||
"VehicleSpawnControl" should {
|
"VehicleSpawnControl" should {
|
||||||
"abandon a vehicle on the spawn pad if driver is unfit to drive (blocking)" in {
|
"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")
|
val probe = new TestProbe(system, "zone-events")
|
||||||
player.ExoSuit = ExoSuitType.MAX
|
player.ExoSuit = ExoSuitType.MAX
|
||||||
|
|
||||||
zone.VehicleEvents = probe.ref //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[VehicleSpawnPad.ConcealPlayer])
|
||||||
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
|
probe.expectMsgClass(1 minute, classOf[VehicleServiceMessage])
|
||||||
|
|
@ -210,7 +211,7 @@ object VehicleSpawnPadControlTest {
|
||||||
|
|
||||||
def SetUpAgents(
|
def SetUpAgents(
|
||||||
faction: PlanetSideEmpire.Value
|
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.NumberPoolHub
|
||||||
import net.psforever.objects.guid.source.MaxNumberSource
|
import net.psforever.objects.guid.source.MaxNumberSource
|
||||||
import net.psforever.objects.serverobject.structures.Building
|
import net.psforever.objects.serverobject.structures.Building
|
||||||
|
|
@ -218,6 +219,7 @@ object VehicleSpawnPadControlTest {
|
||||||
import net.psforever.objects.Tool
|
import net.psforever.objects.Tool
|
||||||
import net.psforever.types.CharacterSex
|
import net.psforever.types.CharacterSex
|
||||||
|
|
||||||
|
val terminal = Terminal(GlobalDefinitions.vehicle_terminal_combined)
|
||||||
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
|
||||||
val weapon = vehicle.WeaponControlledFromSeat(1).get.asInstanceOf[Tool]
|
val weapon = vehicle.WeaponControlledFromSeat(1).get.asInstanceOf[Tool]
|
||||||
val guid: NumberPoolHub = new NumberPoolHub(MaxNumberSource(5))
|
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
|
//note: pad and vehicle are both at Vector3(1,0,0) so they count as blocking
|
||||||
pad.Position = Vector3(1, 0, 0)
|
pad.Position = Vector3(1, 0, 0)
|
||||||
vehicle.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.vehicles._
|
||||||
import net.psforever.objects.vital._
|
import net.psforever.objects.vital._
|
||||||
import net.psforever.objects.vital.base._
|
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.interaction.DamageInteraction
|
||||||
import net.psforever.objects.vital.projectile.ProjectileReason
|
import net.psforever.objects.vital.projectile.ProjectileReason
|
||||||
import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning}
|
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.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
|
||||||
import net.psforever.services.properties.PropertyOverrideManager
|
import net.psforever.services.properties.PropertyOverrideManager
|
||||||
import net.psforever.services.support.SupportActor
|
import net.psforever.services.support.SupportActor
|
||||||
import net.psforever.services.teamwork.{
|
import net.psforever.services.teamwork.{SquadResponse, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction}
|
||||||
SquadResponse,
|
|
||||||
SquadServiceMessage,
|
|
||||||
SquadServiceResponse,
|
|
||||||
SquadAction => SquadServiceAction
|
|
||||||
}
|
|
||||||
import net.psforever.services.hart.HartTimer
|
import net.psforever.services.hart.HartTimer
|
||||||
import net.psforever.services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
|
import net.psforever.services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
|
||||||
import net.psforever.services.{RemoverActor, Service, ServiceManager, InterstellarClusterService => ICS}
|
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
|
session.player.spectator = spectator
|
||||||
|
|
||||||
case Recall() =>
|
case Recall() =>
|
||||||
|
player.ZoningRequest = Zoning.Method.Recall
|
||||||
zoningType = Zoning.Method.Recall
|
zoningType = Zoning.Method.Recall
|
||||||
zoningChatMessageType = ChatMessageType.CMT_RECALL
|
zoningChatMessageType = ChatMessageType.CMT_RECALL
|
||||||
zoningStatus = Zoning.Status.Request
|
zoningStatus = Zoning.Status.Request
|
||||||
|
|
@ -456,6 +453,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
})
|
})
|
||||||
|
|
||||||
case InstantAction() =>
|
case InstantAction() =>
|
||||||
|
player.ZoningRequest = Zoning.Method.InstantAction
|
||||||
zoningType = Zoning.Method.InstantAction
|
zoningType = Zoning.Method.InstantAction
|
||||||
zoningChatMessageType = ChatMessageType.CMT_INSTANTACTION
|
zoningChatMessageType = ChatMessageType.CMT_INSTANTACTION
|
||||||
zoningStatus = Zoning.Status.Request
|
zoningStatus = Zoning.Status.Request
|
||||||
|
|
@ -482,10 +480,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
cluster ! ICS.GetInstantActionSpawnPoint(player.Faction, context.self)
|
cluster ! ICS.GetInstantActionSpawnPoint(player.Faction, context.self)
|
||||||
|
|
||||||
case Quit() =>
|
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) {
|
if (session.zoningType == Zoning.Method.InstantAction || session.zoningType == Zoning.Method.Recall) {
|
||||||
CancelZoningProcessWithDescriptiveReason("cancel")
|
CancelZoningProcessWithDescriptiveReason("cancel")
|
||||||
}
|
}
|
||||||
|
player.ZoningRequest = Zoning.Method.Quit
|
||||||
zoningType = Zoning.Method.Quit
|
zoningType = Zoning.Method.Quit
|
||||||
zoningChatMessageType = ChatMessageType.CMT_QUIT
|
zoningChatMessageType = ChatMessageType.CMT_QUIT
|
||||||
zoningStatus = Zoning.Status.Request
|
zoningStatus = Zoning.Status.Request
|
||||||
|
|
@ -1726,6 +1725,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
*/
|
*/
|
||||||
def CancelZoningProcess(): Unit = {
|
def CancelZoningProcess(): Unit = {
|
||||||
zoningTimer.cancel()
|
zoningTimer.cancel()
|
||||||
|
player.ZoningRequest = Zoning.Method.None
|
||||||
zoningType = Zoning.Method.None
|
zoningType = Zoning.Method.None
|
||||||
zoningStatus = Zoning.Status.None
|
zoningStatus = Zoning.Status.None
|
||||||
zoningCounter = 0
|
zoningCounter = 0
|
||||||
|
|
@ -1859,6 +1859,16 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
|
|
||||||
DropSpecialSlotItem()
|
DropSpecialSlotItem()
|
||||||
ToggleMaxSpecialState(enable = false)
|
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
|
keepAliveFunc = NormalKeepAlive
|
||||||
zoningStatus = Zoning.Status.None
|
zoningStatus = Zoning.Status.None
|
||||||
|
|
@ -1866,7 +1876,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
|
|
||||||
continent.GUID(mount) match {
|
continent.GUID(mount) match {
|
||||||
case Some(obj: Vehicle) =>
|
case Some(obj: Vehicle) =>
|
||||||
TotalDriverVehicleControl(obj)
|
ConditionalDriverVehicleControl(obj)
|
||||||
UnaccessContainer(obj)
|
UnaccessContainer(obj)
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
@ -2190,6 +2200,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
specialItemSlotGuid match {
|
specialItemSlotGuid match {
|
||||||
case Some(guid: PlanetSideGUID) =>
|
case Some(guid: PlanetSideGUID) =>
|
||||||
specialItemSlotGuid = None
|
specialItemSlotGuid = None
|
||||||
|
player.Carrying = None
|
||||||
continent.GUID(guid) match {
|
continent.GUID(guid) match {
|
||||||
case Some(llu: CaptureFlag) =>
|
case Some(llu: CaptureFlag) =>
|
||||||
llu.Carrier match {
|
llu.Carrier match {
|
||||||
|
|
@ -2398,6 +2409,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case Some(guid) =>
|
case Some(guid) =>
|
||||||
if (guid == llu.GUID) {
|
if (guid == llu.GUID) {
|
||||||
specialItemSlotGuid = None
|
specialItemSlotGuid = None
|
||||||
|
player.Carrying = None
|
||||||
}
|
}
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
@ -2622,7 +2634,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
val player_guid: PlanetSideGUID = tplayer.GUID
|
val player_guid: PlanetSideGUID = tplayer.GUID
|
||||||
if (player_guid == player.GUID) {
|
if (player_guid == player.GUID) {
|
||||||
//disembarking self
|
//disembarking self
|
||||||
TotalDriverVehicleControl(obj)
|
ConditionalDriverVehicleControl(obj)
|
||||||
UnaccessContainer(obj)
|
UnaccessContainer(obj)
|
||||||
DismountAction(tplayer, obj, seat_num)
|
DismountAction(tplayer, obj, seat_num)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -2675,7 +2687,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case None =>
|
case None =>
|
||||||
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
|
avatarActor ! AvatarActor.UpdatePurchaseTime(item.Definition)
|
||||||
continent.tasks ! BuyNewEquipmentPutInInventory(
|
continent.tasks ! BuyNewEquipmentPutInInventory(
|
||||||
continent.GUID(tplayer.VehicleSeated) match { case Some(v: Vehicle) => v; case _ => player },
|
continent.GUID(tplayer.VehicleSeated) match { case Some(v : Vehicle) => v; case _ => player },
|
||||||
tplayer,
|
tplayer,
|
||||||
msg.terminal_guid
|
msg.terminal_guid
|
||||||
)(item)
|
)(item)
|
||||||
|
|
@ -2701,13 +2713,18 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
lastTerminalOrderFulfillment = true
|
lastTerminalOrderFulfillment = true
|
||||||
|
|
||||||
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
|
case Terminal.BuyVehicle(vehicle, weapons, trunk) =>
|
||||||
continent.map.terminalToSpawnPad.get(msg.terminal_guid.guid) match {
|
tplayer.avatar.purchaseCooldown(vehicle.Definition) match {
|
||||||
case Some(padGuid) =>
|
case Some(_) =>
|
||||||
tplayer.avatar.purchaseCooldown(vehicle.Definition) match {
|
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
||||||
case Some(_) =>
|
case None =>
|
||||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
continent.map.terminalToSpawnPad
|
||||||
case None =>
|
.find { case (termid, _) => termid == msg.terminal_guid.guid }
|
||||||
val pad = continent.GUID(padGuid).get.asInstanceOf[VehicleSpawnPad]
|
.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.Faction = tplayer.Faction
|
||||||
vehicle.Position = pad.Position
|
vehicle.Position = pad.Position
|
||||||
vehicle.Orientation = pad.Orientation + Vector3.z(pad.Definition.VehicleCreationZOrientOffset)
|
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
|
entry.obj.Faction = tplayer.Faction
|
||||||
vTrunk.InsertQuickly(entry.start, entry.obj)
|
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))
|
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
|
||||||
|
case _ =>
|
||||||
|
log.error(
|
||||||
|
s"${tplayer.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it"
|
||||||
|
)
|
||||||
|
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, success = false))
|
||||||
}
|
}
|
||||||
case None =>
|
|
||||||
log.error(
|
|
||||||
s"${tplayer.Name} wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
lastTerminalOrderFulfillment = true
|
lastTerminalOrderFulfillment = true
|
||||||
|
|
||||||
|
|
@ -2793,7 +2811,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
ObjectDetachMessage(
|
ObjectDetachMessage(
|
||||||
pad_guid,
|
pad_guid,
|
||||||
vehicle_guid,
|
vehicle_guid,
|
||||||
pad_position + Vector3(0, 0, pad.VehicleCreationZOffset),
|
pad_position + Vector3.z(pad.VehicleCreationZOffset),
|
||||||
pad_orientation_z + pad.VehicleCreationZOrientOffset
|
pad_orientation_z + pad.VehicleCreationZOrientOffset
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
@ -2958,6 +2976,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, pad) =>
|
case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, pad) =>
|
||||||
val vehicle_guid = vehicle.GUID
|
val vehicle_guid = vehicle.GUID
|
||||||
PlayerActionsToCancel()
|
PlayerActionsToCancel()
|
||||||
|
serverVehicleControlVelocity = Some(0)
|
||||||
CancelAllProximityUnits()
|
CancelAllProximityUnits()
|
||||||
if (player.VisibleSlots.contains(player.DrawnSlot)) {
|
if (player.VisibleSlots.contains(player.DrawnSlot)) {
|
||||||
player.DrawnSlot = Player.HandsDownSlot
|
player.DrawnSlot = Player.HandsDownSlot
|
||||||
|
|
@ -2988,16 +3007,23 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
case VehicleResponse.ServerVehicleOverrideEnd(vehicle, pad) =>
|
case VehicleResponse.ServerVehicleOverrideEnd(vehicle, pad) =>
|
||||||
DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2)
|
DriverVehicleControl(vehicle, vehicle.Definition.AutoPilotSpeed2)
|
||||||
|
|
||||||
case VehicleResponse.PeriodicReminder(cause, data) =>
|
case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) =>
|
||||||
val msg: String = cause match {
|
sendResponse(ChatMsg(
|
||||||
case VehicleSpawnPad.Reminders.Blocked =>
|
ChatMessageType.CMT_OPEN,
|
||||||
s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}"
|
true,
|
||||||
case VehicleSpawnPad.Reminders.Queue =>
|
"",
|
||||||
s"Your position in the vehicle spawn queue is ${data.getOrElse("dead last")}."
|
s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}",
|
||||||
case VehicleSpawnPad.Reminders.Cancelled =>
|
None
|
||||||
"Your vehicle order has been cancelled."
|
))
|
||||||
|
|
||||||
|
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) =>
|
case VehicleResponse.ChangeLoadout(target, old_weapons, added_weapons, old_inventory, new_inventory) =>
|
||||||
//TODO when vehicle weapons can be changed without visual glitches, rewrite this
|
//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 _ => log.warn("Item in specialItemSlotGuid is not registered with continent or is not a LLU")
|
||||||
}
|
}
|
||||||
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
||||||
case Some(obj: FacilityTurret) =>
|
case Some(obj: FacilityTurret) =>
|
||||||
|
|
@ -5101,6 +5128,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
if (specialItemSlotGuid.isEmpty) {
|
if (specialItemSlotGuid.isEmpty) {
|
||||||
if (obj.Faction == player.Faction) {
|
if (obj.Faction == player.Faction) {
|
||||||
specialItemSlotGuid = Some(obj.GUID)
|
specialItemSlotGuid = Some(obj.GUID)
|
||||||
|
player.Carrying = SpecialCarry.CaptureFlag
|
||||||
continent.LocalEvents ! CaptureFlagManager.PickupFlag(obj, player)
|
continent.LocalEvents ! CaptureFlagManager.PickupFlag(obj, player)
|
||||||
} else {
|
} else {
|
||||||
log.warn(
|
log.warn(
|
||||||
|
|
@ -5504,11 +5532,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
projectile.profile.JammerProjectile ||
|
projectile.profile.JammerProjectile ||
|
||||||
projectile.profile.SympatheticExplosion
|
projectile.profile.SympatheticExplosion
|
||||||
) {
|
) {
|
||||||
Zone.causeSpecialEmp(
|
//can also substitute 'projectile.profile' for 'SpecialEmp.emp'
|
||||||
|
Zone.serverSideDamage(
|
||||||
continent,
|
continent,
|
||||||
player,
|
player,
|
||||||
explosion_pos,
|
SpecialEmp.emp,
|
||||||
GlobalDefinitions.special_emp.innateDamage.get
|
SpecialEmp.createEmpInteraction(SpecialEmp.emp, explosion_pos),
|
||||||
|
SpecialEmp.prepareDistanceCheck(player, explosion_pos, player.Faction),
|
||||||
|
SpecialEmp.findAllBoomers
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
|
if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
|
||||||
|
|
@ -6033,12 +6064,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* @see `RegisterVehicle`
|
* @see `RegisterVehicle`
|
||||||
* @return a `TaskResolver.GiveTask` message
|
* @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(
|
TaskResolver.GiveTask(
|
||||||
new Task() {
|
new Task() {
|
||||||
private val localVehicle = obj
|
private val localVehicle = obj
|
||||||
private val localPad = pad.Actor
|
private val localPad = pad.Actor
|
||||||
private val localPlayer = player
|
private val localTerminal = terminal
|
||||||
|
private val localPlayer = player
|
||||||
|
|
||||||
override def Description: String = s"register a ${localVehicle.Definition.Name} for spawn pad"
|
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 = {
|
def Execute(resolver: ActorRef): Unit = {
|
||||||
localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle)
|
localPad ! VehicleSpawnPad.VehicleOrder(localPlayer, localVehicle, localTerminal)
|
||||||
resolver ! Success(this)
|
resolver ! Success(this)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -7027,10 +7059,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
progressBarUpdate.cancel()
|
progressBarUpdate.cancel()
|
||||||
progressBarValue = None
|
progressBarValue = None
|
||||||
lastTerminalOrderFulfillment = true
|
lastTerminalOrderFulfillment = true
|
||||||
serverVehicleControlVelocity = None
|
|
||||||
accessedContainer match {
|
accessedContainer match {
|
||||||
case Some(v: Vehicle) =>
|
case Some(v: Vehicle) =>
|
||||||
val vguid = v.GUID
|
val vguid = v.GUID
|
||||||
|
ConditionalDriverVehicleControl(v)
|
||||||
if (v.AccessingTrunk.contains(player.GUID)) {
|
if (v.AccessingTrunk.contains(player.GUID)) {
|
||||||
if (player.VehicleSeated.contains(vguid)) {
|
if (player.VehicleSeated.contains(vguid)) {
|
||||||
v.AccessingTrunk = None //player is seated; just stop accessing trunk
|
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
|
* Set the vehicle to move in reverse
|
||||||
*/
|
*/
|
||||||
def ServerVehicleLockReverse(): Unit = {
|
def ServerVehicleLockReverse(): Unit = {
|
||||||
serverVehicleControlVelocity = Some(0)
|
serverVehicleControlVelocity = Some(-1)
|
||||||
sendResponse(
|
sendResponse(
|
||||||
ServerVehicleOverrideMsg(
|
ServerVehicleOverrideMsg(
|
||||||
lock_accelerator = true,
|
lock_accelerator = true,
|
||||||
|
|
@ -7770,7 +7802,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* Set the vehicle to strafe right
|
* Set the vehicle to strafe right
|
||||||
*/
|
*/
|
||||||
def ServerVehicleLockStrafeRight(): Unit = {
|
def ServerVehicleLockStrafeRight(): Unit = {
|
||||||
serverVehicleControlVelocity = Some(0)
|
serverVehicleControlVelocity = Some(-1)
|
||||||
sendResponse(
|
sendResponse(
|
||||||
ServerVehicleOverrideMsg(
|
ServerVehicleOverrideMsg(
|
||||||
lock_accelerator = true,
|
lock_accelerator = true,
|
||||||
|
|
@ -7791,7 +7823,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* Set the vehicle to strafe left
|
* Set the vehicle to strafe left
|
||||||
*/
|
*/
|
||||||
def ServerVehicleLockStrafeLeft(): Unit = {
|
def ServerVehicleLockStrafeLeft(): Unit = {
|
||||||
serverVehicleControlVelocity = Some(0)
|
serverVehicleControlVelocity = Some(-1)
|
||||||
sendResponse(
|
sendResponse(
|
||||||
ServerVehicleOverrideMsg(
|
ServerVehicleOverrideMsg(
|
||||||
lock_accelerator = true,
|
lock_accelerator = true,
|
||||||
|
|
@ -7812,7 +7844,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* @param vehicle the vehicle being controlled
|
* @param vehicle the vehicle being controlled
|
||||||
*/
|
*/
|
||||||
def ServerVehicleLock(vehicle: Vehicle): Unit = {
|
def ServerVehicleLock(vehicle: Vehicle): Unit = {
|
||||||
serverVehicleControlVelocity = Some(0)
|
serverVehicleControlVelocity = Some(-1)
|
||||||
sendResponse(ServerVehicleOverrideMsg(true, true, false, false, 0, 1, 0, Some(0)))
|
sendResponse(ServerVehicleOverrideMsg(true, true, false, false, 0, 1, 0, Some(0)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7847,13 +7879,17 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
* Stop all movement entirely.
|
* Stop all movement entirely.
|
||||||
* @param vehicle the vehicle
|
* @param vehicle the vehicle
|
||||||
*/
|
*/
|
||||||
def TotalDriverVehicleControl(vehicle: Vehicle): Unit = {
|
def ConditionalDriverVehicleControl(vehicle: Vehicle): Unit = {
|
||||||
if (serverVehicleControlVelocity.nonEmpty) {
|
if (serverVehicleControlVelocity.nonEmpty && !serverVehicleControlVelocity.contains(0)) {
|
||||||
serverVehicleControlVelocity = None
|
TotalDriverVehicleControl(vehicle)
|
||||||
sendResponse(ServerVehicleOverrideMsg(false, false, false, false, 0, 0, 0, None))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def TotalDriverVehicleControl(vehicle: Vehicle): Unit = {
|
||||||
|
serverVehicleControlVelocity = None
|
||||||
|
sendResponse(ServerVehicleOverrideMsg(false, false, false, false, 0, 0, 0, None))
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a globally unique identifier in the 40100 to 40124 range
|
* Given a globally unique identifier in the 40100 to 40124 range
|
||||||
* (with an optional 25 as buffer),
|
* (with an optional 25 as buffer),
|
||||||
|
|
@ -8883,6 +8919,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
|
||||||
if (player.avatar.vehicle.nonEmpty && player.VehicleSeated != player.avatar.vehicle) {
|
if (player.avatar.vehicle.nonEmpty && player.VehicleSeated != player.avatar.vehicle) {
|
||||||
continent.GUID(player.avatar.vehicle) match {
|
continent.GUID(player.avatar.vehicle) match {
|
||||||
case Some(vehicle: Vehicle) if vehicle.Actor != Default.Actor =>
|
case Some(vehicle: Vehicle) if vehicle.Actor != Default.Actor =>
|
||||||
|
TotalDriverVehicleControl(vehicle)
|
||||||
vehicle.Actor ! Vehicle.Ownership(None)
|
vehicle.Actor ! Vehicle.Ownership(None)
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -170,7 +170,12 @@ object ExplosiveDeployableControl {
|
||||||
val zone = target.Zone
|
val zone = target.Zone
|
||||||
zone.Activity ! Zone.HotSpot.Activity(cause)
|
zone.Activity ! Zone.HotSpot.Activity(cause)
|
||||||
zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.Detonate(target.GUID, target))
|
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.terminals._
|
||||||
import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
|
import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
|
||||||
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
|
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
|
||||||
import net.psforever.objects.serverobject.structures.{
|
import net.psforever.objects.serverobject.structures.{AmenityDefinition, AutoRepairStats, BuildingDefinition, WarpGateDefinition}
|
||||||
AmenityDefinition,
|
|
||||||
AutoRepairStats,
|
|
||||||
BuildingDefinition,
|
|
||||||
WarpGateDefinition
|
|
||||||
}
|
|
||||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalDefinition
|
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalDefinition
|
||||||
import net.psforever.objects.serverobject.terminals.implant.{ImplantTerminalDefinition, ImplantTerminalMechDefinition}
|
import net.psforever.objects.serverobject.terminals.implant.{ImplantTerminalDefinition, ImplantTerminalMechDefinition}
|
||||||
import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade}
|
import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade}
|
||||||
|
|
@ -982,8 +977,6 @@ object GlobalDefinitions {
|
||||||
|
|
||||||
val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
|
val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
|
||||||
|
|
||||||
val special_emp = ExplosiveDeployableDefinition(DeployedItem.jammer_mine)
|
|
||||||
|
|
||||||
//this is only treated like a deployable
|
//this is only treated like a deployable
|
||||||
val internal_router_telepad_deployable = InternalTelepadDefinition() //objectId: 744
|
val internal_router_telepad_deployable = InternalTelepadDefinition() //objectId: 744
|
||||||
init_deployables()
|
init_deployables()
|
||||||
|
|
@ -6788,9 +6781,7 @@ object GlobalDefinitions {
|
||||||
dropship.MaxShields = 1000
|
dropship.MaxShields = 1000
|
||||||
dropship.CanFly = true
|
dropship.CanFly = true
|
||||||
dropship.Seats += 0 -> new SeatDefinition()
|
dropship.Seats += 0 -> new SeatDefinition()
|
||||||
dropship.Seats += 1 -> new SeatDefinition() {
|
dropship.Seats += 1 -> bailableSeat
|
||||||
bailable = true
|
|
||||||
}
|
|
||||||
dropship.Seats += 2 -> bailableSeat
|
dropship.Seats += 2 -> bailableSeat
|
||||||
dropship.Seats += 3 -> bailableSeat
|
dropship.Seats += 3 -> bailableSeat
|
||||||
dropship.Seats += 4 -> bailableSeat
|
dropship.Seats += 4 -> bailableSeat
|
||||||
|
|
@ -7039,11 +7030,10 @@ object GlobalDefinitions {
|
||||||
* Initialize `Deployable` globals.
|
* Initialize `Deployable` globals.
|
||||||
*/
|
*/
|
||||||
private def init_deployables(): Unit = {
|
private def init_deployables(): Unit = {
|
||||||
val mine = GeometryForm.representByCylinder(radius = 0.1914f, height = 0.0957f) _
|
val mine = GeometryForm.representByCylinder(radius = 0.1914f, height = 0.0957f) _
|
||||||
val smallTurret = GeometryForm.representByCylinder(radius = 0.48435f, height = 1.23438f) _
|
val smallTurret = GeometryForm.representByCylinder(radius = 0.48435f, height = 1.23438f) _
|
||||||
val sensor = GeometryForm.representByCylinder(radius = 0.1914f, height = 1.21875f) _
|
val sensor = GeometryForm.representByCylinder(radius = 0.1914f, height = 1.21875f) _
|
||||||
val largeTurret = GeometryForm.representByCylinder(radius = 0.8437f, height = 2.29687f) _
|
val largeTurret = GeometryForm.representByCylinder(radius = 0.8437f, height = 2.29687f) _
|
||||||
|
|
||||||
boomer.Name = "boomer"
|
boomer.Name = "boomer"
|
||||||
boomer.Descriptor = "Boomers"
|
boomer.Descriptor = "Boomers"
|
||||||
boomer.MaxHealth = 100
|
boomer.MaxHealth = 100
|
||||||
|
|
@ -7066,7 +7056,6 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
boomer.Geometry = mine
|
boomer.Geometry = mine
|
||||||
|
|
||||||
he_mine.Name = "he_mine"
|
he_mine.Name = "he_mine"
|
||||||
he_mine.Descriptor = "Mines"
|
he_mine.Descriptor = "Mines"
|
||||||
he_mine.MaxHealth = 100
|
he_mine.MaxHealth = 100
|
||||||
|
|
@ -7088,7 +7077,6 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
he_mine.Geometry = mine
|
he_mine.Geometry = mine
|
||||||
|
|
||||||
jammer_mine.Name = "jammer_mine"
|
jammer_mine.Name = "jammer_mine"
|
||||||
jammer_mine.Descriptor = "JammerMines"
|
jammer_mine.Descriptor = "JammerMines"
|
||||||
jammer_mine.MaxHealth = 100
|
jammer_mine.MaxHealth = 100
|
||||||
|
|
@ -7098,14 +7086,13 @@ object GlobalDefinitions {
|
||||||
jammer_mine.DeployTime = Duration.create(1000, "ms")
|
jammer_mine.DeployTime = Duration.create(1000, "ms")
|
||||||
jammer_mine.DetonateOnJamming = false
|
jammer_mine.DetonateOnJamming = false
|
||||||
jammer_mine.Geometry = mine
|
jammer_mine.Geometry = mine
|
||||||
|
|
||||||
spitfire_turret.Name = "spitfire_turret"
|
spitfire_turret.Name = "spitfire_turret"
|
||||||
spitfire_turret.Descriptor = "Spitfires"
|
spitfire_turret.Descriptor = "Spitfires"
|
||||||
spitfire_turret.MaxHealth = 100
|
spitfire_turret.MaxHealth = 100
|
||||||
spitfire_turret.Damageable = true
|
spitfire_turret.Damageable = true
|
||||||
spitfire_turret.Repairable = true
|
spitfire_turret.Repairable = true
|
||||||
spitfire_turret.RepairIfDestroyed = false
|
spitfire_turret.RepairIfDestroyed = false
|
||||||
spitfire_turret.WeaponPaths += 1 -> new mutable.HashMap()
|
spitfire_turret.WeaponPaths += 1 -> new mutable.HashMap()
|
||||||
spitfire_turret.WeaponPaths(1) += TurretUpgrade.None -> spitfire_weapon
|
spitfire_turret.WeaponPaths(1) += TurretUpgrade.None -> spitfire_weapon
|
||||||
spitfire_turret.ReserveAmmunition = false
|
spitfire_turret.ReserveAmmunition = false
|
||||||
spitfire_turret.DeployCategory = DeployableCategory.SmallTurrets
|
spitfire_turret.DeployCategory = DeployableCategory.SmallTurrets
|
||||||
|
|
@ -7122,14 +7109,13 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
spitfire_turret.Geometry = smallTurret
|
spitfire_turret.Geometry = smallTurret
|
||||||
|
|
||||||
spitfire_cloaked.Name = "spitfire_cloaked"
|
spitfire_cloaked.Name = "spitfire_cloaked"
|
||||||
spitfire_cloaked.Descriptor = "CloakingSpitfires"
|
spitfire_cloaked.Descriptor = "CloakingSpitfires"
|
||||||
spitfire_cloaked.MaxHealth = 100
|
spitfire_cloaked.MaxHealth = 100
|
||||||
spitfire_cloaked.Damageable = true
|
spitfire_cloaked.Damageable = true
|
||||||
spitfire_cloaked.Repairable = true
|
spitfire_cloaked.Repairable = true
|
||||||
spitfire_cloaked.RepairIfDestroyed = false
|
spitfire_cloaked.RepairIfDestroyed = false
|
||||||
spitfire_cloaked.WeaponPaths += 1 -> new mutable.HashMap()
|
spitfire_cloaked.WeaponPaths += 1 -> new mutable.HashMap()
|
||||||
spitfire_cloaked.WeaponPaths(1) += TurretUpgrade.None -> spitfire_weapon
|
spitfire_cloaked.WeaponPaths(1) += TurretUpgrade.None -> spitfire_weapon
|
||||||
spitfire_cloaked.ReserveAmmunition = false
|
spitfire_cloaked.ReserveAmmunition = false
|
||||||
spitfire_cloaked.DeployCategory = DeployableCategory.SmallTurrets
|
spitfire_cloaked.DeployCategory = DeployableCategory.SmallTurrets
|
||||||
|
|
@ -7145,14 +7131,13 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
spitfire_cloaked.Geometry = smallTurret
|
spitfire_cloaked.Geometry = smallTurret
|
||||||
|
|
||||||
spitfire_aa.Name = "spitfire_aa"
|
spitfire_aa.Name = "spitfire_aa"
|
||||||
spitfire_aa.Descriptor = "FlakSpitfires"
|
spitfire_aa.Descriptor = "FlakSpitfires"
|
||||||
spitfire_aa.MaxHealth = 100
|
spitfire_aa.MaxHealth = 100
|
||||||
spitfire_aa.Damageable = true
|
spitfire_aa.Damageable = true
|
||||||
spitfire_aa.Repairable = true
|
spitfire_aa.Repairable = true
|
||||||
spitfire_aa.RepairIfDestroyed = false
|
spitfire_aa.RepairIfDestroyed = false
|
||||||
spitfire_aa.WeaponPaths += 1 -> new mutable.HashMap()
|
spitfire_aa.WeaponPaths += 1 -> new mutable.HashMap()
|
||||||
spitfire_aa.WeaponPaths(1) += TurretUpgrade.None -> spitfire_aa_weapon
|
spitfire_aa.WeaponPaths(1) += TurretUpgrade.None -> spitfire_aa_weapon
|
||||||
spitfire_aa.ReserveAmmunition = false
|
spitfire_aa.ReserveAmmunition = false
|
||||||
spitfire_aa.DeployCategory = DeployableCategory.SmallTurrets
|
spitfire_aa.DeployCategory = DeployableCategory.SmallTurrets
|
||||||
|
|
@ -7168,7 +7153,6 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
spitfire_aa.Geometry = smallTurret
|
spitfire_aa.Geometry = smallTurret
|
||||||
|
|
||||||
motionalarmsensor.Name = "motionalarmsensor"
|
motionalarmsensor.Name = "motionalarmsensor"
|
||||||
motionalarmsensor.Descriptor = "MotionSensors"
|
motionalarmsensor.Descriptor = "MotionSensors"
|
||||||
motionalarmsensor.MaxHealth = 100
|
motionalarmsensor.MaxHealth = 100
|
||||||
|
|
@ -7177,7 +7161,6 @@ object GlobalDefinitions {
|
||||||
motionalarmsensor.RepairIfDestroyed = false
|
motionalarmsensor.RepairIfDestroyed = false
|
||||||
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
|
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
|
||||||
motionalarmsensor.Geometry = sensor
|
motionalarmsensor.Geometry = sensor
|
||||||
|
|
||||||
sensor_shield.Name = "sensor_shield"
|
sensor_shield.Name = "sensor_shield"
|
||||||
sensor_shield.Descriptor = "SensorShields"
|
sensor_shield.Descriptor = "SensorShields"
|
||||||
sensor_shield.MaxHealth = 100
|
sensor_shield.MaxHealth = 100
|
||||||
|
|
@ -7186,7 +7169,6 @@ object GlobalDefinitions {
|
||||||
sensor_shield.RepairIfDestroyed = false
|
sensor_shield.RepairIfDestroyed = false
|
||||||
sensor_shield.DeployTime = Duration.create(5000, "ms")
|
sensor_shield.DeployTime = Duration.create(5000, "ms")
|
||||||
sensor_shield.Geometry = sensor
|
sensor_shield.Geometry = sensor
|
||||||
|
|
||||||
tank_traps.Name = "tank_traps"
|
tank_traps.Name = "tank_traps"
|
||||||
tank_traps.Descriptor = "TankTraps"
|
tank_traps.Descriptor = "TankTraps"
|
||||||
tank_traps.MaxHealth = 5000
|
tank_traps.MaxHealth = 5000
|
||||||
|
|
@ -7205,7 +7187,6 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
tank_traps.Geometry = GeometryForm.representByCylinder(radius = 2.89680997f, height = 3.57812f)
|
tank_traps.Geometry = GeometryForm.representByCylinder(radius = 2.89680997f, height = 3.57812f)
|
||||||
|
|
||||||
val fieldTurretConverter = new FieldTurretConverter
|
val fieldTurretConverter = new FieldTurretConverter
|
||||||
portable_manned_turret.Name = "portable_manned_turret"
|
portable_manned_turret.Name = "portable_manned_turret"
|
||||||
portable_manned_turret.Descriptor = "FieldTurrets"
|
portable_manned_turret.Descriptor = "FieldTurrets"
|
||||||
|
|
@ -7213,11 +7194,11 @@ object GlobalDefinitions {
|
||||||
portable_manned_turret.Damageable = true
|
portable_manned_turret.Damageable = true
|
||||||
portable_manned_turret.Repairable = true
|
portable_manned_turret.Repairable = true
|
||||||
portable_manned_turret.RepairIfDestroyed = false
|
portable_manned_turret.RepairIfDestroyed = false
|
||||||
portable_manned_turret.controlledWeapons += 0 -> 1
|
portable_manned_turret.controlledWeapons += 0 -> 1
|
||||||
portable_manned_turret.WeaponPaths += 1 -> new mutable.HashMap()
|
portable_manned_turret.WeaponPaths += 1 -> new mutable.HashMap()
|
||||||
portable_manned_turret.WeaponPaths(1) += TurretUpgrade.None -> energy_gun
|
portable_manned_turret.WeaponPaths(1) += TurretUpgrade.None -> energy_gun
|
||||||
portable_manned_turret.MountPoints += 1 -> MountInfo(0)
|
portable_manned_turret.MountPoints += 1 -> MountInfo(0)
|
||||||
portable_manned_turret.MountPoints += 2 -> MountInfo(0)
|
portable_manned_turret.MountPoints += 2 -> MountInfo(0)
|
||||||
portable_manned_turret.ReserveAmmunition = true
|
portable_manned_turret.ReserveAmmunition = true
|
||||||
portable_manned_turret.FactionLocked = true
|
portable_manned_turret.FactionLocked = true
|
||||||
portable_manned_turret.Packet = fieldTurretConverter
|
portable_manned_turret.Packet = fieldTurretConverter
|
||||||
|
|
@ -7234,18 +7215,17 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
portable_manned_turret.Geometry = largeTurret
|
portable_manned_turret.Geometry = largeTurret
|
||||||
|
|
||||||
portable_manned_turret_nc.Name = "portable_manned_turret_nc"
|
portable_manned_turret_nc.Name = "portable_manned_turret_nc"
|
||||||
portable_manned_turret_nc.Descriptor = "FieldTurrets"
|
portable_manned_turret_nc.Descriptor = "FieldTurrets"
|
||||||
portable_manned_turret_nc.MaxHealth = 1000
|
portable_manned_turret_nc.MaxHealth = 1000
|
||||||
portable_manned_turret_nc.Damageable = true
|
portable_manned_turret_nc.Damageable = true
|
||||||
portable_manned_turret_nc.Repairable = true
|
portable_manned_turret_nc.Repairable = true
|
||||||
portable_manned_turret_nc.RepairIfDestroyed = false
|
portable_manned_turret_nc.RepairIfDestroyed = false
|
||||||
portable_manned_turret_nc.WeaponPaths += 1 -> new mutable.HashMap()
|
portable_manned_turret_nc.WeaponPaths += 1 -> new mutable.HashMap()
|
||||||
portable_manned_turret_nc.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_nc
|
portable_manned_turret_nc.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_nc
|
||||||
portable_manned_turret_nc.controlledWeapons += 0 -> 1
|
portable_manned_turret_nc.controlledWeapons += 0 -> 1
|
||||||
portable_manned_turret_nc.MountPoints += 1 -> MountInfo(0)
|
portable_manned_turret_nc.MountPoints += 1 -> MountInfo(0)
|
||||||
portable_manned_turret_nc.MountPoints += 2 -> MountInfo(0)
|
portable_manned_turret_nc.MountPoints += 2 -> MountInfo(0)
|
||||||
portable_manned_turret_nc.ReserveAmmunition = true
|
portable_manned_turret_nc.ReserveAmmunition = true
|
||||||
portable_manned_turret_nc.FactionLocked = true
|
portable_manned_turret_nc.FactionLocked = true
|
||||||
portable_manned_turret_nc.Packet = fieldTurretConverter
|
portable_manned_turret_nc.Packet = fieldTurretConverter
|
||||||
|
|
@ -7262,18 +7242,17 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
portable_manned_turret_nc.Geometry = largeTurret
|
portable_manned_turret_nc.Geometry = largeTurret
|
||||||
|
|
||||||
portable_manned_turret_tr.Name = "portable_manned_turret_tr"
|
portable_manned_turret_tr.Name = "portable_manned_turret_tr"
|
||||||
portable_manned_turret_tr.Descriptor = "FieldTurrets"
|
portable_manned_turret_tr.Descriptor = "FieldTurrets"
|
||||||
portable_manned_turret_tr.MaxHealth = 1000
|
portable_manned_turret_tr.MaxHealth = 1000
|
||||||
portable_manned_turret_tr.Damageable = true
|
portable_manned_turret_tr.Damageable = true
|
||||||
portable_manned_turret_tr.Repairable = true
|
portable_manned_turret_tr.Repairable = true
|
||||||
portable_manned_turret_tr.RepairIfDestroyed = false
|
portable_manned_turret_tr.RepairIfDestroyed = false
|
||||||
portable_manned_turret_tr.WeaponPaths += 1 -> new mutable.HashMap()
|
portable_manned_turret_tr.WeaponPaths += 1 -> new mutable.HashMap()
|
||||||
portable_manned_turret_tr.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_tr
|
portable_manned_turret_tr.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_tr
|
||||||
portable_manned_turret_tr.controlledWeapons += 0 -> 1
|
portable_manned_turret_tr.controlledWeapons += 0 -> 1
|
||||||
portable_manned_turret_tr.MountPoints += 1 -> MountInfo(0)
|
portable_manned_turret_tr.MountPoints += 1 -> MountInfo(0)
|
||||||
portable_manned_turret_tr.MountPoints += 2 -> MountInfo(0)
|
portable_manned_turret_tr.MountPoints += 2 -> MountInfo(0)
|
||||||
portable_manned_turret_tr.ReserveAmmunition = true
|
portable_manned_turret_tr.ReserveAmmunition = true
|
||||||
portable_manned_turret_tr.FactionLocked = true
|
portable_manned_turret_tr.FactionLocked = true
|
||||||
portable_manned_turret_tr.Packet = fieldTurretConverter
|
portable_manned_turret_tr.Packet = fieldTurretConverter
|
||||||
|
|
@ -7290,18 +7269,17 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
portable_manned_turret_tr.Geometry = largeTurret
|
portable_manned_turret_tr.Geometry = largeTurret
|
||||||
|
|
||||||
portable_manned_turret_vs.Name = "portable_manned_turret_vs"
|
portable_manned_turret_vs.Name = "portable_manned_turret_vs"
|
||||||
portable_manned_turret_vs.Descriptor = "FieldTurrets"
|
portable_manned_turret_vs.Descriptor = "FieldTurrets"
|
||||||
portable_manned_turret_vs.MaxHealth = 1000
|
portable_manned_turret_vs.MaxHealth = 1000
|
||||||
portable_manned_turret_vs.Damageable = true
|
portable_manned_turret_vs.Damageable = true
|
||||||
portable_manned_turret_vs.Repairable = true
|
portable_manned_turret_vs.Repairable = true
|
||||||
portable_manned_turret_vs.RepairIfDestroyed = false
|
portable_manned_turret_vs.RepairIfDestroyed = false
|
||||||
portable_manned_turret_vs.WeaponPaths += 1 -> new mutable.HashMap()
|
portable_manned_turret_vs.WeaponPaths += 1 -> new mutable.HashMap()
|
||||||
portable_manned_turret_vs.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_vs
|
portable_manned_turret_vs.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_vs
|
||||||
portable_manned_turret_vs.controlledWeapons += 0 -> 1
|
portable_manned_turret_vs.controlledWeapons += 0 -> 1
|
||||||
portable_manned_turret_vs.MountPoints += 1 -> MountInfo(0)
|
portable_manned_turret_vs.MountPoints += 1 -> MountInfo(0)
|
||||||
portable_manned_turret_vs.MountPoints += 2 -> MountInfo(0)
|
portable_manned_turret_vs.MountPoints += 2 -> MountInfo(0)
|
||||||
portable_manned_turret_vs.ReserveAmmunition = true
|
portable_manned_turret_vs.ReserveAmmunition = true
|
||||||
portable_manned_turret_vs.FactionLocked = true
|
portable_manned_turret_vs.FactionLocked = true
|
||||||
portable_manned_turret_vs.Packet = fieldTurretConverter
|
portable_manned_turret_vs.Packet = fieldTurretConverter
|
||||||
|
|
@ -7318,7 +7296,6 @@ object GlobalDefinitions {
|
||||||
Modifiers = ExplodingRadialDegrade
|
Modifiers = ExplodingRadialDegrade
|
||||||
}
|
}
|
||||||
portable_manned_turret_vs.Geometry = largeTurret
|
portable_manned_turret_vs.Geometry = largeTurret
|
||||||
|
|
||||||
deployable_shield_generator.Name = "deployable_shield_generator"
|
deployable_shield_generator.Name = "deployable_shield_generator"
|
||||||
deployable_shield_generator.Descriptor = "ShieldGenerators"
|
deployable_shield_generator.Descriptor = "ShieldGenerators"
|
||||||
deployable_shield_generator.MaxHealth = 1700
|
deployable_shield_generator.MaxHealth = 1700
|
||||||
|
|
@ -7328,7 +7305,6 @@ object GlobalDefinitions {
|
||||||
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
|
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
|
||||||
deployable_shield_generator.Model = ComplexDeployableResolutions.calculate
|
deployable_shield_generator.Model = ComplexDeployableResolutions.calculate
|
||||||
deployable_shield_generator.Geometry = GeometryForm.representByCylinder(radius = 0.6562f, height = 2.17188f)
|
deployable_shield_generator.Geometry = GeometryForm.representByCylinder(radius = 0.6562f, height = 2.17188f)
|
||||||
|
|
||||||
router_telepad_deployable.Name = "router_telepad_deployable"
|
router_telepad_deployable.Name = "router_telepad_deployable"
|
||||||
router_telepad_deployable.MaxHealth = 100
|
router_telepad_deployable.MaxHealth = 100
|
||||||
router_telepad_deployable.Damageable = true
|
router_telepad_deployable.Damageable = true
|
||||||
|
|
@ -7338,7 +7314,6 @@ object GlobalDefinitions {
|
||||||
router_telepad_deployable.Packet = new TelepadDeployableConverter
|
router_telepad_deployable.Packet = new TelepadDeployableConverter
|
||||||
router_telepad_deployable.Model = SimpleResolutions.calculate
|
router_telepad_deployable.Model = SimpleResolutions.calculate
|
||||||
router_telepad_deployable.Geometry = GeometryForm.representByRaisedSphere(radius = 1.2344f)
|
router_telepad_deployable.Geometry = GeometryForm.representByRaisedSphere(radius = 1.2344f)
|
||||||
|
|
||||||
internal_router_telepad_deployable.Name = "router_telepad_deployable"
|
internal_router_telepad_deployable.Name = "router_telepad_deployable"
|
||||||
internal_router_telepad_deployable.MaxHealth = 1
|
internal_router_telepad_deployable.MaxHealth = 1
|
||||||
internal_router_telepad_deployable.Damageable = false
|
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.DeployTime = Duration.create(1, "ms")
|
||||||
internal_router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
|
internal_router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
|
||||||
internal_router_telepad_deployable.Packet = new InternalTelepadDeployableConverter
|
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.Name = "mb_pad_creation"
|
||||||
mb_pad_creation.Damageable = false
|
mb_pad_creation.Damageable = false
|
||||||
mb_pad_creation.Repairable = 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.Name = "dropship_pad_doors"
|
||||||
dropship_pad_doors.Damageable = false
|
dropship_pad_doors.Damageable = false
|
||||||
dropship_pad_doors.Repairable = 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.Name = "vanu_vehicle_creation_pad"
|
||||||
vanu_vehicle_creation_pad.Damageable = false
|
vanu_vehicle_creation_pad.Damageable = false
|
||||||
vanu_vehicle_creation_pad.Repairable = 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.Name = "mb_locker"
|
||||||
mb_locker.Damageable = false
|
mb_locker.Damageable = false
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects
|
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.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
|
||||||
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
||||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
|
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.Vitality
|
||||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||||
import net.psforever.objects.vital.resolution.DamageResistanceModel
|
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 net.psforever.types.{PlanetSideGUID, _}
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
|
|
@ -30,6 +30,7 @@ class Player(var avatar: Avatar)
|
||||||
with ZoneAware
|
with ZoneAware
|
||||||
with AuraContainer {
|
with AuraContainer {
|
||||||
private var backpack: Boolean = false
|
private var backpack: Boolean = false
|
||||||
|
private var released: Boolean = false
|
||||||
private var armor: Int = 0
|
private var armor: Int = 0
|
||||||
|
|
||||||
private var capacitor: Float = 0f
|
private var capacitor: Float = 0f
|
||||||
|
|
@ -44,12 +45,14 @@ class Player(var avatar: Avatar)
|
||||||
private var drawnSlot: Int = Player.HandsDownSlot
|
private var drawnSlot: Int = Player.HandsDownSlot
|
||||||
private var lastDrawnSlot: Int = Player.HandsDownSlot
|
private var lastDrawnSlot: Int = Player.HandsDownSlot
|
||||||
private var backpackAccess: Option[PlanetSideGUID] = None
|
private var backpackAccess: Option[PlanetSideGUID] = None
|
||||||
|
private var carrying: Option[SpecialCarry] = None
|
||||||
|
|
||||||
private var facingYawUpper: Float = 0f
|
private var facingYawUpper: Float = 0f
|
||||||
private var crouching: Boolean = false
|
private var crouching: Boolean = false
|
||||||
private var jumping: Boolean = false
|
private var jumping: Boolean = false
|
||||||
private var cloaked: Boolean = false
|
private var cloaked: Boolean = false
|
||||||
private var afk: Boolean = false
|
private var afk: Boolean = false
|
||||||
|
private var zoning: Zoning.Method.Value = Zoning.Method.None
|
||||||
|
|
||||||
private var vehicleSeated: Option[PlanetSideGUID] = None
|
private var vehicleSeated: Option[PlanetSideGUID] = None
|
||||||
|
|
||||||
|
|
@ -95,6 +98,7 @@ class Player(var avatar: Avatar)
|
||||||
Health = Definition.DefaultHealth
|
Health = Definition.DefaultHealth
|
||||||
Armor = MaxArmor
|
Armor = MaxArmor
|
||||||
Capacitor = 0
|
Capacitor = 0
|
||||||
|
released = false
|
||||||
}
|
}
|
||||||
isAlive
|
isAlive
|
||||||
}
|
}
|
||||||
|
|
@ -108,18 +112,18 @@ class Player(var avatar: Avatar)
|
||||||
def Revive: Boolean = {
|
def Revive: Boolean = {
|
||||||
Destroyed = false
|
Destroyed = false
|
||||||
Health = Definition.DefaultHealth
|
Health = Definition.DefaultHealth
|
||||||
|
released = false
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
def Release: Boolean = {
|
def Release: Boolean = {
|
||||||
if (!isAlive) {
|
released = true
|
||||||
backpack = true
|
backpack = !isAlive
|
||||||
true
|
true
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def isReleased: Boolean = released
|
||||||
|
|
||||||
def Armor: Int = armor
|
def Armor: Int = armor
|
||||||
|
|
||||||
def Armor_=(assignArmor: Int): Int = {
|
def Armor_=(assignArmor: Int): Int = {
|
||||||
|
|
@ -498,6 +502,23 @@ class Player(var avatar: Avatar)
|
||||||
VehicleSeated
|
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 DamageModel = exosuit.asInstanceOf[DamageResistanceModel]
|
||||||
|
|
||||||
def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
|
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 = {
|
override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = {
|
||||||
super.DestructionAwareness(target, cause)
|
super.DestructionAwareness(target, cause)
|
||||||
Deployables.AnnounceDestroyDeployable(trap, None)
|
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
|
//uninitialize implants
|
||||||
avatarActor ! AvatarActor.DeinitializeImplants()
|
avatarActor ! AvatarActor.DeinitializeImplants()
|
||||||
|
|
||||||
|
//log historical event
|
||||||
|
target.History(cause)
|
||||||
|
//log message
|
||||||
cause.adversarial match {
|
cause.adversarial match {
|
||||||
case Some(a) =>
|
case Some(a) =>
|
||||||
damageLog.info(s"DisplayDestroy: ${a.defender} was killed by ${a.attacker}")
|
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)
|
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
|
//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
|
//special considerations for certain vehicles
|
||||||
Vehicles.BeforeUnloadVehicle(obj, zone)
|
Vehicles.BeforeUnloadVehicle(obj, zone)
|
||||||
//shields
|
//shields
|
||||||
|
|
|
||||||
|
|
@ -93,7 +93,7 @@ trait DamageableWeaponTurret
|
||||||
EndAllAggravation()
|
EndAllAggravation()
|
||||||
DamageableWeaponTurret.DestructionAwareness(obj, cause)
|
DamageableWeaponTurret.DestructionAwareness(obj, cause)
|
||||||
DamageableMountable.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
|
queuedExplosion = Default.Cancellable
|
||||||
imminentExplosion = false
|
imminentExplosion = false
|
||||||
//hate on everything nearby
|
//hate on everything nearby
|
||||||
Zone.causeExplosion(gen.Zone, gen, gen.LastDamage, explosionFunc)
|
Zone.serverSideDamage(gen.Zone, gen, Zone.explosionDamage(gen.LastDamage), explosionFunc)
|
||||||
gen.ClearHistory()
|
gen.ClearHistory()
|
||||||
|
|
||||||
case GeneratorControl.Restored() =>
|
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
|
* 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.
|
* that the target is not "outside" of the detection radius of the room.
|
||||||
* Magic numbers for the room dimensions are employed.
|
* Magic numbers for the room dimensions are employed.
|
||||||
* @see `Zone.causeExplosion`
|
|
||||||
* @see `Zone.distanceCheck`
|
* @see `Zone.distanceCheck`
|
||||||
|
* @see `Zone.serverSideDamage`
|
||||||
* @param g1ctrXY the center of the generator on the xy-axis
|
* @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;
|
* @param ufront a `Vector3` entity that points to the "front" direction of the generator;
|
||||||
* the `u` prefix indicates a "unit vector"
|
* the `u` prefix indicates a "unit vector"
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,15 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.serverobject.pad
|
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.guid.GUIDTask.UnregisterVehicle
|
||||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||||
import net.psforever.objects.serverobject.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer}
|
import net.psforever.objects.serverobject.pad.process.{VehicleSpawnControlBase, VehicleSpawnControlConcealPlayer}
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.{Zone, ZoneAware, Zoning}
|
||||||
import net.psforever.objects.{Default, Player, Vehicle}
|
import net.psforever.objects.{Default, PlanetSideGameObject, Player, Vehicle}
|
||||||
|
import net.psforever.types.Vector3
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
@ -37,8 +40,11 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
/** a reminder sent to future customers */
|
/** a reminder sent to future customers */
|
||||||
var periodicReminder: Cancellable = Default.Cancellable
|
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 */
|
/** 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;
|
/** the current vehicle order being acted upon;
|
||||||
* used as a guard condition to control order processing rate
|
* used as a guard condition to control order processing rate
|
||||||
|
|
@ -46,7 +52,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
var trackedOrder: Option[VehicleSpawnControl.Order] = None
|
var trackedOrder: Option[VehicleSpawnControl.Order] = None
|
||||||
|
|
||||||
/** how to process either the first order or every subsequent order */
|
/** 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 = ""
|
def LogId = ""
|
||||||
|
|
||||||
|
|
@ -67,39 +73,60 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override def postStop() : Unit = {
|
||||||
|
periodicReminder.cancel()
|
||||||
|
queueManagement.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
def receive: Receive =
|
def receive: Receive =
|
||||||
checkBehavior.orElse {
|
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")
|
trace(s"order from ${player.Name} for a ${vehicle.Definition.Name} received")
|
||||||
try {
|
try {
|
||||||
handleOrderFunc(VehicleSpawnControl.Order(player, vehicle))
|
handleOrderFunc(msg)
|
||||||
} catch {
|
} catch {
|
||||||
case _: AssertionError => ; //ehhh
|
case _: AssertionError => ; //ehhh
|
||||||
case e: Exception => //something unexpected
|
case e: Exception => //something unexpected
|
||||||
e.printStackTrace()
|
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 =>
|
case VehicleSpawnControl.ProcessControl.GetNewOrder =>
|
||||||
if (sender() == concealPlayer) {
|
if (sender() == concealPlayer) {
|
||||||
trackedOrder = None //guard off
|
trackedOrder = None //guard off
|
||||||
SelectOrder()
|
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.
|
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.
|
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.
|
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
|
During this time, a periodic message about the spawn pad being blocked will be broadcast to the order queue.
|
||||||
will be broadcast to all current customers in the order queue.
|
*/
|
||||||
*/
|
|
||||||
case VehicleSpawnControl.ProcessControl.Reminder =>
|
case VehicleSpawnControl.ProcessControl.Reminder =>
|
||||||
trackedOrder match {
|
trackedOrder match {
|
||||||
case Some(entry) =>
|
case Some(entry) =>
|
||||||
if (periodicReminder.isCancelled) {
|
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(
|
periodicReminder = context.system.scheduler.scheduleWithFixedDelay(
|
||||||
VehicleSpawnControl.initialReminderDelay,
|
VehicleSpawnControl.periodicReminderTestDelay,
|
||||||
VehicleSpawnControl.periodicReminderDelay,
|
VehicleSpawnControl.periodicReminderTestDelay,
|
||||||
self,
|
self,
|
||||||
VehicleSpawnControl.ProcessControl.Reminder
|
VehicleSpawnControl.ProcessControl.Reminder
|
||||||
)
|
)
|
||||||
|
|
@ -112,19 +139,16 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
|
|
||||||
case VehicleSpawnControl.ProcessControl.Flush =>
|
case VehicleSpawnControl.ProcessControl.Flush =>
|
||||||
periodicReminder.cancel()
|
periodicReminder.cancel()
|
||||||
orders.foreach {
|
orders.foreach { CancelOrder(_, Some("@SVCP_RemovedFromVehicleQueue_Generic")) }
|
||||||
CancelOrder
|
|
||||||
}
|
|
||||||
orders = Nil
|
orders = Nil
|
||||||
trackedOrder match {
|
trackedOrder match {
|
||||||
case Some(entry) =>
|
case Some(entry) => CancelOrder(entry, Some("@SVCP_RemovedFromVehicleQueue_Generic"))
|
||||||
CancelOrder(entry)
|
|
||||||
case None => ;
|
case None => ;
|
||||||
}
|
}
|
||||||
trackedOrder = None
|
trackedOrder = None
|
||||||
handleOrderFunc = NewTasking
|
handleOrderFunc = NewTasking
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad) //cautious animation reset
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad) //cautious animation reset
|
||||||
concealPlayer ! akka.actor.Kill //should cause the actor to restart, which will abort any trapped messages
|
concealPlayer ! akka.actor.Kill //should cause the actor to restart, which will abort any trapped messages
|
||||||
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
@ -134,7 +158,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
* All orders accepted in the meantime will be queued and a note about priority will be issued.
|
* All orders accepted in the meantime will be queued and a note about priority will be issued.
|
||||||
* @param order the order being accepted
|
* @param order the order being accepted
|
||||||
*/
|
*/
|
||||||
def NewTasking(order: VehicleSpawnControl.Order): Unit = {
|
def NewTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
|
||||||
handleOrderFunc = QueuedTasking
|
handleOrderFunc = QueuedTasking
|
||||||
ProcessOrder(Some(order))
|
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.
|
* all orders accepted in the meantime will be queued and a note about priority will be issued.
|
||||||
* @param order the order being accepted
|
* @param order the order being accepted
|
||||||
*/
|
*/
|
||||||
def QueuedTasking(order: VehicleSpawnControl.Order): Unit = {
|
def QueuedTasking(order: VehicleSpawnPad.VehicleOrder): Unit = {
|
||||||
val name = order.driver.Name
|
val name = order.player.Name
|
||||||
if (
|
if (trackedOrder match {
|
||||||
(trackedOrder match {
|
case Some(tracked) =>
|
||||||
case Some(tracked) => !tracked.driver.Name.equals(name)
|
!tracked.driver.Name.equals(name)
|
||||||
case None => true
|
case None =>
|
||||||
}) && orders.forall { !_.driver.Name.equals(name) }
|
handleOrderFunc = NewTasking
|
||||||
) {
|
NewTasking(order)
|
||||||
//not a second order from an existing order's player
|
false
|
||||||
orders = orders :+ order
|
}) {
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
|
orders.indexWhere { _.player.Name.equals(name) } match {
|
||||||
name,
|
case -1 if orders.isEmpty =>
|
||||||
VehicleSpawnPad.Reminders.Queue,
|
//first queued order
|
||||||
Some(orders.length + 1)
|
orders = List(order)
|
||||||
)
|
queueManagementTask()
|
||||||
} else {
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
|
||||||
VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
|
name,
|
||||||
|
VehicleSpawnPad.Reminders.Queue,
|
||||||
|
Some(s"@SVCP_PositionInQueue^2~^2~")
|
||||||
|
)
|
||||||
|
case -1 =>
|
||||||
|
//new order
|
||||||
|
orders = orders :+ order
|
||||||
|
val size = orders.size + 1
|
||||||
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
|
||||||
|
name,
|
||||||
|
VehicleSpawnPad.Reminders.Queue,
|
||||||
|
Some(s"@SVCP_PositionInQueue^$size~^$size~")
|
||||||
|
)
|
||||||
|
case n if orders(n).vehicle.Definition ne order.vehicle.Definition =>
|
||||||
|
//replace existing order with new order
|
||||||
|
val zone = pad.Zone
|
||||||
|
val originalOrder = orders(n)
|
||||||
|
val originalVehicle = originalOrder.vehicle.Definition.Name
|
||||||
|
orders = (orders.take(n) :+ order) ++ orders.drop(n+1)
|
||||||
|
VehicleSpawnControl.DisposeVehicle(originalOrder.vehicle, zone)
|
||||||
|
zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
|
||||||
|
name,
|
||||||
|
VehicleSpawnPad.Reminders.Queue,
|
||||||
|
Some(s"@SVCP_ReplacedVehicleWithVehicle^@$originalVehicle~^@${order.vehicle.Definition.Name}~")
|
||||||
|
)
|
||||||
|
case _ =>
|
||||||
|
//order is the duplicate of an existing order; do nothing to the queue
|
||||||
|
CancelOrder(order, None)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -174,17 +226,19 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
* If the queue has been exhausted, set functionality to prepare to accept the next order as a "first order."
|
* If the queue has been exhausted, set functionality to prepare to accept the next order as a "first order."
|
||||||
* @return the next-available order
|
* @return the next-available order
|
||||||
*/
|
*/
|
||||||
def SelectFirstOrder(): Option[VehicleSpawnControl.Order] = {
|
def SelectFirstOrder(): Option[VehicleSpawnPad.VehicleOrder] = {
|
||||||
trackedOrder match {
|
trackedOrder match {
|
||||||
case None =>
|
case None =>
|
||||||
val (completeOrder, remainingOrders): (Option[VehicleSpawnControl.Order], List[VehicleSpawnControl.Order]) =
|
val (completeOrder, remainingOrders): (Option[VehicleSpawnPad.VehicleOrder], List[VehicleSpawnPad.VehicleOrder]) =
|
||||||
orders match {
|
orderCredentialsCheck(orders) match {
|
||||||
case x :: Nil =>
|
case x :: Nil =>
|
||||||
|
queueManagement.cancel()
|
||||||
(Some(x), Nil)
|
(Some(x), Nil)
|
||||||
case x :: b =>
|
case x :: b =>
|
||||||
(Some(x), b)
|
(Some(x), b)
|
||||||
case Nil =>
|
case Nil =>
|
||||||
handleOrderFunc = NewTasking
|
handleOrderFunc = NewTasking
|
||||||
|
queueManagement.cancel()
|
||||||
(None, Nil)
|
(None, Nil)
|
||||||
}
|
}
|
||||||
orders = remainingOrders
|
orders = remainingOrders
|
||||||
|
|
@ -201,35 +255,93 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
* @param order the order being accepted;
|
* @param order the order being accepted;
|
||||||
* `None`, if no order found or submitted
|
* `None`, if no order found or submitted
|
||||||
*/
|
*/
|
||||||
def ProcessOrder(order: Option[VehicleSpawnControl.Order]): Unit = {
|
def ProcessOrder(order: Option[VehicleSpawnPad.VehicleOrder]): Unit = {
|
||||||
periodicReminder.cancel()
|
periodicReminder.cancel()
|
||||||
order match {
|
order match {
|
||||||
case Some(_order) =>
|
case Some(_order) =>
|
||||||
recursiveOrderReminder(orders.iterator)
|
val size = orders.size + 1
|
||||||
trace(s"processing next order - a ${_order.vehicle.Definition.Name} for ${_order.driver.Name}")
|
val driver = _order.player
|
||||||
trackedOrder = order //guard on
|
val name = driver.Name
|
||||||
context.system.scheduler.scheduleOnce(2000 milliseconds, concealPlayer, _order)
|
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 => ;
|
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
|
* na
|
||||||
* @param blockedOrder the previous order whose vehicle is blocking the spawn pad from operating
|
* @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
|
* @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
|
val user = blockedOrder.vehicle
|
||||||
.Seats(0).occupant
|
.Seats(0).occupant
|
||||||
.orElse(pad.Zone.GUID(blockedOrder.vehicle.Owner))
|
.orElse(pad.Zone.GUID(blockedOrder.vehicle.Owner))
|
||||||
.orElse(pad.Zone.GUID(blockedOrder.DriverGUID))
|
.orElse(pad.Zone.GUID(blockedOrder.DriverGUID))
|
||||||
val relevantRecipients = user match {
|
val relevantRecipients: Iterator[VehicleSpawnPad.VehicleOrder] = user match {
|
||||||
case Some(p: Player) if !p.HasGUID =>
|
case Some(p: Player) if !p.HasGUID =>
|
||||||
recipients.iterator
|
recipients.iterator
|
||||||
case Some(p: Player) if blockedOrder.driver == p =>
|
case Some(_: Player) =>
|
||||||
(blockedOrder +: recipients).iterator
|
(VehicleSpawnPad.VehicleOrder(
|
||||||
case Some(p: Player) =>
|
blockedOrder.driver,
|
||||||
(VehicleSpawnControl.Order(p, blockedOrder.vehicle) +: recipients).iterator //who took possession of the vehicle
|
blockedOrder.vehicle,
|
||||||
|
null //permissible
|
||||||
|
) +: recipients).iterator //one who took possession of the vehicle
|
||||||
case _ =>
|
case _ =>
|
||||||
recipients.iterator
|
recipients.iterator
|
||||||
}
|
}
|
||||||
|
|
@ -245,24 +357,37 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
/**
|
/**
|
||||||
* Cancel this vehicle order and inform the person who made it, if possible.
|
* Cancel this vehicle order and inform the person who made it, if possible.
|
||||||
* @param entry the order being cancelled
|
* @param entry the order being cancelled
|
||||||
* @param context an `ActorContext` object for which to create the `TaskResolver` object
|
|
||||||
*/
|
*/
|
||||||
def CancelOrder(entry: VehicleSpawnControl.Order)(implicit context: ActorContext): Unit = {
|
def CancelOrder(entry: VehicleSpawnControl.Order, msg: Option[String]): Unit = {
|
||||||
val vehicle = entry.vehicle
|
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) {
|
if (vehicle.Seats.values.count(_.isOccupied) == 0) {
|
||||||
VehicleSpawnControl.DisposeSpawnedVehicle(entry, pad.Zone)
|
VehicleSpawnControl.DisposeSpawnedVehicle(vehicle, player, pad.Zone)
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(entry.driver.Name, VehicleSpawnPad.Reminders.Cancelled)
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(player.Name, VehicleSpawnPad.Reminders.Cancelled, msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@tailrec private final def recursiveBlockedReminder(
|
@tailrec private final def recursiveBlockedReminder(
|
||||||
iter: Iterator[VehicleSpawnControl.Order],
|
iter: Iterator[VehicleSpawnPad.VehicleOrder],
|
||||||
cause: Option[Any]
|
cause: Option[Any]
|
||||||
): Unit = {
|
): Unit = {
|
||||||
if (iter.hasNext) {
|
if (iter.hasNext) {
|
||||||
val recipient = iter.next()
|
val recipient = iter.next()
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
|
||||||
recipient.driver.Name,
|
recipient.player.Name,
|
||||||
VehicleSpawnPad.Reminders.Blocked,
|
VehicleSpawnPad.Reminders.Blocked,
|
||||||
cause
|
cause
|
||||||
)
|
)
|
||||||
|
|
@ -271,30 +396,36 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
|
||||||
}
|
}
|
||||||
|
|
||||||
@tailrec private final def recursiveOrderReminder(
|
@tailrec private final def recursiveOrderReminder(
|
||||||
iter: Iterator[VehicleSpawnControl.Order],
|
iter: Iterator[VehicleSpawnPad.VehicleOrder],
|
||||||
|
size: Int,
|
||||||
position: Int = 2
|
position: Int = 2
|
||||||
): Unit = {
|
): Unit = {
|
||||||
if (iter.hasNext) {
|
if (iter.hasNext) {
|
||||||
val recipient = iter.next()
|
val recipient = iter.next()
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
|
||||||
recipient.driver.Name,
|
recipient.player.Name,
|
||||||
VehicleSpawnPad.Reminders.Queue,
|
VehicleSpawnPad.Reminders.Queue,
|
||||||
Some(position)
|
Some(s"@SVCP_PositionInQueue^$position~^$size~")
|
||||||
)
|
)
|
||||||
recursiveOrderReminder(iter, position + 1)
|
recursiveOrderReminder(iter, size, position + 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object VehicleSpawnControl {
|
object VehicleSpawnControl {
|
||||||
private final val initialReminderDelay: FiniteDuration = 10000 milliseconds
|
private final val periodicReminderTestDelay: FiniteDuration = 10 seconds
|
||||||
private final val periodicReminderDelay: FiniteDuration = 10000 milliseconds
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An `Enumeration` of non-data control messages for the vehicle spawn process.
|
* Control messages for the vehicle spawn process.
|
||||||
*/
|
*/
|
||||||
object ProcessControl extends Enumeration {
|
object ProcessControl {
|
||||||
val Reminder, GetNewOrder, Flush = Value
|
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(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")
|
assert(vehicle.HasGUID, s"when ordering a vehicle, the ${vehicle.Definition.Name} does not have a GUID")
|
||||||
val DriverGUID = driver.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.
|
* Properly clean up a vehicle that has been registered and spawned into the game world.
|
||||||
* Call this downstream of "`ConcealPlayer`".
|
* 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)
|
* @param zone the zone in which the vehicle is registered (should be located)
|
||||||
*/
|
*/
|
||||||
def DisposeSpawnedVehicle(entry: VehicleSpawnControl.Order, zone: Zone): Unit = {
|
def DisposeSpawnedVehicle(vehicle: Vehicle, player: Player, zone: Zone): Unit = {
|
||||||
DisposeVehicle(entry.vehicle, zone)
|
DisposeVehicle(vehicle, zone)
|
||||||
zone.VehicleEvents ! VehicleSpawnPad.RevealPlayer(entry.DriverGUID)
|
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)
|
* @param zone the zone in which the vehicle is registered (should be located)
|
||||||
*/
|
*/
|
||||||
def DisposeVehicle(vehicle: Vehicle, zone: Zone): Unit = {
|
def DisposeVehicle(vehicle: Vehicle, zone: Zone): Unit = {
|
||||||
if (zone.Vehicles.exists(_.GUID == vehicle.GUID)) { //already added to zone
|
if (zone.Vehicles.contains(vehicle)) { //already added to zone
|
||||||
vehicle.Actor ! Vehicle.Deconstruct()
|
vehicle.Actor ! Vehicle.Deconstruct(Some(0.seconds))
|
||||||
} else { //just registered to zone
|
} else { //just registered to zone
|
||||||
zone.tasks ! UnregisterVehicle(vehicle)(zone.GUID)
|
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.{Player, Vehicle}
|
||||||
import net.psforever.objects.serverobject.structures.Amenity
|
import net.psforever.objects.serverobject.structures.Amenity
|
||||||
|
import net.psforever.objects.serverobject.terminals.Terminal
|
||||||
import net.psforever.types.PlanetSideGUID
|
import net.psforever.types.PlanetSideGUID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -28,7 +29,7 @@ object VehicleSpawnPad {
|
||||||
* @param player the player who submitted the order (the "owner")
|
* @param player the player who submitted the order (the "owner")
|
||||||
* @param vehicle the vehicle produced from the order
|
* @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.
|
* 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.
|
* An `Enumeration` of reasons for sending a periodic reminder to the user.
|
||||||
*/
|
*/
|
||||||
object Reminders extends Enumeration {
|
object Reminders extends Enumeration {
|
||||||
val Queue, //optional data is the numeric position in the queue
|
val
|
||||||
Blocked, //optional data is a message regarding the blockage
|
Queue, //optional data is the numeric position in the queue
|
||||||
Cancelled = Value
|
Blocked, //optional data is a message regarding the blockage
|
||||||
|
Cancelled //optional data is the message
|
||||||
|
= Value
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,9 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.serverobject.pad
|
package net.psforever.objects.serverobject.pad
|
||||||
|
|
||||||
|
import net.psforever.objects.PlanetSideGameObject
|
||||||
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
import net.psforever.objects.serverobject.structures.AmenityDefinition
|
||||||
|
import net.psforever.types.Vector3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The definition for any `VehicleSpawnPad`.
|
* The definition for any `VehicleSpawnPad`.
|
||||||
|
|
@ -39,4 +41,166 @@ class VehicleSpawnPadDefinition(objectId: Int) extends AmenityDefinition(objectI
|
||||||
case 947 => Name = "vanu_vehicle_creation_pad"
|
case 947 => Name = "vanu_vehicle_creation_pad"
|
||||||
case _ => throw new IllegalArgumentException("Not a valid object id with the type 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")
|
context.actorOf(Props(classOf[VehicleSpawnControlLoadVehicle], pad), s"${context.parent.path.name}-load")
|
||||||
|
|
||||||
def receive: Receive = {
|
def receive: Receive = {
|
||||||
case order @ VehicleSpawnControl.Order(driver, _) =>
|
case order @ VehicleSpawnControl.Order(driver, vehicle) =>
|
||||||
//TODO how far can the driver stray from the Terminal before his order is cancelled?
|
if (VehicleSpawnControl.validateOrderCredentials(pad, driver, vehicle).isEmpty) {
|
||||||
if (driver.Continent == pad.Continent && driver.VehicleSeated.isEmpty && driver.isAlive) {
|
|
||||||
trace(s"hiding ${driver.Name}")
|
trace(s"hiding ${driver.Name}")
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.ConcealPlayer(driver.GUID)
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.ConcealPlayer(driver.GUID)
|
||||||
context.system.scheduler.scheduleOnce(2000 milliseconds, loadVehicle, order)
|
context.system.scheduler.scheduleOnce(2000 milliseconds, loadVehicle, order)
|
||||||
} else {
|
} else {
|
||||||
trace(s"integral component lost; abort order fulfillment")
|
trace(s"integral component lost; abort order fulfillment")
|
||||||
VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
|
context.parent ! VehicleSpawnControl.ProcessControl.OrderCancelled
|
||||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
|
case msg @ (VehicleSpawnControl.ProcessControl.Reminder |
|
||||||
|
VehicleSpawnControl.ProcessControl.GetNewOrder |
|
||||||
|
VehicleSpawnControl.ProcessControl.OrderCancelled) =>
|
||||||
context.parent ! msg
|
context.parent ! msg
|
||||||
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,10 @@ class VehicleSpawnControlDriverControl(pad: VehicleSpawnPad) extends VehicleSpaw
|
||||||
|
|
||||||
def receive: Receive = {
|
def receive: Receive = {
|
||||||
case order @ VehicleSpawnControl.Order(driver, vehicle) =>
|
case order @ VehicleSpawnControl.Order(driver, vehicle) =>
|
||||||
if (vehicle.Health > 0 && vehicle.PassengerInSeat(driver).contains(0)) {
|
trace(s"returning control of ${vehicle.Definition.Name} to its current driver")
|
||||||
trace(s"returning control of ${vehicle.Definition.Name} to ${driver.Name}")
|
if (vehicle.PassengerInSeat(driver).nonEmpty) {
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.ServerVehicleOverrideEnd(driver.Name, vehicle, pad)
|
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
|
finalClear ! order
|
||||||
|
|
||||||
case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
|
case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.serverobject.pad.process
|
package net.psforever.objects.serverobject.pad.process
|
||||||
|
|
||||||
|
import akka.actor.Cancellable
|
||||||
|
import net.psforever.objects.Default
|
||||||
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
|
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
|
||||||
import net.psforever.types.{PlanetSideGUID, Vector3}
|
import net.psforever.types.{PlanetSideGUID, Vector3}
|
||||||
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
|
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.
|
* 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>
|
* Each object performs on (or more than one related) actions upon the vehicle order that was submitted.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* There is nothing left to do
|
* There is nothing left to do except make certain that the vehicle has moved far enough away from the spawn pad
|
||||||
* 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.
|
* 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.
|
* 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
|
* @param pad the `VehicleSpawnPad` object being governed
|
||||||
|
|
@ -22,10 +23,16 @@ import scala.concurrent.duration._
|
||||||
class VehicleSpawnControlFinalClearance(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
|
class VehicleSpawnControlFinalClearance(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
|
||||||
def LogId = "-clearer"
|
def LogId = "-clearer"
|
||||||
|
|
||||||
|
var temp: Cancellable = Default.Cancellable
|
||||||
|
|
||||||
|
override def postStop() : Unit = {
|
||||||
|
temp.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
def receive: Receive = {
|
def receive: Receive = {
|
||||||
case order @ VehicleSpawnControl.Order(driver, vehicle) =>
|
case order @ VehicleSpawnControl.Order(_, vehicle) =>
|
||||||
if (vehicle.PassengerInSeat(driver).isEmpty) {
|
if (!vehicle.Seats(0).isOccupied) {
|
||||||
//ensure the vacant vehicle is above the trench and doors
|
//ensure the vacant vehicle is above the trench and the doors
|
||||||
vehicle.Position = pad.Position + Vector3.z(pad.Definition.VehicleCreationZOffset)
|
vehicle.Position = pad.Position + Vector3.z(pad.Definition.VehicleCreationZOffset)
|
||||||
val definition = vehicle.Definition
|
val definition = vehicle.Definition
|
||||||
pad.Zone.VehicleEvents ! VehicleServiceMessage(
|
pad.Zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
|
@ -43,12 +50,20 @@ class VehicleSpawnControlFinalClearance(pad: VehicleSpawnPad) extends VehicleSpa
|
||||||
self ! VehicleSpawnControlFinalClearance.Test(order)
|
self ! VehicleSpawnControlFinalClearance.Test(order)
|
||||||
|
|
||||||
case test @ VehicleSpawnControlFinalClearance.Test(entry) =>
|
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")
|
trace("pad cleared")
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad)
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad)
|
||||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
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 {
|
} else {
|
||||||
context.system.scheduler.scheduleOnce(2000 milliseconds, self, test)
|
temp = context.system.scheduler.scheduleOnce(2000 milliseconds, self, test)
|
||||||
}
|
}
|
||||||
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.serverobject.pad.process
|
package net.psforever.objects.serverobject.pad.process
|
||||||
|
|
||||||
import akka.actor.{Cancellable, Props}
|
import akka.actor.Props
|
||||||
import net.psforever.objects.{Default, GlobalDefinitions}
|
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.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.services.Service
|
import net.psforever.services.Service
|
||||||
|
|
@ -11,6 +13,7 @@ import net.psforever.types.Vector3
|
||||||
|
|
||||||
import scala.concurrent.ExecutionContext.Implicits.global
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
import scala.util.Success
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
|
* An `Actor` that handles vehicle spawning orders for a `VehicleSpawnPad`.
|
||||||
|
|
@ -28,63 +31,64 @@ class VehicleSpawnControlLoadVehicle(pad: VehicleSpawnPad) extends VehicleSpawnC
|
||||||
|
|
||||||
val railJack = context.actorOf(Props(classOf[VehicleSpawnControlRailJack], pad), s"${context.parent.path.name}-rails")
|
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 = {
|
implicit val timeout = Timeout(3.seconds)
|
||||||
temp.cancel()
|
|
||||||
super.postStop()
|
|
||||||
}
|
|
||||||
|
|
||||||
def receive: Receive = {
|
def receive: Receive = {
|
||||||
case order @ VehicleSpawnControl.Order(driver, vehicle) =>
|
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}")
|
trace(s"loading the ${vehicle.Definition.Name}")
|
||||||
vehicle.Position = vehicle.Position - Vector3.z(
|
vehicle.Position = vehicle.Position - Vector3.z(
|
||||||
if (GlobalDefinitions.isFlightVehicle(vehicle.Definition)) 9 else 5
|
if (GlobalDefinitions.isFlightVehicle(vehicle.Definition)) 9 else 5
|
||||||
) //appear below the trench and doors
|
) //appear below the trench and doors
|
||||||
vehicle.Cloaked = vehicle.Definition.CanCloak && driver.Cloaked
|
vehicle.Cloaked = vehicle.Definition.CanCloak && driver.Cloaked
|
||||||
pad.Zone.Transport.tell(Zone.Vehicle.Spawn(vehicle), self)
|
|
||||||
temp = context.system.scheduler.scheduleOnce(
|
temp = Some(order)
|
||||||
delay = 100 milliseconds,
|
val result = ask(pad.Zone.Transport, Zone.Vehicle.Spawn(vehicle))
|
||||||
self,
|
//if too long, or something goes wrong
|
||||||
VehicleSpawnControlLoadVehicle.WaitOnSpawn(order)
|
result.recover {
|
||||||
)
|
case _ =>
|
||||||
|
temp = None
|
||||||
|
context.parent ! VehicleSpawnControl.ProcessControl.OrderCancelled
|
||||||
|
}
|
||||||
|
//resolution
|
||||||
|
result.onComplete {
|
||||||
|
case Success(Zone.Vehicle.HasSpawned(zone, v))
|
||||||
|
if (temp match { case Some(_order) => _order.vehicle eq v; case _ => false }) =>
|
||||||
|
val definition = v.Definition
|
||||||
|
val vtype = definition.ObjectId
|
||||||
|
val vguid = v.GUID
|
||||||
|
val vdata = definition.Packet.ConstructorData(v).get
|
||||||
|
zone.VehicleEvents ! VehicleServiceMessage(
|
||||||
|
zone.id,
|
||||||
|
VehicleAction.LoadVehicle(Service.defaultPlayerGUID, v, vtype, vguid, vdata)
|
||||||
|
)
|
||||||
|
railJack ! temp.get
|
||||||
|
temp = None
|
||||||
|
|
||||||
|
case Success(Zone.Vehicle.CanNotSpawn(_, _, reason)) =>
|
||||||
|
trace(s"vehicle can not spawn - $reason; abort order fulfillment")
|
||||||
|
temp = None
|
||||||
|
context.parent ! VehicleSpawnControl.ProcessControl.OrderCancelled
|
||||||
|
|
||||||
|
case _ =>
|
||||||
|
temp match {
|
||||||
|
case Some(_) =>
|
||||||
|
trace(s"abort order fulfillment")
|
||||||
|
context.parent ! VehicleSpawnControl.ProcessControl.OrderCancelled
|
||||||
|
case None => ; //should we have gotten this message?
|
||||||
|
}
|
||||||
|
temp = None
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
trace("owner lost or vehicle in poor condition; abort order fulfillment")
|
trace("owner lost or vehicle in poor condition; abort order fulfillment")
|
||||||
VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
|
context.parent ! VehicleSpawnControl.ProcessControl.OrderCancelled
|
||||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case Zone.Vehicle.HasSpawned(zone, vehicle) =>
|
|
||||||
val definition = vehicle.Definition
|
|
||||||
val vtype = definition.ObjectId
|
|
||||||
val vguid = vehicle.GUID
|
|
||||||
val vdata = definition.Packet.ConstructorData(vehicle).get
|
|
||||||
zone.VehicleEvents ! VehicleServiceMessage(
|
|
||||||
zone.id,
|
|
||||||
VehicleAction.LoadVehicle(Service.defaultPlayerGUID, vehicle, vtype, vguid, vdata)
|
|
||||||
)
|
|
||||||
|
|
||||||
case VehicleSpawnControlLoadVehicle.WaitOnSpawn(order) =>
|
|
||||||
if (pad.Zone.Vehicles.contains(order.vehicle)) {
|
|
||||||
railJack ! order
|
|
||||||
} else {
|
|
||||||
VehicleSpawnControl.DisposeVehicle(order.vehicle, pad.Zone)
|
|
||||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
|
||||||
}
|
|
||||||
|
|
||||||
case Zone.Vehicle.CanNotSpawn(_, _, reason) =>
|
|
||||||
trace(s"vehicle $reason; abort order fulfillment")
|
|
||||||
temp.cancel()
|
|
||||||
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
|
|
||||||
|
|
||||||
case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
|
case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
|
||||||
context.parent ! msg
|
context.parent ! msg
|
||||||
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object VehicleSpawnControlLoadVehicle {
|
|
||||||
private case class WaitOnSpawn(order: VehicleSpawnControl.Order)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,15 @@
|
||||||
package net.psforever.objects.serverobject.pad.process
|
package net.psforever.objects.serverobject.pad.process
|
||||||
|
|
||||||
import akka.actor.Props
|
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.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.ExecutionContext.Implicits.global
|
||||||
import scala.concurrent.duration._
|
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>
|
* Each object performs on (or more than one related) actions upon the vehicle order that was submitted.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* When the vehicle is added into the environment, it is attached to the spawn pad platform.
|
* 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.
|
* On cue, the trapdoor of the platform will open, and the vehicle will be raised on a railed platform.
|
||||||
* 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.
|
|
||||||
* @param pad the `VehicleSpawnPad` object being governed
|
* @param pad the `VehicleSpawnPad` object being governed
|
||||||
*/
|
*/
|
||||||
class VehicleSpawnControlRailJack(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
|
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")
|
context.actorOf(Props(classOf[VehicleSpawnControlSeatDriver], pad), s"${context.parent.path.name}-mount")
|
||||||
|
|
||||||
def receive: Receive = {
|
def receive: Receive = {
|
||||||
case order @ VehicleSpawnControl.Order(_, vehicle) =>
|
case order @ VehicleSpawnControl.Order(driver, vehicle) =>
|
||||||
vehicle.MountedIn = pad.GUID
|
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)
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.AttachToRails(vehicle, pad)
|
||||||
context.system.scheduler.scheduleOnce(10 milliseconds, seatDriver, order)
|
context.system.scheduler.scheduleOnce(10 milliseconds, seatDriver, order)
|
||||||
|
|
||||||
|
|
@ -37,3 +48,46 @@ class VehicleSpawnControlRailJack(pad: VehicleSpawnPad) extends VehicleSpawnCont
|
||||||
case _ => ;
|
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
|
val vehicle = entry.vehicle
|
||||||
//avoid unattended vehicle blocking the pad; user should mount (and does so normally) to reset decon timer
|
//avoid unattended vehicle blocking the pad; user should mount (and does so normally) to reset decon timer
|
||||||
vehicle.Actor ! Vehicle.Deconstruct(Some(30 seconds))
|
vehicle.Actor ! Vehicle.Deconstruct(Some(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")
|
trace("driver to be made seated in vehicle")
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.StartPlayerSeatedInVehicle(driver.Name, vehicle, pad)
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.StartPlayerSeatedInVehicle(driver.Name, vehicle, pad)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -53,7 +53,10 @@ class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnCo
|
||||||
context.system.scheduler.scheduleOnce(2500 milliseconds, self, VehicleSpawnControlSeatDriver.DriverInSeat(entry))
|
context.system.scheduler.scheduleOnce(2500 milliseconds, self, VehicleSpawnControlSeatDriver.DriverInSeat(entry))
|
||||||
|
|
||||||
case 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")
|
trace(s"driver ${entry.driver.Name} has taken the wheel")
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.PlayerSeatedInVehicle(entry.driver.Name, entry.vehicle, pad)
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.PlayerSeatedInVehicle(entry.driver.Name, entry.vehicle, pad)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ class VehicleSpawnControlServerVehicleOverride(pad: VehicleSpawnPad) extends Veh
|
||||||
val vehicleFailState = vehicle.Health == 0 || vehicle.Position == Vector3.Zero
|
val vehicleFailState = vehicle.Health == 0 || vehicle.Position == Vector3.Zero
|
||||||
val driverFailState =
|
val driverFailState =
|
||||||
!driver.isAlive || driver.Continent != pad.Continent || !vehicle.PassengerInSeat(driver).contains(0)
|
!driver.isAlive || driver.Continent != pad.Continent || !vehicle.PassengerInSeat(driver).contains(0)
|
||||||
|
vehicle.MountedIn = None
|
||||||
pad.Zone.VehicleEvents ! VehicleSpawnPad.DetachFromRails(vehicle, pad)
|
pad.Zone.VehicleEvents ! VehicleSpawnPad.DetachFromRails(vehicle, pad)
|
||||||
if (vehicleFailState || driverFailState) {
|
if (vehicleFailState || driverFailState) {
|
||||||
if (vehicleFailState) {
|
if (vehicleFailState) {
|
||||||
|
|
|
||||||
|
|
@ -698,7 +698,10 @@ class VehicleControl(vehicle: Vehicle)
|
||||||
percentage,
|
percentage,
|
||||||
body,
|
body,
|
||||||
vehicle.Seats.values
|
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) }
|
.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.PlanetSideGameObject
|
||||||
import net.psforever.objects.ballistics.SourceEntry
|
import net.psforever.objects.ballistics.SourceEntry
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.vital.Vitality
|
import net.psforever.objects.vital.Vitality
|
||||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
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
|
* A wrapper for a "damage source" in damage calculations
|
||||||
* that parameterizes information necessary to explain a server-driven electromagnetic pulse occurring.
|
* that parameterizes information necessary to explain a server-driven electromagnetic pulse occurring.
|
||||||
* @see `VitalityDefinition.explodes`
|
* @see `SpecialEmp.createEmpInteraction`
|
||||||
* @see `VitalityDefinition.innateDamage`
|
* @see `VitalityDefinition.innateDamage`
|
||||||
* @see `Zone.causesSpecialEmp`
|
|
||||||
* @param entity the source of the explosive yield
|
* @param entity the source of the explosive yield
|
||||||
* @param damageModel the model to be utilized in these calculations;
|
* @param damageModel the model to be utilized in these calculations;
|
||||||
* typically, but not always, defined by the target
|
* typically, but not always, defined by the target
|
||||||
|
|
@ -41,7 +39,7 @@ object EmpReason {
|
||||||
def apply(
|
def apply(
|
||||||
owner: PlanetSideGameObject with FactionAffinity,
|
owner: PlanetSideGameObject with FactionAffinity,
|
||||||
source: DamageWithPosition,
|
source: DamageWithPosition,
|
||||||
target: PlanetSideServerObject with Vitality
|
target: PlanetSideGameObject with Vitality
|
||||||
): EmpReason = {
|
): EmpReason = {
|
||||||
EmpReason(SourceEntry(owner), source, target.DamageModel, owner.Definition.ObjectId)
|
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.PlanetSideGameObject
|
||||||
import net.psforever.objects.ballistics.SourceEntry
|
import net.psforever.objects.ballistics.SourceEntry
|
||||||
import net.psforever.objects.definition.ObjectDefinition
|
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.{Vitality, VitalityDefinition}
|
||||||
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution}
|
import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution}
|
||||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
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.vital.resolution.DamageAndResistance
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
|
|
||||||
|
|
@ -18,21 +19,18 @@ import net.psforever.objects.zones.Zone
|
||||||
* @see `VitalityDefinition.explodes`
|
* @see `VitalityDefinition.explodes`
|
||||||
* @see `VitalityDefinition.innateDamage`
|
* @see `VitalityDefinition.innateDamage`
|
||||||
* @see `Zone.causesExplosion`
|
* @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;
|
* @param damageModel the model to be utilized in these calculations;
|
||||||
* typically, but not always, defined by the target
|
* typically, but not always, defined by the target
|
||||||
* @param instigation what previous event happened, if any, that caused this explosion
|
* @param instigation what previous event happened, if any, that caused this explosion
|
||||||
*/
|
*/
|
||||||
final case class ExplodingEntityReason(
|
final case class ExplodingEntityReason(
|
||||||
entity: PlanetSideGameObject with Vitality,
|
entity: SourceEntry,
|
||||||
|
source: DamageProperties,
|
||||||
damageModel: DamageAndResistance,
|
damageModel: DamageAndResistance,
|
||||||
instigation: Option[DamageResult]
|
instigation: Option[DamageResult]
|
||||||
) extends DamageReason {
|
) 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 resolution: DamageResolution.Value = DamageResolution.Explosion
|
||||||
|
|
||||||
def same(test: DamageReason): Boolean = test match {
|
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 */
|
/** lay the blame on that which caused this explosion to occur */
|
||||||
def adversary: Option[SourceEntry] = instigation match {
|
def adversary: Option[SourceEntry] = instigation match {
|
||||||
case Some(prior) => prior.interaction.cause.adversary
|
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 = entity.Definition.ObjectId
|
||||||
override def attribution: Int = 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 {
|
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.actor.{ActorContext, ActorRef, Props}
|
||||||
import akka.routing.RandomPool
|
import akka.routing.RandomPool
|
||||||
import net.psforever.objects.ballistics.{Projectile, SourceEntry}
|
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.ce.{ComplexDeployable, Deployable, SimpleDeployable}
|
||||||
import net.psforever.objects.entity.IdentifiableEntity
|
import net.psforever.objects.entity.IdentifiableEntity
|
||||||
import net.psforever.objects.equipment.Equipment
|
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.avatar.Avatar
|
||||||
import net.psforever.objects.geometry.Geometry3D
|
import net.psforever.objects.geometry.Geometry3D
|
||||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
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.doors.Door
|
||||||
import net.psforever.objects.serverobject.locks.IFFLock
|
import net.psforever.objects.serverobject.locks.IFFLock
|
||||||
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
|
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
|
||||||
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
|
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
|
||||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||||
import net.psforever.objects.vehicles.UtilityType
|
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.interaction.{DamageInteraction, DamageResult}
|
||||||
import net.psforever.objects.vital.prop.DamageWithPosition
|
import net.psforever.objects.vital.prop.DamageWithPosition
|
||||||
import net.psforever.objects.vital.Vitality
|
import net.psforever.objects.vital.Vitality
|
||||||
|
|
@ -1092,177 +1093,169 @@ object Zone {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates `Damageable` targets within the radius of a server-prepared explosion
|
* Allocates `Damageable` targets within the vicinity of server-prepared damage dealing
|
||||||
* and informs those entities that they have affected by the aforementioned explosion.
|
* and informs those entities that they have affected by the aforementioned damage.
|
||||||
* @see `Amenity.Owner`
|
* Usually, this is considered an "explosion;" but, the application can be utilized for a variety of unbound damage.
|
||||||
* @see `ComplexDeployable`
|
* @param zone the zone in which the damage should occur
|
||||||
* @see `DamageInteraction`
|
* @param source the entity that embodies the damage (information)
|
||||||
* @see `DamageResult`
|
* @param createInteraction how the interaction for this damage is to prepared
|
||||||
* @see `DamageWithPosition`
|
* @param testTargetsFromZone a custom test for determining whether the allocated targets are affected by the damage
|
||||||
* @see `ExplodingEntityReason`
|
* @param acquireTargetsFromZone the main target-collecting algorithm
|
||||||
* @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
|
|
||||||
* @return a list of affected entities;
|
* @return a list of affected entities;
|
||||||
* only mostly complete due to the exclusion of objects whose damage resolution is different than usual
|
* only mostly complete due to the exclusion of objects whose damage resolution is different than usual
|
||||||
*/
|
*/
|
||||||
def causeExplosion(
|
def serverSideDamage(
|
||||||
zone: Zone,
|
zone: Zone,
|
||||||
obj: PlanetSideGameObject with Vitality,
|
source: PlanetSideGameObject with FactionAffinity with Vitality,
|
||||||
instigation: Option[DamageResult],
|
createInteraction: (PlanetSideGameObject with FactionAffinity with Vitality, PlanetSideGameObject with FactionAffinity with Vitality) => DamageInteraction,
|
||||||
detectionTest: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = distanceCheck
|
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] = {
|
): List[PlanetSideServerObject] = {
|
||||||
obj.Definition.innateDamage match {
|
source.Definition.innateDamage match {
|
||||||
case Some(explosion) if obj.Definition.explodes =>
|
case Some(damage) =>
|
||||||
//useful in this form
|
serverSideDamage(zone, source, damage, createInteraction, testTargetsFromZone, acquireTargetsFromZone)
|
||||||
val sourcePosition = obj.Position
|
|
||||||
val sourcePositionXY = sourcePosition.xy
|
|
||||||
val radius = explosion.DamageRadius * explosion.DamageRadius
|
|
||||||
//collect all targets that can be damaged
|
|
||||||
//players
|
|
||||||
val playerTargets = zone.LivePlayers.filterNot { _.VehicleSeated.nonEmpty }
|
|
||||||
//vehicles
|
|
||||||
val vehicleTargets = zone.Vehicles.filterNot { v => v.Destroyed || v.MountedIn.nonEmpty }
|
|
||||||
//deployables
|
|
||||||
val (simpleDeployableTargets, complexDeployableTargets) =
|
|
||||||
zone.DeployableList
|
|
||||||
.filterNot { _.Destroyed }
|
|
||||||
.foldRight((List.empty[SimpleDeployable], List.empty[ComplexDeployable])) { case (f, (simp, comp)) =>
|
|
||||||
f match {
|
|
||||||
case o: SimpleDeployable => (simp :+ o, comp)
|
|
||||||
case o: ComplexDeployable => (simp, comp :+ o)
|
|
||||||
case _ => (simp, comp)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//amenities
|
|
||||||
val soiTargets = obj match {
|
|
||||||
case o: Amenity =>
|
|
||||||
//fortunately, even where soi overlap, amenities in different buildings are never that close to each other
|
|
||||||
o.Owner.Amenities
|
|
||||||
case _ =>
|
|
||||||
zone.Buildings.values
|
|
||||||
.filter { b =>
|
|
||||||
val soiRadius = b.Definition.SOIRadius * b.Definition.SOIRadius
|
|
||||||
Vector3.DistanceSquared(sourcePositionXY, b.Position.xy) < soiRadius || soiRadius <= radius
|
|
||||||
}
|
|
||||||
.flatMap { _.Amenities }
|
|
||||||
.filter { _.Definition.Damageable }
|
|
||||||
}
|
|
||||||
//restrict to targets according to the detection plan
|
|
||||||
val allAffectedTargets = (playerTargets ++ vehicleTargets ++ complexDeployableTargets ++ soiTargets)
|
|
||||||
.filter { target =>
|
|
||||||
(target ne obj) && detectionTest(obj, target, radius)
|
|
||||||
}
|
|
||||||
//inform remaining targets that they have suffered an explosion
|
|
||||||
allAffectedTargets
|
|
||||||
.foreach { target =>
|
|
||||||
target.Actor ! Vitality.Damage(
|
|
||||||
DamageInteraction(
|
|
||||||
SourceEntry(target),
|
|
||||||
ExplodingEntityReason(obj, target.DamageModel, instigation),
|
|
||||||
target.Position
|
|
||||||
).calculate()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
//important note - these are not returned as targets that were affected
|
|
||||||
simpleDeployableTargets
|
|
||||||
.filter { target =>
|
|
||||||
(target ne obj) && detectionTest(obj, target, radius)
|
|
||||||
}
|
|
||||||
.foreach { target =>
|
|
||||||
zone.LocalEvents ! Vitality.DamageOn(
|
|
||||||
target,
|
|
||||||
DamageInteraction(
|
|
||||||
SourceEntry(target),
|
|
||||||
ExplodingEntityReason(obj, target.DamageModel, instigation),
|
|
||||||
target.Position
|
|
||||||
).calculate()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
allAffectedTargets
|
|
||||||
case None =>
|
case None =>
|
||||||
Nil
|
Nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates `Damageable` targets within the radius of a server-prepared electromagnetic pulse
|
* Allocates `Damageable` targets within the vicinity of server-prepared damage dealing
|
||||||
* and informs those entities that they have affected by the aforementioned pulse.
|
* and informs those entities that they have affected by the aforementioned damage.
|
||||||
* Targets within the effect radius within other rooms are affected, unlike with normal damage.
|
* Usually, this is considered an "explosion;" but, the application can be utilized for a variety of unbound damage.
|
||||||
* The only affected target is Boomer deployables.
|
|
||||||
* @see `Amenity.Owner`
|
|
||||||
* @see `BoomerDeployable`
|
|
||||||
* @see `DamageInteraction`
|
* @see `DamageInteraction`
|
||||||
* @see `DamageResult`
|
* @see `DamageResult`
|
||||||
* @see `DamageWithPosition`
|
* @see `DamageWithPosition`
|
||||||
* @see `EmpReason`
|
* @see `Vitality.Damage`
|
||||||
* @see `Zone.DeployableList`
|
* @see `Vitality.DamageOn`
|
||||||
* @param zone the zone in which the emp should occur
|
* @see `VitalityDefinition`
|
||||||
* @param obj the entity that triggered the emp (information)
|
* @see `VitalityDefinition.innateDamage`
|
||||||
* @param sourcePosition where the emp physically originates
|
* @see `Zone.LocalEvents`
|
||||||
* @param effect characteristics of the emp produced
|
* @param zone the zone in which the damage should occur
|
||||||
* @param detectionTest a custom test to determine if any given target is affected;
|
* @param source the entity that embodies the damage (information)
|
||||||
* defaults to an internal test for simple radial proximity
|
* @param createInteraction how the interaction for this damage is to prepared
|
||||||
* @return a list of affected entities
|
* @param testTargetsFromZone a custom test for determining whether the allocated targets are affected by the damage
|
||||||
|
* @param acquireTargetsFromZone the main target-collecting algorithm
|
||||||
|
* @return a list of affected entities;
|
||||||
|
* only mostly complete due to the exclusion of objects whose damage resolution is different than usual
|
||||||
*/
|
*/
|
||||||
def causeSpecialEmp(
|
def serverSideDamage(
|
||||||
zone: Zone,
|
zone: Zone,
|
||||||
obj: PlanetSideServerObject with Vitality,
|
source: PlanetSideGameObject with FactionAffinity with Vitality,
|
||||||
sourcePosition: Vector3,
|
properties: DamageWithPosition,
|
||||||
effect: DamageWithPosition,
|
createInteraction: (PlanetSideGameObject with FactionAffinity with Vitality, PlanetSideGameObject with FactionAffinity with Vitality) => DamageInteraction,
|
||||||
detectionTest: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = distanceCheck
|
testTargetsFromZone: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean,
|
||||||
): List[PlanetSideServerObject] = {
|
acquireTargetsFromZone: (Zone, PlanetSideGameObject with FactionAffinity with Vitality, DamageWithPosition) => (List[PlanetSideServerObject with Vitality], List[PlanetSideGameObject with FactionAffinity with Vitality])
|
||||||
val proxy: ExplosiveDeployable = {
|
): List[PlanetSideServerObject] = {
|
||||||
//construct a proxy unit to represent the pulse
|
//collect targets that can be damaged
|
||||||
val o = new ExplosiveDeployable(GlobalDefinitions.special_emp)
|
val (pssos, psgos) = acquireTargetsFromZone(zone, source, properties)
|
||||||
o.Owner = Some(obj.GUID)
|
val radius = properties.DamageRadius * properties.DamageRadius
|
||||||
o.OwnerName = obj match {
|
//restrict to targets according to the detection plan
|
||||||
case p: Player => p.Name
|
val allAffectedTargets = pssos.filter { target => testTargetsFromZone(source, target, radius) }
|
||||||
case o: OwnableByPlayer => o.OwnerName.getOrElse("")
|
//inform remaining targets that they have suffered damage
|
||||||
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
|
allAffectedTargets
|
||||||
.foreach { target =>
|
.foreach { target => target.Actor ! Vitality.Damage(createInteraction(source, target).calculate()) }
|
||||||
target.Actor ! Vitality.Damage(
|
//important note - these are not returned as targets that were affected
|
||||||
DamageInteraction(
|
psgos
|
||||||
SourceEntry(target),
|
.filter { target => testTargetsFromZone(source, target, radius) }
|
||||||
EmpReason(obj, effect, target),
|
.foreach { target => zone.LocalEvents ! Vitality.DamageOn(target, createInteraction(source, target).calculate()) }
|
||||||
sourcePosition
|
|
||||||
).calculate()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
allAffectedTargets
|
allAffectedTargets
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @see `Amenity.Owner`
|
||||||
|
* @see `ComplexDeployable`
|
||||||
|
* @see `DamageWithPosition`
|
||||||
|
* @see `SimpleDeployable`
|
||||||
|
* @see `Zone.Buildings`
|
||||||
|
* @see `Zone.DeployableList`
|
||||||
|
* @see `Zone.LivePlayers`
|
||||||
|
* @see `Zone.Vehicles`
|
||||||
|
* @param zone the zone in which to search
|
||||||
|
* @param source a game entity that is treated as the origin and is excluded from results
|
||||||
|
* @param damagePropertiesBySource information about the effect/damage
|
||||||
|
* @return two lists of objects with different characteristics;
|
||||||
|
* the first list is `PlanetSideServerObject` entities with `Vitality`;
|
||||||
|
* the second list is `PlanetSideGameObject` entities with both `Vitality` and `FactionAffinity`
|
||||||
|
*/
|
||||||
|
def findAllTargets(
|
||||||
|
zone: Zone,
|
||||||
|
source: PlanetSideGameObject with Vitality,
|
||||||
|
damagePropertiesBySource: DamageWithPosition
|
||||||
|
): (List[PlanetSideServerObject with Vitality], List[PlanetSideGameObject with FactionAffinity with Vitality]) = {
|
||||||
|
val sourcePosition = source.Position
|
||||||
|
val sourcePositionXY = sourcePosition.xy
|
||||||
|
val radius = damagePropertiesBySource.DamageRadius * damagePropertiesBySource.DamageRadius
|
||||||
|
//collect all targets that can be damaged
|
||||||
|
//players
|
||||||
|
val playerTargets = zone.LivePlayers.filterNot { _.VehicleSeated.nonEmpty }
|
||||||
|
//vehicles
|
||||||
|
val vehicleTargets = zone.Vehicles.filterNot { v => v.Destroyed || v.MountedIn.nonEmpty }
|
||||||
|
//deployables
|
||||||
|
val (simpleDeployableTargets, complexDeployableTargets) =
|
||||||
|
zone.DeployableList
|
||||||
|
.filterNot { _.Destroyed }
|
||||||
|
.foldRight((List.empty[SimpleDeployable], List.empty[ComplexDeployable])) { case (f, (simp, comp)) =>
|
||||||
|
f match {
|
||||||
|
case o: SimpleDeployable => (simp :+ o, comp)
|
||||||
|
case o: ComplexDeployable => (simp, comp :+ o)
|
||||||
|
case _ => (simp, comp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//amenities
|
||||||
|
val soiTargets = source match {
|
||||||
|
case o: Amenity =>
|
||||||
|
//fortunately, even where soi overlap, amenities in different buildings are never that close to each other
|
||||||
|
o.Owner.Amenities
|
||||||
|
case _ =>
|
||||||
|
zone.Buildings.values
|
||||||
|
.filter { b =>
|
||||||
|
val soiRadius = b.Definition.SOIRadius * b.Definition.SOIRadius
|
||||||
|
Vector3.DistanceSquared(sourcePositionXY, b.Position.xy) < soiRadius || soiRadius <= radius
|
||||||
|
}
|
||||||
|
.flatMap { _.Amenities }
|
||||||
|
.filter { _.Definition.Damageable }
|
||||||
|
}
|
||||||
|
(
|
||||||
|
(playerTargets ++ vehicleTargets ++ complexDeployableTargets ++ soiTargets)
|
||||||
|
.filter { target => target ne source },
|
||||||
|
simpleDeployableTargets
|
||||||
|
.filter { target => target ne source }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* na
|
||||||
|
* @param instigation what previous event happened, if any, that caused this explosion
|
||||||
|
* @param source a game object that represents the source of the explosion
|
||||||
|
* @param target a game object that is affected by the explosion
|
||||||
|
* @return a `DamageInteraction` object
|
||||||
|
*/
|
||||||
|
def explosionDamage(
|
||||||
|
instigation: Option[DamageResult]
|
||||||
|
)
|
||||||
|
(
|
||||||
|
source: PlanetSideGameObject with FactionAffinity with Vitality,
|
||||||
|
target: PlanetSideGameObject with FactionAffinity with Vitality
|
||||||
|
): DamageInteraction = {
|
||||||
|
DamageInteraction(
|
||||||
|
SourceEntry(target),
|
||||||
|
ExplodingEntityReason(source, target.DamageModel, instigation),
|
||||||
|
target.Position
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Two game entities are considered "near" each other if they are within a certain distance of one another.
|
* 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`.
|
* A default function literal mainly used for `serverSideDamage`.
|
||||||
* @see `causeExplosion`
|
|
||||||
* @see `ObjectDefinition.Geometry`
|
* @see `ObjectDefinition.Geometry`
|
||||||
* @param obj1 a game entity, should be the source of the explosion
|
* @see `serverSideDamage`
|
||||||
* @param obj2 a game entity, should be the target of the explosion
|
* @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
|
* @param maxDistance the square of the maximum distance permissible between game entities
|
||||||
* before they are no longer considered "near"
|
* 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
|
* `false`, otherwise
|
||||||
*/
|
*/
|
||||||
def distanceCheck(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float): Boolean = {
|
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;
|
* @return `true`, if the target entities are near enough to each other;
|
||||||
* `false`, otherwise
|
* `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 ||
|
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3) <= maxDistance ||
|
||||||
distanceCheck(g1, g2) <= maxDistance
|
distanceCheck(g1, g2) <= maxDistance
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ object Zoning {
|
||||||
object Method extends Enumeration {
|
object Method extends Enumeration {
|
||||||
type Type = Value
|
type Type = Value
|
||||||
|
|
||||||
val None, InstantAction, Recall, Quit = Value
|
val None, InstantAction, OutfitRecall, Recall, Quit = Value
|
||||||
}
|
}
|
||||||
|
|
||||||
object Status extends Enumeration {
|
object Status extends Enumeration {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue