added sidedness to different entities, both in general and depending on the zone where it matters

This commit is contained in:
Fate-JH 2024-03-17 18:16:32 -04:00
parent 4508c1ae45
commit 84b3d2297a
10 changed files with 279 additions and 91 deletions

View file

@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.doors
import net.psforever.objects.Player
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.UseItemMessage
import net.psforever.types.Vector3
@ -14,6 +15,13 @@ import net.psforever.types.Vector3
class Door(private val ddef: DoorDefinition) extends Amenity {
private var openState: Option[Player] = None
private var outwards: Option[Vector3] = None
/*
* Door sidedness does not reflect on the actual sidedness of anything that is in the door.
* While sidedness will be correct for internal doors -
* they are always passages between different between interior rooms in a facility -
* external doors cross between the outside world and a interior room of a facility.
*/
WhichSide = Sidedness.InsideOf
def isOpen: Boolean = openState.isDefined

View file

@ -5,6 +5,7 @@ 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.interior.Sidedness
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.TriggeredSound
import net.psforever.types.Vector3
@ -23,6 +24,7 @@ class IFFLock(private val idef: IFFLockDefinition) extends Amenity with Hackable
HackSound = TriggeredSound.HackDoor
HackEffectDuration = Array(60, 180, 300, 360)
HackDuration = Array(5, 3, 1, 1)
WhichSide = Sidedness.InsideOf
/** a vector in the direction of the "outside" of a room;
* typically, any locking utility is on that same "outside"

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad
import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.objects.{Player, Vehicle}
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.serverobject.terminals.Terminal
@ -18,6 +19,8 @@ import net.psforever.types.PlanetSideGUID
* @param spDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class VehicleSpawnPad(spDef: VehicleSpawnPadDefinition) extends Amenity {
WhichSide = Sidedness.OutsideOf
def Definition: VehicleSpawnPadDefinition = spDef
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad.process
import akka.actor.Props
import akka.actor.{ActorRef, Props}
import akka.pattern.ask
import akka.util.Timeout
import net.psforever.objects.GlobalDefinitions
@ -29,11 +29,11 @@ import scala.util.Success
class VehicleSpawnControlLoadVehicle(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
def LogId = "-loader"
val railJack = context.actorOf(Props(classOf[VehicleSpawnControlRailJack], pad), s"${context.parent.path.name}-rails")
val railJack: ActorRef = context.actorOf(Props(classOf[VehicleSpawnControlRailJack], pad), s"${context.parent.path.name}-rails")
var temp: Option[VehicleSpawnControl.Order] = None
implicit val timeout = Timeout(3.seconds)
implicit val timeout: Timeout = Timeout(3.seconds)
def receive: Receive = {
case order @ VehicleSpawnControl.Order(driver, vehicle) =>
@ -42,6 +42,7 @@ class VehicleSpawnControlLoadVehicle(pad: VehicleSpawnPad) extends VehicleSpawnC
vehicle.Position = vehicle.Position - Vector3.z(
if (GlobalDefinitions.isFlightVehicle(vehicle.Definition)) 9 else 5
) //appear below the trench and doors
vehicle.WhichSide = pad.WhichSide
vehicle.Cloaked = vehicle.Definition.CanCloak && driver.Cloaked
temp = Some(order)

View file

@ -2,11 +2,14 @@
package net.psforever.objects.serverobject.resourcesilo
import akka.actor.{ActorContext, Props}
import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.objects.{CommonNtuContainer, GlobalDefinitions, Player}
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.UseItemMessage
import net.psforever.types.Vector3
import scala.annotation.unused
class ResourceSilo extends Amenity with CommonNtuContainer {
// For the flashing red light on top of the NTU silo on.
@ -16,6 +19,7 @@ class ResourceSilo extends Amenity with CommonNtuContainer {
// For the NTU display bar
private var capacitorDisplay: Long = 0
NtuCapacitor = Definition.MaxNtuCapacitor
WhichSide = Sidedness.OutsideOf
def MaxNtuCapacitor : Float = Definition.MaxNtuCapacitor
@ -37,7 +41,7 @@ class ResourceSilo extends Amenity with CommonNtuContainer {
def Definition: ResourceSiloDefinition = GlobalDefinitions.resource_silo
def Use(player: Player, msg: UseItemMessage): ResourceSilo.Exchange = {
def Use(@unused player: Player, @unused msg: UseItemMessage): ResourceSilo.Exchange = {
ResourceSilo.ChargeEvent()
}
}

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.structures
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.interior.{Sidedness, TraditionalInteriorAware}
import net.psforever.objects.vital.resistance.StandardResistanceProfile
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.DamageAndResistance
@ -25,7 +26,8 @@ abstract class Amenity
extends PlanetSideServerObject
with Vitality
with StandardResistanceProfile
with BlockMapEntity {
with BlockMapEntity
with TraditionalInteriorAware {
private[this] val log = org.log4s.getLogger("Amenity")
/** what other entity has authority over this amenity; usually either a building or a vehicle */
@ -34,6 +36,8 @@ abstract class Amenity
/** if the entity exists at a specific position relative to the owner's position */
private var offset: Option[Vector3] = None
WhichSide = Sidedness.InsideOf
def Faction: PlanetSideEmpire.Value = Owner.Faction
/**

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.terminals
import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.objects.{Default, Player}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.structures.Amenity
@ -17,6 +18,8 @@ import net.psforever.services.Service
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class ProximityTerminal(tdef: ProximityTerminalDefinition) extends Terminal(tdef) with ProximityUnit {
WhichSide = Sidedness.StrictlyBetweenSides
override def Request(player: Player, msg: Any): Terminal.Exchange = {
msg match {
case message: CommonMessages.Use =>

View file

@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.turret
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.objects.serverobject.structures.{Amenity, AmenityOwner, Building}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware
import net.psforever.objects.serverobject.turret.auto.AutomatedTurret
@ -14,6 +15,7 @@ class FacilityTurret(tDef: FacilityTurretDefinition)
with JammableUnit
with CaptureTerminalAware {
WeaponTurret.LoadDefinition(turret = this)
WhichSide = Sidedness.OutsideOf
def TurretOwner: SourceEntry = {
Seats
@ -36,7 +38,6 @@ class FacilityTurret(tDef: FacilityTurretDefinition)
}
object FacilityTurret {
/**
* Overloaded constructor.
* @param tDef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields

View file

@ -319,7 +319,6 @@ class VehicleControl(vehicle: Vehicle)
case Some(seatNumber) =>
val vsrc = VehicleSource(vehicle)
user.LogActivity(VehicleMountActivity(vsrc, PlayerSource.inSeat(user, vsrc, seatNumber), vehicle.Zone.Number))
obj.WhichSide = user.WhichSide
//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

View file

@ -42,8 +42,10 @@ import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.interior.{InteriorAware, Sidedness}
import net.psforever.objects.serverobject.locks.IFFLock
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal}
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vehicles.{MountedWeapons, UtilityType}
@ -662,11 +664,11 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
(buildings.get(building_id), guid(object_guid)) match {
case (Some(building), Some(amenity: Amenity)) =>
building.Amenities = amenity
//amenity.History(EntitySpawn(SourceEntry(amenity), this))
case (Some(_), _) | (None, _) | (_, None) => ; //let ZoneActor's sanity check catch this error
case (Some(_), _) | (None, _) | (_, None) => () //let ZoneActor's sanity check catch this error
}
})
Zone.AssignDoors(zone = this)
Zone.AssignOutwardSideToDoors(zone = this)
Zone.AssignSidednessToAmenities(zone = this)
//ntu management (eventually move to a generic building startup function)
buildings.values
.flatMap(_.Amenities.filter(_.Definition == GlobalDefinitions.resource_silo))
@ -880,11 +882,10 @@ object Zone {
new Zone(id, map, number)
}
private def AssignDoors(zone: Zone): Unit = {
private def AssignOutwardSideToDoors(zone: Zone): Unit = {
//let ZoneActor's sanity check catch any missing entities
val map = zone.map
if (map.cavern) {
//cavern doors
//todo there are no doors in the training zones so we may skip that
if (zone.map.cavern) {
//todo what do?
//almost all are type ancient_door and don't have many hints to determine outward-ness; there are no IFF locks
} else if (
@ -892,107 +893,269 @@ object Zone {
.filterNot(_ == PlanetSideEmpire.NEUTRAL)
.exists(fac => Zones.sanctuaryZoneNumber(fac) == zone.Number)
) {
//sanctuary doors
AssignIFFLockedDoors(zone)
//spawn building doors
val buildings = zone.Buildings.values
val amenityList = buildings
.collect {
case b
if b.Definition.Name.startsWith("VT_building_") =>
val amenities = b.Amenities
(
amenities.filter(_.Definition == GlobalDefinitions.gr_door_mb_ext),
amenities.filter(_.Definition == GlobalDefinitions.gr_door_mb_lrg),
amenities.filter(_.Definition == GlobalDefinitions.order_terminal),
amenities.filter(_.Definition == GlobalDefinitions.respawn_tube_sanctuary)
)
}
amenityList.foreach { case (entranceDoors, _, terminals, tubes) =>
entranceDoors.foreach { door =>
val doorPosition = door.Position
val closestTerminal = terminals.minBy(t => Vector3.DistanceSquared(doorPosition, t.Position))
val closestTube = tubes.minBy(t => Vector3.DistanceSquared(doorPosition, t.Position))
door.asInstanceOf[Door].Outwards = Vector3.Unit(closestTerminal.Position.xy - closestTube.Position.xy)
}
//todo training zone warp chamber doors
}
//hart building doors
buildings
.collect {
case b
if b.Definition.Name.startsWith("orbital_building_") =>
val amenities = b.Amenities
(
amenities.filter(_.Definition == GlobalDefinitions.gr_door_mb_ext),
amenities.filter(_.Definition == GlobalDefinitions.gr_door_mb_orb)
)
}
.foreach { case (entranceDoors, hartDoors) =>
entranceDoors.foreach { door =>
val doorPosition = door.Position
val closestHartDoor = hartDoors.minBy(t => Vector3.DistanceSquared(doorPosition, t.Position))
door.asInstanceOf[Door].Outwards = Vector3.Unit(doorPosition.xy - closestHartDoor.Position.xy)
}
}
AssignOutwardSideToSanctuaryDoors(zone)
} else {
//above ground zone doors
AssignIFFLockedDoors(zone)
//for major facilities, external doors in the courtyard are without locks but are paired in opposing directions
val unpairedDoors = zone.Buildings
.values
.collect {
case b
if b.BuildingType == StructureType.Facility && b.Amenities.nonEmpty =>
b.Amenities.collect {
case d: Door
if d.Definition == GlobalDefinitions.gr_door_ext && d.Outwards == Vector3.Zero =>
d
}
AssignOutwardSidetoContinentDoors(zone)
}
}
private def AssignOutwardSideToSanctuaryDoors(zone: Zone): Unit = {
val map = zone.map
val guid = zone.guid
AssignOutwardsToIFFLockedDoors(zone)
//doors with IFF locks belong to towers and are always between; the locks are always outside
map.doorToLock
.map { case (door, lock) => (guid(door), guid(lock)) }
.collect { case (Some(door: Door), Some(lock: IFFLock)) =>
door.WhichSide = Sidedness.StrictlyBetweenSides
lock.WhichSide = Sidedness.OutsideOf
}
//spawn building doors
val buildings = zone.Buildings.values
val amenityList = buildings
.collect {
case b
if b.Definition.Name.startsWith("VT_building_") =>
val amenities = b.Amenities
(
amenities.filter(_.Definition == GlobalDefinitions.gr_door_mb_ext),
amenities.filter(_.Definition == GlobalDefinitions.gr_door_mb_lrg),
amenities.filter(_.Definition == GlobalDefinitions.order_terminal),
amenities.filter(_.Definition == GlobalDefinitions.respawn_tube_sanctuary)
)
}
amenityList.foreach { case (entranceDoors, _, terminals, tubes) =>
entranceDoors.foreach { door =>
val isReallyADoor = door.asInstanceOf[Door]
val doorPosition = door.Position
val closestTerminal = terminals.minBy(t => Vector3.DistanceSquared(doorPosition, t.Position))
val closestTube = tubes.minBy(t => Vector3.DistanceSquared(doorPosition, t.Position))
isReallyADoor.WhichSide = Sidedness.StrictlyBetweenSides
isReallyADoor.Outwards = Vector3.Unit(closestTerminal.Position.xy - closestTube.Position.xy)
}
//todo training zone warp chamber doors
}
//hart building doors
buildings
.collect {
case b
if b.Definition.Name.startsWith("orbital_building_") =>
val amenities = b.Amenities
(
amenities.filter(_.Definition == GlobalDefinitions.gr_door_mb_ext),
amenities.filter(_.Definition == GlobalDefinitions.gr_door_mb_orb)
)
}
.foreach { case (entranceDoors, hartDoors) =>
entranceDoors.foreach { door =>
val isReallyADoor = door.asInstanceOf[Door]
val doorPosition = door.Position
val closestHartDoor = hartDoors.minBy(t => Vector3.DistanceSquared(doorPosition, t.Position))
isReallyADoor.WhichSide = Sidedness.StrictlyBetweenSides
isReallyADoor.Outwards = Vector3.Unit(doorPosition.xy - closestHartDoor.Position.xy)
}
var pairedDoors = Seq[(Door, Door)]()
unpairedDoors.foreach { buildingUnPairedDoors =>
var volatileUnpairedDoors = buildingUnPairedDoors
while (volatileUnpairedDoors.size > 1) {
val sampleDoor = volatileUnpairedDoors.head
}
}
private def AssignOutwardSidetoContinentDoors(zone: Zone): Unit = {
val map = zone.map
val guid = zone.guid
AssignOutwardsToIFFLockedDoors(zone)
val buildingsToDoors = zone.Buildings.values.map(b => (b, b.Amenities.collect { case d: Door => d })).toMap
//external doors with IFF locks are always between and outside, respectively
map.doorToLock
.map { case (door, lock) => (guid(door), guid(lock)) }
.collect { case (Some(door: Door), Some(lock: IFFLock))
if door.Definition.geometryInteractionRadius.nonEmpty =>
door.WhichSide = Sidedness.StrictlyBetweenSides
lock.WhichSide = Sidedness.OutsideOf
}
//for major facilities, external doors in the courtyard are paired, connected by a passage between ground and walls
//they are the only external doors that do not have iff locks
buildingsToDoors
.filter { case (b, _) => b.BuildingType == StructureType.Facility }
.foreach { case (_, doors) =>
var unpairedDoors = doors.collect {
case d: Door
if d.Definition == GlobalDefinitions.gr_door_ext && !map.doorToLock.contains(d.GUID.guid) =>
d
}
var pairedDoors = Seq[(Door, Door)]()
while (unpairedDoors.size > 1) {
val sampleDoor = unpairedDoors.head
val sampleDoorPosition = sampleDoor.Position.xy
val distances = Float.MaxValue +: volatileUnpairedDoors
val distances = Float.MaxValue +: unpairedDoors
.map(d => Vector3.DistanceSquared(d.Position.xy, sampleDoorPosition))
.drop(1)
val min = distances.min
val indexOfClosestDoor = distances.indexWhere(_ == min)
val otherDoor = volatileUnpairedDoors(indexOfClosestDoor)
volatileUnpairedDoors = volatileUnpairedDoors.slice(1, indexOfClosestDoor) ++ volatileUnpairedDoors.drop(indexOfClosestDoor + 1)
val otherDoor = unpairedDoors(indexOfClosestDoor)
unpairedDoors = unpairedDoors.slice(1, indexOfClosestDoor) ++ unpairedDoors.drop(indexOfClosestDoor + 1)
pairedDoors = pairedDoors :+ (sampleDoor, otherDoor)
}
pairedDoors.foreach { case (door1, door2) =>
//give each paired courtyard door an outward-ness
val outwards = Vector3.Unit(door1.Position.xy - door2.Position.xy)
door1.Outwards = outwards
door1.WhichSide = Sidedness.StrictlyBetweenSides
door2.Outwards = Vector3.neg(outwards)
door2.WhichSide = Sidedness.StrictlyBetweenSides
}
}
pairedDoors.foreach { case (door1, door2) =>
//give each paired courtyard door an outward-ness
val outwards = Vector3.Unit(door1.Position.xy - door2.Position.xy)
door1.Outwards = outwards
door2.Outwards = Vector3.neg(outwards)
//bunkers do not define a formal interior, so their doors are solely exterior
buildingsToDoors
.filter { case (b, _) => b.BuildingType == StructureType.Bunker }
.foreach { case (_, doors) =>
doors.foreach(_.WhichSide = Sidedness.OutsideOf)
}
//bunker doors do not define an interior
}
}
private def AssignIFFLockedDoors(zone: Zone): Unit = {
val map = zone.map
private def AssignOutwardsToIFFLockedDoors(zone: Zone): Unit = {
val guid = zone.guid
val invalidOutwards = Vector3(0,0,-1) //down
//doors with nearby locks use those locks as their unlocking mechanism and their outwards indication
map.doorToLock
zone.map.doorToLock
.map { case (doorGUID: Int, lockGUID: Int) => (guid(doorGUID), guid(lockGUID)) }
.collect {
case (Some(door: Door), Some(lock: IFFLock)) =>
door.Outwards = lock.Outwards
door.Actor ! Door.UpdateMechanism(IFFLock.testLock(lock))
case (Some(door: Door), _) =>
door.Outwards = invalidOutwards
case _ => ()
}
}
private def AssignSidednessToAmenities(zone: Zone): Unit = {
//let ZoneActor's sanity check catch any missing entities
//todo training zones, where everything is outside
if (zone.map.cavern) {
//todo what do?
/*
quite a few amenities are disconnected from buildings
there are two orientations of terminal/spawn pad
as aforementioned, door outwards and sidedness is not assignable at the moment
*/
} else if (
PlanetSideEmpire.values
.filterNot(_ == PlanetSideEmpire.NEUTRAL)
.exists(fac => Zones.sanctuaryZoneNumber(fac) == zone.Number)
) {
AssignSidednessToSanctuaryAmenities(zone)
} else {
AssignSidednessToContinentAmenities(zone)
}
}
private def AssignSidednessToSanctuaryAmenities(zone: Zone): Unit = {
val map = zone.map
val guid = zone.guid
//only tower doors possess locks and those are always external
map.doorToLock
.map { case (_, lock) => guid(lock) }
.collect {
case Some(lock: IFFLock) =>
lock.WhichSide = Sidedness.OutsideOf
}
//medical terminals are always inside
zone.buildings
.values
.flatMap(_.Amenities)
.collect {
case pt: ProximityTerminal if pt.Definition == GlobalDefinitions.medical_terminal =>
pt.WhichSide = Sidedness.InsideOf
}
//repair silos and landing pads have multiple components and all of these are outside
//we have to search all terminal entities because the repair silos are not installed anywhere
guid
.GetPool(name = "terminals")
.map(_.Numbers.flatMap(number => guid(number)))
.getOrElse(List())
.collect {
case pt: ProximityTerminal
if pt.Definition == GlobalDefinitions.repair_silo =>
val guid = pt.GUID.guid
Seq(guid, guid + 1, guid + 2, guid + 3)
case pt: ProximityTerminal
if pt.Definition.Name.startsWith("pad_landing_") =>
val guid = pt.GUID.guid
Seq(guid, guid + 1, guid + 2)
}
.flatten[Int]
.map(guid(_))
.collect {
case Some(pt: ProximityTerminal) =>
pt.WhichSide = Sidedness.OutsideOf
}
//the following terminals are installed outside
map.terminalToSpawnPad
.keys
.flatMap(guid(_))
.collect {
case terminal: Terminal =>
terminal.WhichSide = Sidedness.OutsideOf
}
}
private def AssignSidednessToContinentAmenities(zone: Zone): Unit = {
val map = zone.map
val guid = zone.guid
val buildingsMap = zone.buildings.values
//door locks on external doors are also external while the door is merely "between"; all other locks are internal
map.doorToLock
.map { case (door, lock) => (guid(door), guid(lock))}
.collect {
case (Some(door: Door), Some(lock: IFFLock))
if door.Definition.geometryInteractionRadius.nonEmpty =>
lock.WhichSide = Sidedness.OutsideOf
}
//medical terminals are always inside
buildingsMap
.flatMap(_.Amenities)
.collect {
case pt: ProximityTerminal
if pt.Definition == GlobalDefinitions.medical_terminal || pt.Definition == GlobalDefinitions.adv_med_terminal =>
pt.WhichSide = Sidedness.InsideOf
}
//repair silos and landing pads have multiple components and all of these are outside
buildingsMap
.flatMap(_.Amenities)
.collect {
case pt: ProximityTerminal
if pt.Definition == GlobalDefinitions.repair_silo =>
val guid = pt.GUID.guid
Seq(guid, guid + 1, guid + 2, guid + 3)
case pt: ProximityTerminal
if pt.Definition.Name.startsWith("pad_landing_") =>
val guid = pt.GUID.guid
Seq(guid, guid + 1, guid + 2)
}
.toSeq
.flatten[Int]
.map(guid(_))
.collect {
case Some(pt: ProximityTerminal) =>
pt.WhichSide = Sidedness.OutsideOf
}
//all vehicle spawn pads are outside, save for the ground vehicle pad in the tech plants
buildingsMap.collect {
case b
if b.Definition == GlobalDefinitions.tech_plant =>
b.Amenities
.collect { case pad: VehicleSpawnPad => pad }
.minBy(_.Position.z)
.WhichSide = Sidedness.InsideOf
}
//all vehicle terminals are outside of their owning facilities in the courtyard
//the only exceptions are vehicle terminals in tech plants and the dropship center air terminal
map.terminalToSpawnPad
.keys
.flatMap(guid(_))
.collect {
case terminal: Terminal
if terminal.Definition != GlobalDefinitions.dropship_vehicle_terminal &&
terminal.Owner.asInstanceOf[Building].Definition != GlobalDefinitions.tech_plant =>
terminal.WhichSide = Sidedness.OutsideOf
}
}
object Population {
/**