mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-04-23 13:25:25 +00:00
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:
parent
4b3f8ea6c0
commit
dd0f5fc928
6 changed files with 167 additions and 18 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects
|
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.avatar.{Avatar, LoadoutManager, SpecialCarry}
|
||||||
import net.psforever.objects.ballistics.InteractWithRadiationClouds
|
import net.psforever.objects.ballistics.InteractWithRadiationClouds
|
||||||
import net.psforever.objects.ce.{Deployable, InteractWithMines, InteractWithTurrets}
|
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 InteractWithMines(range = 10, TriggerOnPlayerRule))
|
||||||
interaction(new InteractWithTurrets())
|
interaction(new InteractWithTurrets())
|
||||||
interaction(new InteractWithRadiationClouds(range = 10f, Some(this)))
|
interaction(new InteractWithRadiationClouds(range = 10f, Some(this)))
|
||||||
|
interaction(new InteractWithForceDomeProtection())
|
||||||
|
|
||||||
private var backpack: Boolean = false
|
private var backpack: Boolean = false
|
||||||
private var released: Boolean = false
|
private var released: Boolean = false
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -29,6 +29,12 @@ object ForceDomeControl {
|
||||||
|
|
||||||
final case object NormalBehavior extends Command
|
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.
|
* 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
|
||||||
|
|
@ -202,8 +208,9 @@ object ForceDomeControl {
|
||||||
Zone.serverSideDamage(
|
Zone.serverSideDamage(
|
||||||
dome.Zone,
|
dome.Zone,
|
||||||
dome,
|
dome,
|
||||||
|
ForceDomeExposure.damageProperties,
|
||||||
makesContactWithForceDome,
|
makesContactWithForceDome,
|
||||||
targetUnderForceDome(perimeter),
|
TargetUnderForceDome(perimeter),
|
||||||
forceDomeTargets(dome.Definition.UseRadius, dome.Faction)
|
forceDomeTargets(dome.Definition.UseRadius, dome.Faction)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -235,14 +242,14 @@ object ForceDomeControl {
|
||||||
* @return `true`, if target is detected within the force dome kill region
|
* @return `true`, if target is detected within the force dome kill region
|
||||||
* `false`, otherwise
|
* `false`, otherwise
|
||||||
*/
|
*/
|
||||||
private def targetUnderForceDome(
|
def TargetUnderForceDome(
|
||||||
segments: List[(Vector3, Vector3)]
|
segments: List[(Vector3, Vector3)]
|
||||||
)
|
)
|
||||||
(
|
(
|
||||||
obj1: PlanetSideGameObject,
|
obj1: PlanetSideGameObject,
|
||||||
obj2: PlanetSideGameObject,
|
obj2: PlanetSideGameObject,
|
||||||
@unused maxDistance: Float
|
@unused maxDistance: Float
|
||||||
): Boolean = {
|
): Boolean = {
|
||||||
val centerPos @ Vector3(centerX, centerY, centerZ) = obj1.Position
|
val centerPos @ Vector3(centerX, centerY, centerZ) = obj1.Position
|
||||||
val Vector3(targetX, targetY, targetZ) = obj2.Position - centerPos //deltas of segment of target to dome
|
val Vector3(targetX, targetY, targetZ) = obj2.Position - centerPos //deltas of segment of target to dome
|
||||||
val checkForIntersection = segments.exists { case (point1, point2) =>
|
val checkForIntersection = segments.exists { case (point1, point2) =>
|
||||||
|
|
@ -343,12 +350,12 @@ 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;
|
/** a capitol force dome's owner should always be a facility;
|
||||||
* to save time, casted this entity and cache it for repeated use once;
|
* to save time, cast this entity and cache it for repeated use once;
|
||||||
* force dome is not immediately owned (by correct facility) so delay determination */
|
* force dome is not immediately owned by its 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) */
|
/** 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 */
|
/** 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
|
||||||
|
|
||||||
|
|
@ -382,6 +389,16 @@ class ForceDomeControl(dome: ForceDomePhysics)
|
||||||
ForceDomeControl.NormalDomeStateMessage(domeOwnerAsABuilding)
|
ForceDomeControl.NormalDomeStateMessage(domeOwnerAsABuilding)
|
||||||
ForceDomeControl.AlignForceDomeStatusAndUpdate(domeOwnerAsABuilding, dome)
|
ForceDomeControl.AlignForceDomeStatusAndUpdate(domeOwnerAsABuilding, dome)
|
||||||
ForceDomeControl.ForceDomeKills(dome, perimeterSegments)
|
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 = {
|
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.
|
* 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.
|
* 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
|
* @param func function to run if not blocked
|
||||||
* @return current energized state of the dome
|
* @return current energized state of the dome
|
||||||
*/
|
*/
|
||||||
private def blockedByCustomStateOr(func: (Building, ForceDomePhysics) => Boolean): Boolean = {
|
private def blockedByCustomStateOr(func: (Building, ForceDomePhysics) => Boolean): Boolean = {
|
||||||
|
import scala.concurrent.duration._
|
||||||
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
|
|
||||||
customState match {
|
customState match {
|
||||||
case None =>
|
case None =>
|
||||||
|
val oldState = dome.Energized
|
||||||
val newState = func(domeOwnerAsABuilding, dome)
|
val newState = func(domeOwnerAsABuilding, dome)
|
||||||
if (!dome.Energized && newState) {
|
if (!oldState && newState) {
|
||||||
ForceDomeControl.ForceDomeKills(dome, perimeterSegments)
|
//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
|
newState
|
||||||
case Some(state) =>
|
case Some(state) =>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,16 @@
|
||||||
// Copyright (c) 2025 PSForever
|
// Copyright (c) 2025 PSForever
|
||||||
package net.psforever.objects.serverobject.dome
|
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.serverobject.structures.AmenityDefinition
|
||||||
|
import net.psforever.objects.sourcing.SourceEntry
|
||||||
import net.psforever.types.Vector3
|
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"
|
||||||
|
Geometry = ForceDomeDefinition.representBy
|
||||||
|
|
||||||
/** offsets that define the perimeter of the pyramidal force "dome" barrier;
|
/** 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;
|
* these points are the closest to where the dome interacts with the ground at a corner;
|
||||||
* should be sequential, either clockwise or counterclockwise */
|
* should be sequential, either clockwise or counterclockwise */
|
||||||
|
|
@ -19,3 +23,22 @@ class ForceDomeDefinition(objectId: Int)
|
||||||
PerimeterOffsets
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,8 @@ class ForceDomePhysics(private val cfddef: ForceDomeDefinition)
|
||||||
with CaptureTerminalAware {
|
with CaptureTerminalAware {
|
||||||
private var energized: Boolean = false
|
private var energized: Boolean = false
|
||||||
|
|
||||||
|
private var perimeter: List[(Vector3, Vector3)] = List()
|
||||||
|
|
||||||
def Energized: Boolean = energized
|
def Energized: Boolean = energized
|
||||||
|
|
||||||
def Energized_=(state: Boolean): Boolean = {
|
def Energized_=(state: Boolean): Boolean = {
|
||||||
|
|
@ -17,6 +19,13 @@ class ForceDomePhysics(private val cfddef: ForceDomeDefinition)
|
||||||
Energized
|
Energized
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def Perimeter: List[(Vector3, Vector3)] = perimeter
|
||||||
|
|
||||||
|
def Perimeter_=(list: List[(Vector3, Vector3)]): List[(Vector3, Vector3)] = {
|
||||||
|
perimeter = list
|
||||||
|
Perimeter
|
||||||
|
}
|
||||||
|
|
||||||
def Definition: ForceDomeDefinition = cfddef
|
def Definition: ForceDomeDefinition = cfddef
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import net.psforever.objects.sourcing.{AmenitySource, SourceEntry}
|
||||||
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
|
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
|
||||||
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
||||||
import net.psforever.objects.vital.damage.DamageCalculations
|
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}
|
import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -52,7 +52,7 @@ object ForceDomeExposure {
|
||||||
Model = SimpleResolutions.calculate
|
Model = SimpleResolutions.calculate
|
||||||
}
|
}
|
||||||
|
|
||||||
final val damageProperties = new DamageProperties {
|
final val damageProperties = new DamageWithPosition {
|
||||||
Damage0 = 99999
|
Damage0 = 99999
|
||||||
DamageToHealthOnly = true
|
DamageToHealthOnly = true
|
||||||
DamageToVehicleOnly = true
|
DamageToVehicleOnly = true
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue