mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-03-23 14:20:45 +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
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 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) =>
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue