diff --git a/.codecov.yml b/.codecov.yml
index d7acf34c4..ca48a57c3 100644
--- a/.codecov.yml
+++ b/.codecov.yml
@@ -21,6 +21,7 @@ ignore:
- "src/main/scala/net/psforever/objects/guid/AvailabilityPolicy.scala"
- "src/main/scala/net/psforever/objects/serverobject/pad/AutoDriveControls.scala"
- "src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala"
+ - "src/main/scala/net/psforever/objects/serverobject/shuttle/ShuttleAmenity.scala"
- "src/main/scala/net/psforever/objects/serverobject/turret/TurretUpgrade.scala"
- "src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala"
- "src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala"
@@ -70,6 +71,8 @@ ignore:
- "src/main/scala/net/psforever/services/avatar/AvatarResponse.scala"
- "src/main/scala/net/psforever/services/galaxy/GalaxyAction.scala"
- "src/main/scala/net/psforever/services/galaxy/GalaxyResponse.scala"
+ - "src/main/scala/net/psforever/services/hart/HartEvent.scala"
+ - "src/main/scala/net/psforever/services/hart/HartTimerActions.scala"
- "src/main/scala/net/psforever/services/local/LocalAction.scala"
- "src/main/scala/net/psforever/services/local/LocalResponse.scala"
- "src/main/scala/net/psforever/services/vehicle/VehicleAction.scala"
diff --git a/server/src/main/scala/net/psforever/server/Server.scala b/server/src/main/scala/net/psforever/server/Server.scala
index 5aeae7c6a..9612fc2d7 100644
--- a/server/src/main/scala/net/psforever/server/Server.scala
+++ b/server/src/main/scala/net/psforever/server/Server.scala
@@ -36,6 +36,7 @@ import org.slf4j
import scopt.OParser
import akka.actor.typed.scaladsl.adapter._
import net.psforever.packet.PlanetSidePacket
+import net.psforever.services.hart.HartService
object Server {
private val logger = org.log4s.getLogger
@@ -129,6 +130,7 @@ object Server {
serviceManager ! ServiceManager.Register(classic.Props[SquadService](), "squad")
serviceManager ! ServiceManager.Register(classic.Props[AccountPersistenceService](), "accountPersistence")
serviceManager ! ServiceManager.Register(classic.Props[PropertyOverrideManager](), "propertyOverrideManager")
+ serviceManager ! ServiceManager.Register(classic.Props[HartService](), "hart")
system.spawn(SocketActor(new InetSocketAddress(bindAddress, Config.app.login.port), login), "login-socket")
system.spawn(SocketActor(new InetSocketAddress(bindAddress, Config.app.world.port), session), "world-socket")
diff --git a/server/src/test/scala/actor/objects/AutoRepairIntegrationTest.scala b/server/src/test/scala/actor/objects/AutoRepairIntegrationTest.scala
index 7da3afeef..24b3c144d 100644
--- a/server/src/test/scala/actor/objects/AutoRepairIntegrationTest.scala
+++ b/server/src/test/scala/actor/objects/AutoRepairIntegrationTest.scala
@@ -203,7 +203,7 @@ class AutoRepairFacilityIntegrationAntGiveNtuTest extends FreedContextActorTest
ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
ant.Zone = zone
- ant.Seats(0).Occupant = player
+ ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed
building.Amenities = terminal
building.Amenities = silo
@@ -297,7 +297,7 @@ class AutoRepairFacilityIntegrationTerminalDestroyedTerminalAntTest extends Free
ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
ant.Zone = zone
- ant.Seats(0).Occupant = player
+ ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed
building.Amenities = terminal
building.Amenities = silo
@@ -399,7 +399,7 @@ class AutoRepairFacilityIntegrationTerminalIncompleteRepairTest extends FreedCon
ant.NtuCapacitor = maxNtuCap
ant.Actor = context.actorOf(Props(classOf[VehicleControl], ant), name = "test-ant")
ant.Zone = zone
- ant.Seats(0).Occupant = player
+ ant.Seats(0).mount(player)
ant.DeploymentState = DriveState.Deployed
building.Amenities = terminal
building.Amenities = silo
diff --git a/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala b/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
index 43fafd4f5..79a2fa0a3 100644
--- a/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
+++ b/server/src/test/scala/actor/objects/VehicleSpawnPadTest.scala
@@ -39,7 +39,7 @@ class VehicleSpawnControl2Test extends ActorTest {
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.LoadVehicle])
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.AttachToRails])
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.StartPlayerSeatedInVehicle])
- vehicle.Seats(0).Occupant = player
+ vehicle.Seats(0).mount(player)
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.PlayerSeatedInVehicle])
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.DetachFromRails])
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.ServerVehicleOverrideStart])
@@ -75,7 +75,7 @@ class VehicleSpawnControl3Test extends ActorTest {
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.LoadVehicle])
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.AttachToRails])
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.StartPlayerSeatedInVehicle])
- vehicle.Seats(0).Occupant = player
+ vehicle.Seats(0).mount(player)
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.PlayerSeatedInVehicle])
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.DetachFromRails])
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.ServerVehicleOverrideStart])
@@ -92,7 +92,7 @@ class VehicleSpawnControl3Test extends ActorTest {
//if we move the vehicle away from the pad, we should receive a second ConcealPlayer message
//that means that the first order has cleared and the spawn pad is now working on the second order successfully
player.VehicleSeated = None //since shared between orders, as necessary
- vehicle.Seats(0).Occupant = None
+ vehicle.Seats(0).unmount(player)
vehicle.Position = Vector3(12, 0, 0)
probe.expectMsgClass(1 minute, classOf[VehicleSpawnPad.ResetSpawnPad])
probe.expectMsgClass(3 seconds, classOf[VehicleSpawnPad.ConcealPlayer])
diff --git a/src/main/resources/application.conf b/src/main/resources/application.conf
index ae2052134..0bc62ab18 100644
--- a/src/main/resources/application.conf
+++ b/src/main/resources/application.conf
@@ -82,6 +82,15 @@ game {
# Modify the amount of NTU drain per autorepair tick for facility amenities
amenity-autorepair-drain-rate = 0.5
+ # HART system, shuttles and facilities
+ hart {
+ # How long the shuttle is not boarding passengers (going through the motions)
+ in-flight-duration = 225000
+
+ # How long the shuttle allows passengers to board
+ boarding-duration = 60000
+ }
+
new-avatar {
# Starting battle rank
br = 1
diff --git a/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala b/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala
index bd56e9f8f..470ca4040 100644
--- a/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala
+++ b/src/main/scala/net/psforever/actors/net/MiddlewareActor.scala
@@ -420,7 +420,7 @@ class MiddlewareActor(
case Successful((packet, None)) =>
in(packet)
case Failure(e) =>
- log.error(s"could not decode packet: $e")
+ log.error(s"Could not decode packet: $e")
}
Behaviors.same
@@ -530,7 +530,7 @@ class MiddlewareActor(
def in(packet: Attempt[PlanetSidePacket]): Unit = {
packet match {
case Successful(_packet) => in(_packet)
- case Failure(cause) => log.error(cause.message)
+ case Failure(cause) => log.error(s"Could not decode packet: ${cause.message}")
}
}
@@ -543,7 +543,7 @@ class MiddlewareActor(
case _ =>
PacketCoding.encodePacket(packet) match {
case Successful(payload) => outQueue.enqueue((packet, payload))
- case Failure(cause) => log.error(cause.message)
+ case Failure(cause) => log.error(s"Could not encode $packet: ${cause.message}")
}
}
}
@@ -615,7 +615,7 @@ class MiddlewareActor(
outQueueBundled.enqueue(smp(slot = 0, data.bytes))
sendFirstBundle()
case Failure(cause) =>
- log.error(cause.message)
+ log.error(s"could not bundle $bundle: ${cause.message}")
//to avoid packets being lost, unwrap bundle and queue the packets individually
bundle.foreach { packet =>
outQueueBundled.enqueue(smp(slot = 0, packet.bytes))
@@ -626,7 +626,7 @@ class MiddlewareActor(
}
} catch {
case e: Throwable =>
- log.error(s"outbound queue processing error - ${Option(e.getMessage).getOrElse(e.getClass.getSimpleName)}")
+ log.error(s"Outbound queue processing error: ${Option(e.getMessage).getOrElse(e.getClass.getSimpleName)}")
}
}
@@ -901,7 +901,7 @@ class MiddlewareActor(
case Successful(data) =>
data.grouped((MTU - 8) * 8).map(vec => smp(slot = 4, vec.bytes)).toSeq
case Failure(cause) =>
- log.error(cause.message)
+ log.error(s"Could not split packet: ${cause.message}")
Seq()
}
} else {
diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala
index 1088fa8cb..b5898ba8d 100644
--- a/src/main/scala/net/psforever/actors/session/SessionActor.scala
+++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala
@@ -7,7 +7,6 @@ import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
import akka.pattern.ask
import akka.util.Timeout
-import java.util.concurrent.TimeUnit
import net.psforever.actors.net.MiddlewareActor
import net.psforever.services.ServiceManager.Lookup
import net.psforever.objects.locker.LockerContainer
@@ -44,7 +43,7 @@ import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate}
import net.psforever.objects.serverobject.terminals._
-import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminals}
+import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret}
@@ -71,8 +70,9 @@ import net.psforever.services.local.{LocalAction, LocalResponse, LocalServiceMes
import net.psforever.services.properties.PropertyOverrideManager
import net.psforever.services.support.SupportActor
import net.psforever.services.teamwork.{SquadResponse, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction}
+import net.psforever.services.hart.HartTimer
import net.psforever.services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse}
-import net.psforever.services.{InterstellarClusterService, RemoverActor, Service, ServiceManager}
+import net.psforever.services.{RemoverActor, Service, ServiceManager, InterstellarClusterService => ICS}
import net.psforever.types._
import net.psforever.util.{Config, DefinitionUtil}
import net.psforever.zones.Zones
@@ -176,7 +176,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
var galaxyService: ActorRef = ActorRef.noSender
var squadService: ActorRef = ActorRef.noSender
var propertyOverrideManager: ActorRef = Actor.noSender
- var cluster: typed.ActorRef[InterstellarClusterService.Command] = Actor.noSender
+ var cluster: typed.ActorRef[ICS.Command] = Actor.noSender
var _session: Session = Session()
var progressBarValue: Option[Float] = None
var shooting: Option[PlanetSideGUID] = None //ChangeFireStateMessage_Start
@@ -240,9 +240,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
/** Upstream message counter
* Checks for server acknowledgement of the following messages in the following conditions:
* `PlayerStateMessageUpstream` (infantry)
- * `VehicleStateMessage` (driver seat only)
- * `ChildObjectStateMessage` (any gunner seat that is not the driver)
- * `KeepAliveMessage` (any passenger seat that is not the driver)
+ * `VehicleStateMessage` (driver mount only)
+ * `ChildObjectStateMessage` (any gunner mount that is not the driver)
+ * `KeepAliveMessage` (any passenger mount that is not the driver)
* As they should arrive roughly every 250 milliseconds this allows for a very crude method of scheduling tasks up to four times per second
*/
var upstreamMessageCount: Int = 0
@@ -300,7 +300,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
serviceManager ! Lookup("propertyOverrideManager")
ServiceManager.receptionist ! Receptionist.Find(
- InterstellarClusterService.InterstellarClusterServiceKey,
+ ICS.InterstellarClusterServiceKey,
context.self
)
@@ -385,7 +385,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
propertyOverrideManager = endpoint
log.info("ID: " + session.id + " Got propertyOverrideManager service " + endpoint)
- case InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings) =>
+ case ICS.InterstellarClusterServiceKey.Listing(listings) =>
cluster = listings.head
// Avatar subscription update
@@ -408,7 +408,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case AvatarActor.AvatarLoginResponse(avatar) =>
session = session.copy(avatar = avatar)
Deployables.InitializeDeployableQuantities(avatar)
- cluster ! InterstellarClusterService.FilterZones(_ => true, context.self)
+ cluster ! ICS.FilterZones(_ => true, context.self)
case packet: PlanetSideGamePacket =>
handleGamePkt(packet)
@@ -436,7 +436,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
zoningChatMessageType = ChatMessageType.CMT_RECALL
zoningStatus = Zoning.Status.Request
beginZoningCountdown(() => {
- cluster ! InterstellarClusterService.GetRandomSpawnPoint(
+ cluster ! ICS.GetRandomSpawnPoint(
Zones.sanctuaryZoneNumber(player.Faction),
player.Faction,
Seq(SpawnGroup.Sanctuary),
@@ -451,24 +451,24 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
/* TODO no ask or adapters from classic to typed so this logic is happening in SpawnPointResponse
implicit val timeout = Timeout(1 seconds)
val future =
- ask(cluster.toClassic, InterstellarClusterService.GetInstantActionSpawnPoint(player.Faction, context.self))
- .mapTo[InterstellarClusterService.SpawnPointResponse]
+ ask(cluster.toClassic, ICS.GetInstantActionSpawnPoint(player.Faction, context.self))
+ .mapTo[ICS.SpawnPointResponse]
Await.result(future, 2 second) match {
- case InterstellarClusterService.SpawnPointResponse(None) =>
+ case ICS.SpawnPointResponse(None) =>
sendResponse(
ChatMsg(ChatMessageType.CMT_INSTANTACTION, false, "", "@InstantActionNoHotspotsAvailable", None)
)
- case InterstellarClusterService.SpawnPointResponse(Some(_)) =>
+ case ICS.SpawnPointResponse(Some(_)) =>
beginZoningCountdown(() => {
- cluster ! InterstellarClusterService.GetInstantActionSpawnPoint(player.Faction, context.self)
+ cluster ! ICS.GetInstantActionSpawnPoint(player.Faction, context.self)
})
}
beginZoningCountdown(() => {
- cluster ! InterstellarClusterService.GetInstantActionSpawnPoint(player.Faction, context.self)
+ cluster ! ICS.GetInstantActionSpawnPoint(player.Faction, context.self)
})
*/
- cluster ! InterstellarClusterService.GetInstantActionSpawnPoint(player.Faction, context.self)
+ cluster ! ICS.GetInstantActionSpawnPoint(player.Faction, context.self)
case Quit() =>
//priority to quitting is given to quit over other zoning methods
@@ -558,37 +558,32 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case GalaxyResponse.TransferPassenger(temp_channel, vehicle, vehicle_to_delete, manifest) =>
(manifest.passengers.find { case (name, _) => player.Name.equals(name) } match {
- case Some((name, index)) if vehicle.Seats(index).Occupant.isEmpty =>
- vehicle.Seats(index).Occupant = player
+ case Some((name, index)) if vehicle.Seats(index).occupant.isEmpty =>
+ vehicle.Seats(index).mount(player)
Some(vehicle)
case Some((name, index)) =>
- log.warn(s"TransferPassenger: seat $index is already occupied")
+ log.warn(s"TransferPassenger: mount $index is already occupied")
None
case None =>
None
}).orElse(manifest.cargo.find { case (name, _) => player.Name.equals(name) } match {
case Some((name, index)) =>
- vehicle.CargoHolds(index).Occupant match {
+ vehicle.CargoHolds(index).occupant match {
case Some(cargo) =>
- cargo.Seats(0).Occupant match {
- case Some(driver) if driver.Name.equals(name) =>
- Some(cargo)
- case _ =>
- None
- }
+ cargo.Seats(0).occupants.find(_.Name.equals(name))
case None =>
None
}
case None =>
None
}) match {
- case Some(v) =>
+ case Some(v: Vehicle) =>
galaxyService ! Service.Leave(Some(temp_channel)) //temporary vehicle-specific channel (see above)
deadState = DeadState.Release
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true))
interstellarFerry = Some(v) //on the other continent and registered to that continent's GUID system
LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds)
- case None =>
+ case _ =>
interstellarFerry match {
case None =>
galaxyService ! Service.Leave(Some(temp_channel)) //no longer being transferred between zones
@@ -956,14 +951,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Zone.Population.PlayerAlreadySpawned(zone, tplayer) =>
log.warn(s"${tplayer.Name} is already spawned on zone ${zone.id}; a clerical error?")
- case InterstellarClusterService.SpawnPointResponse(response) =>
+ case ICS.SpawnPointResponse(response) =>
zoningType match {
case Zoning.Method.InstantAction if response.isEmpty =>
CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable")
case Zoning.Method.InstantAction if zoningStatus == Zoning.Status.Request =>
beginZoningCountdown(() => {
- cluster ! InterstellarClusterService.GetInstantActionSpawnPoint(player.Faction, context.self)
+ cluster ! ICS.GetInstantActionSpawnPoint(player.Faction, context.self)
})
case zoningType =>
@@ -992,9 +987,19 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
}
+ case ICS.DroppodLaunchDenial(errorCode, _) =>
+ sendResponse(DroppodLaunchResponseMessage(errorCode, player.GUID))
+
+ case ICS.DroppodLaunchConfirmation(zone, position) =>
+ LoadZoneLaunchDroppod(zone, position)
+
+ case msg @ Zone.Vehicle.HasSpawned(zone, vehicle) => ;
+
case msg @ Zone.Vehicle.CanNotSpawn(zone, vehicle, reason) =>
log.warn(s"$msg")
+ case msg @ Zone.Vehicle.HasDespawned(zone, vehicle) => ;
+
case msg @ Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) =>
log.warn(s"$msg")
@@ -1132,7 +1137,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Zone.Deployable.DeployableIsDismissed(obj) =>
continent.tasks ! GUIDTask.UnregisterObjectTask(obj)(continent.GUID)
- case InterstellarClusterService.ZonesResponse(zones) =>
+ case ICS.ZonesResponse(zones) =>
zones.foreach { zone =>
val continentNumber = zone.Number
val popBO = 0
@@ -1209,7 +1214,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
self ! NewPlayerLoaded(player)
} else {
zoneReload = true
- cluster ! InterstellarClusterService.GetNearbySpawnPoint(
+ cluster ! ICS.GetNearbySpawnPoint(
continent.Number,
player,
Seq(SpawnGroup.Facility, SpawnGroup.Tower),
@@ -1218,7 +1223,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
}
- case InterstellarClusterService.ZoneResponse(zone) =>
+ case ICS.ZoneResponse(zone) =>
log.info(s"Zone ${zone.get.id} will now load")
loadConfZone = true
val oldZone = session.zone
@@ -1622,55 +1627,29 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
/**
- * You can't instant action to respond to some activity using a droppod!
- * You can't.
- * You just can't.
+ * Attach the player to a droppod vehicle and hurtle them through the stratosphere in some far off world.
+ * Perform all normal operation standardization (state cancels) as if any of form of zoning was being performed,
+ * then assemble the vehicle and work around some inconvenient setup requirements for vehicle gating.
+ * You can't instant action to respond to some activity using a droppod.
* @param zone the destination zone
- * @param hotspotPosition where is the hotspot that is being addressed
- * @param spawnPosition the destination spawn position (may not be related to a literal `SpawnPoint` entity)
+ * @param spawnPosition the destination drop position
*/
- def YouCantInstantActionUsingDroppod(zone: Zone, hotspotPosition: Vector3, spawnPosition: Vector3): Unit = {
+ def LoadZoneLaunchDroppod(zone: Zone, spawnPosition: Vector3): Unit = {
CancelZoningProcess()
PlayerActionsToCancel()
CancelAllProximityUnits()
- //find a safe drop point
- var targetBuildings = zone.Buildings.values
- var whereToDroppod = spawnPosition.xy
- while (targetBuildings.nonEmpty) {
- (targetBuildings
- .filter { building =>
- val radius = building.Definition.SOIRadius
- Vector3.DistanceSquared(building.Position.xy, whereToDroppod) < radius * radius
- }) match {
- case Nil =>
- //no soi interference
- targetBuildings = Nil
- case List(building: Building) =>
- //blocked by a single soi; find space just outside of this soi and confirm no new overlap
- val radius = Vector3(0, building.Definition.SOIRadius.toFloat + 5f, 0)
- whereToDroppod =
- building.Position.xy + Vector3.Rz(radius, math.abs(scala.util.Random.nextInt() % 360).toFloat)
- case buildings =>
- //probably blocked by a facility and its tower (maximum overlap potential is 2?); find space outside of largest soi
- val largestBuilding = buildings.maxBy(_.Definition.SOIRadius)
- val radius = Vector3(0, largestBuilding.Definition.SOIRadius.toFloat + 5f, 0)
- whereToDroppod =
- largestBuilding.Position.xy + Vector3.Rz(radius, math.abs(scala.util.Random.nextInt() % 360).toFloat)
- targetBuildings = buildings
- }
- }
//droppod action
val droppod = Vehicle(GlobalDefinitions.droppod)
droppod.Faction = player.Faction
- droppod.Position = whereToDroppod.xy + Vector3.z(1024)
+ droppod.Position = spawnPosition.xy + Vector3.z(1024)
droppod.Orientation = Vector3.z(180) //you always seems to land looking south; don't know why
- droppod.Seats(0).Occupant = player
+ droppod.Seats(0).mount(player)
droppod.GUID = PlanetSideGUID(0) //droppod is not registered, we must jury-rig this
droppod.Invalidate() //now, we must short-circuit the jury-rig
interstellarFerry = Some(droppod) //leverage vehicle gating
player.Position = droppod.Position
+ player.VehicleSeated = PlanetSideGUID(0)
LoadZonePhysicalSpawnPoint(zone.id, droppod.Position, Vector3.Zero, 0 seconds)
- /* Don't even think about it. */
}
/**
@@ -1850,7 +1829,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
reviveTimer.cancel()
if (player.death_by == 0) {
reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer) {
- cluster ! InterstellarClusterService.GetRandomSpawnPoint(
+ cluster ! ICS.GetRandomSpawnPoint(
Zones.sanctuaryZoneNumber(player.Faction),
player.Faction,
Seq(SpawnGroup.Sanctuary),
@@ -2330,6 +2309,26 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case LocalResponse.SetEmpire(object_guid, empire) =>
sendResponse(SetEmpireMessage(object_guid, empire))
+ case LocalResponse.SendResponse(pkt) =>
+ sendResponse(pkt)
+
+ case LocalResponse.ShuttleEvent(ev) =>
+ val msg = OrbitalShuttleTimeMsg(
+ ev.u1, ev.u2,
+ ev.t1, ev.t2, ev.t3,
+ ev.pairs.map { case ((a, b), c) => PadAndShuttlePair(a, b, c) }
+ )
+ sendResponse(msg)
+
+ case LocalResponse.ShuttleDock(pguid, sguid, slot) =>
+ sendResponse(ObjectAttachMessage(pguid, sguid, slot))
+
+ case LocalResponse.ShuttleUndock(pguid, sguid, pos, orient) =>
+ sendResponse(ObjectDetachMessage(pguid, sguid, pos, orient))
+
+ case LocalResponse.ShuttleState(sguid, pos, orient, state) =>
+ sendResponse(VehicleStateMessage(sguid, 0, pos, orient, None, Some(state), 0, 0, 15, false, false))
+
case LocalResponse.ToggleTeleportSystem(router, system_plan) =>
ToggleTeleportSystem(router, system_plan)
@@ -2350,7 +2349,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case LocalResponse.RechargeVehicleWeapon(vehicle_guid, weapon_guid) => {
if (tplayer_guid == guid) {
continent.GUID(vehicle_guid) match {
- case Some(vehicle: Mountable with MountedWeapons) =>
+ case Some(vehicle: MountableWeapons) =>
vehicle.PassengerInSeat(player) match {
case Some(seat_num: Int) =>
vehicle.WeaponControlledFromSeat(seat_num) match {
@@ -2377,17 +2376,25 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
*/
def HandleMountMessages(tplayer: Player, reply: Mountable.Exchange): Unit = {
reply match {
- case Mountable.CanMount(obj: ImplantTerminalMech, seat_num) =>
+ case Mountable.CanMount(obj: ImplantTerminalMech, seat_number, mount_point) =>
CancelZoningProcessWithDescriptiveReason("cancel_use")
CancelAllProximityUnits()
- MountingAction(tplayer, obj, seat_num)
+ MountingAction(tplayer, obj, seat_number)
// the player will receive no messages consistently except the KeepAliveMessage echo
keepAliveFunc = KeepAlivePersistence
- case Mountable.CanMount(obj: Vehicle, seat_num) =>
+ case Mountable.CanMount(obj: Vehicle, seat_number, _)
+ if obj.Definition == GlobalDefinitions.orbital_shuttle =>
+ CancelZoningProcessWithDescriptiveReason("cancel_mount")
+ CancelAllProximityUnits()
+ MountingAction(tplayer, obj, seat_number)
+ // the player will receive no messages consistently except the KeepAliveMessage echo
+ keepAliveFunc = KeepAlivePersistence
+
+ case Mountable.CanMount(obj: Vehicle, seat_number, _) =>
CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
- log.info(s"MountVehicleMsg: ${player.Name}_guid mounts $obj_guid @ $seat_num")
+ log.info(s"MountVehicleMsg: ${player.Name}_guid mounts $obj_guid @ $seat_number")
CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, 0, obj.Health))
sendResponse(PlanetsideAttributeMessage(obj_guid, 68, obj.Shields)) //shield health
@@ -2398,29 +2405,29 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val capacitor = scala.math.ceil((obj.Capacitor.toFloat / obj.Definition.MaxCapacitor.toFloat) * 10).toInt
sendResponse(PlanetsideAttributeMessage(obj_guid, 113, capacitor))
}
- if (seat_num == 0) {
+ if (seat_number == 0) {
if (obj.Definition == GlobalDefinitions.quadstealth) {
//wraith cloak state matches the cloak state of the driver
//phantasm doesn't uncloak if the driver is uncloaked and no other vehicle cloaks
obj.Cloaked = tplayer.Cloaked
}
- } else if (obj.Seats(seat_num).ControlledWeapon.isEmpty) {
+ } else if (obj.WeaponControlledFromSeat(seat_number).isEmpty) {
// the player will receive no messages consistently except the KeepAliveMessage echo
keepAliveFunc = KeepAlivePersistence
}
AccessContainer(obj)
- UpdateWeaponAtSeatPosition(obj, seat_num)
- MountingAction(tplayer, obj, seat_num)
+ UpdateWeaponAtSeatPosition(obj, seat_number)
+ MountingAction(tplayer, obj, seat_number)
- case Mountable.CanMount(obj: FacilityTurret, seat_num) =>
+ case Mountable.CanMount(obj: FacilityTurret, seat_number, mount_point) =>
CancelZoningProcessWithDescriptiveReason("cancel_mount")
if (!obj.isUpgrading) {
if (obj.Definition == GlobalDefinitions.vanu_sentry_turret) {
obj.Zone.LocalEvents ! LocalServiceMessage(obj.Zone.id, LocalAction.SetEmpire(obj.GUID, player.Faction))
}
sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health))
- UpdateWeaponAtSeatPosition(obj, seat_num)
- MountingAction(tplayer, obj, seat_num)
+ UpdateWeaponAtSeatPosition(obj, seat_number)
+ MountingAction(tplayer, obj, seat_number)
// the player will receive no messages consistently except the KeepAliveMessage echo
keepAliveFunc = KeepAlivePersistence
} else {
@@ -2429,25 +2436,67 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
)
}
- case Mountable.CanMount(obj: PlanetSideGameObject with WeaponTurret, seat_num) =>
+ case Mountable.CanMount(obj: PlanetSideGameObject with WeaponTurret, seat_number, mount_point) =>
CancelZoningProcessWithDescriptiveReason("cancel_mount")
sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health))
- UpdateWeaponAtSeatPosition(obj, seat_num)
- MountingAction(tplayer, obj, seat_num)
+ UpdateWeaponAtSeatPosition(obj, seat_number)
+ MountingAction(tplayer, obj, seat_number)
// the player will receive no messages consistently except the KeepAliveMessage echo
keepAliveFunc = KeepAlivePersistence
- case Mountable.CanMount(obj: Mountable, _) =>
+ case Mountable.CanMount(obj: Mountable, _, _) =>
log.warn(s"MountVehicleMsg: $obj is some generic mountable object and nothing will happen")
- case Mountable.CanDismount(obj: ImplantTerminalMech, seat_num) =>
+ case Mountable.CanDismount(obj: ImplantTerminalMech, seat_num, _) =>
DismountAction(tplayer, obj, seat_num)
- case Mountable.CanDismount(obj: Vehicle, seat_num) if obj.Definition == GlobalDefinitions.droppod =>
+ case Mountable.CanDismount(obj: Vehicle, seat_num, mount_point)
+ if obj.Definition == GlobalDefinitions.orbital_shuttle =>
+ val pguid = player.GUID
+ if (obj.MountedIn.nonEmpty) {
+ //dismount to hart lobby
+ val sguid = obj.GUID
+ val (pos, zang) = Vehicles.dismountShuttle(obj, mount_point)
+ tplayer.Position = pos
+ sendResponse(DelayedPathMountMsg(pguid, sguid, 60, true))
+ continent.LocalEvents ! LocalServiceMessage(
+ continent.id,
+ LocalAction.SendResponse(ObjectDetachMessage(sguid, pguid, pos, 0, 0, zang))
+ )
+ }
+ else {
+ //get ready for orbital drop
+ DismountAction(tplayer, obj, seat_num)
+ //DismountAction(...) uses vehicle service, so use that service to coordinate the remainder of the messages
+ continent.VehicleEvents ! VehicleServiceMessage(
+ player.Name,
+ VehicleAction.SendResponse(Service.defaultPlayerGUID, PlayerStasisMessage(pguid)) //the stasis message
+ )
+ //when the player dismounts, they will be positioned where the shuttle was when it disappeared in the sky
+ //the player will fall to the ground and is perfectly vulnerable in this state
+ //additionally, our player must exist in the current zone
+ //having no in-game avatar target will throw us out of the map screen when deploying and cause softlock
+ continent.VehicleEvents ! VehicleServiceMessage(
+ player.Name,
+ VehicleAction.SendResponse(
+ Service.defaultPlayerGUID,
+ PlayerStateShiftMessage(ShiftState(0, obj.Position, obj.Orientation.z, None)) //cower in the shuttle bay
+ )
+ )
+ continent.VehicleEvents ! VehicleServiceMessage(
+ continent.id,
+ VehicleAction.SendResponse(pguid, GenericObjectActionMessage(pguid, 9)) //conceal the player
+ )
+ }
+ keepAliveFunc = NormalKeepAlive
+
+ case Mountable.CanDismount(obj: Vehicle, seat_num, _)
+ if obj.Definition == GlobalDefinitions.droppod =>
UnaccessContainer(obj)
DismountAction(tplayer, obj, seat_num)
+ obj.Actor ! Vehicle.Deconstruct()
- case Mountable.CanDismount(obj: Vehicle, seat_num) =>
+ case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
val player_guid: PlanetSideGUID = tplayer.GUID
if (player_guid == player.GUID) {
//disembarking self
@@ -2461,26 +2510,28 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
)
}
- case Mountable.CanDismount(obj: PlanetSideGameObject with WeaponTurret, seat_num) =>
+ case Mountable.CanDismount(obj: PlanetSideGameObject with WeaponTurret, seat_num, _) =>
DismountAction(tplayer, obj, seat_num)
- case Mountable.CanDismount(obj: Mountable, _) =>
+ case Mountable.CanDismount(obj: Mountable, _, _) =>
log.warn(s"DismountVehicleMsg: $obj is some generic mountable object and nothing will happen")
- case Mountable.CanNotMount(obj: Vehicle, seat_num) =>
- log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's seat $seat_num, but was not allowed")
- if (obj.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver)) {
- sendResponse(
- ChatMsg(ChatMessageType.CMT_OPEN, false, "", "You are not the driver of this vehicle.", None)
- )
+ case Mountable.CanNotMount(obj: Vehicle, mount_point) =>
+ log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's mount $mount_point, but was not allowed")
+ obj.GetSeatFromMountPoint(mount_point) match {
+ case Some(seatNum) if obj.SeatPermissionGroup(seatNum).contains(AccessPermissionGroup.Driver) =>
+ sendResponse(
+ ChatMsg(ChatMessageType.CMT_OPEN, false, "", "You are not the driver of this vehicle.", None)
+ )
+ case _ =>
}
- case Mountable.CanNotMount(obj: Mountable, seat_num) =>
- log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's seat $seat_num, but was not allowed")
+ case Mountable.CanNotMount(obj: Mountable, mount_point) =>
+ log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's mount $mount_point, but was not allowed")
case Mountable.CanNotDismount(obj, seat_num) =>
log.warn(
- s"DismountVehicleMsg: ${tplayer.Name} attempted to dismount $obj's seat $seat_num, but was not allowed"
+ s"DismountVehicleMsg: ${tplayer.Name} attempted to dismount $obj's mount $seat_num, but was not allowed"
)
}
}
@@ -2657,7 +2708,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
case VehicleResponse.KickPassenger(seat_num, wasKickedByDriver, vehicle_guid) =>
- // seat_num seems to be correct if passenger is kicked manually by driver, but always seems to return 4 if user is kicked by seat permissions
+ // seat_num seems to be correct if passenger is kicked manually by driver, but always seems to return 4 if user is kicked by mount permissions
sendResponse(DismountVehicleMsg(guid, BailType.Kicked, wasKickedByDriver))
player.VehicleSeated = None
if (tplayer_guid == guid) {
@@ -3052,9 +3103,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
sendResponse(
DroppodFreefallingMessage(
vehicle.GUID,
- vehicle.Position + Vector3.z(50),
- Vector3.z(-999),
- vehicle.Position + Vector3.z(25),
+ vehicle.Position,
+ Vector3.z(value = -999),
+ vehicle.Position + Vector3(-20, 1.156f, -50),
Vector3(0, 70.3125f, 90),
Vector3(0, 0, 90)
)
@@ -3328,7 +3379,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
.asInstanceOf[Mountable]
.Seats
.values
- .map(_.Occupant)
+ .map(_.occupant)
.collect {
case Some(occupant) =>
if (occupant.isAlive) {
@@ -3444,10 +3495,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
)
//occupants other than driver
vehicle.Seats
- .filter({ case (index, seat) => seat.isOccupied && live.contains(seat.Occupant.get) && index > 0 })
+ .filter({ case (index, seat) => seat.isOccupied && live.contains(seat.occupant.get) && index > 0 })
.foreach({
case (index, seat) =>
- val targetPlayer = seat.Occupant.get
+ val targetPlayer = seat.occupant.get
val targetDefiniton = targetPlayer.avatar.definition
sendResponse(
ObjectCreateMessage(
@@ -3471,11 +3522,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
vehicle.Seats
.filter({
case (index, seat) =>
- seat.isOccupied && !seat.Occupant.contains(player) && live.contains(seat.Occupant.get) && index > 0
+ seat.isOccupied && !seat.occupant.contains(player) && live.contains(seat.occupant.get) && index > 0
})
.foreach({
case (index, seat) =>
- val targetPlayer = seat.Occupant.get
+ val targetPlayer = seat.occupant.get
val targetDefinition = targetPlayer.avatar.definition
sendResponse(
ObjectCreateMessage(
@@ -3486,7 +3537,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
)
)
})
- //since we would have only subscribed recently, we need to reload seat access states
+ //since we would have only subscribed recently, we need to reload mount access states
(0 to 3).foreach { group =>
sendResponse(PlanetsideAttributeMessage(vguid, group + 10, vehicle.PermissionGroup(group).get.id))
}
@@ -3509,15 +3560,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
//cargo occupants (including our own vehicle as cargo)
allActiveVehicles.collect {
case vehicle if vehicle.CargoHolds.nonEmpty =>
- vehicle.CargoHolds.collect({
- case (index, hold: Cargo) if hold.isOccupied => {
+ vehicle.CargoHolds.collect {
+ case (_index, hold: Cargo) if hold.isOccupied =>
CargoBehavior.CargoMountBehaviorForAll(
vehicle,
- hold.Occupant.get,
- index
+ hold.occupant.get,
+ _index
) //CargoMountBehaviorForUs can fail to attach the cargo vehicle on some clients
- }
- })
+ }
}
//special deploy states
val deployedVehicles = allActiveVehicles.filter(_.DeploymentState == DriveState.Deployed)
@@ -3542,6 +3592,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
sendResponse(DeployRequestMessage(player.GUID, obj.GUID, DriveState.Deployed, 0, false, Vector3.Zero))
ToggleTeleportSystem(obj, TelepadLike.AppraiseTeleportationSystem(obj, continent))
}
+ val name = avatar.name
+ serviceManager.ask(Lookup("hart"))(Timeout(2 seconds))
+ .onComplete {
+ case Success(LookupResult("hart", ref)) =>
+ ref ! HartTimer.Update(continentId, name)
+ case _ =>
+ }
//implant terminals
continent.map.terminalToInterface.foreach({
@@ -3560,11 +3617,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
)
case _ => ;
}
- //seat terminal occupants
+ //mount terminal occupants
continent.GUID(terminal_guid) match {
case Some(obj: Mountable) =>
- obj.Seats(0).Occupant match {
- case Some(targetPlayer) =>
+ obj.Seats(0).occupant match {
+ case Some(targetPlayer: Player) =>
val targetDefinition = targetPlayer.avatar.definition
sendResponse(
ObjectCreateMessage(
@@ -3574,7 +3631,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
targetDefinition.Packet.ConstructorData(targetPlayer).get
)
)
- case None => ;
+ case _ => ;
}
case _ => ;
}
@@ -3604,9 +3661,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
//reserved ammunition?
//TODO need to register if it exists
- //seat turret occupant
- turret.Seats(0).Occupant match {
- case Some(targetPlayer) =>
+ //mount turret occupant
+ turret.Seats(0).occupant match {
+ case Some(targetPlayer: Player) =>
val targetDefinition = targetPlayer.avatar.definition
sendResponse(
ObjectCreateMessage(
@@ -3616,7 +3673,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
targetDefinition.Packet.ConstructorData(targetPlayer).get
)
)
- case None => ;
+ case _ => ;
}
}
continent.VehicleEvents ! VehicleServiceMessage(
@@ -3650,6 +3707,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (isMovingPlus) {
CancelZoningProcessWithDescriptiveReason("cancel_motion")
}
+// if (is_crouching && !player.Crouching) {
+// //dev stuff goes here
+// }
player.Position = pos
player.Velocity = vel
player.Orientation = Vector3(player.Orientation.x, pitch, yaw)
@@ -3780,7 +3840,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
turnCounterFunc(player.GUID)
val seat = obj.Seats(0)
player.Position = pos //convenient
- if (seat.ControlledWeapon.isEmpty) {
+ if (obj.WeaponControlledFromSeat(0).isEmpty) {
player.Orientation = Vector3.z(ang.z) //convenient
}
obj.Position = pos
@@ -3792,12 +3852,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
obj.Velocity = Some(Vector3.Zero)
}
if (obj.Definition.CanFly) {
- obj.Flying = flying.nonEmpty //usually Some(7)
+ obj.Flying = flying //usually Some(7)
}
obj.Cloaked = obj.Definition.CanCloak && is_cloaked
} else {
obj.Velocity = None
- obj.Flying = false
+ obj.Flying = None
}
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
@@ -3808,7 +3868,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
obj.Position,
ang,
obj.Velocity,
- if (obj.Flying) {
+ if (obj.isFlying) {
flying
} else {
None
@@ -3876,7 +3936,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
log.info(s"SpawnRequestMessage: $msg")
if (deadState != DeadState.RespawnTime) {
deadState = DeadState.RespawnTime
- cluster ! InterstellarClusterService.GetNearbySpawnPoint(
+ cluster ! ICS.GetNearbySpawnPoint(
spawnGroup match {
case SpawnGroup.Sanctuary =>
Zones.sanctuaryZoneNumber(player.Faction)
@@ -4246,14 +4306,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(vehicle: Vehicle) =>
/* line 1a: player is admin (and overrules other access requirements) */
/* line 1b: vehicle and player (as the owner) acknowledge each other */
- /* line 1c: vehicle is the same faction as player and either the owner is absent or the vehicle is destroyed */
+ /* line 1c: vehicle is the same faction as player, is ownable, and either the owner is absent or the vehicle is destroyed */
/* line 2: vehicle is not mounted in anything or, if it is, its seats are empty */
if (
(session.account.gm ||
(player.avatar.vehicle.contains(object_guid) && vehicle.Owner.contains(player.GUID)) ||
- (player.Faction == vehicle.Faction && ((vehicle.Owner.isEmpty || continent
- .GUID(vehicle.Owner.get)
- .isEmpty) || vehicle.Destroyed))) &&
+ (player.Faction == vehicle.Faction &&
+ (vehicle.Definition.CanBeOwned.nonEmpty &&
+ (vehicle.Owner.isEmpty || continent.GUID(vehicle.Owner.get).isEmpty) || vehicle.Destroyed))) &&
(vehicle.MountedIn.isEmpty || !vehicle.Seats.values.exists(_.isOccupied))
) {
vehicle.Actor ! Vehicle.Deconstruct()
@@ -5333,7 +5393,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
true
})) =>
deadState = DeadState.RespawnTime
- cluster ! InterstellarClusterService.GetSpawnPoint(
+ cluster ! ICS.GetSpawnPoint(
destinationZoneGuid.guid,
player,
destinationBuildingGuid,
@@ -5355,14 +5415,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
log.info("MountVehicleMsg: " + msg)
ValidObject(mountable_guid) match {
case Some(obj: Mountable) =>
- obj.GetSeatFromMountPoint(entry_point) match {
- case Some(seat_num) =>
- obj.Actor ! Mountable.TryMount(player, seat_num)
- case None =>
- log.warn(
- s"MountVehicleMsg: attempted to board mountable $mountable_guid's seat $entry_point, but no seat exists there"
- )
- }
+ obj.Actor ! Mountable.TryMount(player, entry_point)
case None | Some(_) =>
log.warn(s"MountVehicleMsg: not a mountable thing")
}
@@ -5375,10 +5428,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
log.warn(s"$msg; some vehicle might not know that a player is no longer sitting in it")
}
if (player.GUID == player_guid) {
- //normally disembarking from a seat
+ //normally disembarking from a mount
(interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match {
case out @ Some(obj: Vehicle) =>
- if (obj.MountedIn.isEmpty) out else None
+ continent.GUID(obj.MountedIn) match {
+ case Some(_: Vehicle) => None //cargo vehicle
+ case _ => out //arrangement "may" be permissible
+ }
case out @ Some(_: Mountable) =>
out
case _ =>
@@ -5405,7 +5461,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
//todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct.
//todo: kick cargo passengers out. To be added after PR #216 is merged
obj match {
- case v: Vehicle if bailType == BailType.Bailed && seat_num == 0 && v.Flying =>
+ case v: Vehicle
+ if bailType == BailType.Bailed &&
+ v.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver) &&
+ v.isFlying =>
v.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
case _ => ;
}
@@ -5419,7 +5478,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
dismountWarning(s"DismountVehicleMsg: can not find mountable entity ${player.VehicleSeated}")
}
} else {
- //kicking someone else out of a seat; need to own that seat/mountable
+ //kicking someone else out of a mount; need to own that mount/mountable
player.avatar.vehicle match {
case Some(obj_guid) =>
((ValidObject(obj_guid), ValidObject(player_guid)) match {
@@ -5521,41 +5580,33 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
)
//kick players who should not be seated in the vehicle due to permission changes
if (allow == VehicleLockState.Locked) { //TODO only important permission atm
- vehicle.Definition.MountPoints.values
- .foreach(mountpoint_num => {
- vehicle.Seat(mountpoint_num) match {
- case Some(seat) =>
- seat.Occupant match {
- case Some(tplayer) =>
- if (
- vehicle.SeatPermissionGroup(mountpoint_num).contains(group) && tplayer != player
- ) { //can not kick self
- seat.Occupant = None
- tplayer.VehicleSeated = None
- continent.VehicleEvents ! VehicleServiceMessage(
- continent.id,
- VehicleAction.KickPassenger(tplayer.GUID, 4, false, object_guid)
- )
- }
- case None => ; // No player seated
- }
- case None => ; // Not a seat mounting point
- }
- vehicle.CargoHold(mountpoint_num) match {
- case Some(cargo) =>
- cargo.Occupant match {
- case Some(vehicle) =>
- if (vehicle.SeatPermissionGroup(mountpoint_num).contains(group)) {
- //todo: this probably doesn't work for passengers within the cargo vehicle
- // Instruct client to start bail dismount procedure
- self ! DismountVehicleCargoMsg(player.GUID, vehicle.GUID, true, false, false)
- }
- case None => ; // No vehicle in cargo
- }
- case None => ; // Not a cargo mounting point
- }
-
- })
+ vehicle.Seats.foreach { case (seatIndex, seat) =>
+ seat.occupant match {
+ case Some(tplayer : Player) =>
+ if (
+ vehicle.SeatPermissionGroup(seatIndex).contains(group) && tplayer != player
+ ) { //can not kick self
+ seat.unmount(tplayer)
+ tplayer.VehicleSeated = None
+ continent.VehicleEvents ! VehicleServiceMessage(
+ continent.id,
+ VehicleAction.KickPassenger(tplayer.GUID, 4, false, object_guid)
+ )
+ }
+ case _ => ; // No player seated
+ }
+ }
+ vehicle.CargoHolds.foreach { case (cargoIndex, hold) =>
+ hold.occupant match {
+ case Some(cargo) =>
+ if (vehicle.SeatPermissionGroup(cargoIndex).contains(group)) {
+ //todo: this probably doesn't work for passengers within the cargo vehicle
+ // Instruct client to start bail dismount procedure
+ self ! DismountVehicleCargoMsg(player.GUID, cargo.GUID, true, false, false)
+ }
+ case None => ; // No vehicle in cargo
+ }
+ }
}
case None => ;
}
@@ -5624,6 +5675,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
})
sendResponse(TargetingInfoMessage(targetInfo))
+ case msg @ DroppodLaunchRequestMessage(info, _) =>
+ //log.info(s"Droppod request: $msg")
+ cluster ! ICS.DroppodLaunchRequest(
+ info.zone_number,
+ info.xypos,
+ player.Faction,
+ self.toTyped[ICS.DroppodLaunchExchange]
+ )
+
case msg @ ActionCancelMessage(u1, u2, u3) =>
log.info("Cancelled: " + msg)
progressBarUpdate.cancel()
@@ -6169,7 +6229,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* Along with any discovered item, a containing object such that the statement:
* `container.Find(object) = Some(slot)`
* ... will return a proper result.
- * For a seat controlled weapon, the vehicle is returned.
+ * For a mount controlled weapon, the vehicle is returned.
* For the player's hand, the player is returned.
* @return a `Tuple` of the returned values;
* the first value is a `Container` object;
@@ -6179,7 +6239,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
player.VehicleSeated match {
case Some(vehicle_guid) => //weapon is vehicle turret?
continent.GUID(vehicle_guid) match {
- case Some(vehicle: Mountable with MountedWeapons with Container) =>
+ case Some(vehicle: Mountable with MountableWeapons with Container) =>
vehicle.PassengerInSeat(player) match {
case Some(seat_num) =>
(Some(vehicle), vehicle.WeaponControlledFromSeat(seat_num))
@@ -6663,7 +6723,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* @see `Door`
* @see `GenericObjectStateMsg`
* @see `Hackable`
- * @see `HackCaptureTerminal`
* @see `HackObject`
* @see `PlanetsideAttributeMessage`
* @see `ResourceSilo`
@@ -6831,7 +6890,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
*
* If that player is in a vehicle, it will construct that vehicle.
* If the player is the driver of the vehicle,
- * they must temporarily be removed from the driver seat in order for the vehicle to be constructed properly.
+ * they must temporarily be removed from the driver mount in order for the vehicle to be constructed properly.
* These two previous statements operate through similar though distinct mechanisms and imply different conditions.
* In reality, they produce the same output but enforce different relationships between the components.
* The vehicle without a rendered player will always be created if that vehicle exists.
@@ -6851,7 +6910,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
//if the vehicle is the cargo of another vehicle in this zone
val carrierInfo = continent.GUID(vehicle.MountedIn) match {
case Some(carrier: Vehicle) =>
- (Some(carrier), carrier.CargoHolds.find({ case (index, hold) => hold.Occupant.contains(vehicle) }))
+ (Some(carrier), carrier.CargoHolds.find({ case (index, hold) => hold.occupant.contains(vehicle) }))
case _ =>
(None, None)
}
@@ -6866,13 +6925,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
continent.Transport ! Zone.Vehicle.Spawn(vehicle)
//as the driver, we must temporarily exclude ourselves from being in the vehicle during its creation
val seat = vehicle.Seats(0)
- seat.Occupant = None
+ seat.unmount(player)
+ player.VehicleSeated = None
val data = vdef.Packet.ConstructorData(vehicle).get
sendResponse(ObjectCreateMessage(vehicle.Definition.ObjectId, vguid, data))
- seat.Occupant = player
+ seat.mount(player)
Vehicles.Own(vehicle, player)
vehicle.CargoHolds.values
- .collect { case hold if hold.isOccupied => hold.Occupant.get }
+ .collect { case hold if hold.isOccupied => hold.occupant.get }
.foreach { _.MountedIn = vguid }
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
@@ -6938,12 +6998,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
/**
- * If the player is mounted in some entity, find that entity and get the seat index number at which the player is sat.
+ * If the player is mounted in some entity, find that entity and get the mount index number at which the player is sat.
* The priority of object confirmation is `direct` then `occupant.VehicleSeated`.
* Once an object is found, the remainder are ignored.
* @param direct a game object in which the player may be sat
* @param occupant the player who is sat and may have specified the game object in which mounted
- * @return a tuple consisting of a vehicle reference and a seat index
+ * @return a tuple consisting of a vehicle reference and a mount index
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
* `(None, None)`, otherwise (even if the vehicle can be determined)
*/
@@ -6965,7 +7025,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
/**
- * If the player is seated in a vehicle, find that vehicle and get the seat index number at which the player is sat.
+ * If the player is seated in a vehicle, find that vehicle and get the mount index number at which the player is sat.
*
* For special purposes involved in zone transfers,
* where the vehicle may or may not exist in either of the zones (yet),
@@ -6974,7 +7034,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* to avoid inspecting the wrong vehicle and failing simple vehicle checks where this function may be employed.
* @see `GetMountableAndSeat`
* @see `interstellarFerry`
- * @return a tuple consisting of a vehicle reference and a seat index
+ * @return a tuple consisting of a vehicle reference and a mount index
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
* `(None, None)`, otherwise (even if the vehicle can be determined)
*/
@@ -6985,9 +7045,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
/**
- * If the player is seated in a vehicle, find that vehicle and get the seat index number at which the player is sat.
+ * If the player is seated in a vehicle, find that vehicle and get the mount index number at which the player is sat.
* @see `GetMountableAndSeat`
- * @return a tuple consisting of a vehicle reference and a seat index
+ * @return a tuple consisting of a vehicle reference and a mount index
* if and only if the vehicle is known to this client and the `WorldSessioNActor`-global `player` occupies it;
* `(None, None)`, otherwise (even if the vehicle can be determined)
*/
@@ -6998,7 +7058,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
/**
- * Create an avatar character so that avatar's player is mounted in a vehicle's seat.
+ * Create an avatar character so that avatar's player is mounted in a vehicle's mount.
* A part of the process of spawning the player into the game world.
*
* This is a very specific configuration of the player character that is not visited very often.
@@ -7011,9 +7071,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* to avoid damaging the critical setup of this function.
* @see `AccessContainer`
* @see `UpdateWeaponAtSeatPosition`
- * @param tplayer the player avatar seated in the vehicle's seat
+ * @param tplayer the player avatar seated in the vehicle's mount
* @param vehicle the vehicle the player is riding
- * @param seat the seat index
+ * @param seat the mount index
*/
def AvatarCreateInVehicle(tplayer: Player, vehicle: Vehicle, seat: Int): Unit = {
val pdef = tplayer.avatar.definition
@@ -7023,7 +7083,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val pdata = pdef.Packet.DetailedConstructorData(tplayer).get
tplayer.VehicleSeated = vguid
sendResponse(ObjectCreateDetailedMessage(pdef.ObjectId, pguid, pdata))
- if (seat == 0 || vehicle.Seats(seat).ControlledWeapon.nonEmpty) {
+ if (seat == 0 || vehicle.WeaponControlledFromSeat(seat).nonEmpty) {
sendResponse(ObjectAttachMessage(vguid, pguid, seat))
AccessContainer(vehicle)
UpdateWeaponAtSeatPosition(vehicle, seat)
@@ -7058,7 +7118,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
*
* If that player is in a vehicle, it will construct that vehicle.
* If the player is the driver of the vehicle,
- * they must temporarily be removed from the driver seat in order for the vehicle to be constructed properly.
+ * they must temporarily be removed from the driver mount in order for the vehicle to be constructed properly.
* These two previous statements operate through similar though distinct mechanisms and imply different conditions.
* In reality, they produce the same output but enforce different relationships between the components.
* The vehicle without a rendered player will always be created if that vehicle exists.
@@ -7079,10 +7139,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
val vguid = vehicle.GUID
if (seat == 0) {
val seat = vehicle.Seats(0)
- seat.Occupant = None
+ seat.unmount(player)
val vdata = vdef.Packet.ConstructorData(vehicle).get
sendResponse(ObjectCreateMessage(vehicle.Definition.ObjectId, vguid, vdata))
- seat.Occupant = player
+ seat.mount(player)
} else {
val vdata = vdef.Packet.ConstructorData(vehicle).get
sendResponse(ObjectCreateMessage(vehicle.Definition.ObjectId, vguid, vdata))
@@ -7097,7 +7157,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
player.VehicleSeated = vguid
sendResponse(ObjectCreateDetailedMessage(pdef.ObjectId, pguid, pdata))
//log.info(s"AvatarRejoin: $vguid -> $vdata")
- if (seat == 0 || vehicle.Seats(seat).ControlledWeapon.nonEmpty) {
+ if (seat == 0 || vehicle.WeaponControlledFromSeat(seat).nonEmpty) {
sendResponse(ObjectAttachMessage(vguid, pguid, seat))
AccessContainer(vehicle)
UpdateWeaponAtSeatPosition(vehicle, seat)
@@ -7290,14 +7350,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
continent.GUID(player.VehicleSeated) match {
case Some(obj: Vehicle) if !obj.Destroyed =>
- cluster ! InterstellarClusterService.GetRandomSpawnPoint(
+ cluster ! ICS.GetRandomSpawnPoint(
Zones.sanctuaryZoneNumber(player.Faction),
player.Faction,
Seq(SpawnGroup.WarpGate),
context.self
)
case _ =>
- cluster ! InterstellarClusterService.GetRandomSpawnPoint(
+ cluster ! ICS.GetRandomSpawnPoint(
Zones.sanctuaryZoneNumber(player.Faction),
player.Faction,
Seq(SpawnGroup.Sanctuary),
@@ -7703,7 +7763,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* Common activities/procedure when a player mounts a valid object.
* @param tplayer the player
* @param obj the mountable object
- * @param seatNum the seat into which the player is mounting
+ * @param seatNum the mount into which the player is mounting
*/
def MountingAction(tplayer: Player, obj: PlanetSideGameObject with Mountable, seatNum: Int): Unit = {
val player_guid: PlanetSideGUID = tplayer.GUID
@@ -7723,13 +7783,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* Common activities/procedure when a player dismounts a valid object.
* @param tplayer the player
* @param obj the mountable object
- * @param seatNum the seat out of which which the player is disembarking
+ * @param seatNum the mount out of which which the player is disembarking
*/
def DismountAction(tplayer: Player, obj: PlanetSideGameObject with Mountable, seatNum: Int): Unit = {
val player_guid: PlanetSideGUID = tplayer.GUID
log.info(s"DismountVehicleMsg: ${tplayer.Name} dismounts $obj from $seatNum")
keepAliveFunc = NormalKeepAlive
- sendResponse(DismountVehicleMsg(player_guid, BailType.Normal, false))
+ sendResponse(DismountVehicleMsg(player_guid, BailType.Normal, wasKickedByDriver = false))
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.DismountVehicle(player_guid, BailType.Normal, false)
@@ -8417,15 +8477,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
session = session.copy(player = targetPlayer)
taskThenZoneChange(
GUIDTask.UnregisterObjectTask(original.avatar.locker)(continent.GUID),
- InterstellarClusterService.FindZone(_.id == zoneId, context.self)
+ ICS.FindZone(_.id == zoneId, context.self)
)
} else if (player.HasGUID) {
taskThenZoneChange(
GUIDTask.UnregisterAvatar(original)(continent.GUID),
- InterstellarClusterService.FindZone(_.id == zoneId, context.self)
+ ICS.FindZone(_.id == zoneId, context.self)
)
} else {
- cluster ! InterstellarClusterService.FindZone(_.id == zoneId, context.self)
+ cluster ! ICS.FindZone(_.id == zoneId, context.self)
}
}
@@ -8494,11 +8554,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
)
manifest.cargo.foreach {
case ("MISSING_DRIVER", index) =>
- val cargo = vehicle.CargoHolds(index).Occupant.get
+ val cargo = vehicle.CargoHolds(index).occupant.get
log.error(s"LoadZoneInVehicleAsDriver: eject cargo in hold $index; vehicle missing driver")
CargoBehavior.HandleVehicleCargoDismount(cargo.GUID, cargo, vehicle.GUID, vehicle, false, false, true)
case (name, index) =>
- val cargo = vehicle.CargoHolds(index).Occupant.get
+ val cargo = vehicle.CargoHolds(index).occupant.get
continent.VehicleEvents ! VehicleServiceMessage(
name,
VehicleAction.TransferPassengerChannel(pguid, s"${cargo.Actor}", toChannel, cargo, topLevel)
@@ -8519,7 +8579,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
player.Continent = zoneId //forward-set the continent id to perform a test
taskThenZoneChange(
GUIDTask.UnregisterAvatar(player)(continent.GUID),
- InterstellarClusterService.FindZone(_.id == zoneId, context.self)
+ ICS.FindZone(_.id == zoneId, context.self)
)
} else {
UnaccessContainer(vehicle)
@@ -8543,7 +8603,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
continent.Transport ! Zone.Vehicle.Despawn(vehicle)
taskThenZoneChange(
UnregisterDrivenVehicle(vehicle, player),
- InterstellarClusterService.FindZone(_.id == zoneId, context.self)
+ ICS.FindZone(_.id == zoneId, context.self)
)
}
}
@@ -8587,7 +8647,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
taskThenZoneChange(
GUIDTask.UnregisterAvatar(player)(continent.GUID),
- InterstellarClusterService.FindZone(_.id == zoneId, context.self)
+ ICS.FindZone(_.id == zoneId, context.self)
)
}
}
@@ -8614,7 +8674,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
vehicle.CargoHolds.values
.collect {
case hold if hold.isOccupied =>
- val cargo = hold.Occupant.get
+ val cargo = hold.occupant.get
cargo.Continent = toZoneId
//point to the cargo vehicle to instigate cargo vehicle driver transportation
galaxyService ! GalaxyServiceMessage(
@@ -8630,7 +8690,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
/** Before changing zones, perform the following task (which can be a nesting of subtasks). */
def taskThenZoneChange(
task: TaskResolver.GiveTask,
- zoneMessage: InterstellarClusterService.FindZone
+ zoneMessage: ICS.FindZone
): Unit = {
continent.tasks ! TaskResolver.GiveTask(
new Task() {
@@ -8845,14 +8905,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
}
/**
- * From a seat, find the weapon controlled from it, and update the ammunition counts for that weapon's magazines.
+ * From a mount, find the weapon controlled from it, and update the ammunition counts for that weapon's magazines.
* @param objWithSeat the object that owns seats (and weaponry)
- * @param seatNum the seat
+ * @param seatNum the mount
*/
- def UpdateWeaponAtSeatPosition(objWithSeat: MountedWeapons, seatNum: Int): Unit = {
+ def UpdateWeaponAtSeatPosition(objWithSeat: MountableWeapons, seatNum: Int): Unit = {
objWithSeat.WeaponControlledFromSeat(seatNum) match {
case Some(weapon: Tool) =>
- //update mounted weapon belonging to seat
+ //update mounted weapon belonging to mount
weapon.AmmoSlots.foreach(slot => {
//update the magazine(s) in the weapon, specifically
val magazine = slot.Box
@@ -9094,9 +9154,9 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
.flatMap {
case Some(obj: Vehicle) if !obj.Cloaked =>
//TODO hint: vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.ProjectileAutoLockAwareness(mode))
- obj.Seats.values.collect { case seat if seat.isOccupied => seat.Occupant.get.Name }
+ obj.Seats.values.flatMap { case seat if seat.isOccupied => seat.occupants.map(_.Name) }
case Some(obj: Mountable) =>
- obj.Seats.values.collect { case seat if seat.isOccupied => seat.Occupant.get.Name }
+ obj.Seats.values.flatMap { case seat if seat.isOccupied => seat.occupants.map(_.Name) }
case Some(obj: Player) if obj.ExoSuit == ExoSuitType.MAX =>
Seq(obj.Name)
case _ =>
@@ -9216,15 +9276,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* Until new upstream messages that pass some tests against their data start being reported,
* the counter does not accumulate properly.
*
- * In the case that the transitioning player is seated in a vehicle seat
+ * In the case that the transitioning player is seated in a vehicle mount
* that is not the driver and does not have a mounted weapon under its control,
* no obvious feedback will be provided by the client.
* For example, when as infantry, a `PlayerStateMessageUpstream` packet is dispatched by the client.
- * For example, when in the driver seat, a `VehicleStateMessage` is dispatched by the client.
+ * For example, when in the driver mount, a `VehicleStateMessage` is dispatched by the client.
* In the given case, the only packet that indicates the player is seated is a `KeepAliveMessage`.
* Detection of this `KeepALiveMessage`, for the purpose of transitioning logic,
* can not be instantaneous to the zoning process or other checks for proper zoning conditions that will be disrupted.
- * To avoid complications, the player in such a seat is initially spawned as infantry on their own client,
+ * To avoid complications, the player in such a mount is initially spawned as infantry on their own client,
* realizes the state transition confirmation for infantry (turn counter),
* and is forced to transition into being seated,
* and only at that time will begin registering `KeepAliveMessage` to mark the end of their interim period.
@@ -9265,7 +9325,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* The atypical response to receiving a `KeepAliveMessage` packet from the client.
*
* `KeepAliveMessage` packets are the primary vehicle for persistence due to client reporting
- * in the case where the player's avatar is riding in a vehicle in a seat with no vehicle.
+ * in the case where the player's avatar is riding in a vehicle in a mount with no vehicle.
* @see `KeepAliveMessage`
* @see `keepAliveFunc`
* @see `turnCounterFunc`
@@ -9285,7 +9345,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
GetMountableAndSeat(None, tplayer, continent) match {
case (Some(obj), Some(seatNum)) =>
tplayer.VehicleSeated = None
- obj.Seats(seatNum).Occupant = None
+ obj.Seats(seatNum).unmount(tplayer)
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.KickPassenger(tplayer.GUID, seatNum, false, obj.GUID)
diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala
index cb0ce2bd9..0c2c5cfaf 100644
--- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala
+++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala
@@ -1,6 +1,5 @@
package net.psforever.actors.zone
-import akka.actor.Actor
import akka.actor.typed.receptionist.Receptionist
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy}
@@ -9,10 +8,8 @@ import net.psforever.actors.commands.NtuCommand
import net.psforever.objects.NtuContainer
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl}
-import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate}
import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior}
-import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretControl}
import net.psforever.objects.zones.Zone
import net.psforever.persistence
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
diff --git a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
index 6b9eabb57..fc3c00a35 100644
--- a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
+++ b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
@@ -236,9 +236,10 @@ object ExplosiveDeployableControl {
def detectTarget(g1: Geometry3D, up: Vector3)(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float) : Boolean = {
val g2 = obj2.Definition.Geometry(obj2)
val dir = g2.center.asVector3 - g1.center.asVector3
- val scalar = Vector3.ScalarProjection(dir, up)
+ //val scalar = Vector3.ScalarProjection(dir, up)
val point1 = g1.pointOnOutside(dir).asVector3
val point2 = g2.pointOnOutside(Vector3.neg(dir)).asVector3
+ val scalar = Vector3.ScalarProjection(point2 - point1, up)
(scalar >= 0 || Vector3.MagnitudeSquared(up * scalar) < 0.35f) &&
math.min(
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3),
diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index eea381a8c..7a9bcc38e 100644
--- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -14,16 +14,17 @@ import net.psforever.objects.serverobject.doors.DoorDefinition
import net.psforever.objects.serverobject.generator.GeneratorDefinition
import net.psforever.objects.serverobject.locks.IFFLockDefinition
import net.psforever.objects.serverobject.mblocker.LockerDefinition
+import net.psforever.objects.serverobject.mount._
import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition
import net.psforever.objects.serverobject.painbox.PainboxDefinition
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
-import net.psforever.objects.serverobject.structures.{AutoRepairStats, BuildingDefinition, WarpGateDefinition}
+import net.psforever.objects.serverobject.structures.{AmenityDefinition, AutoRepairStats, BuildingDefinition, WarpGateDefinition}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalDefinition
import net.psforever.objects.serverobject.terminals.implant.{ImplantTerminalDefinition, ImplantTerminalMechDefinition}
import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade}
-import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, SeatArmorRestriction, UtilityType}
+import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, UtilityType}
import net.psforever.objects.vital.base.DamageType
import net.psforever.objects.vital.damage._
import net.psforever.objects.vital.etc.ExplodingRadialDegrade
@@ -938,6 +939,8 @@ object GlobalDefinitions {
val phantasm = VehicleDefinition(ObjectClass.phantasm)
val droppod = VehicleDefinition(ObjectClass.droppod)
+
+ val orbital_shuttle = VehicleDefinition(ObjectClass.orbital_shuttle)
init_vehicles()
/*
@@ -1056,6 +1059,8 @@ object GlobalDefinitions {
val door = new DoorDefinition
+ val gr_door_mb_orb = new DoorDefinition
+
val resource_silo = new ResourceSiloDefinition
val capture_terminal = new CaptureTerminalDefinition(158) // Base CC
@@ -1093,6 +1098,9 @@ object GlobalDefinitions {
val gen_control = new GeneratorTerminalDefinition(349)
val generator = new GeneratorDefinition(351)
+
+ val obbasemesh = new AmenityDefinition(598) { }
+
initMiscellaneous()
/*
@@ -5611,18 +5619,24 @@ object GlobalDefinitions {
val apcForm = GeometryForm.representByCylinder(radius = 4.6211f, height = 3.90626f) _ //TODO hexahedron
val liberatorForm = GeometryForm.representByCylinder(radius = 3.74615f, height = 2.51563f) _
+ val bailableSeat = new SeatDefinition() {
+ bailable = true
+ }
+ val maxOnlySeat = new SeatDefinition() {
+ restriction = MaxOnly
+ }
+
fury.Name = "fury"
fury.MaxHealth = 650
fury.Damageable = true
fury.Repairable = true
fury.RepairIfDestroyed = false
fury.MaxShields = 130
- fury.Seats += 0 -> new SeatDefinition()
- fury.Seats(0).Bailable = true
- fury.Seats(0).ControlledWeapon = 1
+ fury.Seats += 0 -> bailableSeat
+ fury.controlledWeapons += 0 -> 1
fury.Weapons += 1 -> fury_weapon_systema
- fury.MountPoints += 1 -> 0
- fury.MountPoints += 2 -> 0
+ fury.MountPoints += 1 -> MountInfo(0)
+ fury.MountPoints += 2 -> MountInfo(0)
fury.TrunkSize = InventoryTile.Tile1111
fury.TrunkOffset = 30
fury.TrunkLocation = Vector3(-1.71f, 0f, 0f)
@@ -5649,12 +5663,11 @@ object GlobalDefinitions {
quadassault.Repairable = true
quadassault.RepairIfDestroyed = false
quadassault.MaxShields = 130
- quadassault.Seats += 0 -> new SeatDefinition()
- quadassault.Seats(0).Bailable = true
- quadassault.Seats(0).ControlledWeapon = 1
+ quadassault.Seats += 0 -> bailableSeat
+ quadassault.controlledWeapons += 0 -> 1
quadassault.Weapons += 1 -> quadassault_weapon_system
- quadassault.MountPoints += 1 -> 0
- quadassault.MountPoints += 2 -> 0
+ quadassault.MountPoints += 1 -> MountInfo(0)
+ quadassault.MountPoints += 2 -> MountInfo(0)
quadassault.TrunkSize = InventoryTile.Tile1111
quadassault.TrunkOffset = 30
quadassault.TrunkLocation = Vector3(-1.71f, 0f, 0f)
@@ -5682,11 +5695,10 @@ object GlobalDefinitions {
quadstealth.RepairIfDestroyed = false
quadstealth.MaxShields = 130
quadstealth.CanCloak = true
- quadstealth.Seats += 0 -> new SeatDefinition()
- quadstealth.Seats(0).Bailable = true
+ quadstealth.Seats += 0 -> bailableSeat
quadstealth.CanCloak = true
- quadstealth.MountPoints += 1 -> 0
- quadstealth.MountPoints += 2 -> 0
+ quadstealth.MountPoints += 1 -> MountInfo(0)
+ quadstealth.MountPoints += 2 -> MountInfo(0)
quadstealth.TrunkSize = InventoryTile.Tile1111
quadstealth.TrunkOffset = 30
quadstealth.TrunkLocation = Vector3(-1.71f, 0f, 0f)
@@ -5713,14 +5725,12 @@ object GlobalDefinitions {
two_man_assault_buggy.Repairable = true
two_man_assault_buggy.RepairIfDestroyed = false
two_man_assault_buggy.MaxShields = 250
- two_man_assault_buggy.Seats += 0 -> new SeatDefinition()
- two_man_assault_buggy.Seats(0).Bailable = true
- two_man_assault_buggy.Seats += 1 -> new SeatDefinition()
- two_man_assault_buggy.Seats(1).Bailable = true
- two_man_assault_buggy.Seats(1).ControlledWeapon = 2
+ two_man_assault_buggy.Seats += 0 -> bailableSeat
+ two_man_assault_buggy.Seats += 1 -> bailableSeat
+ two_man_assault_buggy.controlledWeapons += 1 -> 2
two_man_assault_buggy.Weapons += 2 -> chaingun_p
- two_man_assault_buggy.MountPoints += 1 -> 0
- two_man_assault_buggy.MountPoints += 2 -> 1
+ two_man_assault_buggy.MountPoints += 1 -> MountInfo(0)
+ two_man_assault_buggy.MountPoints += 2 -> MountInfo(1)
two_man_assault_buggy.TrunkSize = InventoryTile.Tile1511
two_man_assault_buggy.TrunkOffset = 30
two_man_assault_buggy.TrunkLocation = Vector3(-2.5f, 0f, 0f)
@@ -5747,15 +5757,13 @@ object GlobalDefinitions {
skyguard.Repairable = true
skyguard.RepairIfDestroyed = false
skyguard.MaxShields = 200
- skyguard.Seats += 0 -> new SeatDefinition()
- skyguard.Seats(0).Bailable = true
- skyguard.Seats += 1 -> new SeatDefinition()
- skyguard.Seats(1).Bailable = true
- skyguard.Seats(1).ControlledWeapon = 2
+ skyguard.Seats += 0 -> bailableSeat
+ skyguard.Seats += 1 -> bailableSeat
+ skyguard.controlledWeapons += 1 -> 2
skyguard.Weapons += 2 -> skyguard_weapon_system
- skyguard.MountPoints += 1 -> 0
- skyguard.MountPoints += 2 -> 0
- skyguard.MountPoints += 3 -> 1
+ skyguard.MountPoints += 1 -> MountInfo(0)
+ skyguard.MountPoints += 2 -> MountInfo(0)
+ skyguard.MountPoints += 3 -> MountInfo(1)
skyguard.TrunkSize = InventoryTile.Tile1511
skyguard.TrunkOffset = 30
skyguard.TrunkLocation = Vector3(2.5f, 0f, 0f)
@@ -5782,19 +5790,16 @@ object GlobalDefinitions {
threemanheavybuggy.Repairable = true
threemanheavybuggy.RepairIfDestroyed = false
threemanheavybuggy.MaxShields = 340
- threemanheavybuggy.Seats += 0 -> new SeatDefinition()
- threemanheavybuggy.Seats(0).Bailable = true
- threemanheavybuggy.Seats += 1 -> new SeatDefinition()
- threemanheavybuggy.Seats(1).Bailable = true
- threemanheavybuggy.Seats(1).ControlledWeapon = 3
- threemanheavybuggy.Seats += 2 -> new SeatDefinition()
- threemanheavybuggy.Seats(2).Bailable = true
- threemanheavybuggy.Seats(2).ControlledWeapon = 4
+ threemanheavybuggy.Seats += 0 -> bailableSeat
+ threemanheavybuggy.Seats += 1 -> bailableSeat
+ threemanheavybuggy.Seats += 2 -> bailableSeat
+ threemanheavybuggy.controlledWeapons += 1 -> 3
+ threemanheavybuggy.controlledWeapons += 2 -> 4
threemanheavybuggy.Weapons += 3 -> chaingun_p
threemanheavybuggy.Weapons += 4 -> grenade_launcher_marauder
- threemanheavybuggy.MountPoints += 1 -> 0
- threemanheavybuggy.MountPoints += 2 -> 1
- threemanheavybuggy.MountPoints += 3 -> 2
+ threemanheavybuggy.MountPoints += 1 -> MountInfo(0)
+ threemanheavybuggy.MountPoints += 2 -> MountInfo(1)
+ threemanheavybuggy.MountPoints += 3 -> MountInfo(2)
threemanheavybuggy.TrunkSize = InventoryTile.Tile1511
threemanheavybuggy.TrunkOffset = 30
threemanheavybuggy.TrunkLocation = Vector3(3.01f, 0f, 0f)
@@ -5822,14 +5827,12 @@ object GlobalDefinitions {
twomanheavybuggy.Repairable = true
twomanheavybuggy.RepairIfDestroyed = false
twomanheavybuggy.MaxShields = 360
- twomanheavybuggy.Seats += 0 -> new SeatDefinition()
- twomanheavybuggy.Seats(0).Bailable = true
- twomanheavybuggy.Seats += 1 -> new SeatDefinition()
- twomanheavybuggy.Seats(1).Bailable = true
- twomanheavybuggy.Seats(1).ControlledWeapon = 2
+ twomanheavybuggy.Seats += 0 -> bailableSeat
+ twomanheavybuggy.Seats += 1 -> bailableSeat
+ twomanheavybuggy.controlledWeapons += 1 -> 2
twomanheavybuggy.Weapons += 2 -> advanced_missile_launcher_t
- twomanheavybuggy.MountPoints += 1 -> 0
- twomanheavybuggy.MountPoints += 2 -> 1
+ twomanheavybuggy.MountPoints += 1 -> MountInfo(0)
+ twomanheavybuggy.MountPoints += 2 -> MountInfo(1)
twomanheavybuggy.TrunkSize = InventoryTile.Tile1511
twomanheavybuggy.TrunkOffset = 30
twomanheavybuggy.TrunkLocation = Vector3(-0.23f, -2.05f, 0f)
@@ -5857,14 +5860,12 @@ object GlobalDefinitions {
twomanhoverbuggy.Repairable = true
twomanhoverbuggy.RepairIfDestroyed = false
twomanhoverbuggy.MaxShields = 320
- twomanhoverbuggy.Seats += 0 -> new SeatDefinition()
- twomanhoverbuggy.Seats(0).Bailable = true
- twomanhoverbuggy.Seats += 1 -> new SeatDefinition()
- twomanhoverbuggy.Seats(1).Bailable = true
- twomanhoverbuggy.Seats(1).ControlledWeapon = 2
+ twomanhoverbuggy.Seats += 0 -> bailableSeat
+ twomanhoverbuggy.Seats += 1 -> bailableSeat
+ twomanhoverbuggy.controlledWeapons += 1 -> 2
twomanhoverbuggy.Weapons += 2 -> flux_cannon_thresher
- twomanhoverbuggy.MountPoints += 1 -> 0
- twomanhoverbuggy.MountPoints += 2 -> 1
+ twomanhoverbuggy.MountPoints += 1 -> MountInfo(0)
+ twomanhoverbuggy.MountPoints += 2 -> MountInfo(1)
twomanhoverbuggy.TrunkSize = InventoryTile.Tile1511
twomanhoverbuggy.TrunkOffset = 30
twomanhoverbuggy.TrunkLocation = Vector3(-3.39f, 0f, 0f)
@@ -5891,21 +5892,22 @@ object GlobalDefinitions {
mediumtransport.Repairable = true
mediumtransport.RepairIfDestroyed = false
mediumtransport.MaxShields = 500
- mediumtransport.Seats += 0 -> new SeatDefinition()
- mediumtransport.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
+ mediumtransport.Seats += 0 -> new SeatDefinition() {
+ restriction = NoReinforcedOrMax
+ }
mediumtransport.Seats += 1 -> new SeatDefinition()
- mediumtransport.Seats(1).ControlledWeapon = 5
mediumtransport.Seats += 2 -> new SeatDefinition()
- mediumtransport.Seats(2).ControlledWeapon = 6
- mediumtransport.Seats += 3 -> new SeatDefinition()
- mediumtransport.Seats += 4 -> new SeatDefinition()
- mediumtransport.Weapons += 5 -> mediumtransport_weapon_systemA
- mediumtransport.Weapons += 6 -> mediumtransport_weapon_systemB
- mediumtransport.MountPoints += 1 -> 0
- mediumtransport.MountPoints += 2 -> 1
- mediumtransport.MountPoints += 3 -> 2
- mediumtransport.MountPoints += 4 -> 3
- mediumtransport.MountPoints += 5 -> 4
+ mediumtransport.Seats += 3 -> new SeatDefinition()
+ mediumtransport.Seats += 4 -> new SeatDefinition()
+ mediumtransport.controlledWeapons += 1 -> 5
+ mediumtransport.controlledWeapons += 2 -> 6
+ mediumtransport.Weapons += 5 -> mediumtransport_weapon_systemA
+ mediumtransport.Weapons += 6 -> mediumtransport_weapon_systemB
+ mediumtransport.MountPoints += 1 -> MountInfo(0)
+ mediumtransport.MountPoints += 2 -> MountInfo(1)
+ mediumtransport.MountPoints += 3 -> MountInfo(2)
+ mediumtransport.MountPoints += 4 -> MountInfo(3)
+ mediumtransport.MountPoints += 5 -> MountInfo(4)
mediumtransport.TrunkSize = InventoryTile.Tile1515
mediumtransport.TrunkOffset = 30
mediumtransport.TrunkLocation = Vector3(-3.46f, 0f, 0f)
@@ -5933,25 +5935,26 @@ object GlobalDefinitions {
battlewagon.Repairable = true
battlewagon.RepairIfDestroyed = false
battlewagon.MaxShields = 500
- battlewagon.Seats += 0 -> new SeatDefinition()
- battlewagon.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
+ battlewagon.Seats += 0 -> new SeatDefinition() {
+ restriction = NoReinforcedOrMax
+ }
battlewagon.Seats += 1 -> new SeatDefinition()
- battlewagon.Seats(1).ControlledWeapon = 5
battlewagon.Seats += 2 -> new SeatDefinition()
- battlewagon.Seats(2).ControlledWeapon = 6
battlewagon.Seats += 3 -> new SeatDefinition()
- battlewagon.Seats(3).ControlledWeapon = 7
battlewagon.Seats += 4 -> new SeatDefinition()
- battlewagon.Seats(4).ControlledWeapon = 8
+ battlewagon.controlledWeapons += 1 -> 5
+ battlewagon.controlledWeapons += 2 -> 6
+ battlewagon.controlledWeapons += 3 -> 7
+ battlewagon.controlledWeapons += 4 -> 8
battlewagon.Weapons += 5 -> battlewagon_weapon_systema
battlewagon.Weapons += 6 -> battlewagon_weapon_systemb
battlewagon.Weapons += 7 -> battlewagon_weapon_systemc
battlewagon.Weapons += 8 -> battlewagon_weapon_systemd
- battlewagon.MountPoints += 1 -> 0
- battlewagon.MountPoints += 2 -> 1
- battlewagon.MountPoints += 3 -> 2
- battlewagon.MountPoints += 4 -> 3
- battlewagon.MountPoints += 5 -> 4
+ battlewagon.MountPoints += 1 -> MountInfo(0)
+ battlewagon.MountPoints += 2 -> MountInfo(1)
+ battlewagon.MountPoints += 3 -> MountInfo(2)
+ battlewagon.MountPoints += 4 -> MountInfo(3)
+ battlewagon.MountPoints += 5 -> MountInfo(4)
battlewagon.TrunkSize = InventoryTile.Tile1515
battlewagon.TrunkOffset = 30
battlewagon.TrunkLocation = Vector3(-3.46f, 0f, 0f)
@@ -5978,21 +5981,22 @@ object GlobalDefinitions {
thunderer.Repairable = true
thunderer.RepairIfDestroyed = false
thunderer.MaxShields = 500
- thunderer.Seats += 0 -> new SeatDefinition()
- thunderer.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
+ thunderer.Seats += 0 -> new SeatDefinition() {
+ restriction = NoReinforcedOrMax
+ }
thunderer.Seats += 1 -> new SeatDefinition()
- thunderer.Seats(1).ControlledWeapon = 5
thunderer.Seats += 2 -> new SeatDefinition()
- thunderer.Seats(2).ControlledWeapon = 6
- thunderer.Seats += 3 -> new SeatDefinition()
- thunderer.Seats += 4 -> new SeatDefinition()
- thunderer.Weapons += 5 -> thunderer_weapon_systema
- thunderer.Weapons += 6 -> thunderer_weapon_systemb
- thunderer.MountPoints += 1 -> 0
- thunderer.MountPoints += 2 -> 1
- thunderer.MountPoints += 3 -> 2
- thunderer.MountPoints += 4 -> 3
- thunderer.MountPoints += 5 -> 4
+ thunderer.Seats += 3 -> new SeatDefinition()
+ thunderer.Seats += 4 -> new SeatDefinition()
+ thunderer.Weapons += 5 -> thunderer_weapon_systema
+ thunderer.Weapons += 6 -> thunderer_weapon_systemb
+ thunderer.controlledWeapons += 1 -> 5
+ thunderer.controlledWeapons += 2 -> 6
+ thunderer.MountPoints += 1 -> MountInfo(0)
+ thunderer.MountPoints += 2 -> MountInfo(1)
+ thunderer.MountPoints += 3 -> MountInfo(2)
+ thunderer.MountPoints += 4 -> MountInfo(3)
+ thunderer.MountPoints += 5 -> MountInfo(4)
thunderer.TrunkSize = InventoryTile.Tile1515
thunderer.TrunkOffset = 30
thunderer.TrunkLocation = Vector3(-3.46f, 0f, 0f)
@@ -6020,21 +6024,22 @@ object GlobalDefinitions {
aurora.Repairable = true
aurora.RepairIfDestroyed = false
aurora.MaxShields = 500
- aurora.Seats += 0 -> new SeatDefinition()
- aurora.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
+ aurora.Seats += 0 -> new SeatDefinition() {
+ restriction = NoReinforcedOrMax
+ }
aurora.Seats += 1 -> new SeatDefinition()
- aurora.Seats(1).ControlledWeapon = 5
aurora.Seats += 2 -> new SeatDefinition()
- aurora.Seats(2).ControlledWeapon = 6
- aurora.Seats += 3 -> new SeatDefinition()
- aurora.Seats += 4 -> new SeatDefinition()
- aurora.Weapons += 5 -> aurora_weapon_systema
- aurora.Weapons += 6 -> aurora_weapon_systemb
- aurora.MountPoints += 1 -> 0
- aurora.MountPoints += 2 -> 1
- aurora.MountPoints += 3 -> 2
- aurora.MountPoints += 4 -> 3
- aurora.MountPoints += 5 -> 4
+ aurora.Seats += 3 -> new SeatDefinition()
+ aurora.Seats += 4 -> new SeatDefinition()
+ aurora.controlledWeapons += 1 -> 5
+ aurora.controlledWeapons += 2 -> 6
+ aurora.Weapons += 5 -> aurora_weapon_systema
+ aurora.Weapons += 6 -> aurora_weapon_systemb
+ aurora.MountPoints += 1 -> MountInfo(0)
+ aurora.MountPoints += 2 -> MountInfo(1)
+ aurora.MountPoints += 3 -> MountInfo(2)
+ aurora.MountPoints += 4 -> MountInfo(3)
+ aurora.MountPoints += 5 -> MountInfo(4)
aurora.TrunkSize = InventoryTile.Tile1515
aurora.TrunkOffset = 30
aurora.TrunkLocation = Vector3(-3.46f, 0f, 0f)
@@ -6062,43 +6067,41 @@ object GlobalDefinitions {
apc_tr.Repairable = true
apc_tr.RepairIfDestroyed = false
apc_tr.MaxShields = 1200
- apc_tr.Seats += 0 -> new SeatDefinition()
- apc_tr.Seats += 1 -> new SeatDefinition()
- apc_tr.Seats(1).ControlledWeapon = 11
- apc_tr.Seats += 2 -> new SeatDefinition()
- apc_tr.Seats(2).ControlledWeapon = 12
- apc_tr.Seats += 3 -> new SeatDefinition()
- apc_tr.Seats += 4 -> new SeatDefinition()
- apc_tr.Seats += 5 -> new SeatDefinition()
- apc_tr.Seats(5).ControlledWeapon = 15
- apc_tr.Seats += 6 -> new SeatDefinition()
- apc_tr.Seats(6).ControlledWeapon = 16
- apc_tr.Seats += 7 -> new SeatDefinition()
- apc_tr.Seats(7).ControlledWeapon = 13
- apc_tr.Seats += 8 -> new SeatDefinition()
- apc_tr.Seats(8).ControlledWeapon = 14
- apc_tr.Seats += 9 -> new SeatDefinition()
- apc_tr.Seats(9).ArmorRestriction = SeatArmorRestriction.MaxOnly
- apc_tr.Seats += 10 -> new SeatDefinition()
- apc_tr.Seats(10).ArmorRestriction = SeatArmorRestriction.MaxOnly
- apc_tr.Weapons += 11 -> apc_weapon_systemc_tr
- apc_tr.Weapons += 12 -> apc_weapon_systemb
- apc_tr.Weapons += 13 -> apc_weapon_systema
- apc_tr.Weapons += 14 -> apc_weapon_systemd_tr
- apc_tr.Weapons += 15 -> apc_ballgun_r
- apc_tr.Weapons += 16 -> apc_ballgun_l
- apc_tr.MountPoints += 1 -> 0
- apc_tr.MountPoints += 2 -> 0
- apc_tr.MountPoints += 3 -> 1
- apc_tr.MountPoints += 4 -> 2
- apc_tr.MountPoints += 5 -> 3
- apc_tr.MountPoints += 6 -> 4
- apc_tr.MountPoints += 7 -> 5
- apc_tr.MountPoints += 8 -> 6
- apc_tr.MountPoints += 9 -> 7
- apc_tr.MountPoints += 10 -> 8
- apc_tr.MountPoints += 11 -> 9
- apc_tr.MountPoints += 12 -> 10
+ apc_tr.Seats += 0 -> new SeatDefinition()
+ apc_tr.Seats += 1 -> new SeatDefinition()
+ apc_tr.Seats += 2 -> new SeatDefinition()
+ apc_tr.Seats += 3 -> new SeatDefinition()
+ apc_tr.Seats += 4 -> new SeatDefinition()
+ apc_tr.Seats += 5 -> new SeatDefinition()
+ apc_tr.Seats += 6 -> new SeatDefinition()
+ apc_tr.Seats += 7 -> new SeatDefinition()
+ apc_tr.Seats += 8 -> new SeatDefinition()
+ apc_tr.Seats += 9 -> maxOnlySeat
+ apc_tr.Seats += 10 -> maxOnlySeat
+ apc_tr.controlledWeapons += 1 -> 11
+ apc_tr.controlledWeapons += 2 -> 12
+ apc_tr.controlledWeapons += 5 -> 15
+ apc_tr.controlledWeapons += 6 -> 16
+ apc_tr.controlledWeapons += 7 -> 13
+ apc_tr.controlledWeapons += 8 -> 14
+ apc_tr.Weapons += 11 -> apc_weapon_systemc_tr
+ apc_tr.Weapons += 12 -> apc_weapon_systemb
+ apc_tr.Weapons += 13 -> apc_weapon_systema
+ apc_tr.Weapons += 14 -> apc_weapon_systemd_tr
+ apc_tr.Weapons += 15 -> apc_ballgun_r
+ apc_tr.Weapons += 16 -> apc_ballgun_l
+ apc_tr.MountPoints += 1 -> MountInfo(0)
+ apc_tr.MountPoints += 2 -> MountInfo(0)
+ apc_tr.MountPoints += 3 -> MountInfo(1)
+ apc_tr.MountPoints += 4 -> MountInfo(2)
+ apc_tr.MountPoints += 5 -> MountInfo(3)
+ apc_tr.MountPoints += 6 -> MountInfo(4)
+ apc_tr.MountPoints += 7 -> MountInfo(5)
+ apc_tr.MountPoints += 8 -> MountInfo(6)
+ apc_tr.MountPoints += 9 -> MountInfo(7)
+ apc_tr.MountPoints += 10 -> MountInfo(8)
+ apc_tr.MountPoints += 11 -> MountInfo(9)
+ apc_tr.MountPoints += 12 -> MountInfo(10)
apc_tr.TrunkSize = InventoryTile.Tile2016
apc_tr.TrunkOffset = 30
apc_tr.TrunkLocation = Vector3(-5.82f, 0f, 0f)
@@ -6126,43 +6129,41 @@ object GlobalDefinitions {
apc_nc.Repairable = true
apc_nc.RepairIfDestroyed = false
apc_nc.MaxShields = 1200
- apc_nc.Seats += 0 -> new SeatDefinition()
- apc_nc.Seats += 1 -> new SeatDefinition()
- apc_nc.Seats(1).ControlledWeapon = 11
- apc_nc.Seats += 2 -> new SeatDefinition()
- apc_nc.Seats(2).ControlledWeapon = 12
- apc_nc.Seats += 3 -> new SeatDefinition()
- apc_nc.Seats += 4 -> new SeatDefinition()
- apc_nc.Seats += 5 -> new SeatDefinition()
- apc_nc.Seats(5).ControlledWeapon = 15
- apc_nc.Seats += 6 -> new SeatDefinition()
- apc_nc.Seats(6).ControlledWeapon = 16
- apc_nc.Seats += 7 -> new SeatDefinition()
- apc_nc.Seats(7).ControlledWeapon = 13
+ apc_nc.Seats += 0 -> new SeatDefinition()
+ apc_nc.Seats += 1 -> new SeatDefinition()
+ apc_nc.Seats += 2 -> new SeatDefinition()
+ apc_nc.Seats += 3 -> new SeatDefinition()
+ apc_nc.Seats += 4 -> new SeatDefinition()
+ apc_nc.Seats += 5 -> new SeatDefinition()
+ apc_nc.Seats += 6 -> new SeatDefinition()
+ apc_nc.Seats += 7 -> new SeatDefinition()
apc_nc.Seats += 8 -> new SeatDefinition()
- apc_nc.Seats(8).ControlledWeapon = 14
- apc_nc.Seats += 9 -> new SeatDefinition()
- apc_nc.Seats(9).ArmorRestriction = SeatArmorRestriction.MaxOnly
- apc_nc.Seats += 10 -> new SeatDefinition()
- apc_nc.Seats(10).ArmorRestriction = SeatArmorRestriction.MaxOnly
- apc_nc.Weapons += 11 -> apc_weapon_systemc_nc
- apc_nc.Weapons += 12 -> apc_weapon_systemb
- apc_nc.Weapons += 13 -> apc_weapon_systema
- apc_nc.Weapons += 14 -> apc_weapon_systemd_nc
- apc_nc.Weapons += 15 -> apc_ballgun_r
- apc_nc.Weapons += 16 -> apc_ballgun_l
- apc_nc.MountPoints += 1 -> 0
- apc_nc.MountPoints += 2 -> 0
- apc_nc.MountPoints += 3 -> 1
- apc_nc.MountPoints += 4 -> 2
- apc_nc.MountPoints += 5 -> 3
- apc_nc.MountPoints += 6 -> 4
- apc_nc.MountPoints += 7 -> 5
- apc_nc.MountPoints += 8 -> 6
- apc_nc.MountPoints += 9 -> 7
- apc_nc.MountPoints += 10 -> 8
- apc_nc.MountPoints += 11 -> 9
- apc_nc.MountPoints += 12 -> 10
+ apc_nc.Seats += 9 -> maxOnlySeat
+ apc_nc.Seats += 10 -> maxOnlySeat
+ apc_nc.controlledWeapons += 1 -> 11
+ apc_nc.controlledWeapons += 2 -> 12
+ apc_nc.controlledWeapons += 5 -> 15
+ apc_nc.controlledWeapons += 6 -> 16
+ apc_nc.controlledWeapons += 7 -> 13
+ apc_nc.controlledWeapons += 8 -> 14
+ apc_nc.Weapons += 11 -> apc_weapon_systemc_nc
+ apc_nc.Weapons += 12 -> apc_weapon_systemb
+ apc_nc.Weapons += 13 -> apc_weapon_systema
+ apc_nc.Weapons += 14 -> apc_weapon_systemd_nc
+ apc_nc.Weapons += 15 -> apc_ballgun_r
+ apc_nc.Weapons += 16 -> apc_ballgun_l
+ apc_nc.MountPoints += 1 -> MountInfo(0)
+ apc_nc.MountPoints += 2 -> MountInfo(0)
+ apc_nc.MountPoints += 3 -> MountInfo(1)
+ apc_nc.MountPoints += 4 -> MountInfo(2)
+ apc_nc.MountPoints += 5 -> MountInfo(3)
+ apc_nc.MountPoints += 6 -> MountInfo(4)
+ apc_nc.MountPoints += 7 -> MountInfo(5)
+ apc_nc.MountPoints += 8 -> MountInfo(6)
+ apc_nc.MountPoints += 9 -> MountInfo(7)
+ apc_nc.MountPoints += 10 -> MountInfo(8)
+ apc_nc.MountPoints += 11 -> MountInfo(9)
+ apc_nc.MountPoints += 12 -> MountInfo(10)
apc_nc.TrunkSize = InventoryTile.Tile2016
apc_nc.TrunkOffset = 30
apc_nc.TrunkLocation = Vector3(-5.82f, 0f, 0f)
@@ -6190,43 +6191,41 @@ object GlobalDefinitions {
apc_vs.Repairable = true
apc_vs.RepairIfDestroyed = false
apc_vs.MaxShields = 1200
- apc_vs.Seats += 0 -> new SeatDefinition()
- apc_vs.Seats += 1 -> new SeatDefinition()
- apc_vs.Seats(1).ControlledWeapon = 11
- apc_vs.Seats += 2 -> new SeatDefinition()
- apc_vs.Seats(2).ControlledWeapon = 12
- apc_vs.Seats += 3 -> new SeatDefinition()
- apc_vs.Seats += 4 -> new SeatDefinition()
- apc_vs.Seats += 5 -> new SeatDefinition()
- apc_vs.Seats(5).ControlledWeapon = 15
- apc_vs.Seats += 6 -> new SeatDefinition()
- apc_vs.Seats(6).ControlledWeapon = 16
- apc_vs.Seats += 7 -> new SeatDefinition()
- apc_vs.Seats(7).ControlledWeapon = 13
- apc_vs.Seats += 8 -> new SeatDefinition()
- apc_vs.Seats(8).ControlledWeapon = 14
- apc_vs.Seats += 9 -> new SeatDefinition()
- apc_vs.Seats(9).ArmorRestriction = SeatArmorRestriction.MaxOnly
- apc_vs.Seats += 10 -> new SeatDefinition()
- apc_vs.Seats(10).ArmorRestriction = SeatArmorRestriction.MaxOnly
- apc_vs.Weapons += 11 -> apc_weapon_systemc_vs
- apc_vs.Weapons += 12 -> apc_weapon_systemb
- apc_vs.Weapons += 13 -> apc_weapon_systema
- apc_vs.Weapons += 14 -> apc_weapon_systemd_vs
- apc_vs.Weapons += 15 -> apc_ballgun_r
- apc_vs.Weapons += 16 -> apc_ballgun_l
- apc_vs.MountPoints += 1 -> 0
- apc_vs.MountPoints += 2 -> 0
- apc_vs.MountPoints += 3 -> 1
- apc_vs.MountPoints += 4 -> 2
- apc_vs.MountPoints += 5 -> 3
- apc_vs.MountPoints += 6 -> 4
- apc_vs.MountPoints += 7 -> 5
- apc_vs.MountPoints += 8 -> 6
- apc_vs.MountPoints += 9 -> 7
- apc_vs.MountPoints += 10 -> 8
- apc_vs.MountPoints += 11 -> 9
- apc_vs.MountPoints += 12 -> 10
+ apc_vs.Seats += 0 -> new SeatDefinition()
+ apc_vs.Seats += 1 -> new SeatDefinition()
+ apc_vs.Seats += 2 -> new SeatDefinition()
+ apc_vs.Seats += 3 -> new SeatDefinition()
+ apc_vs.Seats += 4 -> new SeatDefinition()
+ apc_vs.Seats += 5 -> new SeatDefinition()
+ apc_vs.Seats += 6 -> new SeatDefinition()
+ apc_vs.Seats += 7 -> new SeatDefinition()
+ apc_vs.Seats += 8 -> new SeatDefinition()
+ apc_vs.Seats += 9 -> maxOnlySeat
+ apc_vs.Seats += 10 -> maxOnlySeat
+ apc_vs.controlledWeapons += 1 -> 11
+ apc_vs.controlledWeapons += 2 -> 12
+ apc_vs.controlledWeapons += 5 -> 15
+ apc_vs.controlledWeapons += 6 -> 16
+ apc_vs.controlledWeapons += 7 -> 13
+ apc_vs.controlledWeapons += 8 -> 14
+ apc_vs.Weapons += 11 -> apc_weapon_systemc_vs
+ apc_vs.Weapons += 12 -> apc_weapon_systemb
+ apc_vs.Weapons += 13 -> apc_weapon_systema
+ apc_vs.Weapons += 14 -> apc_weapon_systemd_vs
+ apc_vs.Weapons += 15 -> apc_ballgun_r
+ apc_vs.Weapons += 16 -> apc_ballgun_l
+ apc_vs.MountPoints += 1 -> MountInfo(0)
+ apc_vs.MountPoints += 2 -> MountInfo(0)
+ apc_vs.MountPoints += 3 -> MountInfo(1)
+ apc_vs.MountPoints += 4 -> MountInfo(2)
+ apc_vs.MountPoints += 5 -> MountInfo(3)
+ apc_vs.MountPoints += 6 -> MountInfo(4)
+ apc_vs.MountPoints += 7 -> MountInfo(5)
+ apc_vs.MountPoints += 8 -> MountInfo(6)
+ apc_vs.MountPoints += 9 -> MountInfo(7)
+ apc_vs.MountPoints += 10 -> MountInfo(8)
+ apc_vs.MountPoints += 11 -> MountInfo(9)
+ apc_vs.MountPoints += 12 -> MountInfo(10)
apc_vs.TrunkSize = InventoryTile.Tile2016
apc_vs.TrunkOffset = 30
apc_vs.TrunkLocation = Vector3(-5.82f, 0f, 0f)
@@ -6254,12 +6253,13 @@ object GlobalDefinitions {
lightning.Repairable = true
lightning.RepairIfDestroyed = false
lightning.MaxShields = 400
- lightning.Seats += 0 -> new SeatDefinition()
- lightning.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
- lightning.Seats(0).ControlledWeapon = 1
- lightning.Weapons += 1 -> lightning_weapon_system
- lightning.MountPoints += 1 -> 0
- lightning.MountPoints += 2 -> 0
+ lightning.Seats += 0 -> new SeatDefinition() {
+ restriction = NoReinforcedOrMax
+ }
+ lightning.controlledWeapons += 0 -> 1
+ lightning.Weapons += 1 -> lightning_weapon_system
+ lightning.MountPoints += 1 -> MountInfo(0)
+ lightning.MountPoints += 2 -> MountInfo(0)
lightning.TrunkSize = InventoryTile.Tile1511
lightning.TrunkOffset = 30
lightning.TrunkLocation = Vector3(-3f, 0f, 0f)
@@ -6287,17 +6287,18 @@ object GlobalDefinitions {
prowler.Repairable = true
prowler.RepairIfDestroyed = false
prowler.MaxShields = 960
- prowler.Seats += 0 -> new SeatDefinition()
- prowler.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
+ prowler.Seats += 0 -> new SeatDefinition() {
+ restriction = NoReinforcedOrMax
+ }
prowler.Seats += 1 -> new SeatDefinition()
- prowler.Seats(1).ControlledWeapon = 3
prowler.Seats += 2 -> new SeatDefinition()
- prowler.Seats(2).ControlledWeapon = 4
+ prowler.controlledWeapons += 1 -> 3
+ prowler.controlledWeapons += 2 -> 4
prowler.Weapons += 3 -> prowler_weapon_systemA
prowler.Weapons += 4 -> prowler_weapon_systemB
- prowler.MountPoints += 1 -> 0
- prowler.MountPoints += 2 -> 1
- prowler.MountPoints += 3 -> 2
+ prowler.MountPoints += 1 -> MountInfo(0)
+ prowler.MountPoints += 2 -> MountInfo(1)
+ prowler.MountPoints += 3 -> MountInfo(2)
prowler.TrunkSize = InventoryTile.Tile1511
prowler.TrunkOffset = 30
prowler.TrunkLocation = Vector3(-4.71f, 0f, 0f)
@@ -6325,13 +6326,14 @@ object GlobalDefinitions {
vanguard.Repairable = true
vanguard.RepairIfDestroyed = false
vanguard.MaxShields = 1080
- vanguard.Seats += 0 -> new SeatDefinition()
- vanguard.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
+ vanguard.Seats += 0 -> new SeatDefinition() {
+ restriction = NoReinforcedOrMax
+ }
vanguard.Seats += 1 -> new SeatDefinition()
- vanguard.Seats(1).ControlledWeapon = 2
- vanguard.Weapons += 2 -> vanguard_weapon_system
- vanguard.MountPoints += 1 -> 0
- vanguard.MountPoints += 2 -> 1
+ vanguard.controlledWeapons += 1 -> 2
+ vanguard.Weapons += 2 -> vanguard_weapon_system
+ vanguard.MountPoints += 1 -> MountInfo(0)
+ vanguard.MountPoints += 2 -> MountInfo(1)
vanguard.TrunkSize = InventoryTile.Tile1511
vanguard.TrunkOffset = 30
vanguard.TrunkLocation = Vector3(-4.84f, 0f, 0f)
@@ -6359,15 +6361,16 @@ object GlobalDefinitions {
magrider.Repairable = true
magrider.RepairIfDestroyed = false
magrider.MaxShields = 840
- magrider.Seats += 0 -> new SeatDefinition()
- magrider.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
- magrider.Seats(0).ControlledWeapon = 2
+ magrider.Seats += 0 -> new SeatDefinition() {
+ restriction = NoReinforcedOrMax
+ }
magrider.Seats += 1 -> new SeatDefinition()
- magrider.Seats(1).ControlledWeapon = 3
- magrider.Weapons += 2 -> particle_beam_magrider
- magrider.Weapons += 3 -> heavy_rail_beam_magrider
- magrider.MountPoints += 1 -> 0
- magrider.MountPoints += 2 -> 1
+ magrider.controlledWeapons += 0 -> 2
+ magrider.controlledWeapons += 1 -> 3
+ magrider.Weapons += 2 -> particle_beam_magrider
+ magrider.Weapons += 3 -> heavy_rail_beam_magrider
+ magrider.MountPoints += 1 -> MountInfo(0)
+ magrider.MountPoints += 2 -> MountInfo(1)
magrider.TrunkSize = InventoryTile.Tile1511
magrider.TrunkOffset = 30
magrider.TrunkLocation = Vector3(5.06f, 0f, 0f)
@@ -6396,10 +6399,11 @@ object GlobalDefinitions {
ant.Repairable = true
ant.RepairIfDestroyed = false
ant.MaxShields = 400
- ant.Seats += 0 -> new SeatDefinition()
- ant.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
- ant.MountPoints += 1 -> 0
- ant.MountPoints += 2 -> 0
+ ant.Seats += 0 -> new SeatDefinition() {
+ restriction = NoReinforcedOrMax
+ }
+ ant.MountPoints += 1 -> MountInfo(0)
+ ant.MountPoints += 2 -> MountInfo(0)
ant.Deployment = true
ant.DeployTime = 1500
ant.UndeployTime = 1500
@@ -6429,10 +6433,11 @@ object GlobalDefinitions {
ams.Repairable = true
ams.RepairIfDestroyed = false
ams.MaxShields = 1000 // Temporary - original value is 600 + 1
- ams.Seats += 0 -> new SeatDefinition()
- ams.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
- ams.MountPoints += 1 -> 0
- ams.MountPoints += 2 -> 0
+ ams.Seats += 0 -> new SeatDefinition() {
+ restriction = NoReinforcedOrMax
+ }
+ ams.MountPoints += 1 -> MountInfo(0)
+ ams.MountPoints += 2 -> MountInfo(0)
ams.Utilities += 1 -> UtilityType.matrix_terminalc
ams.Utilities += 2 -> UtilityType.ams_respawn_tube
ams.Utilities += 3 -> UtilityType.order_terminala
@@ -6468,7 +6473,7 @@ object GlobalDefinitions {
router.RepairIfDestroyed = false
router.MaxShields = 800
router.Seats += 0 -> new SeatDefinition()
- router.MountPoints += 1 -> 0
+ router.MountPoints += 1 -> MountInfo(0)
router.Utilities += 1 -> UtilityType.teleportpad_terminal
router.Utilities += 2 -> UtilityType.internal_router_telepad_deployable
router.TrunkSize = InventoryTile.Tile1511
@@ -6504,10 +6509,10 @@ object GlobalDefinitions {
switchblade.RepairIfDestroyed = false
switchblade.MaxShields = 350
switchblade.Seats += 0 -> new SeatDefinition()
- switchblade.Seats(0).ControlledWeapon = 1
+ switchblade.controlledWeapons += 0 -> 1
switchblade.Weapons += 1 -> scythe
- switchblade.MountPoints += 1 -> 0
- switchblade.MountPoints += 2 -> 0
+ switchblade.MountPoints += 1 -> MountInfo(0)
+ switchblade.MountPoints += 2 -> MountInfo(0)
switchblade.TrunkSize = InventoryTile.Tile1511
switchblade.TrunkOffset = 30
switchblade.TrunkLocation = Vector3(-2.5f, 0f, 0f)
@@ -6541,9 +6546,9 @@ object GlobalDefinitions {
flail.RepairIfDestroyed = false
flail.MaxShields = 480
flail.Seats += 0 -> new SeatDefinition()
- flail.Seats(0).ControlledWeapon = 1
- flail.Weapons += 1 -> flail_weapon
- flail.MountPoints += 1 -> 0
+ flail.controlledWeapons += 0 -> 1
+ flail.Weapons += 1 -> flail_weapon
+ flail.MountPoints += 1 -> MountInfo(0)
flail.TrunkSize = InventoryTile.Tile1511
flail.TrunkOffset = 30
flail.TrunkLocation = Vector3(-3.75f, 0f, 0f)
@@ -6576,12 +6581,11 @@ object GlobalDefinitions {
mosquito.RepairIfDestroyed = false
mosquito.MaxShields = 133
mosquito.CanFly = true
- mosquito.Seats += 0 -> new SeatDefinition()
- mosquito.Seats(0).Bailable = true
- mosquito.Seats(0).ControlledWeapon = 1
+ mosquito.Seats += 0 -> bailableSeat
+ mosquito.controlledWeapons += 0 -> 1
mosquito.Weapons += 1 -> rotarychaingun_mosquito
- mosquito.MountPoints += 1 -> 0
- mosquito.MountPoints += 2 -> 0
+ mosquito.MountPoints += 1 -> MountInfo(0)
+ mosquito.MountPoints += 2 -> MountInfo(0)
mosquito.TrunkSize = InventoryTile.Tile1111
mosquito.TrunkOffset = 30
mosquito.TrunkLocation = Vector3(-4.6f, 0f, 0f)
@@ -6610,12 +6614,11 @@ object GlobalDefinitions {
lightgunship.RepairIfDestroyed = false
lightgunship.MaxShields = 200
lightgunship.CanFly = true
- lightgunship.Seats += 0 -> new SeatDefinition()
- lightgunship.Seats(0).Bailable = true
- lightgunship.Seats(0).ControlledWeapon = 1
+ lightgunship.Seats += 0 -> bailableSeat
+ lightgunship.controlledWeapons += 0 -> 1
lightgunship.Weapons += 1 -> lightgunship_weapon_system
- lightgunship.MountPoints += 1 -> 0
- lightgunship.MountPoints += 2 -> 0
+ lightgunship.MountPoints += 1 -> MountInfo(0)
+ lightgunship.MountPoints += 2 -> MountInfo(0)
lightgunship.TrunkSize = InventoryTile.Tile1511
lightgunship.TrunkOffset = 30
lightgunship.TrunkLocation = Vector3(-5.61f, 0f, 0f)
@@ -6645,12 +6648,11 @@ object GlobalDefinitions {
wasp.RepairIfDestroyed = false
wasp.MaxShields = 103
wasp.CanFly = true
- wasp.Seats += 0 -> new SeatDefinition()
- wasp.Seats(0).Bailable = true
- wasp.Seats(0).ControlledWeapon = 1
+ wasp.Seats += 0 -> bailableSeat
+ wasp.controlledWeapons += 0 -> 1
wasp.Weapons += 1 -> wasp_weapon_system
- wasp.MountPoints += 1 -> 0
- wasp.MountPoints += 2 -> 0
+ wasp.MountPoints += 1 -> MountInfo(0)
+ wasp.MountPoints += 2 -> MountInfo(0)
wasp.TrunkSize = InventoryTile.Tile1111
wasp.TrunkOffset = 30
wasp.TrunkLocation = Vector3(-4.6f, 0f, 0f)
@@ -6680,20 +6682,18 @@ object GlobalDefinitions {
liberator.MaxShields = 500
liberator.CanFly = true
liberator.Seats += 0 -> new SeatDefinition()
- liberator.Seats(0).ControlledWeapon = 3
- liberator.Seats += 1 -> new SeatDefinition()
- liberator.Seats(1).ControlledWeapon = 4
- liberator.Seats(1).Bailable = true
- liberator.Seats += 2 -> new SeatDefinition()
- liberator.Seats(2).ControlledWeapon = 5
- liberator.Seats(2).Bailable = true
+ liberator.Seats += 1 -> bailableSeat
+ liberator.Seats += 2 -> bailableSeat
+ liberator.controlledWeapons += 0 -> 3
+ liberator.controlledWeapons += 1 -> 4
+ liberator.controlledWeapons += 2 -> 5
liberator.Weapons += 3 -> liberator_weapon_system
liberator.Weapons += 4 -> liberator_bomb_bay
liberator.Weapons += 5 -> liberator_25mm_cannon
- liberator.MountPoints += 1 -> 0
- liberator.MountPoints += 2 -> 1
- liberator.MountPoints += 3 -> 1
- liberator.MountPoints += 4 -> 2
+ liberator.MountPoints += 1 -> MountInfo(0)
+ liberator.MountPoints += 2 -> MountInfo(1)
+ liberator.MountPoints += 3 -> MountInfo(1)
+ liberator.MountPoints += 4 -> MountInfo(2)
liberator.TrunkSize = InventoryTile.Tile1515
liberator.TrunkOffset = 30
liberator.TrunkLocation = Vector3(-0.76f, -1.88f, 0f)
@@ -6724,20 +6724,18 @@ object GlobalDefinitions {
vulture.MaxShields = 500
vulture.CanFly = true
vulture.Seats += 0 -> new SeatDefinition()
- vulture.Seats(0).ControlledWeapon = 3
- vulture.Seats += 1 -> new SeatDefinition()
- vulture.Seats(1).ControlledWeapon = 4
- vulture.Seats(1).Bailable = true
- vulture.Seats += 2 -> new SeatDefinition()
- vulture.Seats(2).ControlledWeapon = 5
- vulture.Seats(2).Bailable = true
+ vulture.Seats += 1 -> bailableSeat
+ vulture.Seats += 2 -> bailableSeat
+ vulture.controlledWeapons += 0 -> 3
+ vulture.controlledWeapons += 1 -> 4
+ vulture.controlledWeapons += 2 -> 5
vulture.Weapons += 3 -> vulture_nose_weapon_system
vulture.Weapons += 4 -> vulture_bomb_bay
vulture.Weapons += 5 -> vulture_tail_cannon
- vulture.MountPoints += 1 -> 0
- vulture.MountPoints += 2 -> 1
- vulture.MountPoints += 3 -> 1
- vulture.MountPoints += 4 -> 2
+ vulture.MountPoints += 1 -> MountInfo(0)
+ vulture.MountPoints += 2 -> MountInfo(1)
+ vulture.MountPoints += 3 -> MountInfo(1)
+ vulture.MountPoints += 4 -> MountInfo(2)
vulture.TrunkSize = InventoryTile.Tile1611
vulture.TrunkOffset = 30
vulture.TrunkLocation = Vector3(-0.76f, -1.88f, 0f)
@@ -6770,50 +6768,46 @@ object GlobalDefinitions {
dropship.MaxShields = 1000
dropship.CanFly = true
dropship.Seats += 0 -> new SeatDefinition()
- dropship.Seats += 1 -> new SeatDefinition()
- dropship.Seats(1).Bailable = true
- dropship.Seats(1).ControlledWeapon = 12
- dropship.Seats += 2 -> new SeatDefinition()
- dropship.Seats(2).Bailable = true
- dropship.Seats(2).ControlledWeapon = 13
- dropship.Seats += 3 -> new SeatDefinition()
- dropship.Seats(3).Bailable = true
- dropship.Seats += 4 -> new SeatDefinition()
- dropship.Seats(4).Bailable = true
- dropship.Seats += 5 -> new SeatDefinition()
- dropship.Seats(5).Bailable = true
- dropship.Seats += 6 -> new SeatDefinition()
- dropship.Seats(6).Bailable = true
- dropship.Seats += 7 -> new SeatDefinition()
- dropship.Seats(7).Bailable = true
- dropship.Seats += 8 -> new SeatDefinition()
- dropship.Seats(8).Bailable = true
- dropship.Seats += 9 -> new SeatDefinition()
- dropship.Seats(9).Bailable = true
- dropship.Seats(9).ArmorRestriction = SeatArmorRestriction.MaxOnly
- dropship.Seats += 10 -> new SeatDefinition()
- dropship.Seats(10).Bailable = true
- dropship.Seats(10).ArmorRestriction = SeatArmorRestriction.MaxOnly
- dropship.Seats += 11 -> new SeatDefinition()
- dropship.Seats(11).Bailable = true
- dropship.Seats(11).ControlledWeapon = 14
+ dropship.Seats += 1 -> new SeatDefinition() {
+ bailable = true
+ }
+ dropship.Seats += 2 -> bailableSeat
+ dropship.Seats += 3 -> bailableSeat
+ dropship.Seats += 4 -> bailableSeat
+ dropship.Seats += 5 -> bailableSeat
+ dropship.Seats += 6 -> bailableSeat
+ dropship.Seats += 7 -> bailableSeat
+ dropship.Seats += 9 -> new SeatDefinition() {
+ bailable = true
+ restriction = MaxOnly
+ }
+ dropship.Seats += 10 -> new SeatDefinition() {
+ bailable = true
+ restriction = MaxOnly
+ }
+ dropship.Seats += 11 -> bailableSeat
+ dropship.controlledWeapons += 1 -> 12
+ dropship.controlledWeapons += 2 -> 13
+ dropship.controlledWeapons += 11 -> 14
dropship.Weapons += 12 -> cannon_dropship_20mm
dropship.Weapons += 13 -> cannon_dropship_20mm
dropship.Weapons += 14 -> dropship_rear_turret
- dropship.Cargo += 15 -> new CargoDefinition()
- dropship.MountPoints += 1 -> 0
- dropship.MountPoints += 2 -> 11
- dropship.MountPoints += 3 -> 1
- dropship.MountPoints += 4 -> 2
- dropship.MountPoints += 5 -> 3
- dropship.MountPoints += 6 -> 4
- dropship.MountPoints += 7 -> 5
- dropship.MountPoints += 8 -> 6
- dropship.MountPoints += 9 -> 7
- dropship.MountPoints += 10 -> 8
- dropship.MountPoints += 11 -> 9
- dropship.MountPoints += 12 -> 10
- dropship.MountPoints += 13 -> 15
+ dropship.Cargo += 15 -> new CargoDefinition() {
+ restriction = SmallCargo
+ }
+ dropship.MountPoints += 1 -> MountInfo(0)
+ dropship.MountPoints += 2 -> MountInfo(11)
+ dropship.MountPoints += 3 -> MountInfo(1)
+ dropship.MountPoints += 4 -> MountInfo(2)
+ dropship.MountPoints += 5 -> MountInfo(3)
+ dropship.MountPoints += 6 -> MountInfo(4)
+ dropship.MountPoints += 7 -> MountInfo(5)
+ dropship.MountPoints += 8 -> MountInfo(6)
+ dropship.MountPoints += 9 -> MountInfo(7)
+ dropship.MountPoints += 10 -> MountInfo(8)
+ dropship.MountPoints += 11 -> MountInfo(9)
+ dropship.MountPoints += 12 -> MountInfo(10)
+ dropship.MountPoints += 13 -> MountInfo(15)
dropship.TrunkSize = InventoryTile.Tile1612
dropship.TrunkOffset = 30
dropship.TrunkLocation = Vector3(-7.39f, -4.96f, 0f)
@@ -6845,32 +6839,27 @@ object GlobalDefinitions {
galaxy_gunship.MaxShields = 1200
galaxy_gunship.CanFly = true
galaxy_gunship.Seats += 0 -> new SeatDefinition()
- galaxy_gunship.Seats += 1 -> new SeatDefinition()
- galaxy_gunship.Seats(1).ControlledWeapon = 6
- galaxy_gunship.Seats(1).Bailable = true
- galaxy_gunship.Seats += 2 -> new SeatDefinition()
- galaxy_gunship.Seats(2).ControlledWeapon = 7
- galaxy_gunship.Seats(2).Bailable = true
- galaxy_gunship.Seats += 3 -> new SeatDefinition()
- galaxy_gunship.Seats(3).ControlledWeapon = 8
- galaxy_gunship.Seats(3).Bailable = true
- galaxy_gunship.Seats += 4 -> new SeatDefinition()
- galaxy_gunship.Seats(4).ControlledWeapon = 9
- galaxy_gunship.Seats(4).Bailable = true
- galaxy_gunship.Seats += 5 -> new SeatDefinition()
- galaxy_gunship.Seats(5).ControlledWeapon = 10
- galaxy_gunship.Seats(5).Bailable = true
+ galaxy_gunship.Seats += 1 -> bailableSeat
+ galaxy_gunship.Seats += 2 -> bailableSeat
+ galaxy_gunship.Seats += 3 -> bailableSeat
+ galaxy_gunship.Seats += 4 -> bailableSeat
+ galaxy_gunship.Seats += 5 -> bailableSeat
+ galaxy_gunship.controlledWeapons += 1 -> 6
+ galaxy_gunship.controlledWeapons += 2 -> 7
+ galaxy_gunship.controlledWeapons += 3 -> 8
+ galaxy_gunship.controlledWeapons += 4 -> 9
+ galaxy_gunship.controlledWeapons += 5 -> 10
galaxy_gunship.Weapons += 6 -> galaxy_gunship_cannon
galaxy_gunship.Weapons += 7 -> galaxy_gunship_cannon
galaxy_gunship.Weapons += 8 -> galaxy_gunship_tailgun
galaxy_gunship.Weapons += 9 -> galaxy_gunship_gun
galaxy_gunship.Weapons += 10 -> galaxy_gunship_gun
- galaxy_gunship.MountPoints += 1 -> 0
- galaxy_gunship.MountPoints += 2 -> 3
- galaxy_gunship.MountPoints += 3 -> 1
- galaxy_gunship.MountPoints += 4 -> 2
- galaxy_gunship.MountPoints += 5 -> 4
- galaxy_gunship.MountPoints += 6 -> 5
+ galaxy_gunship.MountPoints += 1 -> MountInfo(0)
+ galaxy_gunship.MountPoints += 2 -> MountInfo(3)
+ galaxy_gunship.MountPoints += 3 -> MountInfo(1)
+ galaxy_gunship.MountPoints += 4 -> MountInfo(2)
+ galaxy_gunship.MountPoints += 5 -> MountInfo(4)
+ galaxy_gunship.MountPoints += 6 -> MountInfo(5)
galaxy_gunship.TrunkSize = InventoryTile.Tile1816
galaxy_gunship.TrunkOffset = 30
galaxy_gunship.TrunkLocation = Vector3(-9.85f, 0f, 0f)
@@ -6904,8 +6893,8 @@ object GlobalDefinitions {
lodestar.MaxShields = 1000
lodestar.CanFly = true
lodestar.Seats += 0 -> new SeatDefinition()
- lodestar.MountPoints += 1 -> 0
- lodestar.MountPoints += 2 -> 1
+ lodestar.MountPoints += 1 -> MountInfo(0)
+ lodestar.MountPoints += 2 -> MountInfo(1)
lodestar.Cargo += 1 -> new CargoDefinition()
lodestar.Utilities += 2 -> UtilityType.lodestar_repair_terminal
lodestar.UtilityOffset += 2 -> Vector3(0, 20, 0)
@@ -6946,19 +6935,15 @@ object GlobalDefinitions {
phantasm.CanCloak = true
phantasm.CanFly = true
phantasm.Seats += 0 -> new SeatDefinition()
- phantasm.Seats += 1 -> new SeatDefinition()
- phantasm.Seats(1).Bailable = true
- phantasm.Seats += 2 -> new SeatDefinition()
- phantasm.Seats(2).Bailable = true
- phantasm.Seats += 3 -> new SeatDefinition()
- phantasm.Seats(3).Bailable = true
- phantasm.Seats += 4 -> new SeatDefinition()
- phantasm.Seats(4).Bailable = true
- phantasm.MountPoints += 1 -> 0
- phantasm.MountPoints += 2 -> 1
- phantasm.MountPoints += 3 -> 2
- phantasm.MountPoints += 4 -> 3
- phantasm.MountPoints += 5 -> 4
+ phantasm.Seats += 1 -> bailableSeat
+ phantasm.Seats += 2 -> bailableSeat
+ phantasm.Seats += 3 -> bailableSeat
+ phantasm.Seats += 4 -> bailableSeat
+ phantasm.MountPoints += 1 -> MountInfo(0)
+ phantasm.MountPoints += 2 -> MountInfo(1)
+ phantasm.MountPoints += 3 -> MountInfo(2)
+ phantasm.MountPoints += 4 -> MountInfo(3)
+ phantasm.MountPoints += 5 -> MountInfo(4)
phantasm.TrunkSize = InventoryTile.Tile1107
phantasm.TrunkOffset = 30
phantasm.TrunkLocation = Vector3(-6.16f, 0f, 0f)
@@ -6982,17 +6967,51 @@ object GlobalDefinitions {
droppod.Name = "droppod"
droppod.MaxHealth = 20000
- //droppod.Damageable = false
+ droppod.Damageable = false
+ droppod.Repairable = false
droppod.CanFly = true
- droppod.Seats += 0 -> new SeatDefinition
- droppod.MountPoints += 1 -> 0
+ droppod.Seats += 0 -> new SeatDefinition {
+ restriction = Unrestricted
+ }
+ droppod.MountPoints += 1 -> MountInfo(0)
droppod.TrunkSize = InventoryTile.None
droppod.Packet = new DroppodConverter()
droppod.DeconstructionTime = Some(5 seconds)
droppod.DestroyedModel = None //the adb calls out a droppod; the cyclic nature of this confounds me
droppod.DamageUsing = DamageCalculations.AgainstAircraft
droppod.DrownAtMaxDepth = false
- //TODO geometry?
+
+ orbital_shuttle.Name = "orbital_shuttle"
+ orbital_shuttle.MaxHealth = 20000
+ orbital_shuttle.Damageable = false
+ orbital_shuttle.Repairable = false
+ orbital_shuttle.CanFly = true
+ orbital_shuttle.CanBeOwned = None
+ orbital_shuttle.undergoesDecay = false
+ orbital_shuttle.Seats += 0 -> new SeatDefinition {
+ occupancy = 300
+ restriction = Unrestricted
+ }
+ /*
+ these are close to the mount point offsets in the ADB;
+ physically, they correlate to positions in the HART building rather than with the shuttle model by itself;
+ set the shuttle pad based on the zonemap extraction values then position the shuttle relative to that pad;
+ rotation based on the shuttle should place these offsets in the HART lobby whose gantry hall corresponds to that mount index
+ */
+ orbital_shuttle.MountPoints += 1 -> MountInfo(0, Vector3(-62, 4, -28.2f))
+ orbital_shuttle.MountPoints += 2 -> MountInfo(0, Vector3(-62, 28, -28.2f))
+ orbital_shuttle.MountPoints += 3 -> MountInfo(0, Vector3(-62, 4, -18.2f))
+ orbital_shuttle.MountPoints += 4 -> MountInfo(0, Vector3(-62, 28, -18.2f))
+ orbital_shuttle.MountPoints += 5 -> MountInfo(0, Vector3( 62, 4, -28.2f))
+ orbital_shuttle.MountPoints += 6 -> MountInfo(0, Vector3( 62, 28, -28.2f))
+ orbital_shuttle.MountPoints += 7 -> MountInfo(0, Vector3( 62, 4, -18.2f))
+ orbital_shuttle.MountPoints += 8 -> MountInfo(0, Vector3( 62, 28, -18.2f))
+ orbital_shuttle.TrunkSize = InventoryTile.None
+ orbital_shuttle.Packet = new OrbitalShuttleConverter
+ orbital_shuttle.DeconstructionTime = None
+ orbital_shuttle.DestroyedModel = None
+ orbital_shuttle.DamageUsing = DamageCalculations.AgainstNothing
+ orbital_shuttle.DrownAtMaxDepth = false
}
/**
@@ -7065,8 +7084,8 @@ object GlobalDefinitions {
spitfire_turret.Damageable = true
spitfire_turret.Repairable = true
spitfire_turret.RepairIfDestroyed = false
- spitfire_turret.Weapons += 1 -> new mutable.HashMap()
- spitfire_turret.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
+ spitfire_turret.WeaponPaths += 1 -> new mutable.HashMap()
+ spitfire_turret.WeaponPaths(1) += TurretUpgrade.None -> spitfire_weapon
spitfire_turret.ReserveAmmunition = false
spitfire_turret.DeployCategory = DeployableCategory.SmallTurrets
spitfire_turret.DeployTime = Duration.create(5000, "ms")
@@ -7089,8 +7108,8 @@ object GlobalDefinitions {
spitfire_cloaked.Damageable = true
spitfire_cloaked.Repairable = true
spitfire_cloaked.RepairIfDestroyed = false
- spitfire_cloaked.Weapons += 1 -> new mutable.HashMap()
- spitfire_cloaked.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
+ spitfire_cloaked.WeaponPaths += 1 -> new mutable.HashMap()
+ spitfire_cloaked.WeaponPaths(1) += TurretUpgrade.None -> spitfire_weapon
spitfire_cloaked.ReserveAmmunition = false
spitfire_cloaked.DeployCategory = DeployableCategory.SmallTurrets
spitfire_cloaked.DeployTime = Duration.create(5000, "ms")
@@ -7112,8 +7131,8 @@ object GlobalDefinitions {
spitfire_aa.Damageable = true
spitfire_aa.Repairable = true
spitfire_aa.RepairIfDestroyed = false
- spitfire_aa.Weapons += 1 -> new mutable.HashMap()
- spitfire_aa.Weapons(1) += TurretUpgrade.None -> spitfire_aa_weapon
+ spitfire_aa.WeaponPaths += 1 -> new mutable.HashMap()
+ spitfire_aa.WeaponPaths(1) += TurretUpgrade.None -> spitfire_aa_weapon
spitfire_aa.ReserveAmmunition = false
spitfire_aa.DeployCategory = DeployableCategory.SmallTurrets
spitfire_aa.DeployTime = Duration.create(5000, "ms")
@@ -7173,10 +7192,11 @@ object GlobalDefinitions {
portable_manned_turret.Damageable = true
portable_manned_turret.Repairable = true
portable_manned_turret.RepairIfDestroyed = false
- portable_manned_turret.MountPoints += 1 -> 0
- portable_manned_turret.MountPoints += 2 -> 0
- portable_manned_turret.Weapons += 1 -> new mutable.HashMap()
- portable_manned_turret.Weapons(1) += TurretUpgrade.None -> energy_gun
+ portable_manned_turret.controlledWeapons += 0 -> 1
+ portable_manned_turret.WeaponPaths += 1 -> new mutable.HashMap()
+ portable_manned_turret.WeaponPaths(1) += TurretUpgrade.None -> energy_gun
+ portable_manned_turret.MountPoints += 1 -> MountInfo(0)
+ portable_manned_turret.MountPoints += 2 -> MountInfo(0)
portable_manned_turret.ReserveAmmunition = true
portable_manned_turret.FactionLocked = true
portable_manned_turret.Packet = fieldTurretConverter
@@ -7200,10 +7220,11 @@ object GlobalDefinitions {
portable_manned_turret_nc.Damageable = true
portable_manned_turret_nc.Repairable = true
portable_manned_turret_nc.RepairIfDestroyed = false
- portable_manned_turret_nc.MountPoints += 1 -> 0
- portable_manned_turret_nc.MountPoints += 2 -> 0
- portable_manned_turret_nc.Weapons += 1 -> new mutable.HashMap()
- portable_manned_turret_nc.Weapons(1) += TurretUpgrade.None -> energy_gun_nc
+ portable_manned_turret_nc.WeaponPaths += 1 -> new mutable.HashMap()
+ portable_manned_turret_nc.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_nc
+ portable_manned_turret_nc.controlledWeapons += 0 -> 1
+ portable_manned_turret_nc.MountPoints += 1 -> MountInfo(0)
+ portable_manned_turret_nc.MountPoints += 2 -> MountInfo(0)
portable_manned_turret_nc.ReserveAmmunition = true
portable_manned_turret_nc.FactionLocked = true
portable_manned_turret_nc.Packet = fieldTurretConverter
@@ -7227,10 +7248,11 @@ object GlobalDefinitions {
portable_manned_turret_tr.Damageable = true
portable_manned_turret_tr.Repairable = true
portable_manned_turret_tr.RepairIfDestroyed = false
- portable_manned_turret_tr.MountPoints += 1 -> 0
- portable_manned_turret_tr.MountPoints += 2 -> 0
- portable_manned_turret_tr.Weapons += 1 -> new mutable.HashMap()
- portable_manned_turret_tr.Weapons(1) += TurretUpgrade.None -> energy_gun_tr
+ portable_manned_turret_tr.WeaponPaths += 1 -> new mutable.HashMap()
+ portable_manned_turret_tr.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_tr
+ portable_manned_turret_tr.controlledWeapons += 0 -> 1
+ portable_manned_turret_tr.MountPoints += 1 -> MountInfo(0)
+ portable_manned_turret_tr.MountPoints += 2 -> MountInfo(0)
portable_manned_turret_tr.ReserveAmmunition = true
portable_manned_turret_tr.FactionLocked = true
portable_manned_turret_tr.Packet = fieldTurretConverter
@@ -7254,10 +7276,11 @@ object GlobalDefinitions {
portable_manned_turret_vs.Damageable = true
portable_manned_turret_vs.Repairable = true
portable_manned_turret_vs.RepairIfDestroyed = false
- portable_manned_turret_vs.MountPoints += 1 -> 0
- portable_manned_turret_vs.MountPoints += 2 -> 0
- portable_manned_turret_vs.Weapons += 1 -> new mutable.HashMap()
- portable_manned_turret_vs.Weapons(1) += TurretUpgrade.None -> energy_gun_vs
+ portable_manned_turret_vs.WeaponPaths += 1 -> new mutable.HashMap()
+ portable_manned_turret_vs.WeaponPaths(1) += TurretUpgrade.None -> energy_gun_vs
+ portable_manned_turret_vs.controlledWeapons += 0 -> 1
+ portable_manned_turret_vs.MountPoints += 1 -> MountInfo(0)
+ portable_manned_turret_vs.MountPoints += 2 -> MountInfo(0)
portable_manned_turret_vs.ReserveAmmunition = true
portable_manned_turret_vs.FactionLocked = true
portable_manned_turret_vs.Packet = fieldTurretConverter
@@ -7689,6 +7712,10 @@ object GlobalDefinitions {
door.Damageable = false
door.Repairable = false
+ gr_door_mb_orb.Name = "gr_door_mb_orb"
+ gr_door_mb_orb.Damageable = false
+ gr_door_mb_orb.Repairable = false
+
resource_silo.Name = "resource_silo"
resource_silo.Damageable = false
resource_silo.Repairable = false
@@ -7755,11 +7782,12 @@ object GlobalDefinitions {
manned_turret.Repairable = true
manned_turret.autoRepair = AutoRepairStats(1.0909f, 10000, 1600, 0.5f)
manned_turret.RepairIfDestroyed = true
- manned_turret.Weapons += 1 -> new mutable.HashMap()
- manned_turret.Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan
- manned_turret.Weapons(1) += TurretUpgrade.AVCombo -> phalanx_avcombo
- manned_turret.Weapons(1) += TurretUpgrade.FlakCombo -> phalanx_flakcombo
- manned_turret.MountPoints += 1 -> 0
+ manned_turret.WeaponPaths += 1 -> new mutable.HashMap()
+ manned_turret.WeaponPaths(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan
+ manned_turret.WeaponPaths(1) += TurretUpgrade.AVCombo -> phalanx_avcombo
+ manned_turret.WeaponPaths(1) += TurretUpgrade.FlakCombo -> phalanx_flakcombo
+ manned_turret.controlledWeapons += 0 -> 1
+ manned_turret.MountPoints += 1 -> MountInfo(0)
manned_turret.FactionLocked = true
manned_turret.ReserveAmmunition = false
manned_turret.explodes = true
@@ -7771,7 +7799,7 @@ object GlobalDefinitions {
DamageAtEdge = 0.1f
Modifiers = ExplodingRadialDegrade
}
- manned_turret.Geometry = GeometryForm.representByCylinder(radius = 1.2695f, height = 2.6875f)
+ manned_turret.Geometry = GeometryForm.representByCylinder(radius = 1.2695f, height = 4.042f)
vanu_sentry_turret.Name = "vanu_sentry_turret"
vanu_sentry_turret.MaxHealth = 1500
@@ -7780,10 +7808,11 @@ object GlobalDefinitions {
vanu_sentry_turret.Repairable = true
vanu_sentry_turret.autoRepair = AutoRepairStats(3.27272f, 10000, 1000, 0.5f)
vanu_sentry_turret.RepairIfDestroyed = true
- vanu_sentry_turret.Weapons += 1 -> new mutable.HashMap()
- vanu_sentry_turret.Weapons(1) += TurretUpgrade.None -> vanu_sentry_turret_weapon
- vanu_sentry_turret.MountPoints += 1 -> 0
- vanu_sentry_turret.MountPoints += 2 -> 0
+ vanu_sentry_turret.WeaponPaths += 1 -> new mutable.HashMap()
+ vanu_sentry_turret.WeaponPaths(1) += TurretUpgrade.None -> vanu_sentry_turret_weapon
+ vanu_sentry_turret.controlledWeapons += 0 -> 1
+ vanu_sentry_turret.MountPoints += 1 -> MountInfo(0)
+ vanu_sentry_turret.MountPoints += 2 -> MountInfo(0)
vanu_sentry_turret.FactionLocked = false
vanu_sentry_turret.ReserveAmmunition = false
vanu_sentry_turret.Geometry = GeometryForm.representByCylinder(radius = 1.76311f, height = 3.984375f)
@@ -7876,5 +7905,10 @@ object GlobalDefinitions {
//damage is 99999 at 14m, dropping rapidly to ~1 at 14.5m
}
generator.Geometry = GeometryForm.representByCylinder(radius = 1.2617f, height = 9.14063f)
+
+ obbasemesh.Name = "obbasemesh"
+ obbasemesh.Descriptor = "orbital_shuttle_pad"
+ obbasemesh.Damageable = false
+ obbasemesh.Repairable = false
}
}
diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala
index 91dd38966..76f4eabb2 100644
--- a/src/main/scala/net/psforever/objects/TurretDeployable.scala
+++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala
@@ -11,7 +11,7 @@ import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.serverobject.damage.DamageableWeaponTurret
import net.psforever.objects.serverobject.hackable.Hackable
-import net.psforever.objects.serverobject.mount.MountableBehavior
+import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.repair.RepairableWeaponTurret
import net.psforever.objects.serverobject.turret.{TurretDefinition, WeaponTurret}
import net.psforever.objects.vital.damage.DamageCalculations
@@ -25,8 +25,6 @@ class TurretDeployable(tdef: TurretDeployableDefinition)
with Hackable {
WeaponTurret.LoadDefinition(this)
- def MountPoints: Map[Int, Int] = Definition.MountPoints.toMap
-
override def Definition = tdef
}
@@ -65,8 +63,7 @@ class TurretControl(turret: TurretDeployable)
extends Actor
with FactionAffinityBehavior.Check
with JammableMountedWeapons //note: jammable status is reported as vehicle events, not local events
- with MountableBehavior.TurretMount
- with MountableBehavior.Dismount
+ with MountableBehavior
with DamageableWeaponTurret
with RepairableWeaponTurret {
def MountableObject = turret
@@ -91,6 +88,13 @@ class TurretControl(turret: TurretDeployable)
case _ => ;
}
+ override protected def mountTest(
+ obj: PlanetSideServerObject with Mountable,
+ seatNumber: Int,
+ player: Player): Boolean = {
+ (!turret.Definition.FactionLocked || player.Faction == obj.Faction) && !obj.Destroyed
+ }
+
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
super.DestructionAwareness(target, cause)
Deployables.AnnounceDestroyDeployable(turret, None)
diff --git a/src/main/scala/net/psforever/objects/Vehicle.scala b/src/main/scala/net/psforever/objects/Vehicle.scala
index ec3a0be31..633cb5283 100644
--- a/src/main/scala/net/psforever/objects/Vehicle.scala
+++ b/src/main/scala/net/psforever/objects/Vehicle.scala
@@ -1,10 +1,10 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
-import net.psforever.objects.definition.{SeatDefinition, ToolDefinition, VehicleDefinition}
-import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
+import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition}
+import net.psforever.objects.equipment.{EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile}
-import net.psforever.objects.serverobject.mount.Mountable
+import net.psforever.objects.serverobject.mount.{Seat, SeatDefinition}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.aura.AuraContainer
@@ -18,7 +18,6 @@ import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.DamageResistanceModel
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
-import scala.annotation.tailrec
import scala.concurrent.duration.FiniteDuration
import scala.util.{Success, Try}
@@ -33,7 +32,7 @@ import scala.util.{Success, Try}
* The `Map` of `Utility` objects is given using the same inventory index positions.
* Positive indices and zero are considered "represented" and must be assigned a globally unique identifier
* and must be present in the containing vehicle's `ObjectCreateMessage` packet.
- * The index is the seat position, reflecting the position in the zero-index inventory.
+ * The index is the mount position, reflecting the position in the zero-index inventory.
* Negative indices are expected to be excluded from this conversion.
* The value of the negative index does not have a specific meaning.
*
@@ -44,27 +43,27 @@ import scala.util.{Success, Try}
* The driver is the only player that can access a vehicle's saved loadouts through a repair/rearm silo
* and can procure equipment from the said silo.
* The owner of a vehicle and the driver of a vehicle as mostly interchangeable terms for this reason
- * and it can be summarized that the player who has access to the driver seat meets the qualifications for the "owner"
- * so long as that player is the last person to have sat in that seat.
- * All previous ownership information is replaced just as soon as someone else sits in the driver's seat.
+ * and it can be summarized that the player who has access to the driver mount meets the qualifications for the "owner"
+ * so long as that player is the last person to have sat in that mount.
+ * All previous ownership information is replaced just as soon as someone else sits in the driver's mount.
* Ownership is also transferred as players die and respawn (from and to the same client)
* and when they leave a continent without taking the vehicle they currently own with them.
* (They also lose ownership when they leave the game, of course.)
*
* All seats have vehicle-level properties on top of their own internal properties.
- * A seat has a glyph projected onto the ground when the vehicle is not moving
- * that is used to mark where the seat can be accessed, as well as broadcasting the current access condition of the seat.
+ * A mount has a glyph projected onto the ground when the vehicle is not moving
+ * that is used to mark where the mount can be accessed, as well as broadcasting the current access condition of the mount.
* As indicated previously, seats are composed into categories and the categories used to control access.
- * The "driver" group has already been mentioned and is usually composed of a single seat, the "first" one.
- * The driver seat is typically locked to the person who can sit in it - the owner - unless manually unlocked.
- * Any seat besides the "driver" that has a weapon controlled from the seat is called a "gunner" seats.
- * Any other seat besides the "driver" seat and "gunner" seats is called a "passenger" seat.
+ * The "driver" group has already been mentioned and is usually composed of a single mount, the "first" one.
+ * The driver mount is typically locked to the person who can sit in it - the owner - unless manually unlocked.
+ * Any mount besides the "driver" that has a weapon controlled from the mount is called a "gunner" seats.
+ * Any other mount besides the "driver" mount and "gunner" seats is called a "passenger" mount.
* All of these seats are typically unlocked normally.
- * The "trunk" also counts as an access group even though it is not directly attached to a seat and starts as "locked."
+ * The "trunk" also counts as an access group even though it is not directly attached to a mount and starts as "locked."
* The categories all have their own glyphs,
* sharing a red cross glyph as a "can not access" state,
* and may also use their lack of visibility to express state.
- * In terms of individual access, each seat can have its current occupant ejected, save for the driver's seat.
+ * In terms of individual access, each mount can have its current occupant ejected, save for the driver's mount.
* @see `Vehicle.EquipmentUtilities`
* @param vehicleDef the vehicle's definition entry;
* stores and unloads pertinent information about the `Vehicle`'s configuration;
@@ -72,11 +71,10 @@ import scala.util.{Success, Try}
*/
class Vehicle(private val vehicleDef: VehicleDefinition)
extends AmenityOwner
+ with MountableWeapons
with InteractsWithZoneEnvironment
with Hackable
with FactionAffinity
- with Mountable
- with MountedWeapons
with Deployment
with Vitality
with OwnableByPlayer
@@ -90,19 +88,18 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
private var decal: Int = 0
private var trunkAccess: Option[PlanetSideGUID] = None
private var jammered: Boolean = false
+
private var cloaked: Boolean = false
- private var flying: Boolean = false
+ private var flying: Option[Int] = None
private var capacitor: Int = 0
/**
* Permissions control who gets to access different parts of the vehicle;
- * the groups are Driver (seat), Gunner (seats), Passenger (seats), and the Trunk
+ * the groups are Driver (mount), Gunner (seats), Passenger (seats), and the Trunk
*/
private val groupPermissions: Array[VehicleLockState.Value] =
Array(VehicleLockState.Locked, VehicleLockState.Empire, VehicleLockState.Empire, VehicleLockState.Locked)
- private var seats: Map[Int, Seat] = Map.empty
private var cargoHolds: Map[Int, Cargo] = Map.empty
- private var weapons: Map[Int, EquipmentSlot] = Map.empty
private var utilities: Map[Int, Utility] = Map()
private val trunk: GridInventory = GridInventory()
@@ -198,9 +195,13 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
Cloaked
}
- def Flying: Boolean = flying
+ def isFlying: Boolean = flying.nonEmpty
- def Flying_=(isFlying: Boolean): Boolean = {
+ def Flying: Option[Int] = flying
+
+ def Flying_=(isFlying: Int): Option[Int] = Flying_=(Some(isFlying))
+
+ def Flying_=(isFlying: Option[Int]): Option[Int] = {
flying = isFlying
Flying
}
@@ -226,17 +227,6 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
Capacitor
}
- /**
- * Given the index of an entry mounting point, return the infantry-accessible `Seat` associated with it.
- * @param mountPoint an index representing the seat position / mounting point
- * @return a seat number, or `None`
- */
- def GetSeatFromMountPoint(mountPoint: Int): Option[Int] = {
- Definition.MountPoints.get(mountPoint)
- }
-
- def MountPoints: Map[Int, Int] = Definition.MountPoints.toMap
-
/**
* What are the access permissions for a position on this vehicle, seats or trunk?
* @param group the group index
@@ -291,24 +281,6 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
None
}
- /**
- * Get the seat at the index.
- * The specified "seat" can only accommodate a player as opposed to weapon mounts which share the same indexing system.
- * @param seatNumber an index representing the seat position / mounting point
- * @return a `Seat`, or `None`
- */
- def Seat(seatNumber: Int): Option[Seat] = {
- if (seatNumber >= 0 && seatNumber < this.seats.size) {
- this.seats.get(seatNumber)
- } else {
- None
- }
- }
-
- def Seats: Map[Int, Seat] = {
- seats
- }
-
def CargoHold(cargoNumber: Int): Option[Cargo] = {
if (cargoNumber >= 0) {
this.cargoHolds.get(cargoNumber)
@@ -322,12 +294,12 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
}
def SeatPermissionGroup(seatNumber: Int): Option[AccessPermissionGroup.Value] = {
- if (seatNumber == 0) {
+ if (seatNumber == 0) { //valid in almost all cases
Some(AccessPermissionGroup.Driver)
} else {
Seat(seatNumber) match {
- case Some(seat) =>
- seat.ControlledWeapon match {
+ case Some(_) =>
+ Definition.controlledWeapons.get(seatNumber) match {
case Some(_) =>
Some(AccessPermissionGroup.Gunner)
case None =>
@@ -336,50 +308,18 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
case None =>
CargoHold(seatNumber) match {
case Some(_) =>
- Some(AccessPermissionGroup.Passenger)
+ Some(AccessPermissionGroup.Passenger) //TODO confirm this
case None =>
- None
+ if (seatNumber >= trunk.Offset && seatNumber < trunk.Offset + trunk.TotalCapacity) {
+ Some(AccessPermissionGroup.Trunk)
+ } else {
+ None
+ }
}
}
}
}
- def Weapons: Map[Int, EquipmentSlot] = weapons
-
- /**
- * Get the weapon at the index.
- * @param wepNumber an index representing the seat position / mounting point
- * @return a weapon, or `None`
- */
- def ControlledWeapon(wepNumber: Int): Option[Equipment] = {
- weapons.get(wepNumber) match {
- case Some(mount) =>
- mount.Equipment
- case None =>
- None
- }
- }
-
- /**
- * Given a player who may be an occupant, retrieve an number of the seat where this player is sat.
- * @param player the player
- * @return a seat number, or `None` if the `player` is not actually seated in this vehicle
- */
- def PassengerInSeat(player: Player): Option[Int] = recursivePassengerInSeat(seats.iterator, player)
-
- @tailrec private def recursivePassengerInSeat(iter: Iterator[(Int, Seat)], player: Player): Option[Int] = {
- if (!iter.hasNext) {
- None
- } else {
- val (seatNumber, seat) = iter.next()
- if (seat.Occupant.contains(player)) {
- Some(seatNumber)
- } else {
- recursivePassengerInSeat(iter, player)
- }
- }
- }
-
def Utilities: Map[Int, Utility] = utilities
/**
@@ -415,7 +355,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
def Inventory: GridInventory = trunk
- def VisibleSlots: Set[Int] = weapons.keySet
+ def VisibleSlots: Set[Int] = weapons.keys.toSet
override def Slot(slotNum: Int): EquipmentSlot = {
weapons
@@ -535,7 +475,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
def PrepareGatingManifest(): VehicleManifest = {
val manifest = VehicleManifest(this)
- seats.collect { case (index: Int, seat: Seat) if index > 0 => seat.Occupant = None }
+ seats.collect { case (index: Int, seat: Seat) if index > 0 => seat.unmount(seat.occupant) }
vehicleGatingManifest = Some(manifest)
previousVehicleGatingManifest = None
manifest
@@ -676,12 +616,12 @@ object Vehicle {
//create seats
vehicle.seats = vdef.Seats.map[Int, Seat] {
case (num: Int, definition: SeatDefinition) =>
- num -> Seat(definition)
+ num -> new Seat(definition)
}.toMap
// create cargo holds
vehicle.cargoHolds = vdef.Cargo.map[Int, Cargo] {
case (num, definition) =>
- num -> Cargo(definition)
+ num -> new Cargo(definition)
}.toMap
//create utilities
vehicle.utilities = vdef.Utilities.map[Int, Utility] {
diff --git a/src/main/scala/net/psforever/objects/Vehicles.scala b/src/main/scala/net/psforever/objects/Vehicles.scala
index ed5a328d4..89fa4cee9 100644
--- a/src/main/scala/net/psforever/objects/Vehicles.scala
+++ b/src/main/scala/net/psforever/objects/Vehicles.scala
@@ -87,7 +87,7 @@ object Vehicles {
/**
* Disassociate a player from a vehicle that he owns.
* The vehicle must exist in the game world on the specified continent.
- * This is similar but unrelated to the natural exchange of ownership when someone else sits in the vehicle's driver seat.
+ * This is similar but unrelated to the natural exchange of ownership when someone else sits in the vehicle's driver mount.
* This is the player side of vehicle ownership removal.
* @param player the player
*/
@@ -96,7 +96,7 @@ object Vehicles {
/**
* Disassociate a player from a vehicle that he owns.
* The vehicle must exist in the game world on the specified continent.
- * This is similar but unrelated to the natural exchange of ownership when someone else sits in the vehicle's driver seat.
+ * This is similar but unrelated to the natural exchange of ownership when someone else sits in the vehicle's driver mount.
* This is the player side of vehicle ownership removal.
* @param player the player
*/
@@ -117,7 +117,7 @@ object Vehicles {
/**
* Disassociate a player from a vehicle that he owns without associating a different player as the owner.
- * Set the vehicle's driver seat permissions and passenger and gunner seat permissions to "allow empire,"
+ * Set the vehicle's driver mount permissions and passenger and gunner mount permissions to "allow empire,"
* then reload them for all clients.
* This is the vehicle side of vehicle ownership removal.
* @param player the player
@@ -196,7 +196,7 @@ object Vehicles {
val manifestPassengerResults = manifestPassengers.map { name => vzone.Players.exists(_.name.equals(name)) }
manifestPassengerResults.forall(_ == true) &&
vehicle.CargoHolds.values
- .collect { case hold if hold.isOccupied => AllGatedOccupantsInSameZone(hold.Occupant.get) }
+ .collect { case hold if hold.isOccupied => AllGatedOccupantsInSameZone(hold.occupant.get) }
.forall(_ == true)
case _ =>
false
@@ -230,18 +230,18 @@ object Vehicles {
val zone = target.Zone
// Forcefully dismount any cargo
target.CargoHolds.values.foreach(cargoHold => {
- cargoHold.Occupant match {
+ cargoHold.occupant match {
case Some(cargo: Vehicle) =>
- cargo.Seats(0).Occupant match {
+ cargo.Seats(0).occupant match {
case Some(_: Player) =>
CargoBehavior.HandleVehicleCargoDismount(
target.Zone,
cargo.GUID,
- bailed = target.Flying,
+ bailed = target.isFlying,
requestedByPassenger = false,
kicked = true
)
- case None =>
+ case _ =>
log.error("FinishHackingVehicle: vehicle in cargo hold missing driver")
CargoBehavior.HandleVehicleCargoDismount(cargo.GUID, cargo, target.GUID, target, bailed = false, requestedByPassenger = false, kicked = true)
}
@@ -250,9 +250,9 @@ object Vehicles {
})
// Forcefully dismount all seated occupants from the vehicle
target.Seats.values.foreach(seat => {
- seat.Occupant match {
- case Some(tplayer) =>
- seat.Occupant = None
+ seat.occupant match {
+ case Some(tplayer: Player) =>
+ seat.unmount(tplayer)
tplayer.VehicleSeated = None
if (tplayer.HasGUID) {
zone.VehicleEvents ! VehicleServiceMessage(
@@ -260,11 +260,11 @@ object Vehicles {
VehicleAction.KickPassenger(tplayer.GUID, 4, unk2 = false, target.GUID)
)
}
- case None => ;
+ case _ => ;
}
})
// If the vehicle can fly and is flying deconstruct it, and well played to whomever managed to hack a plane in mid air. I'm impressed.
- if (target.Definition.CanFly && target.Flying) {
+ if (target.Definition.CanFly && target.isFlying) {
// todo: Should this force the vehicle to land in the same way as when a pilot bails with passengers on board?
target.Actor ! Vehicle.Deconstruct()
} else { // Otherwise handle ownership transfer as normal
@@ -407,4 +407,21 @@ object Vehicles {
case _ => ;
}
}
+
+ /**
+ * Find the position and angle at which an ejected player will be placed once outside of the shuttle.
+ * Mainly for use with the proper high altitude rapid transport (HART) shuttle and it's corresponding HART building.
+ * @param obj the (shuttle) vehicle
+ * @param mountPoint the mount point that indicates a seat
+ * @return the position and angle
+ */
+ def dismountShuttle(obj: Vehicle, mountPoint: Int): (Vector3, Float) = {
+ val shuttleAngle = obj.Orientation.z
+ val offset = {
+ val baseOffset = obj.MountPoints(mountPoint).positionOffset
+ Vector3.Rz(baseOffset.xy, shuttleAngle) + Vector3.z(baseOffset.z)
+ }
+ val turnAway = if (offset.x >= 0) -90f else 90f
+ (obj.Position + offset, (shuttleAngle + turnAway) % 360f)
+ }
}
diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
index a96b8924e..e50a376b4 100644
--- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
+++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
@@ -27,9 +27,11 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.objects.locker.LockerContainerControl
import net.psforever.objects.serverobject.environment._
+import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
import net.psforever.objects.vital.environment.EnvironmentReason
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
+import net.psforever.services.hart.ShuttleState
import scala.concurrent.duration._
@@ -60,6 +62,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
+ SetInteraction(EnvironmentAttribute.GantryDenialField, doInteractingWithGantryField)
SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
private[this] val log = org.log4s.getLogger(player.Name)
@@ -713,13 +716,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
) //align client interface fields with state
zone.GUID(target.VehicleSeated) match {
case Some(obj: Mountable) =>
- //boot cadaver from seat internally (vehicle perspective)
+ //boot cadaver from mount internally (vehicle perspective)
obj.PassengerInSeat(target) match {
case Some(index) =>
- obj.Seats(index).Occupant = None
+ obj.Seats(index).unmount(target)
case _ => ;
}
- //boot cadaver from seat on client
+ //boot cadaver from mount on client
events ! AvatarServiceMessage(
nameChannel,
AvatarAction.SendResponse(
@@ -1046,6 +1049,38 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
suicide()
}
+ def doInteractingWithGantryField(
+ obj: PlanetSideServerObject,
+ body: PieceOfEnvironment,
+ data: Option[OxygenStateTarget]
+ ): Unit = {
+ import scala.concurrent.ExecutionContext.Implicits.global
+ val field = body.asInstanceOf[GantryDenialField]
+ val zone = player.Zone
+ (zone.GUID(field.obbasemesh) match {
+ case Some(pad : OrbitalShuttlePad) => zone.GUID(pad.shuttle)
+ case _ => None
+ }) match {
+ case Some(shuttle: Vehicle)
+ if shuttle.Flying.contains(ShuttleState.State11.id) || shuttle.Faction != player.Faction =>
+ val (pos, zang) = Vehicles.dismountShuttle(shuttle, field.mountPoint)
+ shuttle.Zone.AvatarEvents ! AvatarServiceMessage(
+ player.Name,
+ AvatarAction.SendResponse(
+ Service.defaultPlayerGUID,
+ PlayerStateShiftMessage(ShiftState(0, pos, zang, None)))
+ )
+ case Some(_: Vehicle) =>
+ interactionTimer = context.system.scheduler.scheduleOnce(
+ delay = 250 milliseconds,
+ self,
+ InteractWithEnvironment(player, body, None)
+ )
+ case _ => ;
+ //something configured incorrectly; no need to keep checking
+ }
+ }
+
/**
* When out of water, the player is no longer suffocating.
* The player does have to endure a recovery period to get back to normal, though.
diff --git a/src/main/scala/net/psforever/objects/definition/CargoDefinition.scala b/src/main/scala/net/psforever/objects/definition/CargoDefinition.scala
index 4aee68ded..470c1aa1f 100644
--- a/src/main/scala/net/psforever/objects/definition/CargoDefinition.scala
+++ b/src/main/scala/net/psforever/objects/definition/CargoDefinition.scala
@@ -1,35 +1,14 @@
-// Copyright (c) 2017 PSForever
+// Copyright (c) 2021 PSForever
package net.psforever.objects.definition
-import net.psforever.objects.vehicles.CargoVehicleRestriction
+import net.psforever.objects.Vehicle
+import net.psforever.objects.serverobject.mount.{LargeCargo, MountRestriction, MountableSpaceDefinition}
-/**
- * The definition for a cargo hold.
- */
-class CargoDefinition extends BasicDefinition {
-
- /** a restriction on the type of exo-suit a person can wear */
- private var vehicleRestriction: CargoVehicleRestriction.Value = CargoVehicleRestriction.Small
-
- /** the user can escape while the vehicle is moving */
- private var bailable: Boolean = true
+class CargoDefinition extends MountableSpaceDefinition[Vehicle] {
Name = "cargo"
+ def occupancy: Int = 1
- def CargoRestriction: CargoVehicleRestriction.Value = {
- this.vehicleRestriction
- }
+ var restriction: MountRestriction[Vehicle] = LargeCargo
- def CargoRestriction_=(restriction: CargoVehicleRestriction.Value): CargoVehicleRestriction.Value = {
- this.vehicleRestriction = restriction
- restriction
- }
-
- def Bailable: Boolean = {
- this.bailable
- }
-
- def Bailable_=(canBail: Boolean): Boolean = {
- this.bailable = canBail
- canBail
- }
+ var bailable: Boolean = true
}
diff --git a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala
index 5378b5224..0df091659 100644
--- a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala
+++ b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala
@@ -89,9 +89,9 @@ abstract class ObjectDefinition(private val objectId: Int) extends BasicDefiniti
private var serverGeometry: Any => Geometry3D = GeometryForm.representByPoint()
def Geometry: Any => Geometry3D = if (ServerSplashTargetsCentroid) {
- serverGeometry
- } else {
GeometryForm.representByPoint()
+ } else {
+ serverGeometry
}
def Geometry_=(func: Any => Geometry3D): Any => Geometry3D = {
diff --git a/src/main/scala/net/psforever/objects/definition/SeatDefinition.scala b/src/main/scala/net/psforever/objects/definition/SeatDefinition.scala
deleted file mode 100644
index 7152f873f..000000000
--- a/src/main/scala/net/psforever/objects/definition/SeatDefinition.scala
+++ /dev/null
@@ -1,52 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.objects.definition
-
-import net.psforever.objects.vehicles.SeatArmorRestriction
-
-/**
- * The definition for a seat.
- */
-class SeatDefinition extends BasicDefinition {
-
- /** a restriction on the type of exo-suit a person can wear */
- private var armorRestriction: SeatArmorRestriction.Value = SeatArmorRestriction.NoMax
-
- /** the user can escape while the vehicle is moving */
- private var bailable: Boolean = false
-
- /** any controlled weapon */
- private var weaponMount: Option[Int] = None
- Name = "seat"
-
- def ArmorRestriction: SeatArmorRestriction.Value = {
- this.armorRestriction
- }
-
- def ArmorRestriction_=(restriction: SeatArmorRestriction.Value): SeatArmorRestriction.Value = {
- this.armorRestriction = restriction
- restriction
- }
-
- /** Determines if the seat can be bailed from while the vehicle is in motion */
- def Bailable: Boolean = {
- this.bailable
- }
-
- def Bailable_=(canBail: Boolean): Boolean = {
- this.bailable = canBail
- canBail
- }
-
- def ControlledWeapon: Option[Int] = {
- this.weaponMount
- }
-
- def ControlledWeapon_=(wep: Int): Option[Int] = {
- ControlledWeapon_=(Some(wep))
- }
-
- def ControlledWeapon_=(wep: Option[Int]): Option[Int] = {
- this.weaponMount = wep
- ControlledWeapon
- }
-}
diff --git a/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
index d208c2c01..83a41a353 100644
--- a/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
+++ b/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
@@ -4,7 +4,7 @@ package net.psforever.objects.definition
import net.psforever.objects.NtuContainerDefinition
import net.psforever.objects.definition.converter.VehicleConverter
import net.psforever.objects.inventory.InventoryTile
-import net.psforever.objects.vehicles.{DestroyedVehicle, UtilityType}
+import net.psforever.objects.vehicles.{DestroyedVehicle, MountableWeaponsDefinition, UtilityType}
import net.psforever.objects.vital._
import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
@@ -20,19 +20,14 @@ import scala.concurrent.duration._
*/
class VehicleDefinition(objectId: Int)
extends ObjectDefinition(objectId)
+ with MountableWeaponsDefinition
with VitalityDefinition
with NtuContainerDefinition
with ResistanceProfileMutators
with DamageResistanceModel {
/** vehicle shields offered through amp station facility benefits (generally: 20% of health + 1) */
private var maxShields: Int = 0
- /* key - seat index, value - seat object */
- private val seats: mutable.HashMap[Int, SeatDefinition] = mutable.HashMap[Int, SeatDefinition]()
private val cargo: mutable.HashMap[Int, CargoDefinition] = mutable.HashMap[Int, CargoDefinition]()
- /* key - entry point index, value - seat index */
- private val mountPoints: mutable.HashMap[Int, Int] = mutable.HashMap()
- /* key - seat index (where this weapon attaches during object construction), value - the weapon on an EquipmentSlot */
- private val weapons: mutable.HashMap[Int, ToolDefinition] = mutable.HashMap[Int, ToolDefinition]()
private var deployment: Boolean = false
private val utilities: mutable.HashMap[Int, UtilityType.Value] = mutable.HashMap()
private val utilityOffsets: mutable.HashMap[Int, Vector3] = mutable.HashMap()
@@ -44,8 +39,16 @@ class VehicleDefinition(objectId: Int)
private var trunkLocation: Vector3 = Vector3.Zero
private var canCloak: Boolean = false
private var canFly: Boolean = false
- private var canBeOwned: Boolean = true
+ /** whether the vehicle gains and/or maintains ownership based on access to the driver seat
+ * `Some(true)` - assign ownership upon the driver mount, maintains ownership after the driver dismounts
+ * `Some(false)` - assign ownership upon the driver mount, becomes unowned after the driver dismounts
+ * `None` - does not assign ownership
+ * Be cautious about using `None` as the client tends to equate the driver seat as the owner's seat for many vehicles
+ * and breaking from the client's convention either requires additional fields or just doesn't work.
+ */
+ private var canBeOwned: Option[Boolean] = Some(true)
private var serverVehicleOverrideSpeeds: (Int, Int) = (0, 0)
+ var undergoesDecay: Boolean = true
private var deconTime: Option[FiniteDuration] = None
private var maxCapacitor: Int = 0
private var destroyedModel: Option[DestroyedVehicle.Value] = None
@@ -64,15 +67,13 @@ class VehicleDefinition(objectId: Int)
MaxShields
}
- def Seats: mutable.HashMap[Int, SeatDefinition] = seats
-
def Cargo: mutable.HashMap[Int, CargoDefinition] = cargo
- def MountPoints: mutable.HashMap[Int, Int] = mountPoints
+ def CanBeOwned: Option[Boolean] = canBeOwned
- def CanBeOwned: Boolean = canBeOwned
+ def CanBeOwned_=(ownable: Boolean): Option[Boolean] = CanBeOwned_=(Some(ownable))
- def CanBeOwned_=(ownable: Boolean): Boolean = {
+ def CanBeOwned_=(ownable: Option[Boolean]): Option[Boolean] = {
canBeOwned = ownable
CanBeOwned
}
@@ -91,8 +92,6 @@ class VehicleDefinition(objectId: Int)
CanFly
}
- def Weapons: mutable.HashMap[Int, ToolDefinition] = weapons
-
def Deployment: Boolean = deployment
def Deployment_=(deployable: Boolean): Boolean = {
diff --git a/src/main/scala/net/psforever/objects/definition/converter/OrbitalShuttleConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/OrbitalShuttleConverter.scala
new file mode 100644
index 000000000..69112381f
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/definition/converter/OrbitalShuttleConverter.scala
@@ -0,0 +1,20 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.definition.converter
+
+import net.psforever.objects.Vehicle
+import net.psforever.packet.game.objectcreate._
+
+import scala.util.{Failure, Success, Try}
+
+class OrbitalShuttleConverter extends ObjectCreateConverter[Vehicle]() {
+ override def ConstructorData(obj: Vehicle): Try[OrbitalShuttleData] = {
+// if (obj.MountedIn.nonEmpty) {
+// Success(OrbitalShuttleData(obj.Faction, None))
+// } else {
+ Success(OrbitalShuttleData(obj.Faction, Some(PlacementData(obj.Position, obj.Orientation))))
+// }
+ }
+
+ override def DetailedConstructorData(obj: Vehicle): Try[OrbitalShuttleData] =
+ Failure(new Exception("OrbitalShuttleConverter should not be used to generate detailed DroppodData (nothing should)"))
+}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/SeatConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/SeatConverter.scala
index ced03f488..a7a23f7b5 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/SeatConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/SeatConverter.scala
@@ -2,7 +2,7 @@
package net.psforever.objects.definition.converter
import net.psforever.objects.Player
-import net.psforever.objects.vehicles.Seat
+import net.psforever.objects.serverobject.mount.Seat
import net.psforever.packet.game.objectcreate.{InventoryItemData, ObjectClass, PlayerData, VehicleData}
object SeatConverter {
@@ -16,14 +16,14 @@ object SeatConverter {
)
}
- //TODO do not use for now; causes seat access permission issues with many passengers; may not mesh with workflows; GUID requirements
+ //TODO do not use for now; causes mount access permission issues with many passengers; may not mesh with workflows; GUID requirements
def MakeSeats(seats: Map[Int, Seat], initialOffset: Long): List[InventoryItemData.InventoryItem] = {
var offset = initialOffset
seats
.filter({ case (_, seat) => seat.isOccupied })
.map({
- case (index, seat) =>
- val player = seat.Occupant.get
+ case (index: Int, seat: Seat) =>
+ val player = seat.occupant.get
val entry = InventoryItemData(ObjectClass.avatar, player.GUID, index, SeatConverter.MakeSeat(player, offset))
offset += entry.bitsize
entry
diff --git a/src/main/scala/net/psforever/objects/definition/converter/VariantVehicleConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/VariantVehicleConverter.scala
index f30cafca9..ed972465f 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/VariantVehicleConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/VariantVehicleConverter.scala
@@ -14,7 +14,7 @@ class VariantVehicleConverter extends VehicleConverter {
*/
Some(
VariantVehicleData(
- if (obj.Definition.CanFly && obj.Flying) 7 else 0
+ if (obj.Definition.CanFly && obj.isFlying) 7 else 0
)
)
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
index 303fd4bf6..596bf5c8e 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala
@@ -76,7 +76,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
private def MakeDriverSeat(obj: Vehicle): List[InventoryItemData.InventoryItem] = {
val offset: Long = VehicleData.InitialStreamLengthToSeatEntries(obj.Velocity.nonEmpty, SpecificFormatModifier)
- obj.Seats(0).Occupant match {
+ obj.Seats(0).occupant match {
case Some(player) =>
List(InventoryItemData(ObjectClass.avatar, player.GUID, 0, SeatConverter.MakeSeat(player, offset)))
case None =>
diff --git a/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala b/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala
index 0bf64cf7b..ce74af5c3 100644
--- a/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala
+++ b/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala
@@ -13,7 +13,7 @@ object EquipmentSize extends Enumeration {
VehicleWeapon, //vehicle-mounted weapons
BaseTurretWeapon, //common phalanx cannons, and cavern turrets
BFRArmWeapon, //duel arm weapons for bfr
- BFRGunnerWeapon, //gunner seat for bfr
+ BFRGunnerWeapon, //gunner mount for bfr
Inventory //reserved
= Value
diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala
index 1aeb52644..6a028144c 100644
--- a/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala
@@ -118,10 +118,10 @@ trait AggravatedBehavior {
): AggravatedBehavior.Entry = {
val cause = data.cause
val aggravatedDamageInfo = DamageInteraction(
- AggravatedDamage.burning(cause.resolution),
target,
+ data.hitPos,
cause,
- data.hitPos
+ AggravatedDamage.burning(cause.resolution)
)
val entry = AggravatedBehavior.Entry(id, effect, retime, aggravatedDamageInfo, powerOffset)
entryIdToEntry += id -> entry
diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala
index 3e5150ce5..1ad0c43d0 100644
--- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala
@@ -36,10 +36,7 @@ object DamageableMountable {
): Unit = {
val zone = target.Zone
val events = zone.AvatarEvents
- val occupants = target.Seats.values.collect {
- case seat if seat.isOccupied && seat.Occupant.get.isAlive =>
- seat.Occupant.get
- }
+ val occupants = target.Seats.values.toSeq.flatMap { seat => seat.occupants.filter(_.isAlive) }
((cause.adversarial match {
case Some(adversarial) => Some(adversarial.attacker)
case None => None
@@ -80,10 +77,10 @@ object DamageableMountable {
val interaction = cause.interaction
target.Seats.values
.filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
+ seat.isOccupied && seat.occupant.get.isAlive
})
.foreach(seat => {
- val tplayer = seat.Occupant.get
+ val tplayer = seat.occupant.get
//tplayer.History(cause)
tplayer.Actor ! Player.Die(
DamageInteraction(interaction.resolution, SourceEntry(tplayer), interaction.cause, interaction.hitPos)
diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
index 694010592..44dfbbc6a 100644
--- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
@@ -145,7 +145,7 @@ trait DamageableVehicle
if (aggravated) {
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(totalDamage, Vector3.Zero))
obj.Seats.values
- .collect { case seat if seat.Occupant.nonEmpty => seat.Occupant.get.Name }
+ .map { case seat if seat.occupant.nonEmpty => seat.occupant.get.Name }
.foreach { channel =>
events ! VehicleServiceMessage(channel, msg)
}
@@ -158,7 +158,7 @@ trait DamageableVehicle
}
//alert cargo occupants to damage source
obj.CargoHolds.values.foreach(hold => {
- hold.Occupant match {
+ hold.occupant match {
case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Damage(cause, totalDamage)
case None => ;
@@ -198,7 +198,7 @@ trait DamageableVehicle
DamageableMountable.DestructionAwareness(obj, cause)
//cargo vehicles die with us
obj.CargoHolds.values.foreach(hold => {
- hold.Occupant match {
+ hold.occupant match {
case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Destruction(cause)
case None => ;
diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala
index 4b8400077..6ba61f483 100644
--- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala
@@ -73,7 +73,7 @@ trait DamageableWeaponTurret
if (aggravated) {
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(damageToHealth, Vector3.Zero))
obj.Seats.values
- .collect { case seat if seat.Occupant.nonEmpty => seat.Occupant.get.Name }
+ .collect { case seat if seat.occupant.nonEmpty => seat.occupant.get.Name }
.foreach { channel =>
events ! VehicleServiceMessage(channel, msg)
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala b/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala
index b10574bf5..e6c34b847 100644
--- a/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala
@@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.doors
import net.psforever.objects.Player
+import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.UseItemMessage
@@ -65,6 +66,14 @@ object Door {
*/
final case class NoEvent() extends Exchange
+ type LockingMechanismLogic = (PlanetSideServerObject, Door) => Boolean
+
+ final case class UpdateMechanism(mechanism: LockingMechanismLogic) extends Exchange
+
+ case object Lock extends Exchange
+
+ case object Unlock extends Exchange
+
/**
* Overloaded constructor.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
@@ -101,12 +110,25 @@ object Door {
* @return the `Door` object
*/
def Constructor(pos: Vector3)(id: Int, context: ActorContext): Door = {
- import akka.actor.Props
import net.psforever.objects.GlobalDefinitions
+ Constructor(pos, GlobalDefinitions.door)(id, context)
+ }
- val obj = Door(GlobalDefinitions.door)
+ /**
+ * Instantiate and configure a `Door` object that has knowledge of both its position and outwards-facing direction.
+ * The assumption is that this door will be paired with an IFF Lock, thus, has conditions for opening.
+ * @param pos the position of the door
+ * @param ddef the definition for this specific type of door
+ * @param id the unique id that will be assigned to this entity
+ * @param context a context to allow the object to properly set up `ActorSystem` functionality
+ * @return the `Door` object
+ */
+ def Constructor(pos: Vector3, ddef: DoorDefinition)(id: Int, context: ActorContext): Door = {
+ import akka.actor.Props
+
+ val obj = Door(ddef)
obj.Position = pos
- obj.Actor = context.actorOf(Props(classOf[DoorControl], obj), s"${GlobalDefinitions.door.Name}_$id")
+ obj.Actor = context.actorOf(Props(classOf[DoorControl], obj), s"${ddef.Name}_$id")
obj
}
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala b/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala
index 10fa58b8a..a13c1b557 100644
--- a/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/doors/DoorControl.scala
@@ -2,13 +2,12 @@
package net.psforever.objects.serverobject.doors
import net.psforever.objects.Player
-import net.psforever.objects.serverobject.CommonMessages
+import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.locks.IFFLock
-import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl}
+import net.psforever.objects.serverobject.structures.PoweredAmenityControl
import net.psforever.services.Service
import net.psforever.services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse}
-import net.psforever.types.{PlanetSideEmpire, Vector3}
/**
* An `Actor` that handles messages being dispatched to a specific `Door`.
@@ -18,44 +17,44 @@ class DoorControl(door: Door)
extends PoweredAmenityControl
with FactionAffinityBehavior.Check {
def FactionObject: FactionAffinity = door
+ var isLocked: Boolean = false
+ var lockingMechanism: Door.LockingMechanismLogic = DoorControl.alwaysOpen
val commonBehavior: Receive = checkBehavior
+ .orElse {
+ case Door.Lock =>
+ isLocked = true
+ if (door.isOpen) {
+ val zone = door.Zone
+ door.Open = None
+ zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.DoorSlamsShut(door))
+ }
+
+ case Door.Unlock =>
+ isLocked = false
+
+ case Door.UpdateMechanism(logic) =>
+ lockingMechanism = logic
+ }
def poweredStateLogic: Receive =
commonBehavior
.orElse {
case CommonMessages.Use(player, _) =>
- val zone = door.Zone
- val doorGUID = door.GUID
- if (
- player.Faction == door.Faction || (zone.GUID(zone.map.doorToLock.getOrElse(doorGUID.guid, 0)) match {
- case Some(lock: IFFLock) =>
- val owner = lock.Owner.asInstanceOf[Building]
- val playerIsOnInside = Vector3.ScalarProjection(lock.Outwards, player.Position - door.Position) < 0f
- /*
- If an IFF lock exists and
- the IFF lock faction doesn't match the current player and
- one of the following conditions are met:
- 1. player is on the inside of the door (determined by the lock orientation)
- 2. lock is hacked
- 3. facility capture terminal has been hacked
- 4. base is neutral
- ... open the door.
- */
- playerIsOnInside || lock.HackedBy.isDefined || owner.CaptureTerminalIsHacked || lock.Faction == PlanetSideEmpire.NEUTRAL
- case _ => true // no linked IFF lock, just try open the door
- })
- ) {
+ if (lockingMechanism(player, door) && !isLocked) {
openDoor(player)
}
+ case IFFLock.DoorOpenResponse(target: Player) if !isLocked =>
+ openDoor(target)
+
case _ => ;
}
def unpoweredStateLogic: Receive = {
commonBehavior
.orElse {
- case CommonMessages.Use(player, _) =>
+ case CommonMessages.Use(player, _) if !isLocked =>
//without power, the door opens freely
openDoor(player)
@@ -88,3 +87,7 @@ class DoorControl(door: Door)
override def powerTurnOnCallback() : Unit = { }
}
+
+object DoorControl {
+ def alwaysOpen(obj: PlanetSideServerObject, door: Door): Boolean = true
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/environment/PieceOfEnvironment.scala b/src/main/scala/net/psforever/objects/serverobject/environment/PieceOfEnvironment.scala
index 105acb1ee..aa9d3274a 100644
--- a/src/main/scala/net/psforever/objects/serverobject/environment/PieceOfEnvironment.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/environment/PieceOfEnvironment.scala
@@ -2,9 +2,9 @@
package net.psforever.objects.serverobject.environment
import enumeratum.{Enum, EnumEntry}
-import net.psforever.objects.PlanetSideGameObject
+import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.objects.vital.Vitality
-import net.psforever.types.Vector3
+import net.psforever.types.{PlanetSideGUID, Vector3}
/**
* The representation of a feature of the game world that is not a formal game object,
@@ -76,6 +76,17 @@ object EnvironmentAttribute extends Enum[EnvironmentTrait] {
}
}
}
+
+ case object GantryDenialField
+ extends EnvironmentTrait {
+ /** only interact with living player characters */
+ def canInteractWith(obj: PlanetSideGameObject): Boolean = {
+ obj match {
+ case p: Player => p.isAlive
+ case _ => false
+ }
+ }
+ }
}
/**
@@ -123,6 +134,14 @@ object Pool {
Pool(attribute, DeepSquare(altitude, north, east, south, west))
}
+final case class GantryDenialField(
+ obbasemesh: PlanetSideGUID,
+ mountPoint: Int,
+ collision: EnvironmentCollision
+ ) extends PieceOfEnvironment {
+ def attribute = EnvironmentAttribute.GantryDenialField
+}
+
object PieceOfEnvironment {
/**
* Did the test point move into or leave the bounds of the represented environment since its previous test?
diff --git a/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala b/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala
index ab13ad787..12953d835 100644
--- a/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/locks/IFFLock.scala
@@ -1,6 +1,9 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.locks
+import akka.actor.ActorRef
+import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.TriggeredSound
@@ -48,6 +51,14 @@ class IFFLock(private val idef: IFFLockDefinition) extends Amenity with Hackable
}
object IFFLock {
+ final case class DoorOpenRequest(requestee: PlanetSideServerObject, door: Door, replyTo: ActorRef)
+
+ final case class DoorOpenResponse(requestee: PlanetSideServerObject)
+
+ def testLock(lock: IFFLock)(target: PlanetSideServerObject, door: Door): Boolean = {
+ lock.Actor ! IFFLock.DoorOpenRequest(target, door, door.Actor)
+ false
+ }
/**
* Overloaded constructor.
diff --git a/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala b/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala
index 59d665ded..9d0f79024 100644
--- a/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala
@@ -6,6 +6,8 @@ import net.psforever.objects.{GlobalDefinitions, SimpleItem}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior}
+import net.psforever.objects.serverobject.structures.Building
+import net.psforever.types.{PlanetSideEmpire, Vector3}
/**
* An `Actor` that handles messages being dispatched to a specific `IFFLock`.
@@ -44,6 +46,27 @@ class IFFLockControl(lock: IFFLock)
log.warn(s"Player - Faction=${player.Faction}")
}
+ case IFFLock.DoorOpenRequest(target, door, replyTo) =>
+ val owner = lock.Owner.asInstanceOf[Building]
+ /*
+ If one of the following conditions are met:
+ 1. target and door have same faction affinity
+ 2. lock or lock owner is neutral
+ 3. lock is hacked
+ 4. facility capture terminal (owner is a building) has been hacked
+ 5. requestee is on the inside of the door (determined by the lock orientation)
+ ... open the door.
+ */
+ if (
+ lock.Faction == target.Faction ||
+ lock.Faction == PlanetSideEmpire.NEUTRAL || owner.Faction == PlanetSideEmpire.NEUTRAL ||
+ lock.HackedBy.isDefined ||
+ owner.CaptureTerminalIsHacked ||
+ Vector3.ScalarProjection(lock.Outwards, target.Position - door.Position) < 0f
+ ) {
+ replyTo ! IFFLock.DoorOpenResponse(target)
+ }
+
case _ => ; //no default message
}
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/MountRestriction.scala b/src/main/scala/net/psforever/objects/serverobject/mount/MountRestriction.scala
new file mode 100644
index 000000000..be674fcb0
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/MountRestriction.scala
@@ -0,0 +1,45 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.serverobject.mount
+
+import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
+import net.psforever.types.ExoSuitType
+
+trait MountRestriction[A] {
+ def test(target: A): Boolean
+}
+
+case object MaxOnly extends MountRestriction[Player] {
+ def test(target: Player): Boolean = target.ExoSuit == ExoSuitType.MAX
+}
+
+case object NoMax extends MountRestriction[Player] {
+ def test(target: Player): Boolean = target.ExoSuit != ExoSuitType.MAX
+}
+
+case object NoReinforcedOrMax extends MountRestriction[Player] {
+ def test(target: Player): Boolean = target.ExoSuit != ExoSuitType.Reinforced && target.ExoSuit != ExoSuitType.MAX
+}
+
+case object Unrestricted extends MountRestriction[Player] {
+ def test(target: Player): Boolean = true
+}
+
+case object SmallCargo extends MountRestriction[Vehicle] {
+ def test(target: Vehicle): Boolean = {
+ target.Definition == GlobalDefinitions.ant ||
+ target.Definition == GlobalDefinitions.quadassault ||
+ target.Definition == GlobalDefinitions.quadstealth ||
+ target.Definition == GlobalDefinitions.fury ||
+ target.Definition == GlobalDefinitions.switchblade ||
+ target.Definition == GlobalDefinitions.two_man_assault_buggy ||
+ target.Definition == GlobalDefinitions.skyguard ||
+ target.Definition == GlobalDefinitions.twomanheavybuggy ||
+ target.Definition == GlobalDefinitions.twomanhoverbuggy ||
+ target.Definition == GlobalDefinitions.threemanheavybuggy ||
+ target.Definition == GlobalDefinitions.lightning
+ }
+}
+
+case object LargeCargo extends MountRestriction[Vehicle] {
+ def test(target : Vehicle) : Boolean = !target.Definition.CanFly
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala b/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala
index abb6e7912..16211210e 100644
--- a/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala
@@ -3,7 +3,8 @@ package net.psforever.objects.serverobject.mount
import akka.actor.ActorRef
import net.psforever.objects.Player
-import net.psforever.objects.vehicles.Seat
+
+import scala.annotation.tailrec
/**
* A `Trait` common to all game objects that permit players to
@@ -12,38 +13,63 @@ import net.psforever.objects.vehicles.Seat
* @see `Seat`
*/
trait Mountable {
+ protected var seats: Map[Int, Seat] = Map.empty
/**
- * Retrieve a mapping of each seat from its internal index.
- * @return the mapping of index to seat
+ * Retrieve a mapping of each mount from its internal index.
+ * @return the mapping of index to mount
*/
- def Seats: Map[Int, Seat]
+ def Seats: Map[Int, Seat] = seats
/**
- * Given a seat's index position, retrieve the internal `Seat` object.
- * @return the specific seat
+ * Given a mount's index position, retrieve the internal `Seat` object.
+ * @return the specific mount
*/
- def Seat(seatNum: Int): Option[Seat]
+ def Seat(seatNumber: Int): Option[Seat] = {
+ if (seatNumber >= 0 && seatNumber < seats.size) {
+ seats.get(seatNumber)
+ } else {
+ None
+ }
+ }
/**
- * Retrieve a mapping of each seat from its mount point index.
- * @return the mapping of mount point to seat
+ * Retrieve a mapping of each mount from its mount point index.
+ * @return the mapping of mount point to mount
*/
- def MountPoints: Map[Int, Int]
+ def MountPoints: Map[Int, MountInfo] = Definition.MountPoints.toMap
/**
- * Given a mount point index, return the associated seat index.
- * @param mount the mount point
- * @return the seat index
+ * Given a mount point index, return the associated mount index.
+ * @param mountPoint the mount point
+ * @return the mount index
*/
- def GetSeatFromMountPoint(mount: Int): Option[Int]
+ def GetSeatFromMountPoint(mountPoint: Int): Option[Int] = {
+ MountPoints.get(mountPoint) match {
+ case Some(mp) => Some(mp.seatIndex)
+ case _ => None
+ }
+ }
/**
* Given a player, determine if that player is seated.
* @param user the player
- * @return the seat index
+ * @return the mount index
*/
- def PassengerInSeat(user: Player): Option[Int]
+ def PassengerInSeat(user: Player): Option[Int] = recursivePassengerInSeat(seats.iterator, user)
+
+ @tailrec private def recursivePassengerInSeat(iter: Iterator[(Int, Seat)], player: Player): Option[Int] = {
+ if (!iter.hasNext) {
+ None
+ } else {
+ val (seatNumber, seat) = iter.next()
+ if (seat.occupant.contains(player)) {
+ Some(seatNumber)
+ } else {
+ recursivePassengerInSeat(iter, player)
+ }
+ }
+ }
/**
* A reference to an `Actor` that governs the logic of the object to accept `Mountable` messages.
@@ -53,6 +79,8 @@ trait Mountable {
* @return the internal `ActorRef`
*/
def Actor: ActorRef //TODO can we enforce this desired association to MountableControl?
+
+ def Definition: MountableDefinition
}
object Mountable {
@@ -60,10 +88,15 @@ object Mountable {
/**
* Message used by the player to indicate the desire to board a `Mountable` object.
* @param player the player who sent this request message
+ * @param mount_point the mount index
+ */
+ final case class TryMount(player: Player, mount_point: Int)
+
+ /**
+ * Message used by the player to indicate the desire to escape a `Mountable` object.
+ * @param player the player who sent this request message
* @param seat_num the seat index
*/
- final case class TryMount(player: Player, seat_num: Int)
-
final case class TryDismount(player: Player, seat_num: Int)
/**
@@ -82,17 +115,17 @@ object Mountable {
* Message sent in response to the player succeeding to access a `Mountable` object.
* The player should be seated at the given index.
* @param obj the `Mountable` object
- * @param seat_num the seat index
+ * @param mount_point the mount index
*/
- final case class CanMount(obj: Mountable, seat_num: Int) extends Exchange
+ final case class CanMount(obj: Mountable, seat_number: Int, mount_point: Int) extends Exchange
/**
* Message sent in response to the player failing to access a `Mountable` object.
* The player would have been be seated at the given index.
* @param obj the `Mountable` object
- * @param seat_num the seat index
+ * @param mount_point the mount index
*/
- final case class CanNotMount(obj: Mountable, seat_num: Int) extends Exchange
+ final case class CanNotMount(obj: Mountable, mount_point: Int) extends Exchange
/**
* Message sent in response to the player succeeding to disembark a `Mountable` object.
@@ -100,7 +133,7 @@ object Mountable {
* @param obj the `Mountable` object
* @param seat_num the seat index
*/
- final case class CanDismount(obj: Mountable, seat_num: Int) extends Exchange
+ final case class CanDismount(obj: Mountable, seat_num: Int, mount_point: Int) extends Exchange
/**
* Message sent in response to the player failing to disembark a `Mountable` object.
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
index f71d1f700..9d30a9990 100644
--- a/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
@@ -2,15 +2,33 @@
package net.psforever.objects.serverobject.mount
import akka.actor.Actor
-import net.psforever.objects.{Player, Vehicle}
-import net.psforever.objects.entity.{Identifiable, WorldEntity}
+import net.psforever.objects.Player
+import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.serverobject.PlanetSideServerObject
-import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.hackable.Hackable
-import net.psforever.objects.serverobject.turret.WeaponTurret
-import net.psforever.types.DriveState
-object MountableBehavior {
+import scala.collection.mutable
+
+trait MountableBehavior {
+ _ : Actor =>
+ def MountableObject: PlanetSideServerObject with Mountable
+
+ /** retain the mount point that was used by this occupant to mount */
+ val usedMountPoint: mutable.HashMap[String, Int] = mutable.HashMap()
+
+ def getUsedMountPoint(playerName: String, seatNumber: Int): Int = {
+ usedMountPoint
+ .remove(playerName)
+ .getOrElse {
+ MountableObject
+ .Definition
+ .MountPoints
+ .find { case (_, mp) => mp.seatIndex == seatNumber } match {
+ case Some((mount, _)) => mount
+ case None => -1
+ }
+ }
+ }
/**
* The logic governing `Mountable` objects that use the `TryMount` message.
@@ -18,54 +36,40 @@ object MountableBehavior {
* @see `Seat`
* @see `Mountable`
*/
- trait Mount {
- _: Actor =>
- def MountableObject: PlanetSideServerObject with Mountable with FactionAffinity
-
- val mountBehavior: Receive = {
- case Mountable.TryMount(user, seat_num) =>
- val obj = MountableObject
- if (MountTest(MountableObject, seat_num, user)) {
+ val mountBehavior: Receive = {
+ case Mountable.TryMount(user, mount_point) =>
+ val obj = MountableObject
+ obj.GetSeatFromMountPoint(mount_point) match {
+ case Some(seatNum) if mountTest(obj, seatNum, user) && tryMount(obj, seatNum, user) =>
user.VehicleSeated = obj.GUID
- sender() ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num))
- } else {
- sender() ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
- }
- }
-
- protected def MountTest(obj: PlanetSideServerObject with Mountable, seatNumber: Int, player: Player): Boolean = {
- (player.Faction == obj.Faction ||
- (obj match {
- case o: Hackable => o.HackedBy.isDefined
- case _ => false
- })) &&
- !obj.Destroyed &&
- (obj.Seats.get(seatNumber) match {
- case Some(seat) => (seat.Occupant = player).contains(player)
- case _ => false
- })
- }
+ usedMountPoint.put(user.Name, mount_point)
+ sender() ! Mountable.MountMessages(user, Mountable.CanMount(obj, seatNum, mount_point))
+ case _ =>
+ sender() ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, mount_point))
+ }
}
- trait TurretMount extends Mount {
- _: Actor =>
+ protected def mountTest(
+ obj: PlanetSideServerObject with Mountable,
+ seatNumber: Int,
+ player: Player
+ ): Boolean = {
+ (player.Faction == obj.Faction ||
+ (obj match {
+ case o : Hackable => o.HackedBy.isDefined
+ case _ => false
+ })) &&
+ !obj.Destroyed
+ }
- override protected def MountTest(
- obj: PlanetSideServerObject with Mountable,
- seatNumber: Int,
- player: Player
- ): Boolean = {
- obj match {
- case wep: WeaponTurret =>
- (!wep.Definition.FactionLocked || player.Faction == obj.Faction) &&
- !obj.Destroyed &&
- (obj.Seats.get(seatNumber) match {
- case Some(seat) => (seat.Occupant = player).contains(player)
- case _ => false
- })
- case _ =>
- super.MountTest(obj, seatNumber, player)
- }
+ private def tryMount(
+ obj: PlanetSideServerObject with Mountable,
+ seatNumber: Int,
+ player: Player
+ ): Boolean = {
+ obj.Seat(seatNumber) match {
+ case Some(seat) => seat.mount(player).contains(player)
+ case _ => false
}
}
@@ -75,29 +79,41 @@ object MountableBehavior {
* @see `Seat`
* @see `Mountable`
*/
- trait Dismount {
- this: Actor =>
+ val dismountBehavior: Receive = {
+ case Mountable.TryDismount(user, seat_number) =>
+ val obj = MountableObject
+ if (dismountTest(obj, seat_number, user) && tryDismount(obj, seat_number, user)) {
+ user.VehicleSeated = None
+ sender() ! Mountable.MountMessages(
+ user,
+ Mountable.CanDismount(obj, seat_number, getUsedMountPoint(user.Name, seat_number))
+ )
+ }
+ else {
+ sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(obj, seat_number))
+ }
+ }
- def MountableObject: Mountable with Identifiable with WorldEntity with FactionAffinity
+ protected def dismountTest(
+ obj: Mountable with WorldEntity,
+ seatNumber: Int,
+ user: Player
+ ): Boolean = {
+ obj.PassengerInSeat(user).contains(seatNumber) &&
+ (obj.Seats.get(seatNumber) match {
+ case Some(seat) => seat.bailable || !obj.isMoving(test = 1)
+ case _ => false
+ })
+ }
- val dismountBehavior: Receive = {
- case Mountable.TryDismount(user, seat_num) =>
- val obj = MountableObject
- obj.Seat(seat_num) match {
- case Some(seat) =>
- if (
- seat.Bailable || !obj.isMoving(1) || (obj
- .isInstanceOf[Vehicle] && obj.asInstanceOf[Vehicle].DeploymentState == DriveState.Deployed)
- ) {
- seat.Occupant = None
- user.VehicleSeated = None
- sender() ! Mountable.MountMessages(user, Mountable.CanDismount(obj, seat_num))
- } else {
- sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(obj, seat_num))
- }
- case None =>
- sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(obj, seat_num))
- }
+ private def tryDismount(
+ obj: Mountable,
+ seatNumber: Int,
+ user: Player
+ ): Boolean = {
+ obj.Seats.get(seatNumber) match {
+ case Some(seat) => seat.unmount(user).isEmpty
+ case _ => false
}
}
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/MountableDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/mount/MountableDefinition.scala
new file mode 100644
index 000000000..e72641f34
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/MountableDefinition.scala
@@ -0,0 +1,23 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.serverobject.mount
+
+import net.psforever.types.Vector3
+
+import scala.collection.mutable
+
+final case class MountInfo(seatIndex: Int, positionOffset: Vector3)
+
+object MountInfo {
+ def apply(seatIndex: Int): MountInfo = MountInfo(seatIndex, Vector3.Zero)
+}
+
+trait MountableDefinition {
+ /* key - mount index, value - mount object */
+ private val seats: mutable.HashMap[Int, SeatDefinition] = mutable.HashMap[Int, SeatDefinition]()
+ /* key - entry point index, value - mount index */
+ private val mountPoints: mutable.HashMap[Int, MountInfo] = mutable.HashMap()
+
+ def Seats: mutable.HashMap[Int, SeatDefinition] = seats
+
+ def MountPoints: mutable.HashMap[Int, MountInfo] = mountPoints
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/MountableSpace.scala b/src/main/scala/net/psforever/objects/serverobject/mount/MountableSpace.scala
new file mode 100644
index 000000000..25c046ba7
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/MountableSpace.scala
@@ -0,0 +1,104 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.serverobject.mount
+
+trait MountableSpace[A] {
+ private var _occupant: Option[A] = None
+
+ /**
+ * A single mounted entity.
+ * @return one mounted entity at most, or `None`
+ */
+ def occupant: Option[A] = _occupant
+
+ /**
+ * A collection of any mounted entity.
+ * Useful for compiling all seated users using `flatMap`.
+ * @return all mounted entities
+ */
+ def occupants: List[A] = _occupant.toList
+
+ /**
+ * Is anything be seated?
+ * Do not use this method as a test for "availability".
+ */
+ def isOccupied: Boolean = _occupant.nonEmpty
+
+ /**
+ * Can something be mounted?
+ * Use this method as a test for "availability".
+ */
+ def canBeOccupied: Boolean = _occupant.isEmpty
+
+ /**
+ * Is this specific entity currently mounted?
+ */
+ def isOccupiedBy(target: A): Boolean = _occupant.contains(target)
+
+ /**
+ * Is this specific entity allowed to be mounted in this space?
+ * Utiltizes restriction tests, but not "availability" tests.
+ * @see `MountableDefinition[A].restriction`
+ */
+ def canBeOccupiedBy(target: A): Boolean = definition.restriction.test(target)
+
+ /**
+ * Attempt to mount the target entity in this space.
+ */
+ def mount(target: A): Option[A] = mount(Some(target))
+
+ /**
+ * Attempt to mount the target entity in this space.
+ */
+ def mount(target: Option[A]): Option[A] = {
+ target match {
+ case Some(p) if testToMount(p) =>
+ _occupant = target
+ target
+ case _ =>
+ occupant
+ }
+ }
+
+ /**
+ * Tests whether the target is allowed to be mounted.
+ * @see `MountableSpace[A].canBeOccupiedBy(A)`
+ */
+ protected def testToMount(target: A): Boolean = canBeOccupied && canBeOccupiedBy(target)
+
+ /**
+ * Attempt to dismount the target entity from this space.
+ */
+ def unmount(target: A): Option[A] = unmount(Some(target))
+
+ /**
+ * Attempt to dismount the target entity from this space.
+ */
+ def unmount(target: Option[A]): Option[A] = {
+ target match {
+ case Some(p) if testToUnmount(p) =>
+ _occupant = None
+ None
+ case _ =>
+ occupant
+ }
+ }
+
+ /**
+ * Tests whether the target is capable of being unmounted from this place.
+ * @see `MountableSpace[A].isOccupiedBy(A)`
+ */
+ protected def testToUnmount(target: A): Boolean = isOccupiedBy(target)
+
+ /**
+ * Does this mountable space count as being "bailable",
+ * a condition whereupon it can be unmounted under duress?
+ * The conditions of the duress do not matter at the moment;
+ * this is only a test of possibility.
+ */
+ def bailable: Boolean = definition.bailable
+
+ /**
+ * The information that establishes the underlying characteristics of this mountable space.
+ */
+ def definition: MountableSpaceDefinition[A]
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/MountableSpaceDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/mount/MountableSpaceDefinition.scala
new file mode 100644
index 000000000..2b199d0f2
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/MountableSpaceDefinition.scala
@@ -0,0 +1,13 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.serverobject.mount
+
+import net.psforever.objects.definition.BasicDefinition
+
+trait MountableSpaceDefinition[A]
+ extends BasicDefinition {
+ def occupancy: Int
+
+ def restriction: MountRestriction[A]
+
+ def bailable: Boolean
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/Seat.scala b/src/main/scala/net/psforever/objects/serverobject/mount/Seat.scala
new file mode 100644
index 000000000..23b5239b5
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/Seat.scala
@@ -0,0 +1,10 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.serverobject.mount
+
+import net.psforever.objects.Player
+
+class Seat(private val sdef: SeatDefinition) extends MountableSpace[Player] {
+ override protected def testToMount(target: Player): Boolean = target.VehicleSeated.isEmpty && super.testToMount(target)
+
+ def definition: SeatDefinition = sdef
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/SeatDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/mount/SeatDefinition.scala
new file mode 100644
index 000000000..dfcd9ed84
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/SeatDefinition.scala
@@ -0,0 +1,13 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.serverobject.mount
+
+import net.psforever.objects.Player
+
+class SeatDefinition extends MountableSpaceDefinition[Player] {
+ Name = "mount"
+ var occupancy: Int = 1
+
+ var restriction: MountRestriction[Player] = NoMax
+
+ var bailable: Boolean = false
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
index 5b5ac1737..8d3dfdb9b 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
@@ -87,7 +87,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
/*
When the vehicle is spawned and added to the pad, it will "occupy" the pad and block it from further action.
- Normally, the player who wanted to spawn the vehicle will be automatically put into the driver seat.
+ Normally, the player who wanted to spawn the vehicle will be automatically put into the driver mount.
If this is blocked, the vehicle will idle on the pad and must be moved far enough away from the point of origin.
During this time, a periodic message about the spawn pad being blocked
will be broadcast to all current customers in the order queue.
@@ -220,8 +220,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
*/
def BlockedReminder(blockedOrder: VehicleSpawnControl.Order, recipients: Seq[VehicleSpawnControl.Order]): Unit = {
val user = blockedOrder.vehicle
- .Seats(0)
- .Occupant
+ .Seats(0).occupant
.orElse(pad.Zone.GUID(blockedOrder.vehicle.Owner))
.orElse(pad.Zone.GUID(blockedOrder.DriverGUID))
val relevantRecipients = user match {
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala
index 61db390dc..f3c9ceeae 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPad.scala
@@ -75,7 +75,7 @@ object VehicleSpawnPad {
final case class ResetSpawnPad(pad: VehicleSpawnPad)
/**
- * Message that acts as callback to the driver that the process of sitting in the driver seat will be initiated soon.
+ * Message that acts as callback to the driver that the process of sitting in the driver mount will be initiated soon.
* This information should only be communicated to the driver's client only.
* @param driver_name the person who will drive the vehicle
* @param vehicle the vehicle being spawned
@@ -84,7 +84,7 @@ object VehicleSpawnPad {
final case class StartPlayerSeatedInVehicle(driver_name: String, vehicle: Vehicle, pad: VehicleSpawnPad)
/**
- * Message that acts as callback to the driver that the process of sitting in the driver seat should be finished.
+ * Message that acts as callback to the driver that the process of sitting in the driver mount should be finished.
* This information should only be communicated to the driver's client only.
* @param driver_name the person who will drive the vehicle
* @param vehicle the vehicle being spawned
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala
index 5b3c39b0b..2667d785b 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlConcealPlayer.scala
@@ -14,7 +14,7 @@ import scala.concurrent.duration._
*
* This object is the first link in the process chain that spawns the ordered vehicle.
* It is devoted to causing the prospective driver to become hidden during the first part of the process
- * with the goal of appearing to be "teleported" into the driver seat.
+ * with the goal of appearing to be "teleported" into the driver mount.
* It has failure cases should the driver be in an incorrect state.
* @param pad the `VehicleSpawnPad` object being governed
*/
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala
index 98e24cd04..d589bf0df 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlRailJack.scala
@@ -23,7 +23,7 @@ class VehicleSpawnControlRailJack(pad: VehicleSpawnPad) extends VehicleSpawnCont
def LogId = "-lifter"
val seatDriver =
- context.actorOf(Props(classOf[VehicleSpawnControlSeatDriver], pad), s"${context.parent.path.name}-seat")
+ context.actorOf(Props(classOf[VehicleSpawnControlSeatDriver], pad), s"${context.parent.path.name}-mount")
def receive: Receive = {
case order @ VehicleSpawnControl.Order(_, vehicle) =>
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala
index 699165304..4235cf554 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala
@@ -13,11 +13,11 @@ import scala.concurrent.duration._
* The basic `VehicleSpawnControl` is the root of a simple tree of "spawn control" objects that chain to each other.
* Each object performs on (or more than one related) actions upon the vehicle order that was submitted.
*
- * This object forces the prospective driver to take the driver seat.
+ * This object forces the prospective driver to take the driver mount.
* Multiple separate but sequentially significant steps occur within the scope of this object.
* First, this step waits for the vehicle to be completely ready to accept the driver.
- * Second, this step triggers the player to actually be moved into the driver seat.
- * Finally, this step waits until the driver is properly in the driver seat.
+ * Second, this step triggers the player to actually be moved into the driver mount.
+ * Finally, this step waits until the driver is properly in the driver mount.
* It has failure cases should the driver or the vehicle be in an incorrect state.
* @see `ZonePopulationActor`
* @param pad the `VehicleSpawnPad` object being governed
diff --git a/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttle.scala b/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttle.scala
new file mode 100644
index 000000000..3725fbb53
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttle.scala
@@ -0,0 +1,85 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.serverobject.shuttle
+
+import net.psforever.objects.Vehicle
+import net.psforever.objects.definition.VehicleDefinition
+import net.psforever.objects.serverobject.mount.Seat
+import net.psforever.objects.vehicles.AccessPermissionGroup
+
+/**
+ * The high altitude rapid transport (HART) orbital shuttle is a special vehicle
+ * that is paired with a formal building `Amenity` called the orbital shuttle pad (`obbasemesh`)
+ * and is only found in the HART buildings (`orbital_building_`{faction}) of a given faction's sanctuary zone.
+ *
+ * It has no pilot and can not be piloted.
+ * Unlike other vehicles, it has the potential for a very sizeable passenger capacity.
+ * Despite this, it is intended to start with a single mount.
+ * That one mount should contain the information needed to create a given number of spontaneous passenger mount points.
+ * Whenever a valid user would try to find a mount, and there are no mounts available,
+ * and the total number of created mounts has not yet exceeded the limits set by the original mount's designation,
+ * then a completely new mount can be created and the user attached.
+ * All spontaneous mounts have the same properties as the original mount.
+ * @param sdef the vehicle's definition entry
+ */
+class OrbitalShuttle(sdef: VehicleDefinition) extends Vehicle(sdef) {
+ /**
+ * Either locate a place for a passenger to mount,
+ * or designate a spontaneous mount point to handle a new passenger.
+ * The only time there is no more space is when the no new spontaneous seats can be counted.
+ * @param mountPoint the mount point
+ * @return the mount index
+ */
+ override def GetSeatFromMountPoint(mountPoint: Int): Option[Int] = {
+ super.GetSeatFromMountPoint(mountPoint) match {
+ case Some(0) =>
+ seats.find { case (_, seat) => !seat.isOccupied } match {
+ case Some((seatNumber, _)) => Some(seatNumber)
+ case None if seats.size < seats(0).definition.occupancy => Some(seats.size)
+ case _ => None
+ }
+ case _ =>
+ None
+ }
+ }
+
+ /**
+ * Either locate a place for a passenger to mount,
+ * or create a spontaneous mount point to handle the new passenger.
+ * The only time there is no more space is when the no new spontaneous seats can be created.
+ * This new seat becomes "real" and will continue to exist after being dismounted.
+ * @param seatNumber the index of a mount point
+ * @return the specific mount
+ */
+ override def Seat(seatNumber: Int): Option[Seat] = {
+ val sdef = seats(0).definition
+ super.Seat(seatNumber) match {
+ case out @ Some(_) =>
+ out
+ case None if seatNumber == seats.size && seatNumber < sdef.occupancy =>
+ val newSeat = new Seat(sdef)
+ seats = seats ++ Map(seatNumber -> newSeat)
+ Some(newSeat)
+ case _ =>
+ None
+ }
+ }
+
+ /**
+ * All players mounted in the shuttle are passengers only. No driver. No gunners.
+ * Even if it does not exist yet, as long as it has the potential to be created,
+ * discuss the next seat that would be created as if it already exists.
+ * @param seatNumber the index of a mount point
+ * @return `Passenger` permissions
+ */
+ override def SeatPermissionGroup(seatNumber : Int) : Option[AccessPermissionGroup.Value] = {
+ Seats.get(seatNumber) match {
+ case Some(_) =>
+ Some(AccessPermissionGroup.Passenger)
+ case None
+ if seats.size == seatNumber && Seats.values.exists { _.definition.occupancy > seats.size } =>
+ Some(AccessPermissionGroup.Passenger)
+ case _ =>
+ None
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttlePad.scala b/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttlePad.scala
new file mode 100644
index 000000000..79d4e5e8f
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttlePad.scala
@@ -0,0 +1,71 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.serverobject.shuttle
+
+import akka.actor.ActorRef
+import net.psforever.objects.Vehicle
+import net.psforever.objects.serverobject.structures.{Amenity, AmenityDefinition}
+import net.psforever.types.PlanetSideGUID
+
+/**
+ * The orbital shuttle pad which is the primary component of the high altitude rapid transport (HART) system.
+ *
+ * The orbital shuttle pad is a type of flat called an `obbasemesh`.
+ * The shuttle component of the HART casually perches on top of the pad and
+ * adjusts its states to control animation and passenger access.
+ * The shuttle that is visible to the player and flies in and out of the zone is actually a hologram
+ * of the real shuttle that is an invisible, intangible vehicle
+ * forever stationary on top of the building.
+ * @param spDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
+ */
+class OrbitalShuttlePad(spDef: AmenityDefinition) extends Amenity {
+ private var _shuttle: Option[PlanetSideGUID] = None
+
+ def shuttle: Option[PlanetSideGUID] = _shuttle
+
+ def shuttle_=(orbitalShuttle: Vehicle): Option[PlanetSideGUID] = {
+ _shuttle = _shuttle.orElse(Some(orbitalShuttle.GUID))
+ _shuttle
+ }
+
+ def Definition: AmenityDefinition = spDef
+}
+
+object OrbitalShuttlePad {
+ final case class GetShuttle(giveTo: ActorRef)
+
+ final case class GiveShuttle(shuttle: Vehicle)
+
+ /**
+ * Overloaded constructor.
+ * @param spDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
+ * @return an `OrbitalShuttlePad` object
+ */
+ def apply(spDef: AmenityDefinition): OrbitalShuttlePad = {
+ new OrbitalShuttlePad(spDef)
+ }
+
+ import akka.actor.ActorContext
+ import net.psforever.types.Vector3
+
+ /**
+ * Instantiate and configure an `OrbitalShuttlePad` object
+ * @param pdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
+ * @param pos the position (used to determine spawn point)
+ * @param orient the orientation (used to indicate spawn direction)
+ * @param id the unique id that will be assigned to this entity
+ * @param context a context to allow the object to properly set up `ActorSystem` functionality
+ * @return the `OrbitalShuttlePad` object
+ */
+ def Constructor(pos: Vector3, pdef: AmenityDefinition, orient: Vector3)(
+ id: Int,
+ context: ActorContext
+ ): OrbitalShuttlePad = {
+ import akka.actor.Props
+
+ val obj = OrbitalShuttlePad(pdef)
+ obj.Position = pos
+ obj.Orientation = orient
+ obj.Actor = context.actorOf(Props(classOf[OrbitalShuttlePadControl], obj), s"${obj.Definition.Name}_$id")
+ obj
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttlePadControl.scala b/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttlePadControl.scala
new file mode 100644
index 000000000..83f218cb4
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/shuttle/OrbitalShuttlePadControl.scala
@@ -0,0 +1,203 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.serverobject.shuttle
+
+import akka.actor.{Actor, ActorRef}
+import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
+import net.psforever.objects.{Player, Vehicle}
+import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.doors.Door
+import net.psforever.objects.zones.Zone
+import net.psforever.packet.game.ChatMsg
+import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
+import net.psforever.services.local.{LocalAction, LocalServiceMessage}
+import net.psforever.services.hart.{HartTimer, HartTimerActions}
+import net.psforever.services.{Service, ServiceManager}
+import net.psforever.types.ChatMessageType
+
+import scala.util.Success
+
+/**
+ * An `Actor` that handles messages being dispatched to a specific `OrbitalShuttlePad`.
+ *
+ * For the purposes of maintaining a close relationship
+ * with the rest of the high altitude rapid transport (HART) system's components,
+ * this control agency also locally creates the vehicle that will the shuttle when it starts up.
+ * The shuttle should be treated like a supporting object to the zone
+ * that exists within the normal vehicle pipeline.
+ * @see `ShuttleState`
+ * @see `ShuttleTimer`
+ * @see `HartService`
+ * @param pad the `OrbitalShuttlePad` object being governed
+ */
+class OrbitalShuttlePadControl(pad: OrbitalShuttlePad) extends Actor {
+ /** the doors that allow would be passengers to access the shuttle boarding gantries
+ * (actually, a hallway with a teleport);
+ * the target doors are of a specific type that flag their purpose - "gr_door_mb_orb"
+ */
+ var managedDoors: List[Door] = Nil
+ var shuttle: Vehicle = _
+
+ def receive: Receive = startUp
+
+ /** the HART system is active and ready to handle state changes */
+ val taxiing: Receive = {
+ case OrbitalShuttlePad.GetShuttle(to) =>
+ to ! OrbitalShuttlePad.GiveShuttle(shuttle)
+
+ case HartTimer.LockDoors =>
+ managedDoors.foreach { door =>
+ door.Actor ! Door.UpdateMechanism(OrbitalShuttlePadControl.lockedWaitingForShuttle)
+ val zone = pad.Zone
+ if(door.isOpen) {
+ zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.DoorSlamsShut(door))
+ }
+ }
+
+ case HartTimer.UnlockDoors =>
+ managedDoors.foreach { _.Actor ! Door.UpdateMechanism(OrbitalShuttlePadControl.shuttleIsBoarding) }
+
+ case HartTimer.ShuttleDocked(forChannel) =>
+ HartTimerActions.ShuttleDocked(pad, shuttle, forChannel)
+
+ case HartTimer.ShuttleFreeFromDock(forChannel) =>
+ HartTimerActions.ShuttleFreeFromDock(pad, shuttle, forChannel)
+
+ case HartTimer.ShuttleStateUpdate(forChannel, state) =>
+ HartTimerActions.ShuttleStateUpdate(pad, shuttle, forChannel, state)
+
+ case _ => ;
+ }
+
+ /** wire the pad and shuttle into a zone-scoped service handler */
+ val shuttleTime: Receive = {
+ case Zone.Vehicle.HasSpawned(_, newShuttle: OrbitalShuttle) =>
+ shuttle = newShuttle
+ pad.shuttle = newShuttle
+ pad.Owner.Amenities = new ShuttleAmenity(newShuttle)
+ ServiceManager.serviceManager ! ServiceManager.Lookup("hart")
+
+ case ServiceManager.LookupResult(_, timer) =>
+ timer ! HartTimer.PairWith(pad.Zone, pad.GUID, shuttle.GUID, self)
+ context.become(taxiing)
+
+ case Zone.Vehicle.CanNotSpawn(zone, _, reason) =>
+ org.log4s
+ .getLogger("OrbitalShuttle")
+ .error(s"shuttle for pad#${pad.Owner.GUID.guid} in zone ${zone.id} did not spawn - $reason")
+ //seal doors
+ managedDoors.foreach { _.Actor ! Door.UpdateMechanism(OrbitalShuttlePadControl.lockedWaitingForShuttle) }
+
+ case msg: HartTimer.Command =>
+ self.forward(msg) //delay?
+
+ case _ => ;
+ }
+
+ /** collect all of the doors that will be controlled by the HART system;
+ * set up the shuttle information based on the pad to which it belongs;
+ * register and add the shuttle as a common vehicle of the said zone
+ */
+ val startUp: Receive = {
+ case Service.Startup() =>
+ import net.psforever.types.Vector3
+ import net.psforever.types.Vector3.DistanceSquared
+ import net.psforever.objects.GlobalDefinitions._
+ val position = pad.Position
+ val zone = pad.Zone
+ //collect managed doors
+ managedDoors = pad.Owner.Amenities
+ .collect { case d: Door if d.Definition == gr_door_mb_orb => d }
+ .sortBy { o => DistanceSquared(position, o.Position) }
+ .take(8)
+ //create shuttle
+ val newShuttle = new OrbitalShuttle(orbital_shuttle)
+ newShuttle.Position = position + Vector3(0, -8.25f, 0).Rz(pad.Orientation.z) //magic offset number
+ newShuttle.Orientation = pad.Orientation
+ newShuttle.Faction = pad.Faction
+ zone.tasks ! OrbitalShuttlePadControl.registerShuttle(zone, newShuttle, self)
+ context.become(shuttleTime)
+
+ case _ => ;
+ }
+}
+
+object OrbitalShuttlePadControl {
+ /**
+ * Register the shuttle as a common vehicle in a zone.
+ * @param zone the zone the shuttle and the pad will occupy
+ * @param shuttle the vehicle that will be the shuttle
+ * @param ref a reference to the control agency for the orbital shuttle pad
+ * @return a `TaskResolver.GiveTask` object
+ */
+ def registerShuttle(zone: Zone, shuttle: Vehicle, ref: ActorRef): TaskResolver.GiveTask = {
+ TaskResolver.GiveTask(
+ new Task() {
+ private val localZone = zone
+ private val localShuttle = shuttle
+ private val localSelf = ref
+
+ override def Description: String = s"register an orbital shuttle"
+
+ override def isComplete : Task.Resolution.Value = if (localShuttle.HasGUID) {
+ Task.Resolution.Success
+ } else {
+ Task.Resolution.Incomplete
+ }
+
+ def Execute(resolver : ActorRef) : Unit = {
+ localZone.Transport.tell(Zone.Vehicle.Spawn(localShuttle), localSelf)
+ resolver ! Success(true)
+ }
+
+ override def onFailure(ex : Throwable) : Unit = {
+ super.onFailure(ex)
+ localSelf ! Zone.Vehicle.CanNotSpawn(localZone, localShuttle, ex.getMessage)
+ }
+ }, List(GUIDTask.RegisterVehicle(shuttle)(zone.GUID))
+ )
+ }
+
+ /**
+ * Logic for door mechanism that allows the shuttle entryway to be opened.
+ * Only opens for users with proper faction affinity.
+ * @param obj what attempted to open the door
+ * @param door the door
+ * @return `true`, if the user is the accepted by the door;
+ * `false`, otherwise
+ */
+ def shuttleIsBoarding(obj: PlanetSideServerObject, door: Door): Boolean = {
+ obj.Faction == door.Faction
+ }
+
+ /**
+ * Logic for door mechanism that keeps select doors shut when the shuttle is not ready for boarding.
+ * A message flashes onscreen to explain this reason.
+ * The message will not flash if the door has no expectation of ever opening for a user.
+ * @see `AvatarAction.SendResponse`
+ * @see `AvatarServiceMessage`
+ * @see `ChatMessageType`
+ * @see `ChatMsg`
+ * @see `Player`
+ * @see `Service`
+ * @see `Zone.AvatarEvents`
+ * @param obj what attempted to open the door
+ * @param door the door
+ * @return `false`, as the door can not be opened in this state
+ */
+ def lockedWaitingForShuttle(obj: PlanetSideServerObject, door: Door): Boolean = {
+ val zone = door.Zone
+ obj match {
+ case p: Player if p.Faction == door.Faction =>
+ zone.AvatarEvents ! AvatarServiceMessage(
+ p.Name,
+ AvatarAction.SendResponse(
+ Service.defaultPlayerGUID,
+ ChatMsg(ChatMessageType.UNK_225, false, "", "@DoorWillOpenWhenShuttleReturns", None)
+ )
+ )
+ p.Name
+ case _ => ;
+ }
+ false
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/shuttle/ShuttleAmenity.scala b/src/main/scala/net/psforever/objects/serverobject/shuttle/ShuttleAmenity.scala
new file mode 100644
index 000000000..e7fdd8fbc
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/serverobject/shuttle/ShuttleAmenity.scala
@@ -0,0 +1,42 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.serverobject.shuttle
+
+import akka.actor.ActorRef
+import net.psforever.objects.serverobject.structures.{Amenity, AmenityDefinition}
+import net.psforever.types.PlanetSideGUID
+
+/**
+ * A pseudo-`Amenity` of the high-altitude rapid transport (HART) building
+ * whose sole purpose is to allow the HART orbital shuttle to be initialized
+ * as if it were a normal `Amenity`-level feature of the building.
+ * This should not be considered an actual game object as defined by the game.
+ * It should resemble the orbital shuttle that it wraps in most important measurable ways.
+ * @see `OrbitalShuttleControl`
+ * @throws `AssertionError` if the vehicle is not a `OrbitalShuttle`
+ * @param shuttle the shuttle
+ */
+class ShuttleAmenity(shuttle: OrbitalShuttle) extends Amenity {
+ override def GUID = shuttle.GUID
+
+ override def GUID_=(guid: PlanetSideGUID) = GUID
+
+ override def DamageModel = shuttle.DamageModel
+
+ override def Actor = shuttle.Actor
+
+ override def Actor_=(control: ActorRef) = Actor
+
+ override def Health = shuttle.Health
+
+ override def Faction = shuttle.Faction
+
+ def Definition = ShuttleAmenity.definition
+}
+
+object ShuttleAmenity {
+ final val definition = new AmenityDefinition(net.psforever.packet.game.objectcreate.ObjectClass.orbital_shuttle) {
+ Name = "orbital_shuttle_fake"
+ Damageable = false
+ Repairable = false
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalAwareBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalAwareBehavior.scala
index 985f122ec..231db45f6 100644
--- a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalAwareBehavior.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminalAwareBehavior.scala
@@ -21,11 +21,12 @@ trait CaptureTerminalAwareBehavior {
if (CaptureTerminalAwareObject.isInstanceOf[Mountable]) {
CaptureTerminalAwareObject.asInstanceOf[Mountable].Seats.filter(x => x._2.isOccupied).foreach(x => {
val (seat_num, seat) = x
+ val user = seat.occupant.get
CaptureTerminalAwareObject.Zone.VehicleEvents ! VehicleServiceMessage(
CaptureTerminalAwareObject.Zone.id,
- VehicleAction.KickPassenger(seat.Occupant.get.GUID, seat_num, true, CaptureTerminalAwareObject.GUID))
-
- seat.Occupant = None
+ VehicleAction.KickPassenger(user.GUID, seat_num, true, CaptureTerminalAwareObject.GUID)
+ )
+ seat.unmount(user)
})
}
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminals.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminals.scala
index 9a37c1a34..7e0dee5ab 100644
--- a/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminals.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/terminals/capture/CaptureTerminals.scala
@@ -1,14 +1,9 @@
package net.psforever.objects.serverobject.terminals.capture
-import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.Player
import net.psforever.objects.serverobject.CommonMessages
-import net.psforever.objects.serverobject.hackable.Hackable
-import net.psforever.packet.game.PlanetsideAttributeEnum
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
-import net.psforever.types.PlanetSideEmpire
-import java.util.concurrent.TimeUnit
import scala.util.{Failure, Success}
object CaptureTerminals {
diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMech.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMech.scala
index 063f1560e..1622650b5 100644
--- a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMech.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMech.scala
@@ -1,12 +1,10 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals.implant
-import net.psforever.objects.Player
import net.psforever.objects.serverobject.hackable.Hackable
-import net.psforever.objects.serverobject.mount.Mountable
+import net.psforever.objects.serverobject.mount.{Mountable, Seat}
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware
-import net.psforever.objects.vehicles.Seat
import net.psforever.packet.game.TriggeredSound
import net.psforever.types.Vector3
@@ -20,28 +18,12 @@ class ImplantTerminalMech(private val idef: ImplantTerminalMechDefinition)
with Mountable
with Hackable
with CaptureTerminalAware {
- private val seats: Map[Int, Seat] = Map(0 -> new Seat(idef.Seats(0)))
+ seats = Map(0 -> new Seat(idef.Seats.head._2))
HackSound = TriggeredSound.HackTerminal
HackEffectDuration = Array(0, 30, 60, 90)
HackDuration = Array(0, 10, 5, 3)
- def Seats: Map[Int, Seat] = seats
-
- def Seat(seatNum: Int): Option[Seat] = seats.get(seatNum)
-
- def MountPoints: Map[Int, Int] = idef.MountPoints
-
- def GetSeatFromMountPoint(mount: Int): Option[Int] = idef.MountPoints.get(mount)
-
- def PassengerInSeat(user: Player): Option[Int] = {
- if (seats(0).Occupant.contains(user)) {
- Some(0)
- } else {
- None
- }
- }
-
def Definition: ImplantTerminalMechDefinition = idef
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechControl.scala
index d2b386b37..4ab3462e9 100644
--- a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechControl.scala
@@ -21,8 +21,7 @@ import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
class ImplantTerminalMechControl(mech: ImplantTerminalMech)
extends PoweredAmenityControl
with FactionAffinityBehavior.Check
- with MountableBehavior.Mount
- with MountableBehavior.Dismount
+ with MountableBehavior
with HackableBehavior.GenericHackable
with DamageableEntity
with RepairableEntity
@@ -68,11 +67,11 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
case _ => ;
}
- override protected def MountTest(
- obj: PlanetSideServerObject with Mountable,
- seatNumber: Int,
- player: Player
- ): Boolean = {
+ override protected def mountTest(
+ obj: PlanetSideServerObject with Mountable,
+ seatNumber: Int,
+ player: Player
+ ): Boolean = {
val zone = obj.Zone
zone.map.terminalToInterface.get(obj.GUID.guid) match {
case Some(interface_guid) =>
@@ -80,7 +79,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
case Some(interface) => !interface.Destroyed
case None => false
}) &&
- super.MountTest(obj, seatNumber, player)
+ super.mountTest(obj, seatNumber, player)
case None =>
false
}
@@ -122,9 +121,9 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
val zoneId = zone.id
val events = zone.VehicleEvents
mech.Seats.values.foreach(seat =>
- seat.Occupant match {
+ seat.occupant match {
case Some(player) =>
- seat.Occupant = None
+ seat.unmount(player)
player.VehicleSeated = None
if (player.HasGUID) {
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid))
diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechDefinition.scala
index 0b351212d..7720b83df 100644
--- a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechDefinition.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMechDefinition.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals.implant
-import net.psforever.objects.definition.SeatDefinition
+import net.psforever.objects.serverobject.mount.{MountInfo, MountableDefinition, SeatDefinition, Unrestricted}
import net.psforever.objects.serverobject.structures.AmenityDefinition
/**
@@ -9,14 +9,15 @@ import net.psforever.objects.serverobject.structures.AmenityDefinition
* Implant terminals are composed of two components.
* This `Definition` constructs the visible mechanical tube component that can be mounted.
*/
-class ImplantTerminalMechDefinition extends AmenityDefinition(410) {
- /* key - seat index, value - seat object */
- private val seats: Map[Int, SeatDefinition] = Map(0 -> new SeatDefinition)
- /* key - entry point index, value - seat index */
- private val mountPoints: Map[Int, Int] = Map(1 -> 0)
+class ImplantTerminalMechDefinition
+ extends AmenityDefinition(410)
+ with MountableDefinition {
Name = "implant_terminal_mech"
- def Seats: Map[Int, SeatDefinition] = seats
-
- def MountPoints: Map[Int, Int] = mountPoints
+ /* key - mount index, value - mount object */
+ Seats += 0 -> new SeatDefinition() {
+ restriction = Unrestricted
+ }
+ /* key - entry point index, value - mount index */
+ MountPoints += 1 -> MountInfo(0)
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala
index cc4de5eb9..ae056a587 100644
--- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala
@@ -13,8 +13,6 @@ class FacilityTurret(tDef: FacilityTurretDefinition)
with CaptureTerminalAware {
WeaponTurret.LoadDefinition(this)
- def MountPoints: Map[Int, Int] = Definition.MountPoints.toMap
-
def Definition: FacilityTurretDefinition = tDef
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
index 53a27e492..d735fe689 100644
--- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
@@ -3,8 +3,8 @@ package net.psforever.objects.serverobject.turret
import net.psforever.objects.{Default, GlobalDefinitions, Player, Tool}
import net.psforever.objects.equipment.{Ammo, JammableMountedWeapons}
-import net.psforever.objects.serverobject.CommonMessages
-import net.psforever.objects.serverobject.mount.MountableBehavior
+import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
+import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.{Damageable, DamageableWeaponTurret}
import net.psforever.objects.serverobject.hackable.GenericHackables
@@ -31,8 +31,7 @@ import scala.concurrent.duration._
class FacilityTurretControl(turret: FacilityTurret)
extends PoweredAmenityControl
with FactionAffinityBehavior.Check
- with MountableBehavior.TurretMount
- with MountableBehavior.Dismount
+ with MountableBehavior
with DamageableWeaponTurret
with RepairableWeaponTurret
with AmenityAutoRepair
@@ -74,7 +73,7 @@ class FacilityTurretControl(turret: FacilityTurret)
item.Magazine > 0 && turret.Seats.values.forall(!_.isOccupied) =>
TurretUpgrade.values.find(_.id == upgradeValue) match {
case Some(upgrade)
- if turret.Upgrade != upgrade && turret.Definition.Weapons.values
+ if turret.Upgrade != upgrade && turret.Definition.WeaponPaths.values
.flatMap(_.keySet)
.exists(_ == upgrade) =>
sender() ! CommonMessages.Progress(
@@ -103,7 +102,7 @@ class FacilityTurretControl(turret: FacilityTurret)
if (weapon.Magazine < weapon.MaxMagazine && System.nanoTime() - weapon.LastDischarge > 3000000000L) {
weapon.Magazine += 1
val seat = turret.Seat(0).get
- seat.Occupant match {
+ seat.occupant match {
case Some(player: Player) =>
turret.Zone.LocalEvents ! LocalServiceMessage(
turret.Zone.id,
@@ -126,6 +125,13 @@ class FacilityTurretControl(turret: FacilityTurret)
case _ => ;
}
+ override protected def mountTest(
+ obj: PlanetSideServerObject with Mountable,
+ seatNumber: Int,
+ player: Player): Boolean = {
+ (!turret.Definition.FactionLocked || player.Faction == obj.Faction) && !obj.Destroyed
+ }
+
override protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any) : Unit = {
tryAutoRepair()
super.DamageAwareness(target, cause, amount)
@@ -172,9 +178,9 @@ class FacilityTurretControl(turret: FacilityTurret)
val zoneId = zone.id
val events = zone.VehicleEvents
turret.Seats.values.foreach(seat =>
- seat.Occupant match {
+ seat.occupant match {
case Some(player) =>
- seat.Occupant = None
+ seat.unmount(player)
player.VehicleSeated = None
if (player.HasGUID) {
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid))
diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala
index 136c846cd..def85e459 100644
--- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala
@@ -9,7 +9,9 @@ import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance
* The definition for any `FacilityTurret`.
* @param objectId the object's identifier number
*/
-class FacilityTurretDefinition(private val objectId: Int) extends AmenityDefinition(objectId) with TurretDefinition {
+class FacilityTurretDefinition(private val objectId: Int)
+ extends AmenityDefinition(objectId)
+ with TurretDefinition {
DamageUsing = DamageCalculations.AgainstVehicle
ResistUsing = StandardVehicleResistance
Model = SimpleResolutions.calculate
diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala
index 9c6e0de98..9936abab3 100644
--- a/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala
@@ -2,7 +2,7 @@
package net.psforever.objects.serverobject.turret
import net.psforever.objects.definition.{ObjectDefinition, ToolDefinition}
-import net.psforever.objects.vehicles.Turrets
+import net.psforever.objects.vehicles.{MountableWeaponsDefinition, Turrets}
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
import net.psforever.objects.vital.resolution.DamageResistanceModel
@@ -11,14 +11,14 @@ import scala.collection.mutable
/**
* The definition for any `MannedTurret`.
*/
-trait TurretDefinition extends ResistanceProfileMutators with DamageResistanceModel {
+trait TurretDefinition
+ extends MountableWeaponsDefinition
+ with ResistanceProfileMutators
+ with DamageResistanceModel {
odef: ObjectDefinition =>
Turrets(odef.ObjectId) //let throw NoSuchElementException
- /* key - entry point index, value - seat index */
- private val mountPoints: mutable.HashMap[Int, Int] = mutable.HashMap()
- /* key - seat number, value - hash map (below) */
/* key - upgrade, value - weapon definition */
- private val weapons: mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]] =
+ private val weaponPaths: mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]] =
mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]]()
/** can only be mounted by owning faction when `true` */
@@ -29,9 +29,7 @@ trait TurretDefinition extends ResistanceProfileMutators with DamageResistanceMo
*/
private var hasReserveAmmunition: Boolean = false
- def MountPoints: mutable.HashMap[Int, Int] = mountPoints
-
- def Weapons: mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]] = weapons
+ def WeaponPaths: mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]] = weaponPaths
def FactionLocked: Boolean = factionLocked
diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurret.scala b/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurret.scala
index 74b8d8b9d..f9a036c46 100644
--- a/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurret.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurret.scala
@@ -1,22 +1,22 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret
-import net.psforever.objects.{AmmoBox, PlanetSideGameObject, Player, Tool}
-import net.psforever.objects.definition.{AmmoBoxDefinition, SeatDefinition, ToolDefinition}
-import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
+import net.psforever.objects.{AmmoBox, PlanetSideGameObject, Tool}
+import net.psforever.objects.definition.{AmmoBoxDefinition, ToolDefinition}
+import net.psforever.objects.equipment.EquipmentSlot
import net.psforever.objects.inventory.{Container, GridInventory}
import net.psforever.objects.serverobject.affinity.FactionAffinity
-import net.psforever.objects.serverobject.mount.Mountable
-import net.psforever.objects.vehicles.{MountedWeapons, Seat => Chair}
+import net.psforever.objects.serverobject.mount.{SeatDefinition, Seat => Chair}
+import net.psforever.objects.vehicles.MountableWeapons
-trait WeaponTurret extends FactionAffinity with Mountable with MountedWeapons with Container {
+trait WeaponTurret
+ extends FactionAffinity
+ with MountableWeapons
+ with Container {
_: PlanetSideGameObject =>
- /** manned turrets have just one seat; this is just standard interface */
- protected val seats: Map[Int, Chair] = Map(0 -> Chair(new SeatDefinition() { ControlledWeapon = Some(1) }))
-
- /** turrets have just one weapon; this is just standard interface */
- protected var weapons: Map[Int, EquipmentSlot] = Map.empty
+ /** manned turrets have just one mount; this is just standard interface */
+ seats = Map(0 -> new Chair(new SeatDefinition()))
/** may or may not have inaccessible inventory space
* see `ReserveAmmunition` in the definition
@@ -45,39 +45,6 @@ trait WeaponTurret extends FactionAffinity with Mountable with MountedWeapons wi
def VisibleSlots: Set[Int] = Set(1)
- def Weapons: Map[Int, EquipmentSlot] = weapons
-
- def MountPoints: Map[Int, Int]
-
- def Seats: Map[Int, Chair] = seats
-
- def Seat(seatNum: Int): Option[Chair] = seats.get(seatNum)
-
- /**
- * Given the index of an entry mounting point, return the infantry-accessible `Seat` associated with it.
- * @param mountPoint an index representing the seat position / mounting point
- * @return a seat number, or `None`
- */
- def GetSeatFromMountPoint(mountPoint: Int): Option[Int] = {
- MountPoints.get(mountPoint)
- }
-
- def PassengerInSeat(user: Player): Option[Int] = {
- if (seats(0).Occupant.contains(user)) {
- Some(0)
- } else {
- None
- }
- }
-
- def ControlledWeapon(wepNumber: Int): Option[Equipment] = {
- if (VisibleSlots.contains(wepNumber)) {
- weapons(wepNumber).Equipment
- } else {
- None
- }
- }
-
def Upgrade: TurretUpgrade.Value = upgradePath
def Upgrade_=(upgrade: TurretUpgrade.Value): TurretUpgrade.Value = {
@@ -86,7 +53,7 @@ trait WeaponTurret extends FactionAffinity with Mountable with MountedWeapons wi
//upgrade each weapon as long as that weapon has a valid option for that upgrade
Definition match {
case definition: TurretDefinition =>
- definition.Weapons.foreach({
+ definition.WeaponPaths.foreach({
case (index, upgradePaths) =>
if (upgradePaths.contains(upgrade)) {
updated = true
@@ -136,7 +103,7 @@ object WeaponTurret {
def LoadDefinition(turret: WeaponTurret, tdef: TurretDefinition): WeaponTurret = {
import net.psforever.objects.equipment.EquipmentSize.BaseTurretWeapon
//create weapons; note the class
- turret.weapons = tdef.Weapons
+ turret.weapons = tdef.WeaponPaths
.map({
case (num, upgradePaths) =>
val slot = EquipmentSlot(BaseTurretWeapon)
@@ -146,7 +113,7 @@ object WeaponTurret {
.toMap
//special inventory ammunition object(s)
if (tdef.ReserveAmmunition) {
- val allAmmunitionTypes = tdef.Weapons.values.flatMap { _.values.flatMap { _.AmmoTypes } }.toSet
+ val allAmmunitionTypes = tdef.WeaponPaths.values.flatMap { _.values.flatMap { _.AmmoTypes } }.toSet
if (allAmmunitionTypes.nonEmpty) {
turret.inventory.Resize(allAmmunitionTypes.size, 1)
var i: Int = 0
diff --git a/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala b/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala
index 8e608ffe9..860959c6f 100644
--- a/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala
@@ -3,9 +3,9 @@ package net.psforever.objects.vehicles
/**
* An `Enumeration` of various permission groups that control access to aspects of a vehicle.
- * - `Driver` is a seat that is always seat number 0.
- * - `Gunner` is a seat that is not the `Driver` and controls a mounted weapon.
- * - `Passenger` is a seat that is not the `Driver` and does not have control of a mounted weapon.
+ * - `Driver` is a mount that is always mount number 0.
+ * - `Gunner` is a mount that is not the `Driver` and controls a mounted weapon.
+ * - `Passenger` is a mount that is not the `Driver` and does not have control of a mounted weapon.
* - `Trunk` represnts access to the vehicle's internal storage space.
* Organized to replicate the `PlanetsideAttributeMessage` value used for that given access level.
* In their respective `PlanetsideAttributeMessage` packet, the groups are indexed in the same order as 10 through 13.
diff --git a/src/main/scala/net/psforever/objects/vehicles/Cargo.scala b/src/main/scala/net/psforever/objects/vehicles/Cargo.scala
index c2e1a71f8..736b771e4 100644
--- a/src/main/scala/net/psforever/objects/vehicles/Cargo.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/Cargo.scala
@@ -1,88 +1,11 @@
-// Copyright (c) 2017 PSForever
+// Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles
import net.psforever.objects.Vehicle
-import net.psforever.objects.definition.{CargoDefinition}
+import net.psforever.objects.serverobject.mount.{MountableSpace, MountableSpaceDefinition}
-/**
- * Server-side support for a slot that vehicles can occupy
- * @param cargoDef the Definition that constructs this item and maintains some of its unchanging fields
- */
-class Cargo(private val cargoDef: CargoDefinition) {
- private var occupant: Option[Vehicle] = None
+class Cargo(private val cdef: MountableSpaceDefinition[Vehicle]) extends MountableSpace[Vehicle] {
+ override protected def testToMount(target: Vehicle): Boolean = target.MountedIn.isEmpty && super.testToMount(target)
- /**
- * Is the cargo hold occupied?
- * @return The vehicle in the cargo hold, or `None` if it is left vacant
- */
- def Occupant: Option[Vehicle] = {
- this.occupant
- }
-
- /**
- * A vehicle is trying to board the cargo hold
- * Cargo holds are exclusive positions that can only hold one vehicle at a time.
- * @param vehicle the vehicle boarding the cargo hold, or `None` if the vehicle is leaving
- * @return the vehicle sitting in this seat, or `None` if it is left vacant
- */
- def Occupant_=(vehicle: Vehicle): Option[Vehicle] = Occupant_=(Some(vehicle))
-
- def Occupant_=(vehicle: Option[Vehicle]): Option[Vehicle] = {
- if (vehicle.isDefined) {
- if (this.occupant.isEmpty) {
- this.occupant = vehicle
- }
- } else {
- this.occupant = None
- }
- this.occupant
- }
-
- /**
- * Is this cargo hold occupied?
- * @return `true`, if it is occupied; `false`, otherwise
- */
- def isOccupied: Boolean = {
- this.occupant.isDefined
- }
-
- def CargoRestriction: CargoVehicleRestriction.Value = {
- cargoDef.CargoRestriction
- }
-
- def Bailable: Boolean = {
- cargoDef.Bailable
- }
-
- /**
- * Override the string representation to provide additional information.
- * @return the string output
- */
- override def toString: String = {
- Cargo.toString(this)
- }
-}
-
-object Cargo {
-
- /**
- * Overloaded constructor.
- * @return a `Cargo` object
- */
- def apply(cargoDef: CargoDefinition): Cargo = {
- new Cargo(cargoDef)
- }
-
- /**
- * Provide a fixed string representation.
- * @return the string output
- */
- def toString(obj: Cargo): String = {
- val cargoStr = if (obj.isOccupied) {
- s", occupied by vehicle ${obj.Occupant.get.GUID}"
- } else {
- ""
- }
- s"cargo$cargoStr"
- }
+ def definition: MountableSpaceDefinition[Vehicle] = cdef
}
diff --git a/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala b/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
index 1262b3c31..509525cf4 100644
--- a/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
@@ -149,7 +149,7 @@ object CargoBehavior {
//cargo vehicle is close enough to assume to be physically within the carrier's hold; mount it
log.info(s"HandleCheckCargoMounting: mounting cargo vehicle in carrier at distance of $distance")
cargo.MountedIn = carrierGUID
- hold.Occupant = cargo
+ hold.mount(cargo)
cargo.Velocity = None
zone.VehicleEvents ! VehicleServiceMessage(
s"${cargo.Actor}",
@@ -168,7 +168,7 @@ object CargoBehavior {
log.info(
"HandleCheckCargoMounting: cargo vehicle is too far away or didn't mount within allocated time - aborting"
)
- val cargoDriverGUID = cargo.Seats(0).Occupant.get.GUID
+ val cargoDriverGUID = cargo.Seats(0).occupant.get.GUID
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
VehicleAction.SendResponse(
@@ -266,7 +266,7 @@ object CargoBehavior {
log.info(
s"HandleCheckCargoDismounting: dismount of cargo vehicle from carrier complete at distance of $distance"
)
- val cargoDriverGUID = cargo.Seats(0).Occupant.get.GUID
+ val cargoDriverGUID = cargo.Seats(0).occupant.get.GUID
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
VehicleAction.SendResponse(
@@ -289,7 +289,7 @@ object CargoBehavior {
} else if (iteration > 40) {
//cargo vehicle has spent too long not getting far enough away; restore the cargo's mount in the carrier hold
cargo.MountedIn = carrierGUID
- hold.Occupant = cargo
+ hold.mount(cargo)
CargoMountBehaviorForAll(carrier, cargo, mountPoint)
false
} else {
@@ -363,11 +363,11 @@ object CargoBehavior {
kicked: Boolean
): Unit = {
val zone = carrier.Zone
- carrier.CargoHolds.find({ case (_, hold) => hold.Occupant.contains(cargo) }) match {
+ carrier.CargoHolds.find({ case (_, hold) => hold.occupant.contains(cargo) }) match {
case Some((mountPoint, hold)) =>
cargo.MountedIn = None
- hold.Occupant = None
- val driverOpt = cargo.Seats(0).Occupant
+ hold.unmount(cargo)
+ val driverOpt = cargo.Seats(0).occupant
val rotation: Vector3 = if (Vehicles.CargoOrientation(cargo) == 1) { //TODO: BFRs will likely also need this set
//dismount router "sideways" in a lodestar
carrier.Orientation.xy + Vector3.z((carrier.Orientation.z - 90) % 360)
@@ -393,7 +393,7 @@ object CargoBehavior {
s"$cargoActor",
VehicleAction.SendResponse(GUID0, PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields))
)
- if (carrier.Flying) {
+ if (carrier.isFlying) {
//the carrier vehicle is flying; eject the cargo vehicle
val ejectCargoMsg =
CargoMountPointStatusMessage(carrierGUID, GUID0, GUID0, cargoGUID, mountPoint, CargoStatus.InProgress, 0)
diff --git a/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestriction.scala b/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestriction.scala
index 19b2b1503..483fc06ab 100644
--- a/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestriction.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestriction.scala
@@ -2,11 +2,11 @@
package net.psforever.objects.vehicles
/**
- * An `Enumeration` of exo-suit-based seat access restrictions.
+ * An `Enumeration` of exo-suit-based mount access restrictions.
*
- * The default value is `NoMax` as that is the most common seat.
+ * The default value is `NoMax` as that is the most common mount.
* `NoReinforcedOrMax` is next most common.
- * `MaxOnly` is a rare seat restriction found in pairs on Galaxies and on the large "Ground Transport" vehicles.
+ * `MaxOnly` is a rare mount restriction found in pairs on Galaxies and on the large "Ground Transport" vehicles.
*/
object CargoVehicleRestriction extends Enumeration {
type Type = Value
diff --git a/src/main/scala/net/psforever/objects/vehicles/MountableWeapons.scala b/src/main/scala/net/psforever/objects/vehicles/MountableWeapons.scala
new file mode 100644
index 000000000..fdbba2b20
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vehicles/MountableWeapons.scala
@@ -0,0 +1,38 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.vehicles
+
+import net.psforever.objects.PlanetSideGameObject
+import net.psforever.objects.equipment.Equipment
+import net.psforever.objects.serverobject.mount.Mountable
+
+trait MountableWeapons
+ extends MountedWeapons
+ with Mountable {
+ this: PlanetSideGameObject =>
+
+ /**
+ * Given a valid mount number, retrieve an index where the weapon controlled from this mount is mounted.
+ * @param seatNumber the mount number
+ * @return a mounted weapon by index, or `None` if either the mount doesn't exist or there is no controlled weapon
+ */
+ def WeaponControlledFromSeat(seatNumber: Int): Option[Equipment] = {
+ Definition
+ .asInstanceOf[MountableWeaponsDefinition]
+ .controlledWeapons.get(seatNumber) match {
+ case Some(wepNumber) if seats.get(seatNumber).nonEmpty => controlledWeapon(wepNumber)
+ case _ => None
+ }
+ }
+
+ def controlledWeapon(wepNumber: Int): Option[Equipment] = ControlledWeapon(wepNumber)
+
+ def ControlledWeapon(wepNumber: Int): Option[Equipment] = {
+ weapons.get(wepNumber) match {
+ case Some(slot) => slot.Equipment
+ case _ => None
+ }
+ }
+
+ def Definition: MountableWeaponsDefinition
+}
+
diff --git a/src/main/scala/net/psforever/objects/vehicles/MountableWeaponsDefinition.scala b/src/main/scala/net/psforever/objects/vehicles/MountableWeaponsDefinition.scala
new file mode 100644
index 000000000..f897b913c
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vehicles/MountableWeaponsDefinition.scala
@@ -0,0 +1,12 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.vehicles
+
+import net.psforever.objects.serverobject.mount.MountableDefinition
+
+import scala.collection.mutable
+
+trait MountableWeaponsDefinition
+ extends MountedWeaponsDefinition
+ with MountableDefinition {
+ val controlledWeapons: mutable.HashMap[Int, Int] = mutable.HashMap[Int, Int]()
+}
diff --git a/src/main/scala/net/psforever/objects/vehicles/MountedWeapons.scala b/src/main/scala/net/psforever/objects/vehicles/MountedWeapons.scala
index 35f675464..997e9f153 100644
--- a/src/main/scala/net/psforever/objects/vehicles/MountedWeapons.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/MountedWeapons.scala
@@ -2,38 +2,13 @@
package net.psforever.objects.vehicles
import net.psforever.objects.PlanetSideGameObject
-import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
-import net.psforever.objects.inventory.Container
-import net.psforever.objects.serverobject.mount.Mountable
-import net.psforever.objects.vehicles.{Seat => Chair}
+import net.psforever.objects.equipment.EquipmentSlot
trait MountedWeapons {
- this: PlanetSideGameObject with Mountable with Container =>
+ this: PlanetSideGameObject =>
+ protected var weapons: Map[Int, EquipmentSlot] = Map[Int, EquipmentSlot]()
- def Weapons: Map[Int, EquipmentSlot]
+ def Weapons: Map[Int, EquipmentSlot] = weapons
- /**
- * Given a valid seat number, retrieve an index where the weapon controlled from this seat is mounted.
- * @param seatNumber the seat number
- * @return a mounted weapon by index, or `None` if either the seat doesn't exist or there is no controlled weapon
- */
- def WeaponControlledFromSeat(seatNumber: Int): Option[Equipment] = {
- Seat(seatNumber) match {
- case Some(seat) =>
- wepFromSeat(seat)
- case None =>
- None
- }
- }
-
- private def wepFromSeat(seat: Chair): Option[Equipment] = {
- seat.ControlledWeapon match {
- case Some(index) =>
- ControlledWeapon(index)
- case None =>
- None
- }
- }
-
- def ControlledWeapon(wepNumber: Int): Option[Equipment]
+ def Definition: MountedWeaponsDefinition
}
diff --git a/src/main/scala/net/psforever/objects/vehicles/MountedWeaponsDefinition.scala b/src/main/scala/net/psforever/objects/vehicles/MountedWeaponsDefinition.scala
new file mode 100644
index 000000000..2ff759c74
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/vehicles/MountedWeaponsDefinition.scala
@@ -0,0 +1,13 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.objects.vehicles
+
+import net.psforever.objects.definition.ToolDefinition
+
+import scala.collection.mutable
+
+trait MountedWeaponsDefinition {
+ /* key - mount index (where this weapon attaches during object construction), value - the weapon on an EquipmentSlot */
+ protected var weapons: mutable.HashMap[Int, ToolDefinition] = mutable.HashMap[Int, ToolDefinition]()
+
+ def Weapons: mutable.HashMap[Int, ToolDefinition] = weapons
+}
diff --git a/src/main/scala/net/psforever/objects/vehicles/Seat.scala b/src/main/scala/net/psforever/objects/vehicles/Seat.scala
deleted file mode 100644
index c32c3b315..000000000
--- a/src/main/scala/net/psforever/objects/vehicles/Seat.scala
+++ /dev/null
@@ -1,104 +0,0 @@
-// Copyright (c) 2017 PSForever
-package net.psforever.objects.vehicles
-
-import net.psforever.objects.definition.SeatDefinition
-import net.psforever.objects.Player
-
-/**
- * Server-side support for a slot that infantry players can occupy, ostensibly called a "seat" and treated like a "seat."
- * (Players can sit in it.)
- * @param seatDef the Definition that constructs this item and maintains some of its unchanging fields
- */
-class Seat(private val seatDef: SeatDefinition) {
- private var occupant: Option[Player] = None
-// private var lockState : VehicleLockState.Value = VehicleLockState.Empire
-
- /**
- * Is this seat occupied?
- * @return the Player object of the player sitting in this seat, or `None` if it is left vacant
- */
- def Occupant: Option[Player] = {
- this.occupant
- }
-
- /**
- * The player is trying to sit down.
- * Seats are exclusive positions that can only hold one occupant at a time.
- * @param player the player who wants to sit, or `None` if the occupant is getting up
- * @return the Player object of the player sitting in this seat, or `None` if it is left vacant
- */
- def Occupant_=(player: Player): Option[Player] = Occupant_=(Some(player))
-
- def Occupant_=(player: Option[Player]): Option[Player] = {
- if (player.isDefined) {
- if (this.occupant.isEmpty) {
- this.occupant = player
- }
- } else {
- this.occupant = None
- }
- this.occupant
- }
-
- /**
- * Is this seat occupied?
- * @return `true`, if it is occupied; `false`, otherwise
- */
- def isOccupied: Boolean = {
- this.occupant.isDefined
- }
-
-// def SeatLockState : VehicleLockState.Value = {
-// this.lockState
-// }
-//
-// def SeatLockState_=(lockState : VehicleLockState.Value) : VehicleLockState.Value = {
-// this.lockState = lockState
-// SeatLockState
-// }
-
- def ArmorRestriction: SeatArmorRestriction.Value = {
- seatDef.ArmorRestriction
- }
-
- /** Determines if the seat can be bailed from while the vehicle is in motion */
- def Bailable: Boolean = {
- seatDef.Bailable
- }
-
- def ControlledWeapon: Option[Int] = {
- seatDef.ControlledWeapon
- }
-
- /**
- * Override the string representation to provide additional information.
- * @return the string output
- */
- override def toString: String = {
- Seat.toString(this)
- }
-}
-
-object Seat {
-
- /**
- * Overloaded constructor.
- * @return a `Seat` object
- */
- def apply(seatDef: SeatDefinition): Seat = {
- new Seat(seatDef)
- }
-
- /**
- * Provide a fixed string representation.
- * @return the string output
- */
- def toString(obj: Seat): String = {
- val seatStr = if (obj.isOccupied) {
- s", occupied by player ${obj.Occupant.get.GUID}"
- } else {
- ""
- }
- s"seat$seatStr"
- }
-}
diff --git a/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala b/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala
index b811d76b7..0da4c97cc 100644
--- a/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/SeatArmorRestriction.scala
@@ -2,14 +2,15 @@
package net.psforever.objects.vehicles
/**
- * An `Enumeration` of exo-suit-based seat access restrictions.
+ * An `Enumeration` of exo-suit-based mount access restrictions.
*
- * The default value is `NoMax` as that is the most common seat type.
+ * The default value is `NoMax` as that is the most common mount type.
* `NoReinforcedOrMax` is next most common.
- * `MaxOnly` is a rare seat restriction found in pairs on Galaxies and on the large "Ground Transport" vehicles.
+ * `MaxOnly` is a rare mount restriction found in pairs on Galaxies and on the large "Ground Transport" vehicles.
+ * `Unrestricted` is for "seats" that do not limit by exo-suit type, such the orbital shuttle.
*/
object SeatArmorRestriction extends Enumeration {
type Type = Value
- val MaxOnly, NoMax, NoReinforcedOrMax = Value
+ val MaxOnly, NoMax, NoReinforcedOrMax, Unrestricted = Value
}
diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
index 80ce112a1..93492d76d 100644
--- a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
@@ -5,6 +5,7 @@ import akka.actor.{Actor, Cancellable}
import net.psforever.objects._
import net.psforever.objects.ballistics.VehicleSource
import net.psforever.objects.ce.TelepadLike
+import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons}
import net.psforever.objects.guid.GUIDTask
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
@@ -48,8 +49,7 @@ class VehicleControl(vehicle: Vehicle)
extends Actor
with FactionAffinityBehavior.Check
with DeploymentBehavior
- with MountableBehavior.Mount
- with MountableBehavior.Dismount
+ with MountableBehavior
with CargoBehavior
with DamageableVehicle
with RepairableVehicle
@@ -129,34 +129,13 @@ class VehicleControl(vehicle: Vehicle)
case Vehicle.Ownership(Some(player)) =>
GainOwnership(player)
- case msg@Mountable.TryMount(player, seat_num) =>
- tryMountBehavior.apply(msg)
- val obj = MountableObject
- //check that the player has actually been sat in the expected seat
- if (obj.PassengerInSeat(player).contains(seat_num)) {
- //if the driver seat, change ownership
- if (seat_num == 0 && !obj.OwnerName.contains(player.Name)) {
- //whatever vehicle was previously owned
- vehicle.Zone.GUID(player.avatar.vehicle) match {
- case Some(v : Vehicle) =>
- v.Actor ! Vehicle.Ownership(None)
- case _ =>
- player.avatar.vehicle = None
- }
- LoseOwnership() //lose our current ownership
- GainOwnership(player) //gain new ownership
- }
- else {
- decaying = false
- decayTimer.cancel()
- }
- //
- updateZoneInteractionProgressUI(player)
- }
+ case msg @ Mountable.TryMount(player, mount_point) =>
+ mountBehavior.apply(msg)
+ mountCleanup(mount_point, player)
- case msg : Mountable.TryDismount =>
+ case msg @ Mountable.TryDismount(_, seat_num) =>
dismountBehavior.apply(msg)
- dismountCleanup()
+ dismountCleanup(seat_num)
case Vehicle.ChargeShields(amount) =>
val now : Long = System.currentTimeMillis()
@@ -261,7 +240,7 @@ class VehicleControl(vehicle: Vehicle)
case Vehicle.Deconstruct(time) =>
time match {
- case Some(delay) =>
+ case Some(delay) if vehicle.Definition.undergoesDecay =>
decaying = true
decayTimer.cancel()
decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
@@ -288,13 +267,13 @@ class VehicleControl(vehicle: Vehicle)
case msg : Deployment.TryUndeploy =>
deployBehavior.apply(msg)
- case msg : Mountable.TryDismount =>
+ case msg @ Mountable.TryDismount(_, seat_num) =>
dismountBehavior.apply(msg)
- dismountCleanup()
+ dismountCleanup(seat_num)
case Vehicle.Deconstruct(time) =>
time match {
- case Some(delay) =>
+ case Some(delay) if vehicle.Definition.undergoesDecay =>
decaying = true
decayTimer.cancel()
decayTimer = context.system.scheduler.scheduleOnce(delay, self, VehicleControl.PrepareForDeletion())
@@ -327,46 +306,77 @@ class VehicleControl(vehicle: Vehicle)
case _ =>
}
- val tryMountBehavior : Receive = {
- case msg @ Mountable.TryMount(user, seat_num) =>
- val exosuit = user.ExoSuit
- val restriction = vehicle.Seats(seat_num).ArmorRestriction
- val seatGroup = vehicle.SeatPermissionGroup(seat_num).getOrElse(AccessPermissionGroup.Passenger)
- val permission = vehicle.PermissionGroup(seatGroup.id).getOrElse(VehicleLockState.Empire)
- if (
- (if (seatGroup == AccessPermissionGroup.Driver) {
- vehicle.Owner.contains(user.GUID) || vehicle.Owner.isEmpty || permission != VehicleLockState.Locked
- }
- else {
- permission != VehicleLockState.Locked
- }) &&
- (exosuit match {
- case ExoSuitType.MAX => restriction == SeatArmorRestriction.MaxOnly
- case ExoSuitType.Reinforced => restriction == SeatArmorRestriction.NoMax
- case _ => restriction != SeatArmorRestriction.MaxOnly
- })
- ) {
- mountBehavior.apply(msg)
- }
- else {
- sender() ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, seat_num))
- }
+ override protected def mountTest(
+ obj: PlanetSideServerObject with Mountable,
+ seatNumber: Int,
+ user: Player
+ ): Boolean = {
+ val seatGroup = vehicle.SeatPermissionGroup(seatNumber).getOrElse(AccessPermissionGroup.Passenger)
+ val permission = vehicle.PermissionGroup(seatGroup.id).getOrElse(VehicleLockState.Empire)
+ (if (seatGroup == AccessPermissionGroup.Driver) {
+ vehicle.Owner.contains(user.GUID) || vehicle.Owner.isEmpty || permission != VehicleLockState.Locked
+ } else {
+ permission != VehicleLockState.Locked
+ }) &&
+ super.mountTest(obj, seatNumber, user)
}
- def dismountCleanup(): Unit = {
+ def mountCleanup(mount_point: Int, user: Player): Unit = {
+ val obj = MountableObject
+ obj.PassengerInSeat(user) match {
+ case Some(seatNumber) =>
+ //if the driver mount, change ownership if that is permissible for this vehicle
+ if (seatNumber == 0 && !obj.OwnerName.contains(user.Name) && obj.Definition.CanBeOwned.nonEmpty) {
+ //whatever vehicle was previously owned
+ vehicle.Zone.GUID(user.avatar.vehicle) match {
+ case Some(v : Vehicle) =>
+ v.Actor ! Vehicle.Ownership(None)
+ case _ =>
+ user.avatar.vehicle = None
+ }
+ GainOwnership(user) //gain new ownership
+ }
+ else {
+ decaying = false
+ decayTimer.cancel()
+ }
+ updateZoneInteractionProgressUI(user)
+ case None => ;
+ }
+ }
+
+ override protected def dismountTest(
+ obj: Mountable with WorldEntity,
+ seatNumber: Int,
+ user: Player
+ ): Boolean = {
+ vehicle.DeploymentState == DriveState.Deployed || super.dismountTest(obj, seatNumber, user)
+ }
+
+ def dismountCleanup(seatBeingDismounted: Int): Unit = {
val obj = MountableObject
// Reset velocity to zero when driver dismounts, to allow jacking/repair if vehicle was moving slightly before dismount
if (!obj.Seats(0).isOccupied) {
obj.Velocity = Some(Vector3.Zero)
}
- //are we already decaying? are we unowned? is no one seated anywhere?
- if (!decaying && obj.Owner.isEmpty && obj.Seats.values.forall(!_.isOccupied)) {
- decaying = true
- decayTimer = context.system.scheduler.scheduleOnce(
- MountableObject.Definition.DeconstructionTime.getOrElse(5 minutes),
- self,
- VehicleControl.PrepareForDeletion()
- )
+ if (!obj.Seats(seatBeingDismounted).isOccupied) { //seat was vacated
+ //we were only owning the vehicle while we sat in its driver seat
+ val canBeOwned = obj.Definition.CanBeOwned
+ if (canBeOwned.contains(false) && seatBeingDismounted == 0) {
+ LoseOwnership()
+ }
+ //are we already decaying? are we unowned? is no one seated anywhere?
+ if (!decaying &&
+ obj.Definition.undergoesDecay &&
+ obj.Owner.isEmpty &&
+ obj.Seats.values.forall(!_.isOccupied)) {
+ decaying = true
+ decayTimer = context.system.scheduler.scheduleOnce(
+ MountableObject.Definition.DeconstructionTime.getOrElse(5 minutes),
+ self,
+ VehicleControl.PrepareForDeletion()
+ )
+ }
}
}
@@ -390,12 +400,12 @@ class VehicleControl(vehicle: Vehicle)
)
case _ => ;
}
- if (!vehicle.Flying || kickPassengers) {
+ if (!vehicle.isFlying || kickPassengers) {
//kick all passengers (either not flying, or being explicitly instructed)
vehicle.Seats.values.foreach { seat =>
- seat.Occupant match {
+ seat.occupant match {
case Some(player) =>
- seat.Occupant = None
+ seat.unmount(player)
player.VehicleSeated = None
if (player.HasGUID) {
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, false, guid))
@@ -408,7 +418,7 @@ class VehicleControl(vehicle: Vehicle)
vehicle.CargoHolds.values
.collect {
case hold if hold.isOccupied =>
- val cargo = hold.Occupant.get
+ val cargo = hold.occupant.get
CargoBehavior.HandleVehicleCargoDismount(
cargo.GUID,
cargo,
@@ -423,10 +433,7 @@ class VehicleControl(vehicle: Vehicle)
def PrepareForDeletion() : Unit = {
decaying = false
- val guid = vehicle.GUID
val zone = vehicle.Zone
- val zoneId = zone.id
- val events = zone.VehicleEvents
//miscellaneous changes
Vehicles.BeforeUnloadVehicle(vehicle, zone)
//cancel jammed behavior
@@ -449,7 +456,10 @@ class VehicleControl(vehicle: Vehicle)
def LoseOwnership(): Unit = {
val obj = MountableObject
Vehicles.Disown(obj.GUID, obj)
- if (!decaying && obj.Seats.values.forall(!_.isOccupied)) {
+ if (!decaying &&
+ obj.Definition.undergoesDecay &&
+ obj.Owner.isEmpty &&
+ obj.Seats.values.forall(!_.isOccupied)) {
decaying = true
decayTimer = context.system.scheduler.scheduleOnce(
obj.Definition.DeconstructionTime.getOrElse(5 minutes),
@@ -460,7 +470,9 @@ class VehicleControl(vehicle: Vehicle)
}
def GainOwnership(player: Player): Unit = {
- Vehicles.Own(MountableObject, player) match {
+ val obj = MountableObject
+ Vehicles.Disown(obj.GUID, obj)
+ Vehicles.Own(obj, player) match {
case Some(_) =>
decaying = false
decayTimer.cancel()
@@ -538,7 +550,7 @@ class VehicleControl(vehicle: Vehicle)
val toChannel = if (obj.VisibleSlots.contains(fromSlot)) zone.id else self.toString
zone.VehicleEvents ! VehicleServiceMessage(
toChannel,
- VehicleAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID)
+ VehicleAction.ObjectDelete(item.GUID)
)
}
@@ -558,7 +570,7 @@ class VehicleControl(vehicle: Vehicle)
val zone = vehicle.Zone
val zoneChannel = zone.id
val GUID0 = Service.defaultPlayerGUID
- val driverChannel = vehicle.Seats(0).Occupant match {
+ val driverChannel = vehicle.Seats(0).occupant match {
case Some(tplayer) => tplayer.Name
case None => ""
}
@@ -623,7 +635,7 @@ class VehicleControl(vehicle: Vehicle)
val guid = vehicle.GUID
val zone = vehicle.Zone
val GUID0 = Service.defaultPlayerGUID
- val driverChannel = vehicle.Seats(0).Occupant match {
+ val driverChannel = vehicle.Seats(0).occupant match {
case Some(tplayer) => tplayer.Name
case None => ""
}
@@ -686,7 +698,7 @@ class VehicleControl(vehicle: Vehicle)
percentage,
body,
vehicle.Seats.values
- .collect { case seat if seat.isOccupied => seat.Occupant.get }
+ .flatMap { case seat if seat.isOccupied => seat.occupants }
.filter { p => p.isAlive && (p.Zone eq vehicle.Zone) }
)
}
@@ -779,7 +791,7 @@ class VehicleControl(vehicle: Vehicle)
percentage,
body,
vehicle.Seats.values
- .collect { case seat if seat.isOccupied => seat.Occupant.get }
+ .flatMap { case seat if seat.isOccupied => seat.occupants }
.filter { p => p.isAlive && (p.Zone eq vehicle.Zone) }
)
}
diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleManifest.scala b/src/main/scala/net/psforever/objects/vehicles/VehicleManifest.scala
index fcf1f544d..2101ff342 100644
--- a/src/main/scala/net/psforever/objects/vehicles/VehicleManifest.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/VehicleManifest.scala
@@ -2,6 +2,7 @@
package net.psforever.objects.vehicles
import net.psforever.objects.Vehicle
+import net.psforever.objects.serverobject.mount.Seat
import net.psforever.objects.zones.Zone
/**
@@ -14,7 +15,7 @@ import net.psforever.objects.zones.Zone
* @param vehicle the vehicle in transport
* @param origin where the vehicle originally was
* @param driverName the name of the driver when the transport process started
- * @param passengers the paired names and seat indices of all passengers when the transport process started
+ * @param passengers the paired names and mount indices of all passengers when the transport process started
* @param cargo the paired driver names and cargo hold indices of all cargo vehicles when the transport process started
*/
final case class VehicleManifest(
@@ -28,17 +29,17 @@ final case class VehicleManifest(
object VehicleManifest {
def apply(vehicle: Vehicle): VehicleManifest = {
- val driverName = vehicle.Seats(0).Occupant match {
+ val driverName = vehicle.Seats(0).occupant match {
case Some(driver) => driver.Name
case None => "MISSING_DRIVER"
}
val passengers = vehicle.Seats.collect {
- case (index, seat) if index > 0 && seat.isOccupied =>
- (seat.Occupant.get.Name, index)
+ case (index: Int, seat: Seat) if index > 0 && seat.isOccupied =>
+ (seat.occupant.get.Name, index)
}
val cargo = vehicle.CargoHolds.collect {
- case (index, hold) if hold.Occupant.nonEmpty =>
- hold.Occupant.get.Seats(0).Occupant match {
+ case (index: Int, hold: Cargo) if hold.occupant.nonEmpty =>
+ hold.occupant.get.Seats(0).occupant match {
case Some(driver) =>
(driver.Name, index)
case None =>
diff --git a/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifierFunctions.scala b/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifierFunctions.scala
index da2178fb1..7157375ad 100644
--- a/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifierFunctions.scala
+++ b/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifierFunctions.scala
@@ -392,7 +392,7 @@ object ProjectileDamageModifierFunctions {
data: DamageInteraction,
cause: ProjectileReason
): Int = {
- if (cause.resolution == resolution) {
+ if (data.resolution == resolution) {
(data.cause.source.Aggravated, data.target) match {
case (Some(aggravation), p: PlayerSource) =>
val degradation = aggravation.info.find(_.damage_type == damageType) match {
diff --git a/src/main/scala/net/psforever/objects/zones/MapInfo.scala b/src/main/scala/net/psforever/objects/zones/MapInfo.scala
index 8191c47b8..6151cce22 100644
--- a/src/main/scala/net/psforever/objects/zones/MapInfo.scala
+++ b/src/main/scala/net/psforever/objects/zones/MapInfo.scala
@@ -2,7 +2,7 @@ package net.psforever.objects.zones
import enumeratum.values.{StringEnum, StringEnumEntry}
import net.psforever.objects.serverobject.environment._
-import net.psforever.types.Vector3
+import net.psforever.types.{PlanetSideGUID, Vector3}
sealed abstract class MapInfo(
val value: String,
@@ -180,7 +180,7 @@ case object MapInfo extends StringEnum[MapInfo] {
Pool(EnvironmentAttribute.Water, 34.96875f, 5899.367f, 3235.5781f, 5573.8516f, 2865.7812f), //northeast of hart c campus
Pool(EnvironmentAttribute.Water, 34.328125f, 3880.7422f, 5261.508f, 3780.9219f, 5166.953f), //east of hart a campus
Pool(EnvironmentAttribute.Water, 31.03125f, 4849.797f, 2415.4297f, 4731.8594f, 2252.1484f) //south of hart c campus
- )
+ ) ++ MapEnvironment.map11Environment
)
case object Map12
@@ -188,7 +188,8 @@ case object MapInfo extends StringEnum[MapInfo] {
value = "map12",
checksum = 962888126L,
scale = MapScale.Dim8192,
- environment = List(SeaLevel(EnvironmentAttribute.Water, 20.03125f))
+ environment = List(SeaLevel(EnvironmentAttribute.Water, 20.03125f)) ++
+ MapEnvironment.map12Environment
)
case object Map13
@@ -196,7 +197,8 @@ case object MapInfo extends StringEnum[MapInfo] {
value = "map13",
checksum = 3904659548L,
scale = MapScale.Dim8192,
- environment = List(SeaLevel(EnvironmentAttribute.Water, 30))
+ environment = List(SeaLevel(EnvironmentAttribute.Water, 30)) ++
+ MapEnvironment.map13Environment
)
case object Map14
@@ -317,3 +319,107 @@ case object MapInfo extends StringEnum[MapInfo] {
val values: IndexedSeq[MapInfo] = findValues
}
+
+object MapEnvironment {
+ /** the pattern of mount points for the HART gantries in most facilities;
+ * eight values - 1-8 - listed as four downstairs - NE SE NW SW - then four upstairs - same
+ */
+ private val hartMountPoints: Seq[Int] = Seq(6,5, 2,1, 8,7, 4,3)
+ /** the pattern of mount points for the HART gantries in VS sanctuary facilities;
+ * eight values - 1-8 - listed as four downstairs - NE SE NW SW - then four upstairs - same
+ */
+ private val vsHartMountPoints: Seq[Int] = Seq(1,2, 5,6, 3,4, 7,8)
+
+ /** HART denial fields for the New Conglomerate sanctuary */
+ final val map11Environment: List[PieceOfEnvironment] =
+ hartGantryDenialFields(PlanetSideGUID(840), Vector3(2258, 5538, 65.20142f), hartMountPoints) ++
+ hartGantryDenialFields(PlanetSideGUID(841), Vector3(4152, 6070, 43.8766136f), hartMountPoints) ++
+ specialHartGantryDenialFields(PlanetSideGUID(842))
+
+ /** HART denial fields for the Terran Republic sanctuary */
+ final val map12Environment: List[PieceOfEnvironment] =
+ hartGantryDenialFields(PlanetSideGUID(808), Vector3(2922, 5230, 35.9989929f), hartMountPoints) ++
+ hartGantryDenialFields(PlanetSideGUID(809), Vector3(3006, 2984, 34.919342f), hartMountPoints) ++
+ hartGantryDenialFields(PlanetSideGUID(810), Vector3(5232, 3908, 35.9291039f), hartMountPoints)
+
+ /** HART denial fields for the Vanu Sovereignty sanctuary */
+ final val map13Environment: List[PieceOfEnvironment] =
+ hartGantryDenialFields(PlanetSideGUID(786), Vector3(2978, 4834, 56.085392f), vsHartMountPoints) ++
+ hartGantryDenialFields(PlanetSideGUID(787), Vector3(3688, 2808, 90.85312f), vsHartMountPoints) ++
+ hartGantryDenialFields(PlanetSideGUID(788), Vector3(5610, 4238, 103.228859f), vsHartMountPoints)
+
+ /**
+ * Generate eight environmental representations that serve to eject players
+ * from the high altitude rapid transport (HART) building boarding gantry hallways
+ * when the HART shuttle associated with that building is no longer boarding
+ * and the doors to those hallways should deny entrance.
+ * When kicked out of the hallway,
+ * ejected players should be placed in the same position as if the player willingly dismounted the shuttle.
+ *
+ * While this task seems daunting, HART buildings are formulaic, not only in layout but in orientation.
+ * @param obbasemesh the globally unique identifier of the orbital shuttle pad,
+ * an amenity of an `orbital_building_*`
+ * @param position a very specific position near the center of the `orbital_building_*` building
+ * @param mountPoints the assignment of mount point for each denial field
+ * @return a list of environmental representations
+ */
+ private def hartGantryDenialFields(
+ obbasemesh: PlanetSideGUID,
+ position: Vector3,
+ mountPoints: Seq[Int]
+ ): List[PieceOfEnvironment] = {
+ val px: Float = position.x
+ val py: Float = position.y
+ val pz: Float = position.z
+ val wall: Float = 14.7188f
+ val door: Float = 55.9219f
+ val gantry: Float = 45.9297f
+ val lower: Float = pz + 6.164608f
+ val upper: Float = pz + 17.508358f
+ //downstairs lobbies are listed before upstairs lobbies to ensure they are tested first
+ List(
+ GantryDenialField(obbasemesh, mountPoints(0), DeepSurface(lower, py + wall, px + door, py + 1, px + gantry)), //NE
+ GantryDenialField(obbasemesh, mountPoints(1), DeepSurface(lower, py - 1, px + door, py - wall, px + gantry)), //SE
+ GantryDenialField(obbasemesh, mountPoints(2), DeepSurface(lower, py + wall, px - gantry, py + 1, px - door)), //NW
+ GantryDenialField(obbasemesh, mountPoints(3), DeepSurface(lower, py - 1, px - gantry, py - wall, px - door)), //SW
+ GantryDenialField(obbasemesh, mountPoints(4), DeepSurface(upper, py + wall, px + door, py + 1, px + gantry)), //NE
+ GantryDenialField(obbasemesh, mountPoints(5), DeepSurface(upper, py - 1, px + door, py - wall, px + gantry)), //SE
+ GantryDenialField(obbasemesh, mountPoints(6), DeepSurface(upper, py + wall, px - gantry, py + 1, px - door)), //NW
+ GantryDenialField(obbasemesh, mountPoints(7), DeepSurface(upper, py - 1, px - gantry, py - wall, px - door)) //SW
+ )
+ }
+
+ /**
+ * Generate eight environmental representations that serve to eject players
+ * from the high altitude rapid transport (HART) building boarding hallways
+ * when the HART shuttle associated with that building is no longer boarding
+ * and the doors to those hallways should deny entrance.
+ * When kicked out of the hallway,
+ * ejected players should be placed in the same position as if the player willingly dismounted the shuttle.
+ *
+ * The New Conglomerate HART A campus building is at an ordinal angle
+ * which makes the typical axis-aligned environment geometry unsuitable for representation of the denial field.
+ * Instead of rectangles, circles will be used.
+ * This facility is centered at 4816, 3506, 68.73806 (x ,y, z).
+ * @param obbasemesh the globally unique identifier of the orbital shuttle pad,
+ * an amenity of an `orbital_building_*`
+ * @return a list of environmental representations
+ */
+ def specialHartGantryDenialFields(obbasemesh: PlanetSideGUID): List[PieceOfEnvironment] = {
+ val lower: Float = 74.902668f
+ val upper: Float = 86.246418f
+ val radius: Float = 6.5f
+ //downstairs lobbies are listed before upstairs lobbies to ensure they are tested first
+ List(
+ GantryDenialField(obbasemesh, 1, DeepCircularSurface(Vector3(4846f, 3547.6016f, lower), radius)), //N
+ GantryDenialField(obbasemesh, 2, DeepCircularSurface(Vector3(4857.5234f, 3536f, lower), radius)), //E
+ GantryDenialField(obbasemesh, 5, DeepCircularSurface(Vector3(4774.3516f, 3476f, lower), radius)), //W
+ GantryDenialField(obbasemesh, 6, DeepCircularSurface(Vector3(4786f, 3464.4453f, lower), radius)), //S
+ GantryDenialField(obbasemesh, 3, DeepCircularSurface(Vector3(4846f, 3547.6016f, upper), radius)), //N
+ GantryDenialField(obbasemesh, 4, DeepCircularSurface(Vector3(4857.5234f, 3536f, upper), radius)), //E
+ GantryDenialField(obbasemesh, 7, DeepCircularSurface(Vector3(4774.3516f, 3476f, upper), radius)), //W
+ GantryDenialField(obbasemesh, 8, DeepCircularSurface(Vector3(4786f, 3464.4453f, upper), radius)) //S
+ )
+ }
+}
+
diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala
index f42442086..92d17f65c 100644
--- a/src/main/scala/net/psforever/objects/zones/Zone.scala
+++ b/src/main/scala/net/psforever/objects/zones/Zone.scala
@@ -40,13 +40,17 @@ import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.avatar.Avatar
import net.psforever.objects.geometry.Geometry3D
import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.doors.Door
+import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
+import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.vehicles.UtilityType
import net.psforever.objects.vital.etc.{EmpReason, ExplodingEntityReason}
-import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.prop.DamageWithPosition
+import net.psforever.objects.vital.Vitality
+import net.psforever.services.Service
/**
* A server object representing the one-landmass planets as well as the individual subterranean caverns.
@@ -647,6 +651,13 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
case (None, _) | (_, None) => ; //let ZoneActor's sanity check catch this error
}
})
+ //doors with nearby locks use those locks as their unlocking mechanism
+ //let ZoneActor's sanity check catch missing entities
+ map.doorToLock
+ .map { case(doorGUID: Int, lockGUID: Int) => (guid(doorGUID), guid(lockGUID)) }
+ .collect { case (Some(door: Door), Some(lock: IFFLock)) =>
+ door.Actor ! Door.UpdateMechanism(IFFLock.testLock(lock))
+ }
//ntu management (eventually move to a generic building startup function)
buildings.values
.flatMap(_.Amenities.filter(_.Definition == GlobalDefinitions.resource_silo))
@@ -661,6 +672,12 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
case painbox: Painbox =>
painbox.Actor ! "startup"
}
+ //the orbital_buildings in sanctuary zones have to establish their shuttle routes
+ map.shuttleBays
+ .map { guid(_) }
+ .collect { case Some(obj: OrbitalShuttlePad) =>
+ obj.Actor ! Service.Startup()
+ }
//allocate soi information
soi ! SOI.Build()
}
@@ -924,6 +941,10 @@ object Zone {
final case class Despawn(vehicle: Vehicle)
+ final case class HasSpawned(zone: Zone, vehicle: Vehicle)
+
+ final case class HasDespawned(zone: Zone, vehicle: Vehicle)
+
final case class CanNotSpawn(zone: Zone, vehicle: Vehicle, reason: String)
final case class CanNotDespawn(zone: Zone, vehicle: Vehicle, reason: String)
@@ -1247,6 +1268,7 @@ object Zone {
def distanceCheck(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float): Boolean = {
distanceCheck(obj1.Definition.Geometry(obj1), obj2.Definition.Geometry(obj2), maxDistance)
}
+
/**
* Two game entities are considered "near" each other if they are within a certain distance of one another.
* @param g1 the geometric representation of a game entity
diff --git a/src/main/scala/net/psforever/objects/zones/ZoneMap.scala b/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
index 47a755e7a..63e34e28e 100644
--- a/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
+++ b/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
@@ -33,12 +33,13 @@ class ZoneMap(val name: String) {
var checksum: Long = 0
var zipLinePaths: List[ZipLinePath] = List()
var cavern: Boolean = false
- var environment: List[PieceOfEnvironment] = List()
+ var environment: List[PieceOfEnvironment] = List()
private var linkTurretWeapon: Map[Int, Int] = Map()
private var linkTerminalPad: Map[Int, Int] = Map()
private var linkTerminalInterface: Map[Int, Int] = Map()
private var linkDoorLock: Map[Int, Int] = Map()
private var linkObjectBase: Map[Int, Int] = Map()
+ private var containsShuttle: List[Int] = List()
private var buildings: Map[(String, Int, Int), FoundationBuilder] = Map()
private var lattice: Set[(String, String)] = Set()
@@ -116,6 +117,12 @@ class ZoneMap(val name: String) {
linkTurretWeapon = linkTurretWeapon ++ Map(turretGuid -> weaponGuid)
}
+ def shuttleBays: List[Int] = containsShuttle
+
+ def linkShuttleToBay(shuttleBayGuid: Int): Unit = {
+ containsShuttle = containsShuttle :+ shuttleBayGuid
+ }
+
def latticeLink: Set[(String, String)] = lattice
def addLatticeLink(source: String, target: String): Unit = {
diff --git a/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala b/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
index a70b18459..3c73b4162 100644
--- a/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
+++ b/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
@@ -44,6 +44,7 @@ class ZoneVehicleActor(zone: Zone, vehicleList: ListBuffer[Vehicle]) extends Act
vehicle.Actor =
context.actorOf(Props(classOf[VehicleControl], vehicle), PlanetSideServerObject.UniqueActorName(vehicle))
}
+ sender() ! Zone.Vehicle.HasSpawned(zone, vehicle)
case Zone.Vehicle.Despawn(vehicle) =>
ZoneVehicleActor.recursiveFindVehicle(vehicleList.iterator, vehicle) match {
@@ -51,6 +52,7 @@ class ZoneVehicleActor(zone: Zone, vehicleList: ListBuffer[Vehicle]) extends Act
vehicleList.remove(index)
context.stop(vehicle.Actor)
vehicle.Actor = Default.Actor
+ sender() ! Zone.Vehicle.HasDespawned(zone, vehicle)
case None => ;
sender() ! Zone.Vehicle.CanNotDespawn(zone, vehicle, "can not find")
}
diff --git a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
index ad006ed21..b56d667fa 100644
--- a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
+++ b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala
@@ -401,16 +401,16 @@ object GamePacketOpcode extends Enumeration {
case 0x50 => game.TargetingInfoMessage.decode
case 0x51 => game.TriggerEffectMessage.decode
case 0x52 => game.WeaponDryFireMessage.decode
- case 0x53 => noDecoder(DroppodLaunchRequestMessage)
+ case 0x53 => game.DroppodLaunchRequestMessage.decode
case 0x54 => game.HackMessage.decode
- case 0x55 => noDecoder(DroppodLaunchResponseMessage)
+ case 0x55 => game.DroppodLaunchResponseMessage.decode
case 0x56 => game.GenericObjectActionMessage.decode
case 0x57 => game.AvatarVehicleTimerMessage.decode
// 0x58
case 0x58 => game.AvatarImplantMessage.decode
case 0x59 => noDecoder(UnknownMessage89)
case 0x5a => game.DelayedPathMountMsg.decode
- case 0x5b => noDecoder(OrbitalShuttleTimeMsg)
+ case 0x5b => game.OrbitalShuttleTimeMsg.decode
case 0x5c => noDecoder(AIDamage)
case 0x5d => game.DeployObjectMessage.decode
case 0x5e => game.FavoritesRequest.decode
diff --git a/src/main/scala/net/psforever/packet/game/DroppodFreefallingMessage.scala b/src/main/scala/net/psforever/packet/game/DroppodFreefallingMessage.scala
index c683e8edc..aa57a4d55 100644
--- a/src/main/scala/net/psforever/packet/game/DroppodFreefallingMessage.scala
+++ b/src/main/scala/net/psforever/packet/game/DroppodFreefallingMessage.scala
@@ -3,10 +3,32 @@ package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
import net.psforever.types.{Angular, PlanetSideGUID, Vector3}
+import scodec.Attempt.Successful
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
+/**
+ * Dispatched by the server to trigger a droppod's traditional behavior of plummeting from lower orbit like a rock and
+ * slowing to a gentle land, breaking apart like flower petals to introduce a soldier to the battlefield.
+ *
+ * Only works on droppod-type vehicles.
+ * Only works if a client avatar is mounted in the vehicle.
+ * The furthest the vehicle will fall is determined by that avatar player's interaction with the ground.
+ * The camera is maneuvered in three ways -
+ * where it starts,
+ * where it tracks the falling vehicle,
+ * where it zooms in upon landing.
+ * Only the "where it starts" portion of the camera is slightly manipulable.
+ * @param guid the global unique identifier of the droppod
+ * @param pos the position of the droppod
+ * @param vel how quickly the droppod is moving
+ * @param pos2 suggestion for positioning external viewpoint while observing the droppod descending;
+ * the most common offset from the model position was `Vector3(-20, 1.156f, -50)`
+ * @param orientation1 na;
+ * the y-component is usually 70.3125f
+ * @param orientation2 na
+ */
final case class DroppodFreefallingMessage(
guid: PlanetSideGUID,
pos: Vector3,
@@ -21,25 +43,23 @@ final case class DroppodFreefallingMessage(
}
object DroppodFreefallingMessage extends Marshallable[DroppodFreefallingMessage] {
+ private val rotation: Codec[Vector3] = (
+ Angular.codec_roll ::
+ Angular.codec_pitch ::
+ Angular.codec_yaw()
+ ).narrow[Vector3](
+ {
+ case u :: v :: w :: HNil => Successful(Vector3(u, v, w))
+ },
+ v => v.x :: v.y :: v.z :: HNil
+ )
+
implicit val codec: Codec[DroppodFreefallingMessage] = (
("guid" | PlanetSideGUID.codec) ::
- ("pos" | Vector3.codec_float) ::
- ("vel" | Vector3.codec_float) ::
- ("pos2" | Vector3.codec_float) ::
- ("unkA" | Angular.codec_roll) ::
- ("unkB" | Angular.codec_pitch) ::
- ("unkC" | Angular.codec_yaw()) ::
- ("unkD" | Angular.codec_roll) ::
- ("unkE" | Angular.codec_pitch) ::
- ("unkF" | Angular.codec_yaw())
- ).xmap[DroppodFreefallingMessage](
- {
- case guid :: pos :: vel :: pos2 :: uA :: uB :: uC :: uD :: uE :: uF :: HNil =>
- DroppodFreefallingMessage(guid, pos, vel, pos2, Vector3(uA, uB, uC), Vector3(uD, uE, uF))
- },
- {
- case DroppodFreefallingMessage(guid, pos, vel, pos2, Vector3(uA, uB, uC), Vector3(uD, uE, uF)) =>
- guid :: pos :: vel :: pos2 :: uA :: uB :: uC :: uD :: uE :: uF :: HNil
- }
- )
+ ("pos" | Vector3.codec_float) ::
+ ("vel" | Vector3.codec_float) ::
+ ("pos2" | Vector3.codec_float) ::
+ ("orientation1" | rotation) ::
+ ("orientation2" | rotation)
+ ).as[DroppodFreefallingMessage]
}
diff --git a/src/main/scala/net/psforever/packet/game/DroppodLaunchInfo.scala b/src/main/scala/net/psforever/packet/game/DroppodLaunchInfo.scala
new file mode 100644
index 000000000..72e73d225
--- /dev/null
+++ b/src/main/scala/net/psforever/packet/game/DroppodLaunchInfo.scala
@@ -0,0 +1,37 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.packet.game
+
+import net.psforever.types.{PlanetSideGUID, Vector3}
+import scodec.Attempt.Successful
+import scodec.Codec
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * Information related to this droppod event.
+ * @see `DroppodLaunchRequestMessage`
+ * @see `DroppodLaunchResponseMessage`
+ * @param guid the player using the droppod
+ * @param zone_number the zone to which the player desires transportation
+ * @param xypos where in the zone (relative to the ground) the player will be placed
+ */
+final case class DroppodLaunchInfo(
+ guid: PlanetSideGUID,
+ zone_number: Int,
+ xypos: Vector3
+ )
+
+object DroppodLaunchInfo {
+ val codec: Codec[DroppodLaunchInfo] = (
+ ("guid" | PlanetSideGUID.codec) ::
+ ("zone_number" | uint16L) ::
+ (floatL :: floatL).narrow[Vector3](
+ {
+ case x :: y :: HNil => Successful(Vector3(x, y, 0))
+ },
+ {
+ case Vector3(x, y, _) => x :: y :: HNil
+ }
+ )
+ ).as[DroppodLaunchInfo]
+}
diff --git a/src/main/scala/net/psforever/packet/game/DroppodLaunchRequestMessage.scala b/src/main/scala/net/psforever/packet/game/DroppodLaunchRequestMessage.scala
new file mode 100644
index 000000000..bfab848b9
--- /dev/null
+++ b/src/main/scala/net/psforever/packet/game/DroppodLaunchRequestMessage.scala
@@ -0,0 +1,47 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.packet.game
+
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
+import net.psforever.types.{PlanetSideGUID, Vector3}
+import scodec.Codec
+import scodec.codecs._
+
+/**
+ * Dispatched from the client to indicate the player wishes to use an orbital droppod
+ * to rapidly deploy into a zone at a pre-approved position.
+ *
+ * Follows after an instance of "player stasis" where they are permitted to make this sort of selection
+ * by referencing a zone from the interstellar deployment map.
+ * This is the conclusion of utilizing the high altitude rapid transport (HART) system
+ * though does not need to be limited only to prior use of the orbital shuttle.
+ * @see `PlayerStasisMessage`
+ * @param info information related to this droppod event
+ * @param unk na;
+ * consistently 3
+ */
+final case class DroppodLaunchRequestMessage(
+ info: DroppodLaunchInfo,
+ unk: Int
+ ) extends PlanetSideGamePacket {
+ type Packet = DroppodLaunchRequestMessage
+ def opcode = GamePacketOpcode.DroppodLaunchRequestMessage
+ def encode = DroppodLaunchRequestMessage.encode(this)
+}
+
+object DroppodLaunchRequestMessage extends Marshallable[DroppodLaunchRequestMessage] {
+ /**
+ * Overloaded constructor that ignores the last field.
+ * Existing fields match `DroppodLaunchInfo`.
+ * @param guid the player using the droppod
+ * @param zoneNumber the zone to which the player desires transportation
+ * @param pos where in the zone (relative to the ground) the player will be placed
+ * @return a `DroppodLaunchRequestMessage` packet
+ */
+ def apply(guid: PlanetSideGUID, zoneNumber: Int, pos: Vector3): DroppodLaunchRequestMessage =
+ DroppodLaunchRequestMessage(DroppodLaunchInfo(guid, zoneNumber, pos), 3)
+
+ implicit val codec: Codec[DroppodLaunchRequestMessage] = (
+ DroppodLaunchInfo.codec ::
+ ("unk" | uint2)
+ ).as[DroppodLaunchRequestMessage]
+}
diff --git a/src/main/scala/net/psforever/packet/game/DroppodLaunchResponseMessage.scala b/src/main/scala/net/psforever/packet/game/DroppodLaunchResponseMessage.scala
new file mode 100644
index 000000000..10bc1038d
--- /dev/null
+++ b/src/main/scala/net/psforever/packet/game/DroppodLaunchResponseMessage.scala
@@ -0,0 +1,178 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.packet.game
+
+import enumeratum.values.{IntEnum, IntEnumEntry}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.types.{PlanetSideGUID, Vector3}
+import scodec.Codec
+import scodec.codecs._
+
+/**
+ * The types of errors that can be reported when attempting to droppod into a zone.
+ *
+ * All codes show the preceding text in the events chat window.
+ * The typo in the message from `BlockedBySOI` can not be resolved by populating any of the greater packet's fields.
+ * `ZoneFullWarpQueue` utilizes the additional packet fields to establish the warp queue prompt
+ * with the warp queue and the player's position in that queue.
+ * The zone to which the player desires transportation is defined elsewhere in the greater packet.
+ */
+sealed abstract class DroppodError(val value: Int, val message: String) extends IntEnumEntry
+
+object DroppodError extends IntEnum[DroppodError] {
+ val values = findValues
+
+ case object ContinentNotAvailable extends DroppodError(
+ value = 1,
+ message = "That continent is not available - please choose another one."
+ )
+
+ case object BlockedBySOI extends DroppodError(
+ value = 2,
+ message = "That location is within a 's Sphere of Influence (SOI). Please try another location." //typo intentional
+ )
+
+ case object InvalidLocation extends DroppodError(
+ value = 3,
+ message = "That is an invalid drop location - please try another location."
+ )
+
+ case object ZoneNotAvailable extends DroppodError(
+ value = 4,
+ message = "This zone is not available - try another zone."
+ )
+
+ case object ZoneFull extends DroppodError(
+ value = 5,
+ message = "That zone is already full of battle hungry people - try another one."
+ )
+
+ case object EnemyBase extends DroppodError(
+ value = 6,
+ message = "You can not drop onto an enemy home base - please choose a valid continent."
+ )
+
+ case object NotOnHart extends DroppodError(
+ value = 7,
+ message = "You are attempting to drop but are not on the HART - be warned you are being watched."
+ )
+
+ case object OwnFactionLocked extends DroppodError(
+ value = 8,
+ message = "You cannot drop onto a continent that is locked to your empire - please choose a valid continent."
+ )
+
+ case object ZoneFullWarpQueue extends DroppodError(
+ value = 9,
+ message = "The zone you are trying to warp to is currently full. You have been placed in the warp queue."
+ )
+}
+
+/**
+ * Information displayed on the zone warp queue in terms of queue size and queue progression.
+ * @param queue_size the number of players trying to warp to this zone in the queue ('b' if a/b)
+ * @param place the player's spot in the queue ('a' if a/b)
+ */
+final case class WarpQueuePrompt(queue_size: Long, place: Long)
+
+/**
+ * Dispatched from the client to indicate the player wished to use an orbital droppod
+ * but the player will be denied that request for a specific reason.
+ * The reason manifests as text appended to the event chat window.
+ * Occasionally, a supplemental window will open with additional information about a delayed action (warp queue).
+ * @see `DroppodLaunchInfo`
+ * @param error_code the error reporting why the zoning through droppod use failed
+ * @param launch_info information related to this droppod event
+ * @param queue_info if the error invokes the warp queue, the current information about the state of the queue
+ * @throws AssertionError if the error code requires additional fields
+ */
+final case class DroppodLaunchResponseMessage(
+ error_code: DroppodError,
+ launch_info: DroppodLaunchInfo,
+ queue_info: Option[WarpQueuePrompt]
+ ) extends PlanetSideGamePacket {
+ assert(
+ error_code != DroppodError.ZoneFullWarpQueue || queue_info.isDefined,
+ "ZoneFullWarpQueue requires queue information"
+ )
+ type Packet = DroppodLaunchResponseMessage
+ def opcode = GamePacketOpcode.DroppodLaunchResponseMessage
+ def encode = DroppodLaunchResponseMessage.encode(this)
+}
+
+object DroppodLaunchResponseMessage extends Marshallable[DroppodLaunchResponseMessage] {
+ /**
+ * Overloaded constructor for most errors.
+ * @param error the error reporting why the zoning through droppod use failed
+ * @param guid the player using the droppod
+ * @return a `DroppodLaunchResponseMessage` packet
+ */
+ def apply(error: DroppodError, guid: PlanetSideGUID): DroppodLaunchResponseMessage = {
+ DroppodLaunchResponseMessage(error, guid, 0, Vector3.Zero)
+ }
+
+ /**
+ * Overloaded constructor for most errors.
+ * @param error the error reporting why the zoning through droppod use failed
+ * @param guid the player using the droppod
+ * @param zoneNumber the zone to which the player desires transportation
+ * @param xypos where in the zone (relative to the ground) the player will be placed
+ * @return a `DroppodLaunchResponseMessage` packet
+ */
+ def apply(error: DroppodError, guid: PlanetSideGUID, zoneNumber: Int, xypos: Vector3): DroppodLaunchResponseMessage = {
+ DroppodLaunchResponseMessage(error, DroppodLaunchInfo(guid, zoneNumber, xypos))
+ }
+
+ /**
+ * Overloaded constructor for quickly reflecting errors.
+ * @param error the error reporting why the zoning through droppod use failed
+ * @param info information related to this droppod event
+ * @return a `DroppodLaunchResponseMessage` packet
+ */
+ def apply(error: DroppodError, info: DroppodLaunchInfo): DroppodLaunchResponseMessage = {
+ DroppodLaunchResponseMessage(error, info, None)
+ }
+
+ /**
+ * Overloaded constructor for `ZoneFullWarpQueue` errors.
+ * @param guid the player using the droppod
+ * @param zoneNumber the zone to which the player desires transportation
+ * @param queueSize the number of players trying to warp to this zone in the queue ('b' if a/b)
+ * @param placeInQueue the player's spot in the queue ('a' if a/b)
+ * @return a `DroppodLaunchResponseMessage` packet
+ */
+ def apply(guid: PlanetSideGUID, zoneNumber: Int, queueSize: Int, placeInQueue: Int): DroppodLaunchResponseMessage = {
+ DroppodLaunchResponseMessage(
+ DroppodLaunchInfo(guid, zoneNumber, Vector3.Zero),
+ queueSize, placeInQueue
+ )
+ }
+
+ /**
+ * Overloaded constructor for quickly reflecting `ZoneFullWarpQueue` errors.
+ * @param info information related to this droppod event
+ * @param queueSize the number of players trying to warp to this zone in the queue ('b' if a/b)
+ * @param placeInQueue the player's spot in the queue ('a' if a/b)
+ * @return a `DroppodLaunchResponseMessage` packet
+ */
+ def apply(info: DroppodLaunchInfo, queueSize: Int, placeInQueue: Int): DroppodLaunchResponseMessage = {
+ DroppodLaunchResponseMessage(
+ DroppodError.ZoneFullWarpQueue,
+ info,
+ Some(WarpQueuePrompt(queueSize, placeInQueue))
+ )
+ }
+
+ private val droppodErrorCodec: Codec[DroppodError] = PacketHelpers.createIntEnumCodec(DroppodError, uint4)
+
+ private val extra_codec: Codec[WarpQueuePrompt] = (
+ ("place" | uint32L) ::
+ ("queue_size" | uint32L)
+ ).as[WarpQueuePrompt]
+
+ implicit val codec: Codec[DroppodLaunchResponseMessage] = (
+ ("error_code" | droppodErrorCodec) >>:~ { ecode =>
+ ("launch_info" | DroppodLaunchInfo.codec) ::
+ ("queue_info" | conditional(ecode == DroppodError.ZoneFullWarpQueue, extra_codec))
+ }
+ ).as[DroppodLaunchResponseMessage]
+}
diff --git a/src/main/scala/net/psforever/packet/game/GenericActionMessage.scala b/src/main/scala/net/psforever/packet/game/GenericActionMessage.scala
index a7b60f0af..3b6ae389e 100644
--- a/src/main/scala/net/psforever/packet/game/GenericActionMessage.scala
+++ b/src/main/scala/net/psforever/packet/game/GenericActionMessage.scala
@@ -39,6 +39,7 @@ import scodec.codecs._
* 16 - Max unanchor
* 20 - Client requests MAX special effect (NC shield and TR overdrive. VS jump jets are handled by the jump_thrust boolean on PlayerStateMessageUpstream)
* 21 - Disable MAX special effect (NC shield)
+ * 28 - Cancel warp queue (see: `DroppodLaunchResponseMessage`)
* 29 - AFK
* 30 - back in game
* 36 - turn on "Looking for Squad"
diff --git a/src/main/scala/net/psforever/packet/game/MountVehicleMsg.scala b/src/main/scala/net/psforever/packet/game/MountVehicleMsg.scala
index 2084239c4..5f9901099 100644
--- a/src/main/scala/net/psforever/packet/game/MountVehicleMsg.scala
+++ b/src/main/scala/net/psforever/packet/game/MountVehicleMsg.scala
@@ -12,13 +12,13 @@ import scodec.codecs._
* The client will only dispatch this packet when it feels confident that the player can get into a vehicle.
* It makes its own check whether or not to display that "enter vehicle here" icon on the ground.
* This is called an "entry point."
- * Entry points and seat numbers are not required as one-to-one;
- * multiple entry points can lead to the same seat, such as the driver seat of an ANT.
+ * Entry points and mount numbers are not required as one-to-one;
+ * multiple entry points can lead to the same mount, such as the driver mount of an ANT.
*
* The player is not allowed to board anything until the server responds in affirmation.
* @param player_guid the player
* @param vehicle_guid the vehicle
- * @param entry_point the entry index that maps to a seat index, specific to the selected vehicle
+ * @param entry_point the entry index that maps to a mount index, specific to the selected vehicle
*/
final case class MountVehicleMsg(player_guid: PlanetSideGUID, vehicle_guid: PlanetSideGUID, entry_point: Int)
extends PlanetSideGamePacket {
diff --git a/src/main/scala/net/psforever/packet/game/OrbitalShuttleTimeMsg.scala b/src/main/scala/net/psforever/packet/game/OrbitalShuttleTimeMsg.scala
new file mode 100644
index 000000000..f02d1010d
--- /dev/null
+++ b/src/main/scala/net/psforever/packet/game/OrbitalShuttleTimeMsg.scala
@@ -0,0 +1,125 @@
+// Copyright (c) 2021 PSForever
+package net.psforever.packet.game
+
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.types.{HartSequence, PlanetSideGUID}
+import scodec.Codec
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+/**
+ * Paired globally unique identifier numbers,
+ * the first one being the pad (`obbasemesh`) of a HART shuttle building,
+ * the second being the shuttle itself.
+ * @param pad the HART shuttle pad
+ * @param shuttle the HART orbital shuttle
+ * @param unk a control code;
+ * has indeterminate purpose regardless of the phase expressed in the greater packet;
+ * frequently `20` but also frequently varies
+ */
+final case class PadAndShuttlePair(pad: PlanetSideGUID, shuttle: PlanetSideGUID, unk: Int)
+
+/**
+ * Control the animation state transitions of the high altitude rapid transport (HART) orbital shuttle building
+ * and the accompanying orbital shuttle model.
+ *
+ * The animation sequence is controlled primarily by the first field and
+ * goes through a strict cycle of boarding, lift shuttle, takeoff, land, lower shuttle.
+ * All HART facilities (amenity `obbasemesh`) in a given zone are controlled by this packet.
+ * Multiple systems are controlled by a single field during a given animation,
+ * e.g., the boarding gantries are retracted or extended during the same part where the shuttle is raised or lowered.
+ * Certain neutral animation states - `State0`, `State5`, and `State7` - all behave the same way
+ * though denote different points in the sequence.
+ * Animation subsequence states are coordinated by the second field,
+ * though the specific purpose of the subsequence isn't always obvious,
+ * and the field isn't always necessary to achieve the result of the primary sequence.
+ *
+ * The total time of the system is bound between two states:
+ * whether the shuttle has left or whether it is boarding.
+ * When separated ("has left"),
+ * the shuttle will be lifted out of the bay to atop the building and will fly off into the horizon,
+ * remaining despawned until it returns to view, perches atop the building again, and is lowered into the bay.
+ * When boarding,
+ * the shuttle is fixed in the bay and is accepting passengers via one of the boarding hallways.
+ * Upon boarding the shuttle, the time until takeoff ("has left") is displayed to all waiting passengers
+ * in the form of a progress bar.
+ * This progress bar is fixed to a full time of 60 seconds (60000 milliseconds) in the client and
+ * will start at fractions of completion for boarding times under 60 seconds.
+ *
+ * Pairs of globally unique identifiers for the shuttle facility and the shuttle
+ * link the time fields to their function.
+ * All facilities and shuttles in a given zone are paired and enumerated for a single packet.
+ * If the HART facility identifier is missing or incorrect,
+ * the absent facility will continue to undergo correct animation state transition,
+ * but the door timer will not animate correctly and constantly display the time 10:37 and
+ * the door lights will be neither locked closed (red) or openable (green).
+ * If the shuttle identifier is missing or incorrect,
+ * the absent shuttle will continue to undergo partially correct animation state transitions,
+ * cycling between visible and invisible atop the HART facility,
+ * and the aforementioned progress bars visible by shuttle passengers will not display during the boarding phase
+ * if the shuttle is made available for boarding.
+ * @param model_state a control code that affects the over-all state of the HART system
+ * @param unk0 na
+ * @param arrival_time the time for the orbital shuttle to return during instances when the shuttle is away;
+ * displayed on a related time near the shuttle boarding entryways;
+ * in milliseconds
+ * @param boarding_time the time for the orbital shuttle to depart during instances when the shuttle is boarding;
+ * frequently `8000L` when not in use;
+ * in milliseconds
+ * @param other_time time field used for a variety of things;
+ * in most uses, the amount of time that has passed since the start of the event,
+ * so usually `0` (at start of event);
+ * with respects to `model_state` and `unk3`:
+ * full departure time when `5`-`3` (variant of `7`-`3`);
+ * occasionally, full departure time when `0`-`0`
+ * in milliseconds
+ * @param pairs a list of entries that pair
+ * a paired facility pad unique identifier and shuttle unique identifier
+ * with a control code
+ */
+final case class OrbitalShuttleTimeMsg(
+ model_state: HartSequence,
+ unk0: Int,
+ arrival_time: Long,
+ boarding_time: Long,
+ other_time: Long,
+ pairs: List[PadAndShuttlePair]
+ )
+ extends PlanetSideGamePacket {
+ type Packet = OrbitalShuttleTimeMsg
+ def opcode = GamePacketOpcode.OrbitalShuttleTimeMsg
+ def encode = OrbitalShuttleTimeMsg.encode(this)
+}
+
+object OrbitalShuttleTimeMsg extends Marshallable[OrbitalShuttleTimeMsg] {
+ private val uint3: Codec[Int] = uint(bits = 3)
+
+ private val hartSequenceCodec: Codec[HartSequence] = PacketHelpers.createIntEnumCodec(HartSequence, uint3)
+
+ private val padShuttlePair_codec: Codec[PadAndShuttlePair] = (
+ ("pad" | PlanetSideGUID.codec) ::
+ ("shuttle" | PlanetSideGUID.codec) ::
+ ("unk" | uint(bits = 6))
+ ).as[PadAndShuttlePair]
+
+ implicit val codec: Codec[OrbitalShuttleTimeMsg] = (
+ uint3 >>:~ { size =>
+ ("model_state" | hartSequenceCodec) ::
+ ("unk0" | uint3) ::
+ ("arrival_time" | uint32L) ::
+ ("boarding_time" | uint32L) ::
+ bool ::
+ ("other_time" | uint32L) ::
+ ("pairs" | PacketHelpers.listOfNSized(size, padShuttlePair_codec))
+ }
+ ).xmap[OrbitalShuttleTimeMsg](
+ {
+ case _ :: model :: u0 :: arrival :: boarding :: _ :: other :: pairs :: HNil =>
+ OrbitalShuttleTimeMsg(model, u0, arrival, boarding, other, pairs)
+ },
+ {
+ case OrbitalShuttleTimeMsg(model, u0, arrival, boarding, other, pairs) =>
+ pairs.length :: model :: u0 :: arrival :: boarding :: true :: other :: pairs :: HNil
+ }
+ )
+}
diff --git a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
index a89222147..6a3770b18 100644
--- a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
+++ b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
@@ -179,14 +179,14 @@ import scodec.codecs._
* `228 - Player/vehicle leaves black ops`
*
* `Vehicles:`
- * `10 - Driver seat permissions`
+ * `10 - Driver mount permissions`
*