mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-07-13 15:34:45 +00:00
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:
parent
6a960ed5ac
commit
4b3f8ea6c0
5 changed files with 245 additions and 100 deletions
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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`
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue