the force dome exhibits a perimeter in which enemies will be destroyed when it energizes; the facility generator will become undestroyable when the force dome is energized

This commit is contained in:
Fate-JH 2025-12-18 19:29:06 -05:00
parent 6a960ed5ac
commit 4b3f8ea6c0
5 changed files with 245 additions and 100 deletions

View file

@ -6,6 +6,7 @@ import akka.actor.typed.{ActorRef, Behavior}
import akka.actor.typed.scaladsl.{ActorContext, Behaviors} import akka.actor.typed.scaladsl.{ActorContext, Behaviors}
import net.psforever.actors.commands.NtuCommand import net.psforever.actors.commands.NtuCommand
import net.psforever.actors.zone.{BuildingActor, BuildingControlDetails, ZoneActor} import net.psforever.actors.zone.{BuildingActor, BuildingControlDetails, ZoneActor}
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.dome.ForceDomePhysics import net.psforever.objects.serverobject.dome.ForceDomePhysics
import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl} import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl}
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloControl import net.psforever.objects.serverobject.resourcesilo.ResourceSiloControl
@ -107,9 +108,12 @@ case object MajorFacilityLogic
} }
// No map update needed - will be sent by `HackCaptureActor` when required // No map update needed - will be sent by `HackCaptureActor` when required
case dome: ForceDomePhysics => case dome: ForceDomePhysics =>
val building = details.building
// The force dome being expanded modifies the NTU drain rate // The force dome being expanded modifies the NTU drain rate
val multiplier: Float = calculateNtuDrainMultiplierFrom(details.building, domeOpt = Some(dome)) val multiplier: Float = calculateNtuDrainMultiplierFrom(details.building, domeOpt = Some(dome))
details.building.NtuSource.foreach(_.Actor ! ResourceSiloControl.DrainMultiplier(multiplier)) building.NtuSource.foreach(_.Actor ! ResourceSiloControl.DrainMultiplier(multiplier))
// The force dome being expanded marks the generator as being invulnerable; it can be damaged otherwise
building.Generator.foreach { _.Actor ! Damageable.Vulnerability(dome.Energized) }
case _ => case _ =>
details.galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(details.building.infoUpdateMessage())) details.galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(details.building.infoUpdateMessage()))
} }
@ -373,7 +377,10 @@ case object MajorFacilityLogic
): Float = { ): Float = {
// The force dome being expanded means all repairs are essentially for free // The force dome being expanded means all repairs are essentially for free
dome dome
.map { case d if d.Energized => 0f } .flatMap {
case d if d.Energized => Some(0f)
case _ => None
}
.orElse { .orElse {
mainTerminal.flatMap { _ => Some(2f) } //todo main terminal and viruses mainTerminal.flatMap { _ => Some(2f) } //todo main terminal and viruses
} }

View file

@ -959,17 +959,57 @@ object GlobalDefinitionsMiscellaneous {
force_dome_amp_physics.Name = "force_dome_amp_physics" force_dome_amp_physics.Name = "force_dome_amp_physics"
force_dome_amp_physics.UseRadius = 142.26f force_dome_amp_physics.UseRadius = 142.26f
force_dome_amp_physics.PerimeterOffsets = List(
Vector3(83.05469f, 114.875f, 0f),
Vector3(-90.328125f, 114.875f, 0f),
Vector3(-90.328125f, -106.90625f, 0f),
Vector3(83.05469f, -106.90625f, 0f)
)
force_dome_comm_physics.Name = "force_dome_comm_physics" force_dome_comm_physics.Name = "force_dome_comm_physics"
force_dome_comm_physics.UseRadius = 121.8149f force_dome_comm_physics.UseRadius = 121.8149f
force_dome_comm_physics.PerimeterOffsets = List(
Vector3(35.1875f, -89.859375f, 0f),
Vector3(80.96875f, -43.773438f, 0f),
Vector3(80.96875f, 91.08594f, 0f),
Vector3(-37.296875f, 91.08594f, 0f),
Vector3(-83.640625f, 45.601562f, 0f),
Vector3(-83.640625f, -89.859375f, 0f)
)
force_dome_cryo_physics.Name = "force_dome_cryo_physics" force_dome_cryo_physics.Name = "force_dome_cryo_physics"
force_dome_cryo_physics.UseRadius = 127.9241f //127.7963f force_dome_cryo_physics.UseRadius = 127.9241f //127.7963f
force_dome_cryo_physics.PerimeterOffsets = List(
Vector3(72.75476f, 39.902725f, 0),
Vector3(24.505968f, 88.03482f, 0),
Vector3(-74.73426f, 88.03482f, 0),
Vector3(-74.73426f, -103.47f, 0),
Vector3(72.75476f, -103.47f, 0)
)
force_dome_dsp_physics.Name = "force_dome_dsp_physics" force_dome_dsp_physics.Name = "force_dome_dsp_physics"
force_dome_dsp_physics.UseRadius = 175.8838f //175.7081f force_dome_dsp_physics.UseRadius = 175.8838f //175.7081f
force_dome_dsp_physics.PerimeterOffsets = List(
Vector3(35.03125f, -93.25f, 0f),
Vector3(-83.1875f, -93.25f, 0f),
Vector3(-83.1875f, 114.515625f, 0f),
Vector3(-12.109375f, 188.26562f, 0f),
Vector3(130.44531f, 188.26562f, 0f),
Vector3(130.44531f, -93.28125f, 0f)
)
force_dome_tech_physics.Name = "force_dome_tech_physics" force_dome_tech_physics.Name = "force_dome_tech_physics"
force_dome_tech_physics.UseRadius = 150.1284f force_dome_tech_physics.UseRadius = 150.1284f
force_dome_tech_physics.PerimeterOffsets = List( //todo double-check eisa, esamir
Vector3(130.14636f, -95.20665f, 0f),
Vector3(130.14636f, 34.441734f, 0f),
Vector3(103.98575f, 52.58408f, 0f),
Vector3(16.405174f, 54.746464f, 0f),
Vector3(14.256668f, 107.01521f, 0f),
Vector3(-92.08687f, 107.01521f, 0f),
Vector3(-92.08687f, -96.176155f, 0f),
Vector3(-73.64424f, -114.65837f, 0f),
Vector3(102.12191f, -114.65837f, 0f)
)
} }
} }

