force domes should be on the zone blockmap; correct issue with force dome death; interaction that sets players found under the force dome to be invulnerable works

This commit is contained in:
Fate-JH 2025-12-22 20:58:15 -05:00
parent 4b3f8ea6c0
commit dd0f5fc928
6 changed files with 167 additions and 18 deletions

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.avatar.interaction.{TriggerOnPlayerRule, WithEntrance, WithGantry, WithLava, WithWater}
import net.psforever.objects.avatar.interaction.{InteractWithForceDomeProtection, TriggerOnPlayerRule, WithEntrance, WithGantry, WithLava, WithWater}
import net.psforever.objects.avatar.{Avatar, LoadoutManager, SpecialCarry}
import net.psforever.objects.ballistics.InteractWithRadiationClouds
import net.psforever.objects.ce.{Deployable, InteractWithMines, InteractWithTurrets}
@ -51,6 +51,7 @@ class Player(var avatar: Avatar)
interaction(new InteractWithMines(range = 10, TriggerOnPlayerRule))
interaction(new InteractWithTurrets())
interaction(new InteractWithRadiationClouds(range = 10f, Some(this)))
interaction(new InteractWithForceDomeProtection())
private var backpack: Boolean = false
private var released: Boolean = false

View file

@ -0,0 +1,89 @@
// Copyright (c) 2025 PSForever
package net.psforever.objects.avatar.interaction
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.dome.{ForceDomeControl, ForceDomePhysics}
import net.psforever.objects.zones.blockmap.SectorPopulation
import net.psforever.objects.zones.{InteractsWithZone, ZoneInteraction, ZoneInteractionType}
case object ForceZoneProtection extends ZoneInteractionType
/**
* Entities under the capitol force dome that have not died in its initial activation
* do not take further damage until removed from under the dome or until the dome is deactivated.
*/
class InteractWithForceDomeProtection
extends ZoneInteraction {
def Type: ZoneInteractionType = ForceZoneProtection
def range: Float = 10f
/** increment to n, reevaluate the dome protecting the target, reset counter to 0 */
private var protectSkipCounter: Int = 0
/** dome currently protecting the target */
private var protectedBy: Option[ForceDomePhysics] = None
/**
* na
* @see `ForceDomeControl.TargetUnderForceDome`
* @param sector the portion of the block map being tested
* @param target the fixed element in this test
*/
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
if (protectSkipCounter < 4) {
protectSkipCounter += 1
} else {
protectSkipCounter = 0
protectedBy match {
case Some(dome)
if dome.Perimeter.isEmpty ||
target.Zone != dome.Zone ||
!ForceDomeControl.TargetUnderForceDome(dome.Perimeter)(target, dome, maxDistance = 0f) =>
resetInteraction(target)
case Some(_) =>
() //no action
case None =>
searchForInteractionCause(sector, target)
}
}
}
/**
* Look the through the list of amenities in this sector for capitol force domes,
* determine which force domes are energized (activated, expanded, enveloping, etc.),
* and find the first active dome under which the target `entity` is positioned.
* The target `entity` is considered protected and can not be damaged until further notice.
* @see `Damageable.MakeInvulnerable`
* @see `ForceDomeControl.TargetUnderForceDome`
* @param sector the portion of the block map being tested
* @param target the fixed element in this test
* @return whichever force dome entity is detected to encircle this target `entity`, if any
*/
private def searchForInteractionCause(sector: SectorPopulation, target: InteractsWithZone): Option[ForceDomePhysics] = {
sector
.amenityList
.flatMap {
case dome: ForceDomePhysics if dome.Perimeter.nonEmpty => Some(dome)
case _ => None
}
.find { dome =>
ForceDomeControl.TargetUnderForceDome(dome.Perimeter)(target, dome, maxDistance = 0f)
}
.map { dome =>
protectedBy = Some(dome)
target.Actor ! Damageable.MakeInvulnerable
dome
}
}
/**
* na
* @see `Damageable.MakeVulnerable`
* @param target the fixed element in this test
*/
def resetInteraction(target: InteractsWithZone): Unit = {
protectSkipCounter = 0
protectedBy = None
target.Actor ! Damageable.MakeVulnerable
}
}

View file

