Better Respawn (#810)

* biolab spawn benefit should now be active; spawn point integrity at point of player avatar creation, or else try again

* streamlined the process of determining whether a facility possesses individual benefits; warp gates do not have benefits

* fix for mounted turret persistance
This commit is contained in:
Fate-JH 2021-05-09 07:17:49 -04:00 committed by GitHub
parent 97b4ad8f67
commit 010b1c5845
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 196 additions and 95 deletions

View file

@ -200,6 +200,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
var lastTerminalOrderFulfillment: Boolean = true var lastTerminalOrderFulfillment: Boolean = true
var shiftPosition: Option[Vector3] = None var shiftPosition: Option[Vector3] = None
var shiftOrientation: Option[Vector3] = None var shiftOrientation: Option[Vector3] = None
var nextSpawnPoint: Option[SpawnPoint] = None
var setupAvatarFunc: () => Unit = AvatarCreate var setupAvatarFunc: () => Unit = AvatarCreate
var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally
var persist: () => Unit = NoPersistence var persist: () => Unit = NoPersistence
@ -509,14 +510,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(0) => case Some(0) =>
deadState = DeadState.Release // cancel movement updates deadState = DeadState.Release // cancel movement updates
vehicle.Position = position vehicle.Position = position
LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds) LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds, None)
case _ => // not seated as the driver, in which case we can't move case _ => // not seated as the driver, in which case we can't move
} }
case None => case None =>
deadState = DeadState.Release // cancel movement updates deadState = DeadState.Release // cancel movement updates
player.Position = position player.Position = position
// continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, player.GUID)) // continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, player.GUID))
LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds) LoadZonePhysicalSpawnPoint(zoneId, position, Vector3.Zero, 0 seconds, None)
case _ => // seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move case _ => // seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move
} }
} }
@ -530,7 +531,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(0) => case Some(0) =>
deadState = DeadState.Release // cancel movement updates deadState = DeadState.Release // cancel movement updates
vehicle.Position = position vehicle.Position = position
LoadZonePhysicalSpawnPoint(continent.id, position, Vector3.z(vehicle.Orientation.z), 0 seconds) LoadZonePhysicalSpawnPoint(continent.id, position, Vector3.z(vehicle.Orientation.z), 0 seconds, None)
case _ => // not seated as the driver, in which case we can't move case _ => // not seated as the driver, in which case we can't move
} }
case None => case None =>
@ -610,7 +611,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
deadState = DeadState.Release deadState = DeadState.Release
sendResponse(AvatarDeadStateMessage(DeadState.Release, 0, 0, player.Position, player.Faction, true)) 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 interstellarFerry = Some(v) //on the other continent and registered to that continent's GUID system
LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds) LoadZonePhysicalSpawnPoint(v.Continent, v.Position, v.Orientation, 1 seconds, None)
case _ => case _ =>
interstellarFerry match { interstellarFerry match {
case None => case None =>
@ -1003,16 +1004,14 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
response match { response match {
case Some((zone, spawnPoint)) => case Some((zone, spawnPoint)) =>
val obj = continent.GUID(player.VehicleSeated) match { val obj = continent.GUID(player.VehicleSeated) match {
case Some(obj: Vehicle) if !obj.Destroyed => case Some(obj: Vehicle) if !obj.Destroyed => obj
obj case _ => player
case _ =>
player
} }
val (pos, ori) = spawnPoint.SpecificPoint(obj) val (pos, ori) = spawnPoint.SpecificPoint(obj)
if (previousZoningType == Zoning.Method.InstantAction) if (previousZoningType == Zoning.Method.InstantAction)
LoadZonePhysicalSpawnPoint(zone.id, pos, ori, respawnTime = 0 seconds) LoadZonePhysicalSpawnPoint(zone.id, pos, ori, respawnTime = 0 seconds, Some(spawnPoint))
else else
LoadZonePhysicalSpawnPoint(zone.id, pos, ori, CountSpawnDelay(zone.id, spawnPoint, continent.id)) LoadZonePhysicalSpawnPoint(zone.id, pos, ori, CountSpawnDelay(zone.id, spawnPoint, continent.id), Some(spawnPoint))
case None => case None =>
log.warn( log.warn(
s"SpawnPointResponse: ${player.Name} received no spawn point response when asking InterstellarClusterService; sending home" s"SpawnPointResponse: ${player.Name} received no spawn point response when asking InterstellarClusterService; sending home"
@ -1300,37 +1299,58 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
session.zone.map.checksum session.zone.map.checksum
) )
) )
//important! the LoadMapMessage must be processed by the client before the avatar is created if (isAcceptableNextSpawnPoint()) {
setupAvatarFunc() //important! the LoadMapMessage must be processed by the client before the avatar is created
//interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable setupAvatarFunc()
turnCounterFunc = interimUngunnedVehicle match { //interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable
case Some(_) => turnCounterFunc = interimUngunnedVehicle match {
TurnCounterDuringInterimWhileInPassengerSeat case Some(_) =>
case None => TurnCounterDuringInterimWhileInPassengerSeat
TurnCounterDuringInterim case None =>
TurnCounterDuringInterim
}
keepAliveFunc = NormalKeepAlive
upstreamMessageCount = 0
setAvatar = false
persist()
} else {
//look for different spawn point in same zone
cluster ! ICS.GetNearbySpawnPoint(
session.zone.Number,
tplayer,
Seq(SpawnGroup.Facility, SpawnGroup.Tower, SpawnGroup.AMS),
context.self
)
} }
keepAliveFunc = NormalKeepAlive
upstreamMessageCount = 0
setAvatar = false
persist()
case PlayerLoaded(tplayer) => case PlayerLoaded(tplayer) =>
//same zone //same zone
log.info(s"${tplayer.Name} will respawn") log.info(s"${tplayer.Name} will respawn")
tplayer.avatar = avatar tplayer.avatar = avatar
session = session.copy(player = tplayer) session = session.copy(player = tplayer)
setupAvatarFunc() if (isAcceptableNextSpawnPoint()) {
//interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable //try this spawn point
turnCounterFunc = interimUngunnedVehicle match { setupAvatarFunc()
case Some(_) => //interimUngunnedVehicle should have been setup by setupAvatarFunc, if it is applicable
TurnCounterDuringInterimWhileInPassengerSeat turnCounterFunc = interimUngunnedVehicle match {
case None => case Some(_) =>
TurnCounterDuringInterim TurnCounterDuringInterimWhileInPassengerSeat
case None =>
TurnCounterDuringInterim
}
keepAliveFunc = NormalKeepAlive
upstreamMessageCount = 0
setAvatar = false
persist()
} else {
//look for different spawn point in same zone
cluster ! ICS.GetNearbySpawnPoint(
continent.Number,
tplayer,
Seq(SpawnGroup.Facility, SpawnGroup.Tower, SpawnGroup.AMS),
context.self
)
} }
keepAliveFunc = NormalKeepAlive
upstreamMessageCount = 0
setAvatar = false
persist()
case PlayerFailedToLoad(tplayer) => case PlayerFailedToLoad(tplayer) =>
player.Continent match { player.Continent match {
@ -1386,7 +1406,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
continent.Population ! Zone.Population.Leave(avatar) //does not matter if it doesn't work continent.Population ! Zone.Population.Leave(avatar) //does not matter if it doesn't work
zoneLoaded = None zoneLoaded = None
zoneReload = true zoneReload = true
LoadZonePhysicalSpawnPoint(toZoneId, pos, orient, 0 seconds) LoadZonePhysicalSpawnPoint(toZoneId, pos, orient, respawnTime = 0 seconds, None)
} }
} else if (tplayer.isAlive) { } else if (tplayer.isAlive) {
if ( if (
@ -1399,7 +1419,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
if (!setAvatar || waitingOnUpstream) { if (!setAvatar || waitingOnUpstream) {
setCurrentAvatarFunc(tplayer) setCurrentAvatarFunc(tplayer)
respawnTimer = context.system.scheduler.scheduleOnce( respawnTimer = context.system.scheduler.scheduleOnce(
delay = (if (attempt <= max_attempts / 2) 10 else 5) seconds, delay = (if (attempt <= max_attempts / 2) 10
else 5) seconds,
self, self,
SetCurrentAvatar(tplayer, max_attempts, attempt + max_attempts / 3) SetCurrentAvatar(tplayer, max_attempts, attempt + max_attempts / 3)
) )
@ -1408,8 +1429,10 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case (Some(v: Vehicle), Some(seatNumber)) case (Some(v: Vehicle), Some(seatNumber))
if seatNumber > 0 && v.WeaponControlledFromSeat(seatNumber).isEmpty => if seatNumber > 0 && v.WeaponControlledFromSeat(seatNumber).isEmpty =>
KeepAlivePersistence KeepAlivePersistence
case _ => NormalKeepAlive case _ =>
NormalKeepAlive
} }
nextSpawnPoint = None
} }
//if not the condition above, player has started playing normally //if not the condition above, player has started playing normally
} else { } else {
@ -1693,7 +1716,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
interstellarFerry = Some(droppod) //leverage vehicle gating interstellarFerry = Some(droppod) //leverage vehicle gating
player.Position = droppod.Position player.Position = droppod.Position
player.VehicleSeated = PlanetSideGUID(0) player.VehicleSeated = PlanetSideGUID(0)
LoadZonePhysicalSpawnPoint(zone.id, droppod.Position, Vector3.Zero, 0 seconds) LoadZonePhysicalSpawnPoint(zone.id, droppod.Position, Vector3.Zero, 0 seconds, None)
} }
/** /**
@ -2571,7 +2594,6 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health)) sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health))
UpdateWeaponAtSeatPosition(obj, seat_number) UpdateWeaponAtSeatPosition(obj, seat_number)
MountingAction(tplayer, obj, seat_number) MountingAction(tplayer, obj, seat_number)
keepAliveFunc = KeepAlivePersistence
case Mountable.CanMount(obj: Mountable, _, _) => case Mountable.CanMount(obj: Mountable, _, _) =>
log.warn(s"MountVehicleMsg: $obj is some mountable object and nothing will happen for ${player.Name}") log.warn(s"MountVehicleMsg: $obj is some mountable object and nothing will happen for ${player.Name}")
@ -3948,11 +3970,11 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
FindContainedWeapon match { FindContainedWeapon match {
case (Some(o), Some(tool)) => case (Some(o), Some(tool)) =>
(o match { (o match {
case mount: Mountable => mount.PassengerInSeat(player) case mount: Mountable => (o, mount.PassengerInSeat(player))
case _ => None case _ => (None, None)
}) match { }) match {
case None | Some(0) => ; case (None, None) | (_, None) | (_: Vehicle, Some(0)) => ;
case Some(_) => case _ =>
persist() persist()
turnCounterFunc(player.GUID) turnCounterFunc(player.GUID)
} }
@ -8593,7 +8615,13 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
* @param respawnTime the character downtime spent respawning, as clocked on the redeployment screen; * @param respawnTime the character downtime spent respawning, as clocked on the redeployment screen;
* does not factor in any time required for loading zone or game objects * does not factor in any time required for loading zone or game objects
*/ */
def LoadZonePhysicalSpawnPoint(zoneId: String, pos: Vector3, ori: Vector3, respawnTime: FiniteDuration): Unit = { def LoadZonePhysicalSpawnPoint(
zoneId: String,
pos: Vector3,
ori: Vector3,
respawnTime: FiniteDuration,
physSpawnPoint: Option[SpawnPoint]
): Unit = {
log.info(s"${player.Name} will load in zone $zoneId at position $pos in $respawnTime") log.info(s"${player.Name} will load in zone $zoneId at position $pos in $respawnTime")
respawnTimer.cancel() respawnTimer.cancel()
reviveTimer.cancel() reviveTimer.cancel()
@ -8608,6 +8636,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
unk5 = true unk5 = true
) )
) )
nextSpawnPoint = physSpawnPoint
shiftPosition = Some(pos) shiftPosition = Some(pos)
shiftOrientation = Some(ori) shiftOrientation = Some(ori)
@ -9128,6 +9157,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
/** /**
* Given an origin and a destination, determine how long the process of traveling should take in reconstruction time. * Given an origin and a destination, determine how long the process of traveling should take in reconstruction time.
* For most destinations, the unit of receiving ("spawn point") determines the reconstruction time. * For most destinations, the unit of receiving ("spawn point") determines the reconstruction time.
* Possession of a lattice-linked friendly Bio Laboratory halves the time of spawning at facilities.
* In a special consideration, travel to any sanctuary or sanctuary-special zone should be as immediate as zone loading. * In a special consideration, travel to any sanctuary or sanctuary-special zone should be as immediate as zone loading.
* *
* @param toZoneId the zone where the target is headed * @param toZoneId the zone where the target is headed
@ -9137,12 +9167,18 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
*/ */
def CountSpawnDelay(toZoneId: String, toSpawnPoint: SpawnPoint, fromZoneId: String): FiniteDuration = { def CountSpawnDelay(toZoneId: String, toSpawnPoint: SpawnPoint, fromZoneId: String): FiniteDuration = {
val sanctuaryZoneId = Zones.sanctuaryZoneId(player.Faction) val sanctuaryZoneId = Zones.sanctuaryZoneId(player.Faction)
if (fromZoneId.equals("Nowhere") || sanctuaryZoneId.equals(toZoneId)) { //to sanctuary if (fromZoneId.equals("Nowhere") || sanctuaryZoneId.equals(toZoneId) || !isAcceptableNextSpawnPoint()) {
//first login, to sanctuary, resolution of invalid spawn point
0 seconds 0 seconds
} else if (!player.isAlive) {
toSpawnPoint.Definition.Delay seconds //TODO +cumulative death penalty
} else { } else {
toSpawnPoint.Definition.Delay seconds //for other zones ...
//biolabs have/grant benefits
val cryoBenefit: Float = toSpawnPoint.Owner match {
case b: Building if b.hasLatticeBenefit(GlobalDefinitions.cryo_facility) => 0.5f
case _ => 1f
}
//TODO cumulative death penalty
toSpawnPoint.Definition.Delay.toFloat * cryoBenefit seconds
} }
} }
@ -9714,6 +9750,24 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
} }
} }
def isAcceptableNextSpawnPoint(): Boolean = isAcceptableSpawnPoint(nextSpawnPoint)
def isAcceptableSpawnPoint(spawnPoint: SpawnPoint): Boolean = isAcceptableSpawnPoint(Some(spawnPoint))
def isAcceptableSpawnPoint(spawnPoint: Option[SpawnPoint]): Boolean = {
spawnPoint match {
case Some(aSpawnPoint) =>
!aSpawnPoint.isOffline &&
(aSpawnPoint.Owner match {
case w: WarpGate => w.Active
case b: Building => b.Faction == player.Faction
case v: Vehicle => v.Faction == player.Faction && !v.Destroyed && v.DeploymentState == DriveState.Deployed
case _ => true
})
case None => true
}
}
def failWithError(error: String) = { def failWithError(error: String) = {
log.error(error) log.error(error)
middlewareActor ! MiddlewareActor.Teardown() middlewareActor ! MiddlewareActor.Teardown()

View file

@ -182,53 +182,6 @@ class Building(
(o.nonEmpty, false) //TODO poll pain field strength (o.nonEmpty, false) //TODO poll pain field strength
} }
val latticeBenefit: Int = {
if (Faction == PlanetSideEmpire.NEUTRAL) 0
else {
def FindLatticeBenefit(
wantedBenefit: ObjectDefinition,
subGraph: Graph[Building, GraphEdge.UnDiEdge]
): Boolean = {
var found = false
subGraph find this match {
case Some(self) =>
if (this.Definition == wantedBenefit) found = true
else {
self pathUntil (_.Definition == wantedBenefit) match {
case Some(_) => found = true
case None => ;
}
}
case None => ;
}
found
}
// Check this Building is on the lattice first
zone.Lattice find this match {
case Some(_) =>
val subGraph = Zone.Lattice filter ((b: Building) =>
b.Faction == this.Faction
&& !b.CaptureTerminalIsHacked
&& b.NtuLevel > 0
&& (b.Generator.isEmpty || b.Generator.get.Condition != PlanetSideGeneratorState.Destroyed)
)
var stackedBenefit = 0
if (FindLatticeBenefit(GlobalDefinitions.amp_station, subGraph)) stackedBenefit |= 1
if (FindLatticeBenefit(GlobalDefinitions.comm_station_dsp, subGraph)) stackedBenefit |= 2
if (FindLatticeBenefit(GlobalDefinitions.cryo_facility, subGraph)) stackedBenefit |= 4
if (FindLatticeBenefit(GlobalDefinitions.comm_station, subGraph)) stackedBenefit |= 8
if (FindLatticeBenefit(GlobalDefinitions.tech_plant, subGraph)) stackedBenefit |= 16
stackedBenefit
case None => 0;
}
}
}
BuildingInfoUpdateMessage( BuildingInfoUpdateMessage(
Zone.Number, Zone.Number,
MapId, MapId,
@ -242,7 +195,7 @@ class Building(
generatorState, generatorState,
spawnTubesNormal, spawnTubesNormal,
forceDomeActive, forceDomeActive,
if (generatorState != PlanetSideGeneratorState.Destroyed) latticeBenefit else 0, if (generatorState != PlanetSideGeneratorState.Destroyed) latticeBenefitsValue() else 0,
if (generatorState != PlanetSideGeneratorState.Destroyed) 48 else 0, // cavern benefit if (generatorState != PlanetSideGeneratorState.Destroyed) 48 else 0, // cavern benefit
Nil, // unk4, Nil, // unk4,
0, // unk5 0, // unk5
@ -254,6 +207,95 @@ class Building(
) )
} }
def hasLatticeBenefit(wantedBenefit: ObjectDefinition): Boolean = {
if (Faction == PlanetSideEmpire.NEUTRAL) {
false
} else {
// Check this Building is on the lattice first
zone.Lattice find this match {
case Some(_) =>
val subGraph = Zone.Lattice filter (
(b : Building) =>
b.Faction == this.Faction &&
!b.CaptureTerminalIsHacked &&
b.NtuLevel > 0 &&
(b.Generator.isEmpty || b.Generator.get.Condition != PlanetSideGeneratorState.Destroyed)
)
findLatticeBenefit(wantedBenefit, subGraph)
case None =>
false
}
}
}
private def findLatticeBenefit(
wantedBenefit: ObjectDefinition,
subGraph: Graph[Building, GraphEdge.UnDiEdge]
): Boolean = {
var found = false
subGraph find this match {
case Some(self) =>
if (this.Definition == wantedBenefit) {
found = true
} else {
self pathUntil (_.Definition == wantedBenefit) match {
case Some(_) => found = true
case None => ;
}
}
case None => ;
}
found
}
def latticeConnectedFacilityBenefits(): Set[ObjectDefinition] = {
if (Faction == PlanetSideEmpire.NEUTRAL) {
Set.empty
} else {
// Check this Building is on the lattice first
zone.Lattice find this match {
case Some(_) =>
val subGraph = Zone.Lattice filter ((b: Building) =>
b.Faction == this.Faction
&& !b.CaptureTerminalIsHacked
&& b.NtuLevel > 0
&& (b.Generator.isEmpty || b.Generator.get.Condition != PlanetSideGeneratorState.Destroyed)
)
import scala.collection.mutable
var connectedBases: mutable.Set[ObjectDefinition] = mutable.Set()
if (findLatticeBenefit(GlobalDefinitions.amp_station, subGraph)) {
connectedBases.add(GlobalDefinitions.amp_station)
}
if (findLatticeBenefit(GlobalDefinitions.comm_station_dsp, subGraph)) {
connectedBases.add(GlobalDefinitions.comm_station_dsp)
}
if (findLatticeBenefit(GlobalDefinitions.cryo_facility, subGraph)) {
connectedBases.add(GlobalDefinitions.cryo_facility)
}
if (findLatticeBenefit(GlobalDefinitions.comm_station, subGraph)) {
connectedBases.add(GlobalDefinitions.comm_station)
}
if (findLatticeBenefit(GlobalDefinitions.tech_plant, subGraph)) {
connectedBases.add(GlobalDefinitions.tech_plant)
}
connectedBases.toSet
case None =>
Set.empty
}
}
}
def latticeBenefitsValue(): Int = {
latticeConnectedFacilityBenefits().collect {
case GlobalDefinitions.amp_station => 1
case GlobalDefinitions.comm_station_dsp => 2
case GlobalDefinitions.cryo_facility => 4
case GlobalDefinitions.comm_station => 8
case GlobalDefinitions.tech_plant => 16
}.sum
}
def BuildingType: StructureType = buildingType def BuildingType: StructureType = buildingType
override def Zone_=(zone: Zone): Zone = Zone //building never leaves zone after being set in constructor override def Zone_=(zone: Zone): Zone = Zone //building never leaves zone after being set in constructor

View file

@ -9,6 +9,7 @@ import net.psforever.packet.game.BuildingInfoUpdateMessage
import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3}
import akka.actor.typed.scaladsl.adapter._ import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.BuildingActor import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.definition.ObjectDefinition
import scala.collection.mutable import scala.collection.mutable
@ -154,6 +155,10 @@ class WarpGate(name: String, building_guid: Int, map_id: Int, zone: Zone, buildi
def NtuCapacitor_=(value: Float): Float = NtuCapacitor def NtuCapacitor_=(value: Float): Float = NtuCapacitor
override def hasLatticeBenefit(wantedBenefit: ObjectDefinition): Boolean = false
override def latticeConnectedFacilityBenefits(): Set[ObjectDefinition] = Set.empty
override def Definition: WarpGateDefinition = buildingDefinition override def Definition: WarpGateDefinition = buildingDefinition
} }