diff --git a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala
index 7c493e365..812bf74af 100644
--- a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala
@@ -533,7 +533,7 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
case _ =>
GUIDTask.registerObject(continent.GUID, dObj)
}
- TaskWorkflow.execute(CallBackForTask(tasking, continent.Deployables, Zone.Deployable.BuildByOwner(dObj, player, obj)))
+ TaskWorkflow.execute(CallBackForTask(tasking, continent.Deployables, Zone.Deployable.BuildByOwner(dObj, player, obj), context.self))
case Some(obj) =>
log.warn(s"DeployObject: what is $obj, ${player.Name}? It's not a construction tool!")
case None =>
diff --git a/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala b/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala
index 9c49e8dd5..d6fd141e3 100644
--- a/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/NormalMode.scala
@@ -4,6 +4,7 @@ package net.psforever.actors.session.normal
import akka.actor.Actor.Receive
import akka.actor.ActorRef
import net.psforever.actors.session.support.{ChatFunctions, GeneralFunctions, LocalHandlerFunctions, MountHandlerFunctions, SquadHandlerFunctions, TerminalHandlerFunctions, VehicleFunctions, VehicleHandlerFunctions, WeaponAndProjectileFunctions}
+import net.psforever.objects.Players
import net.psforever.packet.game.UplinkRequest
import net.psforever.services.chat.ChatService
//
@@ -248,9 +249,11 @@ class NormalModeLogic(data: SessionData) extends ModeLogic {
case _: Zone.Vehicle.HasDespawned => ;
case Zone.Deployable.IsDismissed(obj: TurretDeployable) => //only if target deployable was never fully introduced
+ Players.buildCooldownReset(data.continent, data.player.Name, obj)
TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(data.continent.GUID, obj))
case Zone.Deployable.IsDismissed(obj) => //only if target deployable was never fully introduced
+ Players.buildCooldownReset(data.continent, data.player.Name, obj)
TaskWorkflow.execute(GUIDTask.unregisterObject(data.continent.GUID, obj))
case msg: Containable.ItemPutInSlot =>
diff --git a/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala b/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala
index 8d39fcc6b..f6fe40c83 100644
--- a/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala
@@ -272,24 +272,29 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
def handleDeployRequest(pkt: DeployRequestMessage): Unit = {
val DeployRequestMessage(_, vehicle_guid, deploy_state, _, _, _) = pkt
- val vehicle = player.avatar.vehicle
- if (vehicle.contains(vehicle_guid)) {
- if (vehicle == player.VehicleSeated) {
- continent.GUID(vehicle_guid) match {
- case Some(obj: Vehicle) =>
+ continent.GUID(vehicle_guid)
+ .collect {
+ case obj: Vehicle =>
+ val vehicle = player.avatar.vehicle
+ if (!vehicle.contains(vehicle_guid)) {
+ log.warn(s"DeployRequest: ${player.Name} does not own the would-be-deploying ${obj.Definition.Name}")
+ } else if (vehicle != player.VehicleSeated) {
+ log.warn(s"${player.Name} must be mounted as the driver to request a deployment change")
+ } else {
log.info(s"${player.Name} is requesting a deployment change for ${obj.Definition.Name} - $deploy_state")
obj.Actor ! Deployment.TryDeploymentChange(deploy_state)
-
- case _ =>
- log.error(s"DeployRequest: ${player.Name} can not find vehicle $vehicle_guid")
- avatarActor ! AvatarActor.SetVehicle(None)
- }
- } else {
- log.warn(s"${player.Name} must be mounted to request a deployment change")
+ continent.Transport ! Zone.Vehicle.TryDeploymentChange(obj, deploy_state)
+ }
+ obj
+ case obj =>
+ log.error(s"DeployRequest: ${player.Name} expected a vehicle, but found a ${obj.Definition.Name} instead")
+ obj
+ }
+ .orElse {
+ log.error(s"DeployRequest: ${player.Name} can not find entity $vehicle_guid")
+ avatarActor ! AvatarActor.SetVehicle(None) //todo is this safe
+ None
}
- } else {
- log.warn(s"DeployRequest: ${player.Name} does not own the deploying $vehicle_guid object")
- }
}
/* messages */
diff --git a/src/main/scala/net/psforever/login/WorldSession.scala b/src/main/scala/net/psforever/login/WorldSession.scala
index f49d54d65..ae02c6f64 100644
--- a/src/main/scala/net/psforever/login/WorldSession.scala
+++ b/src/main/scala/net/psforever/login/WorldSession.scala
@@ -1080,4 +1080,22 @@ object WorldSession {
task
)
}
+
+ def CallBackForTask(task: TaskBundle, sendTo: ActorRef, pass: Any, replyTo: ActorRef): TaskBundle = {
+ TaskBundle(
+ new StraightforwardTask() {
+ private val localDesc = task.description()
+ private val destination = sendTo
+ private val passMsg = pass
+
+ override def description(): String = s"callback for tasking $localDesc"
+
+ def action() : Future[Any] = {
+ destination.tell(passMsg, replyTo)
+ Future(this)
+ }
+ },
+ task
+ )
+ }
}
diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index 190b098bd..778f9abeb 100644
--- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -27,6 +27,7 @@ import net.psforever.objects.vital._
import net.psforever.types.{ExoSuitType, ImplantType, PlanetSideEmpire, Vector3}
import net.psforever.types._
import net.psforever.objects.serverobject.llu.{CaptureFlagDefinition, CaptureFlagSocketDefinition}
+import net.psforever.objects.serverobject.zipline.GenericTeleportationDefinition
import scala.annotation.switch
@@ -1277,6 +1278,10 @@ object GlobalDefinitions {
val targeting_laser_dispenser = new OrderTerminalDefinition(851)
+ val stationaryteleportpad = new GenericTeleportationDefinition(836)
+
+ val zipline = new GenericTeleportationDefinition(1047)
+
/*
Buildings
*/
@@ -1294,29 +1299,28 @@ object GlobalDefinitions {
val vanu_core = new BuildingDefinition(932)
- //the following group borrows the object id from entity mainbase1
- val ground_bldg_a = new BuildingDefinition(474)
- val ground_bldg_b = new BuildingDefinition(474)
- val ground_bldg_c = new BuildingDefinition(474)
- val ground_bldg_d = new BuildingDefinition(474)
- val ground_bldg_e = new BuildingDefinition(474)
- val ground_bldg_f = new BuildingDefinition(474)
- val ground_bldg_g = new BuildingDefinition(474)
- val ground_bldg_h = new BuildingDefinition(474)
- val ground_bldg_i = new BuildingDefinition(474)
- val ground_bldg_j = new BuildingDefinition(474)
- val ground_bldg_z = new BuildingDefinition(474)
- val ceiling_bldg_a = new BuildingDefinition(474)
- val ceiling_bldg_b = new BuildingDefinition(474)
- val ceiling_bldg_c = new BuildingDefinition(474)
- val ceiling_bldg_d = new BuildingDefinition(474)
- val ceiling_bldg_e = new BuildingDefinition(474)
- val ceiling_bldg_f = new BuildingDefinition(474)
- val ceiling_bldg_g = new BuildingDefinition(474)
- val ceiling_bldg_h = new BuildingDefinition(474)
- val ceiling_bldg_i = new BuildingDefinition(474)
- val ceiling_bldg_j = new BuildingDefinition(474)
- val ceiling_bldg_z = new BuildingDefinition(474)
+ val ground_bldg_a = new BuildingDefinition(373)
+ val ground_bldg_b = new BuildingDefinition(374)
+ val ground_bldg_c = new BuildingDefinition(375)
+ val ground_bldg_d = new BuildingDefinition(376)
+ val ground_bldg_e = new BuildingDefinition(377)
+ val ground_bldg_f = new BuildingDefinition(378)
+ val ground_bldg_g = new BuildingDefinition(379)
+ val ground_bldg_h = new BuildingDefinition(380)
+ val ground_bldg_i = new BuildingDefinition(381)
+ val ground_bldg_j = new BuildingDefinition(382)
+ val ground_bldg_z = new BuildingDefinition(383)
+ val ceiling_bldg_a = new BuildingDefinition(159)
+ val ceiling_bldg_b = new BuildingDefinition(160)
+ val ceiling_bldg_c = new BuildingDefinition(161)
+ val ceiling_bldg_d = new BuildingDefinition(162)
+ val ceiling_bldg_e = new BuildingDefinition(163)
+ val ceiling_bldg_f = new BuildingDefinition(164)
+ val ceiling_bldg_g = new BuildingDefinition(165)
+ val ceiling_bldg_h = new BuildingDefinition(166)
+ val ceiling_bldg_i = new BuildingDefinition(167)
+ val ceiling_bldg_j = new BuildingDefinition(168)
+ val ceiling_bldg_z = new BuildingDefinition(169)
val mainbase1 = new BuildingDefinition(474)
diff --git a/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala b/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala
index 91451b4ea..2a6f4d23b 100644
--- a/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala
+++ b/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala
@@ -33,7 +33,7 @@ class DeployableToolbox {
* keys: categories, values: quantity storage object
*/
private val categoryCounts =
- DeployableCategory.values.toSeq.map(value => { value -> new DeployableToolbox.Bin }).toMap
+ DeployableCategory.values.map(value => { value -> new DeployableToolbox.Bin }).toMap
/**
* a map of bins for keeping track of the quantities of individual deployables
@@ -46,7 +46,7 @@ class DeployableToolbox {
* keys: categories, values: deployable objects
*/
private val deployableLists =
- DeployableCategory.values.toSeq
+ DeployableCategory.values
.map(value => { value -> mutable.ListBuffer[DeployableToolbox.AcceptableDeployable]() })
.toMap
@@ -73,7 +73,7 @@ class DeployableToolbox {
}
}
- def UpdateMaxCounts(certifications: Set[Certification]) = {
+ def UpdateMaxCounts(certifications: Set[Certification]): Unit = {
DeployableToolbox.UpdateMaxCounts(deployableCounts, categoryCounts, certifications)
}
@@ -247,7 +247,7 @@ class DeployableToolbox {
* @param category the target category
* @return any deployable that is found
*/
- def DisplaceFirst(category: DeployableCategory.Value): Option[DeployableToolbox.AcceptableDeployable] = {
+ def DisplaceFirst(category: DeployableCategory): Option[DeployableToolbox.AcceptableDeployable] = {
val categoryList = deployableLists(category)
if (categoryList.nonEmpty) {
val found = categoryList.remove(0)
@@ -294,7 +294,7 @@ class DeployableToolbox {
* @param filter the type of deployable
* @return a list of globally unique identifiers that should be valid for the current zone
*/
- def Category(filter: DeployableCategory.Value): List[PlanetSideGUID] = {
+ def Category(filter: DeployableCategory): List[PlanetSideGUID] = {
deployableLists(filter).map(_.GUID).toList
}
@@ -478,7 +478,7 @@ object DeployableToolbox {
*/
private def UpdateMaxCounts(
counts: Map[DeployedItem.Value, DeployableToolbox.Bin],
- categories: Map[DeployableCategory.Value, DeployableToolbox.Bin],
+ categories: Map[DeployableCategory, DeployableToolbox.Bin],
certifications: Set[Certification]
): Unit = {
import Certification._
diff --git a/src/main/scala/net/psforever/objects/ce/Deployable.scala b/src/main/scala/net/psforever/objects/ce/Deployable.scala
index 65387285b..0a4664945 100644
--- a/src/main/scala/net/psforever/objects/ce/Deployable.scala
+++ b/src/main/scala/net/psforever/objects/ce/Deployable.scala
@@ -77,18 +77,18 @@ object Deployable {
}
object Category {
- def Of(item: DeployedItem.Value): DeployableCategory.Value = deployablesToCategories(item)
+ def Of(item: DeployedItem.Value): DeployableCategory = deployablesToCategories(item)
- def Includes(category: DeployableCategory.Value): List[DeployedItem.Value] = {
+ def Includes(category: DeployableCategory): List[DeployedItem.Value] = {
(for {
- (ce: DeployedItem.Value, cat: DeployableCategory.Value) <- deployablesToCategories
+ (ce: DeployedItem.Value, cat: DeployableCategory) <- deployablesToCategories
if cat == category
} yield ce) toList
}
- def OfAll(): Map[DeployedItem.Value, DeployableCategory.Value] = deployablesToCategories
+ def OfAll(): Map[DeployedItem.Value, DeployableCategory] = deployablesToCategories
- private val deployablesToCategories: Map[DeployedItem.Value, DeployableCategory.Value] = Map(
+ private val deployablesToCategories: Map[DeployedItem.Value, DeployableCategory] = Map(
DeployedItem.boomer -> DeployableCategory.Boomers,
DeployedItem.he_mine -> DeployableCategory.Mines,
DeployedItem.jammer_mine -> DeployableCategory.Mines,
diff --git a/src/main/scala/net/psforever/objects/ce/DeployableCategory.scala b/src/main/scala/net/psforever/objects/ce/DeployableCategory.scala
index 0a40d1fc6..adc5eac27 100644
--- a/src/main/scala/net/psforever/objects/ce/DeployableCategory.scala
+++ b/src/main/scala/net/psforever/objects/ce/DeployableCategory.scala
@@ -1,8 +1,26 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.ce
-object DeployableCategory extends Enumeration {
- type Type = Value
+abstract class DeployableCategory(val name: String)
- val Boomers, Mines, SmallTurrets, Sensors, TankTraps, FieldTurrets, ShieldGenerators, Telepads = Value
+object DeployableCategory {
+ case object None extends DeployableCategory(name = "None")
+
+ case object Boomers extends DeployableCategory(name = "Boomers")
+
+ case object Mines extends DeployableCategory(name = "Mines")
+
+ case object SmallTurrets extends DeployableCategory(name = "SmallTurrets")
+
+ case object Sensors extends DeployableCategory(name = "Sensors")
+
+ case object TankTraps extends DeployableCategory(name = "TankTraps")
+
+ case object FieldTurrets extends DeployableCategory(name = "FieldTurrets")
+
+ case object ShieldGenerators extends DeployableCategory(name = "ShieldGenerators")
+
+ case object Telepads extends DeployableCategory(name = "Telepads")
+
+ val values: Seq[DeployableCategory] = Seq(None, Boomers, Mines, SmallTurrets, Sensors, TankTraps, FieldTurrets, ShieldGenerators, Telepads)
}
diff --git a/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala b/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
index 4f9308292..763cb49cd 100644
--- a/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
+++ b/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
@@ -19,15 +19,15 @@ object DeployAnimation extends Enumeration {
}
trait BaseDeployableDefinition {
- private var category: DeployableCategory.Value = DeployableCategory.Boomers
+ private var category: DeployableCategory = DeployableCategory.None
private var deployTime: Long = (1 second).toMillis //ms
var deployAnimation: DeployAnimation.Value = DeployAnimation.None
def Item: DeployedItem.Value
- def DeployCategory: DeployableCategory.Value = category
+ def DeployCategory: DeployableCategory = category
- def DeployCategory_=(cat: DeployableCategory.Value): DeployableCategory.Value = {
+ def DeployCategory_=(cat: DeployableCategory): DeployableCategory = {
category = cat
DeployCategory
}
diff --git a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala
index c1b12cb21..4cd0e824c 100644
--- a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala
+++ b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala
@@ -5,6 +5,7 @@ import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.definition.converter.{ObjectCreateConverter, PacketConverter}
import net.psforever.objects.geometry.GeometryForm
import net.psforever.objects.geometry.d3.VolumetricGeometry
+import net.psforever.objects.serverobject.deploy.{Interference, InterferenceRange}
import net.psforever.types.OxygenState
/**
@@ -121,5 +122,10 @@ abstract class ObjectDefinition(private val objectId: Int)
*/
var maxForwardSpeed: Float = 0f
+ /**
+ * na
+ */
+ var interference: InterferenceRange = Interference.AllowAll
+
def ObjectId: Int = objectId
}
diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsDeployable.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsDeployable.scala
index e59828e23..da633d60a 100644
--- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsDeployable.scala
+++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsDeployable.scala
@@ -8,6 +8,7 @@ import net.psforever.objects.definition.converter.{FieldTurretConverter, Interna
import net.psforever.objects.equipment.{EffectTarget, TargetValidation}
import net.psforever.objects.geometry.GeometryForm
import net.psforever.objects.geometry.d3.VolumetricGeometry
+import net.psforever.objects.serverobject.deploy.InterferenceRange
import net.psforever.objects.serverobject.mount.{MountInfo, SeatDefinition}
import net.psforever.objects.serverobject.turret.{AutoChecks, AutoCooldowns, AutoRanges, Automation, TurretUpgrade}
import net.psforever.objects.vital.{CollisionXYData, CollisionZData, ComplexDeployableResolutions, SimpleResolutions}
@@ -40,6 +41,7 @@ object GlobalDefinitionsDeployable {
boomer.DeployCategory = DeployableCategory.Boomers
boomer.DeployTime = Duration.create(1000, "ms")
boomer.deployAnimation = DeployAnimation.Standard
+ boomer.interference = InterferenceRange(main = 0.2f)
boomer.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.Splash
SympatheticExplosion = true
@@ -62,6 +64,7 @@ object GlobalDefinitionsDeployable {
he_mine.Repairable = false
he_mine.DeployTime = Duration.create(1000, "ms")
he_mine.deployAnimation = DeployAnimation.Standard
+ he_mine.interference = InterferenceRange(main = 7f, sharedGroupId = 1, shared = 7f, deployables = 0.1f)
he_mine.triggerRadius = 3f
he_mine.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.Splash
@@ -85,6 +88,7 @@ object GlobalDefinitionsDeployable {
jammer_mine.Repairable = false
jammer_mine.DeployTime = Duration.create(1000, "ms")
jammer_mine.deployAnimation = DeployAnimation.Standard
+ jammer_mine.interference = InterferenceRange(main = 7f, sharedGroupId = 1, shared = 7f, deployables = 0.1f)
jammer_mine.DetonateOnJamming = false
jammer_mine.triggerRadius = 3f
jammer_mine.innateDamage = new DamageWithPosition {
@@ -133,6 +137,7 @@ object GlobalDefinitionsDeployable {
spitfire_turret.DeployTime = Duration.create(5000, "ms")
spitfire_turret.Model = ComplexDeployableResolutions.calculate
spitfire_turret.deployAnimation = DeployAnimation.Standard
+ spitfire_turret.interference = InterferenceRange(main = 25f, sharedGroupId = 2, shared = 25f, deployables = 0.1f)
spitfire_turret.AutoFire = Automation(
AutoRanges(
detection = 75f,
@@ -176,6 +181,7 @@ object GlobalDefinitionsDeployable {
spitfire_cloaked.DeployCategory = DeployableCategory.SmallTurrets
spitfire_cloaked.DeployTime = Duration.create(5000, "ms")
spitfire_cloaked.deployAnimation = DeployAnimation.Standard
+ spitfire_cloaked.interference = InterferenceRange(main = 25f, sharedGroupId = 2, shared = 25f, deployables = 0.1f)
spitfire_cloaked.Model = ComplexDeployableResolutions.calculate
spitfire_cloaked.AutoFire = Automation(
AutoRanges(
@@ -226,6 +232,7 @@ object GlobalDefinitionsDeployable {
spitfire_aa.DeployCategory = DeployableCategory.SmallTurrets
spitfire_aa.DeployTime = Duration.create(5000, "ms")
spitfire_aa.deployAnimation = DeployAnimation.Standard
+ spitfire_aa.interference = InterferenceRange(main = 25f, sharedGroupId = 2, shared = 25f, deployables = 0.1f)
spitfire_aa.Model = ComplexDeployableResolutions.calculate
spitfire_aa.AutoFire = Automation(
AutoRanges(
@@ -263,6 +270,7 @@ object GlobalDefinitionsDeployable {
motionalarmsensor.RepairIfDestroyed = false
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
motionalarmsensor.deployAnimation = DeployAnimation.Standard
+ motionalarmsensor.interference = InterferenceRange(main = 25f, deployables = 0.1f)
motionalarmsensor.Geometry = sensor
sensor_shield.Name = "sensor_shield"
@@ -273,6 +281,7 @@ object GlobalDefinitionsDeployable {
sensor_shield.RepairIfDestroyed = false
sensor_shield.DeployTime = Duration.create(5000, "ms")
sensor_shield.deployAnimation = DeployAnimation.Standard
+ sensor_shield.interference = InterferenceRange(main = 20f, deployables = 0.1f)
sensor_shield.Geometry = sensor
tank_traps.Name = "tank_traps"
@@ -284,6 +293,8 @@ object GlobalDefinitionsDeployable {
tank_traps.DeployCategory = DeployableCategory.TankTraps
tank_traps.DeployTime = Duration.create(6000, "ms")
tank_traps.deployAnimation = DeployAnimation.Fdu
+ tank_traps.interference = InterferenceRange(main = 3.5f, sharedGroupId = 3, shared = 60f, deployables = 3f)
+ //todo what is tank_traps interference2 60
//tank_traps do not explode
tank_traps.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One
@@ -318,6 +329,7 @@ object GlobalDefinitionsDeployable {
portable_manned_turret.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret.DeployTime = Duration.create(6000, "ms")
portable_manned_turret.deployAnimation = DeployAnimation.Fdu
+ portable_manned_turret.interference = InterferenceRange(main = 60f, sharedGroupId = 3, shared = 40f, deployables = 2.5f)
portable_manned_turret.Model = ComplexDeployableResolutions.calculate
portable_manned_turret.RadiationShielding = 0.5f
portable_manned_turret.innateDamage = new DamageWithPosition {
@@ -351,6 +363,7 @@ object GlobalDefinitionsDeployable {
portable_manned_turret_nc.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_nc.DeployTime = Duration.create(6000, "ms")
portable_manned_turret_nc.deployAnimation = DeployAnimation.Fdu
+ portable_manned_turret_nc.interference = InterferenceRange(main = 60f, sharedGroupId = 3, shared = 40f, deployables = 2.5f)
portable_manned_turret_nc.Model = ComplexDeployableResolutions.calculate
portable_manned_turret_nc.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One
@@ -383,6 +396,7 @@ object GlobalDefinitionsDeployable {
portable_manned_turret_tr.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_tr.DeployTime = Duration.create(6000, "ms")
portable_manned_turret_tr.deployAnimation = DeployAnimation.Fdu
+ portable_manned_turret_tr.interference = InterferenceRange(main = 60f, sharedGroupId = 3, shared = 40f, deployables = 2.5f)
portable_manned_turret_tr.Model = ComplexDeployableResolutions.calculate
portable_manned_turret_tr.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One
@@ -415,6 +429,7 @@ object GlobalDefinitionsDeployable {
portable_manned_turret_vs.DeployCategory = DeployableCategory.FieldTurrets
portable_manned_turret_vs.DeployTime = Duration.create(6000, "ms")
portable_manned_turret_vs.deployAnimation = DeployAnimation.Fdu
+ portable_manned_turret_vs.interference = InterferenceRange(main = 60f, sharedGroupId = 3, shared = 40f, deployables = 2.5f)
portable_manned_turret_vs.Model = ComplexDeployableResolutions.calculate
portable_manned_turret_vs.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One
@@ -437,6 +452,7 @@ object GlobalDefinitionsDeployable {
deployable_shield_generator.RepairIfDestroyed = false
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
deployable_shield_generator.deployAnimation = DeployAnimation.Fdu
+ deployable_shield_generator.interference = InterferenceRange(main = 125f, sharedGroupId = 3, shared = 60f, deployables = 2f)
deployable_shield_generator.Model = ComplexDeployableResolutions.calculate
deployable_shield_generator.Geometry = GeometryForm.representByCylinder(radius = 0.6562f, height = 2.17188f)
diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala
index f625b38f5..12868e001 100644
--- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala
+++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala
@@ -6,6 +6,7 @@ import net.psforever.objects.avatar.Certification
import net.psforever.objects.equipment.EffectTarget
import net.psforever.objects.geometry.GeometryForm
import net.psforever.objects.geometry.d3.VolumetricGeometry
+import net.psforever.objects.serverobject.deploy.InterferenceRange
import net.psforever.objects.serverobject.doors.InteriorDoorField
import net.psforever.objects.serverobject.mount.{MountInfo, SeatDefinition}
import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition
@@ -934,5 +935,11 @@ object GlobalDefinitionsMiscellaneous {
obbasemesh.Descriptor = "orbital_shuttle_pad"
obbasemesh.Damageable = false
obbasemesh.Repairable = false
+
+ stationaryteleportpad.Name = "stationaryteleportpad"
+ stationaryteleportpad.interference = InterferenceRange(deployables = 5.5f)
+
+ zipline.Name = "zipline"
+ zipline.interference = InterferenceRange(deployables = 5.5f)
}
}
diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala
index df3b9b793..c3dd0c9dd 100644
--- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala
+++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala
@@ -6,6 +6,7 @@ import net.psforever.objects.definition.converter._
import net.psforever.objects.geometry.GeometryForm
import net.psforever.objects.inventory.InventoryTile
import net.psforever.objects.GlobalDefinitions
+import net.psforever.objects.serverobject.deploy.InterferenceRange
import net.psforever.objects.serverobject.mount._
import net.psforever.objects.vehicles.{DestroyedVehicle, UtilityType, VehicleSubsystemEntry}
import net.psforever.objects.vital.base.DamageType
@@ -969,6 +970,7 @@ object GlobalDefinitionsVehicle {
ams.Deployment = true
ams.DeployTime = 2000
ams.UndeployTime = 2000
+ ams.interference = InterferenceRange(main = 125f, sharedGroupId = 3, shared = 30f)
ams.DeconstructionTime = Some(20 minutes)
ams.AutoPilotSpeeds = (18, 6)
ams.Packet = utilityConverter
diff --git a/src/main/scala/net/psforever/objects/serverobject/deploy/Interference.scala b/src/main/scala/net/psforever/objects/serverobject/deploy/Interference.scala
new file mode 100644
index 000000000..16ba67795
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/deploy/Interference.scala
@@ -0,0 +1,128 @@
+// Copyright (c) 2024 PSForever
+package net.psforever.objects.serverobject.deploy
+
+import net.psforever.objects.PlanetSideGameObject
+import net.psforever.objects.definition.{DeployableDefinition, ObjectDefinition}
+import net.psforever.objects.serverobject.affinity.FactionAffinity
+import net.psforever.objects.zones.Zone
+import net.psforever.types.{DriveState, Vector3}
+
+import scala.annotation.unused
+
+/**
+ * Block the deployment of certain entities within a certain distance.
+ * Deployable vehicles and combat engineer entities both have a deployment condition that can be influenced by these ranges.
+ * Vehicles of an object type block other vehicles of that object type.
+ * Combat engineering entities block combat engineering entities of the same category.
+ * @param main distance between which this kind of entity blocks itself (m)
+ * @param sharedGroupId identifier for the similar entity group
+ * @param shared distance between entities that belong to the similar group block one another (m)
+ * @param deployables distance between which this entity may block deployment of combat engineering entities (m);
+ * defaults to 0
+ */
+final case class InterferenceRange(main: Float = 0f, sharedGroupId: Int = 0, shared: Float = 0f, deployables: Float = 0f) {
+ assert(
+ main > -1f && deployables > -1f,
+ "if set, interference range must be positive non-zero float value"
+ )
+ assert(
+ sharedGroupId == 0 || shared > 0f,
+ "if set, a shared group of a non-zero id should have a positive non-zero float value"
+ )
+}
+
+object Interference {
+ final val AllowAll: InterferenceRange = InterferenceRange()
+
+ final val MaxRange: Float = 125f
+
+ /**
+ * When two entities connected by similarity or distance exert influence on one another
+ * that stops the later-acting entity from manifesting
+ * or disables certain behaviors from the later-acting entity.
+ * Only dynamic entities are examined.
+ * Static entities like facility amenities or zone-specific elements that exert interference
+ * must manage their interactions through other methods.
+ * @param zone game world in which this test will be conducted;
+ * entity should be `ZoneAware`, but it may not be set correctly during this part of its internal process
+ * @param obj entity that may be interfered with
+ * @return a different entity that causes the test entity to suffer interference
+ */
+ def Test(zone: Zone, obj: PlanetSideGameObject with FactionAffinity): Option[PlanetSideGameObject with FactionAffinity] = {
+ val (data, filterFunc) = SetupForTest(zone, obj)
+ data.find(filterFunc)
+ }
+
+ /**
+ * When two entities connected by similarity or distance exert influence on one another
+ * that stops the later-acting entity from manifesting
+ * or disables certain behaviors from the later-acting entity.
+ * @param zone game world in which this test will be conducted;
+ * entity should be `ZoneAware`, but it may not be set correctly during this part of its internal process
+ * @param obj entity that may be interfered with
+ * @return list of entities to run an interference test on, and
+ * predicate to determine which entities pass the test
+ */
+ def SetupForTest(
+ zone: Zone,
+ obj: PlanetSideGameObject with FactionAffinity
+ ): (List[PlanetSideGameObject with FactionAffinity], PlanetSideGameObject => Boolean) = {
+ val objectDefinition = obj.Definition
+ if (objectDefinition.interference eq Interference.AllowAll) {
+ (List(), interferenceTestNoResults) //no targets can block, and test will never pass
+ } else {
+ val position = obj.Position
+ val faction = obj.Faction
+ val sharedGroupId = objectDefinition.interference.sharedGroupId
+ val sector = zone.blockMap.sector(position, Interference.MaxRange)
+ val targets = (sector.deployableList ++ sector.vehicleList.filter(_.DeploymentState >= DriveState.Deploying))
+ .collect { case target: PlanetSideGameObject with FactionAffinity
+ if target.Faction != faction &&
+ (target.Definition.asInstanceOf[ObjectDefinition].interference ne Interference.AllowAll) =>
+ target
+ }
+ if (sharedGroupId != 0) {
+ (targets, interferenceTestWithSharedGroup(position, objectDefinition, sharedGroupId))
+ } else {
+ (targets, interferenceTestNoSharedGroup(position, objectDefinition))
+ }
+ }
+ }
+
+ private def interferenceTestNoResults(@unused p: PlanetSideGameObject): Boolean = false
+
+ private def interferenceTestNoSharedGroup(
+ position: Vector3,
+ objectDefinition: ObjectDefinition
+ ): PlanetSideGameObject => Boolean = { p =>
+ val pDefinition = p.Definition
+ val objectInterference = objectDefinition.interference
+ lazy val distanceSq = Vector3.DistanceSquared(position, p.Position)
+ if (pDefinition == objectDefinition) {
+ distanceSq < objectInterference.main * objectInterference.main
+ } else if (pDefinition.isInstanceOf[DeployableDefinition]) {
+ distanceSq < objectInterference.deployables * objectInterference.deployables
+ } else {
+ false
+ }
+ }
+
+ private def interferenceTestWithSharedGroup(
+ position: Vector3,
+ objectDefinition: ObjectDefinition,
+ sharedGroupId: Int
+ ): PlanetSideGameObject => Boolean = { p =>
+ val pDefinition = p.Definition
+ val objectInterference = objectDefinition.interference
+ lazy val distanceSq = Vector3.DistanceSquared(position, p.Position)
+ if (pDefinition == objectDefinition) {
+ distanceSq < objectInterference.main * objectInterference.main
+ } else if (sharedGroupId == pDefinition.interference.sharedGroupId) {
+ distanceSq < objectInterference.shared * objectInterference.shared
+ } else if (pDefinition.isInstanceOf[DeployableDefinition]) {
+ distanceSq < objectInterference.deployables * objectInterference.deployables
+ } else {
+ false
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/zipline/GenericTeleportationDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/zipline/GenericTeleportationDefinition.scala
new file mode 100644
index 000000000..1f8a39f39
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/zipline/GenericTeleportationDefinition.scala
@@ -0,0 +1,12 @@
+// Copyright (c) 2024 PSForever
+package net.psforever.objects.serverobject.zipline
+
+import net.psforever.objects.definition.ObjectDefinition
+
+/**
+ * The definition for any generic teleportation entity.
+ * Two entities are described by this object definition - zip lines and the teleportation rings, both in the caverns.
+ * Entities of these objects are environmental, like facilities, do not get constructed by the user.
+ * The said entities also do not require being configured at login or zone load.
+ */
+class GenericTeleportationDefinition(private val objectId: Int) extends ObjectDefinition(objectId)
diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala
index 6e6d7aef2..3f71a937f 100644
--- a/src/main/scala/net/psforever/objects/zones/Zone.scala
+++ b/src/main/scala/net/psforever/objects/zones/Zone.scala
@@ -1317,6 +1317,10 @@ object Zone {
final case class CanNotSpawn(zone: Zone, vehicle: Vehicle, reason: String)
final case class CanNotDespawn(zone: Zone, vehicle: Vehicle, reason: String)
+
+ final case class TryDeploymentChange(vehicle: Vehicle, toDeployState: DriveState.Value)
+
+ final case class CanNotDeploy(zone: Zone, vehicle: Vehicle, toDeployState: DriveState.Value, reason: String)
}
object HotSpot {
diff --git a/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala b/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
index 728cbce85..3c4a92a53 100644
--- a/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
+++ b/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
@@ -5,6 +5,7 @@ import akka.actor.Actor
import net.psforever.objects.Player
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.ce.Deployable
+import net.psforever.objects.serverobject.deploy.Interference
import net.psforever.objects.sourcing.ObjectSource
import net.psforever.objects.vehicles.MountedWeapons
import net.psforever.objects.vital.SpawningActivity
@@ -28,7 +29,7 @@ class ZoneDeployableActor(
def receive: Receive = {
case Zone.Deployable.Build(obj) =>
- if (DeployableBuild(obj, deployableList)) {
+ if (DeployableBuild(zone, obj, deployableList)) {
obj.Zone = zone
obj match {
case mounting: MountedWeapons =>
@@ -40,11 +41,10 @@ class ZoneDeployableActor(
.foreach { guid =>
turretToMount.put(guid, dguid)
}
- case _ => ;
+ case _ => ()
}
obj.Definition.Initialize(obj, context)
- zone.actor ! ZoneActor.AddToBlockMap(obj, obj.Position)
- //obj.History(EntitySpawn(SourceEntry(obj), obj.Zone))
+ obj.LogActivity(SpawningActivity(ObjectSource(obj), zone.Number, None))
obj.Actor ! Zone.Deployable.Setup()
} else {
log.warn(s"failed to build a ${obj.Definition.Name}")
@@ -52,7 +52,7 @@ class ZoneDeployableActor(
}
case Zone.Deployable.BuildByOwner(obj, owner, tool) =>
- if (DeployableBuild(obj, deployableList)) {
+ if (DeployableBuild(zone, obj, deployableList)) {
obj.Zone = zone
obj match {
case mounting: MountedWeapons =>
@@ -64,10 +64,9 @@ class ZoneDeployableActor(
.foreach { guid =>
turretToMount.put(guid, dguid)
}
- case _ => ;
+ case _ => ()
}
obj.Definition.Initialize(obj, context)
- zone.actor ! ZoneActor.AddToBlockMap(obj, obj.Position)
obj.LogActivity(SpawningActivity(ObjectSource(obj), zone.Number, None))
owner.Actor ! Player.BuildDeployable(obj, tool)
} else {
@@ -76,50 +75,54 @@ class ZoneDeployableActor(
}
case Zone.Deployable.Dismiss(obj) =>
- if (DeployableDismiss(obj, deployableList)) {
+ if (DeployableDismiss(zone, obj, deployableList)) {
obj match {
case _: MountedWeapons =>
val dguid = obj.GUID.guid
turretToMount.filterInPlace { case (_, guid) => guid != dguid }
- case _ => ;
+ case _ => ()
}
obj.Actor ! Zone.Deployable.IsDismissed(obj)
obj.Definition.Uninitialize(obj, context)
obj.ClearHistory()
- zone.actor ! ZoneActor.RemoveFromBlockMap(obj)
}
- case Zone.Deployable.IsBuilt(_) => ;
+ case Zone.Deployable.IsBuilt(_) => ()
- case Zone.Deployable.IsDismissed(_) => ;
+ case Zone.Deployable.IsDismissed(_) => ()
- case _ => ;
+ case _ => ()
}
}
object ZoneDeployableActor {
def DeployableBuild(
- obj: Deployable,
- deployableList: ListBuffer[Deployable]
- ): Boolean = {
- deployableList.find(d => d == obj) match {
- case Some(_) =>
- false
- case None =>
+ zone: Zone,
+ obj: Deployable,
+ deployableList: ListBuffer[Deployable]
+ ): Boolean = {
+ val position = obj.Position
+ deployableList.find(_ eq obj) match {
+ case None if Interference.Test(zone, obj).isEmpty =>
deployableList += obj
+ zone.actor ! ZoneActor.AddToBlockMap(obj, position)
true
+ case _ =>
+ false
}
}
def DeployableDismiss(
- obj: Deployable,
- deployableList: ListBuffer[Deployable]
- ): Boolean = {
+ zone: Zone,
+ obj: Deployable,
+ deployableList: ListBuffer[Deployable]
+ ): Boolean = {
recursiveFindDeployable(deployableList.iterator, obj) match {
case None =>
false
case Some(index) =>
deployableList.remove(index)
+ zone.actor ! ZoneActor.RemoveFromBlockMap(obj)
true
}
}
diff --git a/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala b/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
index ebabcc10d..c1b7780be 100644
--- a/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
+++ b/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
@@ -3,9 +3,11 @@ package net.psforever.objects.zones
import akka.actor.Actor
import net.psforever.actors.zone.ZoneActor
+import net.psforever.objects.definition.VehicleDefinition
+import net.psforever.objects.serverobject.deploy.{Deployment, Interference}
import net.psforever.objects.vital.InGameHistory
import net.psforever.objects.{Default, Vehicle}
-import net.psforever.types.Vector3
+import net.psforever.types.{DriveState, PlanetSideEmpire, Vector3}
import scala.annotation.tailrec
import scala.collection.mutable
@@ -13,27 +15,14 @@ import scala.collection.mutable
/**
* Synchronize management of the list of `Vehicles` maintained by some `Zone`.
*/
-//COMMENTS IMPORTED FROM FORMER VehicleContextActor:
- /*
- * Provide a context for a `Vehicle` `Actor` - the `VehicleControl`.
- *
- * A vehicle can be passed between different zones and, therefore, does not belong to the zone.
- * A vehicle cna be given to different players and can persist and change though players have gone.
- * Therefore, also does not belong to `WorldSessionActor`.
- * A vehicle must anchored to something that exists outside of the `InterstellarCluster` and its agents.
- *
- * The only purpose of this `Actor` is to allow vehicles to borrow a context for the purpose of `Actor` creation.
- * It is also be allowed to be responsible for cleaning up that context.
- * (In reality, it can be cleaned up anywhere a `PoisonPill` can be sent.)
- *
- * This `Actor` is intended to sit on top of the event system that handles broadcast messaging.
- */
class ZoneVehicleActor(
zone: Zone,
vehicleList: mutable.ListBuffer[Vehicle],
turretToMount: mutable.HashMap[Int, Int]
) extends Actor {
- //private[this] val log = org.log4s.getLogger
+ private val log = org.log4s.getLogger(s"${zone.id}-vehicles")
+
+ private var temporaryInterference: Seq[(Vector3, PlanetSideEmpire.Value, VehicleDefinition)] = Seq()
def receive: Receive = {
case Zone.Vehicle.Spawn(vehicle) =>
@@ -73,19 +62,62 @@ class ZoneVehicleActor(
vehicle.ClearHistory()
zone.actor ! ZoneActor.RemoveFromBlockMap(vehicle)
sender() ! Zone.Vehicle.HasDespawned(zone, vehicle)
- case None => ;
+ case None =>
sender() ! Zone.Vehicle.CanNotDespawn(zone, vehicle, "can not find")
}
- case Zone.Vehicle.HasDespawned(_, _) => ;
+ case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState)
+ if toDeployState == DriveState.Deploying &&
+ (ZoneVehicleActor.temporaryInterferenceTest(vehicle, temporaryInterference) || Interference.Test(zone, vehicle).nonEmpty) =>
+ sender() ! Zone.Vehicle.CanNotDeploy(zone, vehicle, toDeployState, "blocked by a nearby entity")
- case Zone.Vehicle.CanNotDespawn(_, _, _) => ;
+ case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState)
+ if toDeployState == DriveState.Deploying =>
+ tryAddToInterferenceField(vehicle.Position, vehicle.Faction, vehicle.Definition)
+ vehicle.Actor.tell(Deployment.TryDeploymentChange(toDeployState), sender())
- case _ => ;
+ case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState) =>
+ vehicle.Actor.tell(Deployment.TryDeploymentChange(toDeployState), sender())
+
+ case Zone.Vehicle.HasDespawned(_, _) => ()
+
+ case Zone.Vehicle.CanNotDespawn(_, _, _) => ()
+
+ case Zone.Vehicle.CanNotDeploy(_, vehicle, _, reason) => ()
+ val pos = vehicle.Position
+ val driverMoniker = vehicle.Seats.headOption.flatMap(_._2.occupant).map(_.Name).getOrElse("Driver")
+ log.warn(s"$driverMoniker's ${vehicle.Definition.Name} can not deploy in ${zone.id} because $reason")
+ temporaryInterference = temporaryInterference.filterNot(_._1 == pos)
+
+ case ZoneVehicleActor.ClearInterference(pos) =>
+ temporaryInterference = temporaryInterference.filterNot(_._1 == pos)
+
+ case _ => ()
+ }
+
+ private def tryAddToInterferenceField(
+ position: Vector3,
+ faction: PlanetSideEmpire.Value,
+ definition: VehicleDefinition
+ ): Boolean = {
+ import scala.concurrent.duration._
+ import scala.concurrent.ExecutionContext.Implicits.global
+ val causesInterference = definition.interference ne Interference.AllowAll
+ if (causesInterference) {
+ temporaryInterference = temporaryInterference :+ (position, faction, definition)
+ context.system.scheduler.scheduleOnce(
+ definition.DeployTime.milliseconds,
+ self,
+ ZoneVehicleActor.ClearInterference(position)
+ )
+ }
+ causesInterference
}
}
object ZoneVehicleActor {
+ private case class ClearInterference(pos: Vector3)
+
@tailrec final def recursiveFindVehicle(iter: Iterator[Vehicle], target: Vehicle, index: Int = 0): Option[Int] = {
if (!iter.hasNext) {
None
@@ -97,4 +129,26 @@ object ZoneVehicleActor {
}
}
}
+
+ private def temporaryInterferenceTest(
+ vehicle: Vehicle,
+ existingInterferences: Seq[(Vector3, PlanetSideEmpire.Value, VehicleDefinition)]
+ ): Boolean = {
+ val vPosition = vehicle.Position
+ val vFaction = vehicle.Faction
+ val vDefinition = vehicle.Definition
+ if (vDefinition.interference eq Interference.AllowAll) {
+ false
+ } else {
+ existingInterferences
+ .collect { case (p, faction, d) if faction == vFaction => (p, d) }
+ .exists { case (position, definition) =>
+ val interference = definition.interference
+ (interference ne Interference.AllowAll) && {
+ lazy val distanceSq = Vector3.DistanceSquared(position, vPosition)
+ definition == vDefinition && distanceSq < interference.main * interference.main
+ }
+ }
+ }
+ }
}
diff --git a/src/main/scala/net/psforever/types/DriveState.scala b/src/main/scala/net/psforever/types/DriveState.scala
index be192c369..7bd39043c 100644
--- a/src/main/scala/net/psforever/types/DriveState.scala
+++ b/src/main/scala/net/psforever/types/DriveState.scala
@@ -15,9 +15,9 @@ object DriveState extends Enumeration {
val Undeploying = Value(1)
val Deploying = Value(2)
val Deployed = Value(3)
- val UNK4 = Value(4) // to have Xtoolspar working
- val UNK5 = Value(5) // to have Xtoolspar working
- val UNK6 = Value(6) // to have Xtoolspar working
+ val UNK4 = Value(4) // to have decoding tool working
+ val UNK5 = Value(5) // to have decoding tool working
+ val UNK6 = Value(6) // to have decoding tool working
val State7 = Value(7) //unknown; not encountered on a vehicle that can deploy; functions like Mobile
val State127 = Value(127) //unknown