@ -29,6 +29,12 @@ object ForceDomeControl {
final case object NormalBehavior extends Command
final case object ApplyProtection extends Command
final case object RemoveProtection extends Command
final case object Purge extends Command
/**
* Dispatch a message to update the state of the clients with the server state of the capitol force dome.
* @param dome force dome
@ -202,8 +208,9 @@ object ForceDomeControl {
Zone.serverSideDamage(
dome.Zone,
dome,
ForceDomeExposure.damageProperties,
makesContactWithForceDome,
targetUnderForceDome(perimeter),
TargetUnderForceDome(perimeter),
forceDomeTargets(dome.Definition.UseRadius, dome.Faction)
)
}
@ -235,14 +242,14 @@ object ForceDomeControl {
* @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 = {
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) =>
@ -343,12 +350,12 @@ class ForceDomeControl(dome: ForceDomePhysics)
def CaptureTerminalAwareObject: Amenity with CaptureTerminalAware = 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 */
/** a capitol force dome's owner should always be a facility;
* to save time, cast this entity and cache it for repeated use once;
* force dome is not immediately owned by its correct facility so delay determination */
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)
private lazy 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
@ -382,6 +389,16 @@ class ForceDomeControl(dome: ForceDomePhysics)
ForceDomeControl.NormalDomeStateMessage(domeOwnerAsABuilding)
ForceDomeControl.AlignForceDomeStatusAndUpdate(domeOwnerAsABuilding, dome)
ForceDomeControl.ForceDomeKills(dome, perimeterSegments)
case ForceDomeControl.ApplyProtection
if dome.Energized =>
dome.Perimeter = perimeterSegments
case ForceDomeControl.RemoveProtection =>
dome.Perimeter = List.empty
case ForceDomeControl.Purge =>
ForceDomeControl.ForceDomeKills(dome, perimeterSegments)
}
def poweredStateLogic: Receive = {
@ -437,15 +454,25 @@ class ForceDomeControl(dome: ForceDomePhysics)
/**
* 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.
* Apply changes to region represented as "bound" by the perimeter as indicated by a state change.
* @param func function to run if not blocked
* @return current energized state of the dome
*/
private def blockedByCustomStateOr(func: (Building, ForceDomePhysics) => Boolean): Boolean = {
import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global
customState match {
case None =>
val oldState = dome.Energized
val newState = func(domeOwnerAsABuilding, dome)
if (!dome.Energized && newState) {
ForceDomeControl.ForceDomeKills(dome, perimeterSegments)
if (!oldState && newState) {
//dome activating
context.system.scheduler.scheduleOnce(delay = 1500 milliseconds, self, ForceDomeControl.Purge)
context.system.scheduler.scheduleOnce(delay = 4000 milliseconds, self, ForceDomeControl.ApplyProtection)
} else if (oldState && !newState) {
//dome de-activating
dome.Zone.blockMap.removeFrom(dome)
}
newState
case Some(state) =>

View file

@ -1,12 +1,16 @@
// Copyright (c) 2025 PSForever
package net.psforever.objects.serverobject.dome
import net.psforever.objects.geometry.d3.{Sphere, VolumetricGeometry}
import net.psforever.objects.serverobject.structures.AmenityDefinition
import net.psforever.objects.sourcing.SourceEntry
import net.psforever.types.Vector3
class ForceDomeDefinition(objectId: Int)
extends AmenityDefinition(objectId) {
Name = "force_dome"
Geometry = ForceDomeDefinition.representBy
/** 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 */
@ -19,3 +23,22 @@ class ForceDomeDefinition(objectId: Int)
PerimeterOffsets
}
}
object ForceDomeDefinition {
/**
* na
* @param o na
* @return na
*/
def representBy(o: Any): VolumetricGeometry = {
import net.psforever.objects.geometry.GeometryForm.invalidPoint
o match {
case fdp: ForceDomePhysics =>
Sphere(fdp.Position, fdp.Definition.UseRadius)
case s: SourceEntry =>
Sphere(s.Position, s.Definition.UseRadius)
case _ =>
Sphere(invalidPoint, 1f)
}
}
}

View file

@ -10,6 +10,8 @@ class ForceDomePhysics(private val cfddef: ForceDomeDefinition)
with CaptureTerminalAware {
private var energized: Boolean = false
private var perimeter: List[(Vector3, Vector3)] = List()
def Energized: Boolean = energized
def Energized_=(state: Boolean): Boolean = {
@ -17,6 +19,13 @@ class ForceDomePhysics(private val cfddef: ForceDomeDefinition)
Energized
}
def Perimeter: List[(Vector3, Vector3)] = perimeter
def Perimeter_=(list: List[(Vector3, Vector3)]): List[(Vector3, Vector3)] = {
perimeter = list
Perimeter
}
def Definition: ForceDomeDefinition = cfddef
}

View file

@ -5,7 +5,7 @@ import net.psforever.objects.sourcing.{AmenitySource, SourceEntry}
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.vital.prop.DamageProperties
import net.psforever.objects.vital.prop.{DamageProperties, DamageWithPosition}
import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel}
/**
@ -52,7 +52,7 @@ object ForceDomeExposure {
Model = SimpleResolutions.calculate
}
final val damageProperties = new DamageProperties {
final val damageProperties = new DamageWithPosition {
Damage0 = 99999
DamageToHealthOnly = true
DamageToVehicleOnly = true