View file

@ -17,7 +17,6 @@ import net.psforever.objects.vital.resolution.ResolutionCalculations
* All of these should be affected by the damage where applicable. * All of these should be affected by the damage where applicable.
*/ */
trait Damageable { trait Damageable {
/** /**
* Contextual access to the object being the target of this damage. * Contextual access to the object being the target of this damage.
* Needs declaration in lowest implementing code. * Needs declaration in lowest implementing code.
@ -25,19 +24,34 @@ trait Damageable {
*/ */
def DamageableObject: Damageable.Target def DamageableObject: Damageable.Target
/** a local `canDamage` flag */
private var isVulnerable: Boolean = true
/** the official mixin hook; /** the official mixin hook;
* `orElse` onto the "control" `Actor` `receive`; or, * `orElse` onto the "control" `Actor` `receive`; or,
* cite the `originalTakesDamage` protocol during inheritance overrides */ * cite the `originalTakesDamage` protocol during inheritance overrides */
val takesDamage: Receive = { val takesDamage: Receive = {
case Damageable.MakeVulnerable =>
isVulnerable = false
case Damageable.MakeInvulnerable =>
isVulnerable = true
case Vitality.Damage(damage_func) => case Vitality.Damage(damage_func) =>
val obj = DamageableObject val obj = DamageableObject
if (obj.CanDamage) { if (isVulnerable && obj.CanDamage) {
PerformDamage(obj, damage_func) PerformDamage(obj, damage_func)
} }
} }
/** a duplicate of the core implementation for the default mixin hook, for use in overriding */ /** a duplicate of the core implementation for the default mixin hook, for use in overriding */
final val originalTakesDamage: Receive = { final val originalTakesDamage: Receive = {
case Damageable.MakeVulnerable =>
isVulnerable = false
case Damageable.MakeInvulnerable =>
isVulnerable = true
case Vitality.Damage(damage_func) => case Vitality.Damage(damage_func) =>
val obj = DamageableObject val obj = DamageableObject
if (obj.CanDamage) { if (obj.CanDamage) {
@ -67,6 +81,20 @@ object Damageable {
*/ */
final val LogChannel: String = "DamageResolution" final val LogChannel: String = "DamageResolution"
trait PersonalVulnerability
final case object MakeVulnerable extends PersonalVulnerability
final case object MakeInvulnerable extends PersonalVulnerability
def Vulnerability(state: Boolean): PersonalVulnerability = {
if (state) {
MakeInvulnerable
} else {
MakeVulnerable
}
}
/** /**
* Does the possibility exist that the designated target can be affected by this projectile's damage? * Does the possibility exist that the designated target can be affected by this projectile's damage?
* @see `Hackable` * @see `Hackable`

View file

@ -7,7 +7,6 @@ import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.structures.{Amenity, Building, PoweredAmenityControl} import net.psforever.objects.serverobject.structures.{Amenity, Building, PoweredAmenityControl}
import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior} import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAware, CaptureTerminalAwareBehavior}
import net.psforever.objects.serverobject.turret.FacilityTurret
import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.etc.ForceDomeExposure import net.psforever.objects.vital.etc.ForceDomeExposure
@ -19,6 +18,8 @@ import net.psforever.services.Service
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGeneratorState, Vector3} import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGeneratorState, Vector3}
import scala.annotation.unused
object ForceDomeControl { object ForceDomeControl {
trait Command trait Command
@ -30,7 +31,7 @@ object ForceDomeControl {
/** /**
* Dispatch a message to update the state of the clients with the server state of the capitol force dome. * Dispatch a message to update the state of the clients with the server state of the capitol force dome.
* @param dome force dome * @param dome force dome
* @param activationState new force dome status * @param activationState new force dome status
*/ */
def ChangeDomeEnergizedState(dome: ForceDomePhysics, activationState: Boolean): Unit = { def ChangeDomeEnergizedState(dome: ForceDomePhysics, activationState: Boolean): Unit = {
@ -49,7 +50,7 @@ object ForceDomeControl {
* use the faction affinity, the generator status, and the resource silo's capacitance level * use the faction affinity, the generator status, and the resource silo's capacitance level
* to determine if the capitol force dome should be active. * to determine if the capitol force dome should be active.
* @param building building being evaluated * @param building building being evaluated
* @param dome force dome * @param dome force dome
* @return the condition of the capitol force dome; * @return the condition of the capitol force dome;
* `None`, if the facility is not a capitol building; * `None`, if the facility is not a capitol building;
* `Some(true|false)` to indicate the state of the force dome * `Some(true|false)` to indicate the state of the force dome
@ -78,7 +79,7 @@ object ForceDomeControl {
* for capitol force dome expansion. * for capitol force dome expansion.
* @param building target building * @param building target building
* @return `true`, if the conditions for capitol force dome are not met; * @return `true`, if the conditions for capitol force dome are not met;
* `false`, otherwise * `false`, otherwise
*/ */
def InvalidBuildingCapitolForceDomeConditions(building: Building): Boolean = { def InvalidBuildingCapitolForceDomeConditions(building: Building): Boolean = {
building.Faction == PlanetSideEmpire.NEUTRAL || building.Faction == PlanetSideEmpire.NEUTRAL ||
@ -87,59 +88,30 @@ object ForceDomeControl {
} }
/** /**
* na * Apply a fixed point and a rotation value to a series of vertex offsets,
* then daisy-chain the resulting vertices in such a way that
* it creates a perimeter around the (building) owner of the capitol force dome.
* The resulting capitol force dome barrier is a blocky pyramoid shape.
* @param dome force dome * @param dome force dome
* @return na * @return perimeter of the force dome barrier
*/ */
def GeneralFacilityPerimeter(dome: ForceDomePhysics): List[(Vector3, Vector3)] = { def SetupForceDomePerimeter(dome: ForceDomePhysics): List[(Vector3, Vector3)] = {
val generatorTowerCenter = dome.Position.xy val center = dome.Position.xy
val turretPoints = dome.Owner.Amenities.filter(_.isInstanceOf[FacilityTurret]).map(_.Position.xy) val rotation = math.toRadians(dome.Owner.Orientation.z).toFloat
val pointsOfForceDomePerimeter = turretPoints.map { p => val perimeterOffsets = dome.Definition.PerimeterOffsets
val segmentFromTowerToTurret = p - generatorTowerCenter val perimeterPoints = perimeterOffsets.map {
Vector3.Unit(segmentFromTowerToTurret) * (Vector3.Magnitude(segmentFromTowerToTurret) + 20) //todo get correct distance offset center + Vector3.PlanarRotateAroundPoint(_, Vector3(0, 0, 1), rotation)
} }
pointsOfForceDomePerimeter ((0 until perimeterPoints.size - 1).map { index =>
.flatMap { point => (perimeterPoints(index), perimeterPoints(index + 1))
pointsOfForceDomePerimeter } :+ (perimeterPoints.last, perimeterPoints.head)).toList
.sortBy(p => Vector3.DistanceSquared(p, point))
.slice(1, 3)
.map { otherPoint =>
if (point.y > otherPoint.y || point.x < otherPoint.x) {
(point, otherPoint)
} else {
(otherPoint, point)
}
}
}
.distinct
}
import scala.annotation.unused
def TechPlantFacilityPerimeter(@unused dome: ForceDomePhysics): List[(Vector3, Vector3)] = {
// val generatorTowerCenter = dome.Position.xy
// val turretPoints = dome.Owner.Amenities.filter(_.isInstanceOf[FacilityTurret]).map(_.Position.xy)
// val organizedByClosestToGarage = dome
// .Owner
// .Amenities
// .find(_.Definition.Name.equals("gr_door_garage_ext"))
// .map { garage =>
// val doorPosition = garage.Position.xy
// turretPoints.sortBy(point => Vector3.DistanceSquared(doorPosition, point))
// }
// .getOrElse(List[Vector3]())
//
// //val turretPoints = dome.Owner.Amenities.filter(_.isInstanceOf[FacilityTurret]).map(_.Position.xy)
// val pointsOfForceDomePerimeter = turretPoints.map { p =>
// val segmentFromTowerToTurret = p - generatorTowerCenter
// Vector3.Unit(segmentFromTowerToTurret) * (Vector3.Magnitude(segmentFromTowerToTurret) + 20) //todo get correct distance offset
// }
Nil
} }
/** /**
* na * The capitol force dome should have changed states but it will not!
* Make certain everyone knows!
* @param building target building * @param building target building
* @param state na * @param state whether the force dome is energized or not
*/ */
def CustomDomeStateEnforcedMessage( def CustomDomeStateEnforcedMessage(
building: Building, building: Building,
@ -156,7 +128,8 @@ object ForceDomeControl {
} }
/** /**
* na * The capitol force dome will start changing states normally.
* Make certain everyone knows.
* @param building facility * @param building facility
*/ */
def NormalDomeStateMessage(building: Building): Unit = { def NormalDomeStateMessage(building: Building): Unit = {
@ -179,19 +152,19 @@ object ForceDomeControl {
* pass a message onto that facility that it should check its own state alignment. * pass a message onto that facility that it should check its own state alignment.
* @param building facility with `dome` * @param building facility with `dome`
*/ */
def AlignForceDomeStatusAndUpdate(building: Building, dome: ForceDomePhysics): Unit = { def AlignForceDomeStatusAndUpdate(building: Building, dome: ForceDomePhysics): Boolean = {
CheckForceDomeStatus(building, dome).foreach { val energizedState = dome.Energized
case true => CheckForceDomeStatus(building, dome).exists {
if (!dome.Energized) { case true if !energizedState =>
ChangeDomeEnergizedState(dome, activationState = true) dome.Owner.Actor ! BuildingActor.MapUpdate()
ForceDomeKills(dome) ChangeDomeEnergizedState(dome, activationState = true)
dome.Owner.Actor ! BuildingActor.MapUpdate() true
} case false if energizedState =>
case false => ChangeDomeEnergizedState(dome, activationState = false)
if (dome.Energized) { dome.Owner.Actor ! BuildingActor.MapUpdate()
ChangeDomeEnergizedState(dome, activationState = false) false
dome.Owner.Actor ! BuildingActor.MapUpdate() case _ =>
} energizedState
} }
} }
@ -204,44 +177,44 @@ object ForceDomeControl {
* pass a message onto that facility that it should check its own state alignment. * pass a message onto that facility that it should check its own state alignment.
* @param building facility with `dome` * @param building facility with `dome`
*/ */
private def AlignForceDomeStatus(building: Building, dome: ForceDomePhysics): Unit = { private def AlignForceDomeStatus(building: Building, dome: ForceDomePhysics): Boolean = {
CheckForceDomeStatus(building, dome).foreach { val energizedState = dome.Energized
case true => CheckForceDomeStatus(building, dome).exists {
if (!dome.Energized) { case true if !energizedState =>
ChangeDomeEnergizedState(dome, activationState = true) ChangeDomeEnergizedState(dome, activationState = true)
ForceDomeKills(dome) true
} case false if energizedState =>
case false => ChangeDomeEnergizedState(dome, activationState = false)
if (dome.Energized) { false
ChangeDomeEnergizedState(dome, activationState = false) case _ =>
} energizedState
} }
} }
/** /**
* Being too close to the force dome can destroy targets if they do not match the faction alignment of the dome. * Being too close to the force dome can destroy targets if they do not match the faction alignment of the dome.
* This is the usual fate of opponents upon it being expanded (energeized). * This is the usual fate of opponents upon it being expanded (energized).
* @see `Zone.serverSideDamage` * @see `Zone.serverSideDamage`
* @param dome force dome * @param dome force dome
* @return a list of affected entities * @return a list of affected entities
*/ */
def ForceDomeKills(dome: ForceDomePhysics): List[PlanetSideServerObject] = { def ForceDomeKills(dome: ForceDomePhysics, perimeter: List[(Vector3, Vector3)]): List[PlanetSideServerObject] = {
Zone.serverSideDamage( Zone.serverSideDamage(
dome.Zone, dome.Zone,
dome, dome,
contactWithForceDome, makesContactWithForceDome,
Zone.distanceCheck, targetUnderForceDome(perimeter),
forceDomeTargets(dome.Definition.UseRadius, dome.Faction) forceDomeTargets(dome.Definition.UseRadius, dome.Faction)
) )
} }
/** /**
* na * Prepare damage information related to being caugt underneath the capitol force dome when it expands.
* @param source a game object that represents the source of the explosion * @param source a game object that represents the source of the explosion
* @param target a game object that is affected by the explosion * @param target a game object that is affected by the explosion
* @return a `DamageInteraction` object * @return a `DamageInteraction` object
*/ */
private def contactWithForceDome( private def makesContactWithForceDome(
source: PlanetSideGameObject with FactionAffinity with Vitality, source: PlanetSideGameObject with FactionAffinity with Vitality,
target: PlanetSideGameObject with FactionAffinity with Vitality target: PlanetSideGameObject with FactionAffinity with Vitality
): DamageInteraction = { ): DamageInteraction = {
@ -254,6 +227,84 @@ object ForceDomeControl {
/** /**
* na * na
* @see `Zone.distanceCheck`
* @param segments ground-level perimeter of the force dome is defined by these segments (as vertex pairs)
* @param obj1 a game entity, should be the force dome
* @param obj2 a game entity, should be a damageable target of the force dome's wrath
* @param maxDistance ot applicable
* @return `true`, if target is detected within the force dome kill region
* `false`, otherwise
*/
private def targetUnderForceDome(
segments: List[(Vector3, Vector3)]
)
(
obj1: PlanetSideGameObject,
obj2: PlanetSideGameObject,
@unused maxDistance: Float
): Boolean = {
val centerPos @ Vector3(centerX, centerY, centerZ) = obj1.Position
val Vector3(targetX, targetY, targetZ) = obj2.Position - centerPos //deltas of segment of target to dome
val checkForIntersection = segments.exists { case (point1, point2) =>
//want targets within the perimeter; if there's an intersection, target is outside of the perimeter
segmentIntersectionTestPerSegment(centerX, centerY, targetX, targetY, point1.x, point1.y, point2.x, point2.y)
}
!checkForIntersection && (targetZ < centerZ || Zone.distanceCheck(obj1, obj2, math.pow(obj1.Definition.UseRadius, 2).toFloat))
}
/**
* A function to assist line segment intersection tests.
* The important frame of reference is checking whether a hypothetical segment between a point and a target
* intersects with an established line segment between two other points.
* For our purposes, the resulting line segments will never be collinear, so there is no reason to test that.
* @param pointX x-coordinate used to create a test segment
* @param pointY y-coordinate used to create a test segment
* @param targetX x-coordinate of an important point for a test segment
* @param targetY y-coordinate of an important point for a test segment
* @param segmentPoint1x x-coordinate of one point from a segment
* @param segmentPoint1y y-coordinate of one point from a segment
* @param segmentPoint2x x-coordinate of a different point from a segment
* @param segmentPoint2y y-coordinate of a different point from a segment
* @return `true`, if the points form into two segments that intersect;
* `false`, otherwise
*/
private def segmentIntersectionTestPerSegment(
pointX: Float,
pointY: Float,
targetX: Float,
targetY: Float,
segmentPoint1x: Float,
segmentPoint1y: Float,
segmentPoint2x: Float,
segmentPoint2y: Float
): Boolean = {
//based on Franklin Antonio's "Faster Line Segment Intersection" topic "in Graphics Gems III" book (http://www.graphicsgems.org/)
//compare, java.awt.geom.Line2D.linesIntersect
val bx = segmentPoint1x - segmentPoint2x //delta-x of segment
val by = segmentPoint1y - segmentPoint2y //delta-y of segment
val cx = pointX - segmentPoint1x //delta-x of hypotenuse of triangle formed by center, segment endpoint, and intersection point
val cy = pointY - segmentPoint1y //delta-y of hypotenuse of triangle formed by center, segment endpoint, and intersection point
val alphaNumerator = by * cx - bx * cy
val commonDenominator = targetY * bx - targetX * by
val betaNumerator = targetX * cy - targetY * cx
if (
commonDenominator > 0 &&
(alphaNumerator < 0 || alphaNumerator > commonDenominator || betaNumerator < 0 || betaNumerator > commonDenominator)
) {
false
} else if (
commonDenominator < 0 &&
(alphaNumerator > 0 || alphaNumerator < commonDenominator || betaNumerator > 0 || betaNumerator < commonDenominator)
) {
false
} else {
//a collinear line test could go here, but we don't need it
true
}
}
/**
* Collect all enemy players, vehicles, and combat engineering deployables in a sector.
* @see `DamageWithPosition` * @see `DamageWithPosition`
* @see `Zone.blockMap.sector` * @see `Zone.blockMap.sector`
* @param zone the zone in which the explosion should occur * @param zone the zone in which the explosion should occur
@ -292,8 +343,13 @@ class ForceDomeControl(dome: ForceDomePhysics)
def CaptureTerminalAwareObject: Amenity with CaptureTerminalAware = dome def CaptureTerminalAwareObject: Amenity with CaptureTerminalAware = dome
def FactionObject: FactionAffinity = dome def FactionObject: FactionAffinity = dome
/** a capitol force dome's owner should always be a facility, preferably the capitol facility of the continent;
* to save time, casted this entity and cache it for repeated use once;
* force dome is not immediately owned (by correct facility) so delay determination */
private lazy val domeOwnerAsABuilding = dome.Owner.asInstanceOf[Building] private lazy val domeOwnerAsABuilding = dome.Owner.asInstanceOf[Building]
/** ground-level perimeter of the force dome is defined by these segments (as vertex pairs) */
private val perimeterSegments: List[(Vector3, Vector3)] = ForceDomeControl.SetupForceDomePerimeter(dome)
/** force the dome into a certain state regardless of what conditions would normally transition it into that state */
private var customState: Option[Boolean] = None private var customState: Option[Boolean] = None
def commonBehavior: Receive = checkBehavior def commonBehavior: Receive = checkBehavior
@ -325,6 +381,7 @@ class ForceDomeControl(dome: ForceDomePhysics)
customState = None customState = None
ForceDomeControl.NormalDomeStateMessage(domeOwnerAsABuilding) ForceDomeControl.NormalDomeStateMessage(domeOwnerAsABuilding)
ForceDomeControl.AlignForceDomeStatusAndUpdate(domeOwnerAsABuilding, dome) ForceDomeControl.AlignForceDomeStatusAndUpdate(domeOwnerAsABuilding, dome)
ForceDomeControl.ForceDomeKills(dome, perimeterSegments)
} }
def poweredStateLogic: Receive = { def poweredStateLogic: Receive = {
@ -363,6 +420,10 @@ class ForceDomeControl(dome: ForceDomePhysics)
deenergizeUnlessSuppressedDueToCustomState() deenergizeUnlessSuppressedDueToCustomState()
} }
/**
* Power down the force dome if it was previously being powered and
* as long as a custom state of being energized is not being enforced.
*/
private def deenergizeUnlessSuppressedDueToCustomState(): Unit = { private def deenergizeUnlessSuppressedDueToCustomState(): Unit = {
if (dome.Energized) { if (dome.Energized) {
if (customState.isEmpty) { if (customState.isEmpty) {
@ -374,25 +435,22 @@ class ForceDomeControl(dome: ForceDomePhysics)
} }
/** /**
* na * Yield to a custom value enforcing a certain force dome state - energized or powered down.
* If the custom state is not declared, run the function and analyze any change in the force dome's natural state.
* @param func function to run if not blocked * @param func function to run if not blocked
* @return next behavior for an actor state * @return current energized state of the dome
*/ */
private def blockedByCustomStateOr(func: (Building, ForceDomePhysics) => Unit): Unit = { private def blockedByCustomStateOr(func: (Building, ForceDomePhysics) => Boolean): Boolean = {
blockedByCustomStateOr(func, domeOwnerAsABuilding, dome)
}
/**
* na
* @param func function to run if not blocked
* @param building facility to operate upon (parameter to `func`)
* @return next behavior for an actor state
*/
private def blockedByCustomStateOr(func: (Building, ForceDomePhysics) => Unit, building: Building, dome: ForceDomePhysics): Unit = {
customState match { customState match {
case None => case None =>
func(building, dome) val newState = func(domeOwnerAsABuilding, dome)
if (!dome.Energized && newState) {
ForceDomeControl.ForceDomeKills(dome, perimeterSegments)
}
newState
case Some(state) => case Some(state) =>
ForceDomeControl.CustomDomeStateEnforcedMessage(building, state) ForceDomeControl.CustomDomeStateEnforcedMessage(domeOwnerAsABuilding, state)
state
} }
} }
} }

View file

@ -2,8 +2,20 @@
package net.psforever.objects.serverobject.dome package net.psforever.objects.serverobject.dome
import net.psforever.objects.serverobject.structures.AmenityDefinition import net.psforever.objects.serverobject.structures.AmenityDefinition
import net.psforever.types.Vector3
class ForceDomeDefinition(objectId: Int) class ForceDomeDefinition(objectId: Int)
extends AmenityDefinition(objectId) { extends AmenityDefinition(objectId) {
Name = "force_dome" Name = "force_dome"
/** offsets that define the perimeter of the pyramidal force "dome" barrier;
* these points are the closest to where the dome interacts with the ground at a corner;
* should be sequential, either clockwise or counterclockwise */
private var perimeter: List[Vector3] = List()
def PerimeterOffsets: List[Vector3] = perimeter
def PerimeterOffsets_=(points: List[Vector3]): List[Vector3] = {
perimeter = points
PerimeterOffsets
}
} }