From d0d7632f82066208512b9ec291a8e0eb019a9c89 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 20 Apr 2026 20:34:34 -0400 Subject: [PATCH] corrected input data to force door wall inclusion test; adjusted force dome ceiling inclusion test(s); force dome generator locations now retained; invulnerability can not be granted to players or vehicles if they've taken damage recently --- .../objects/avatar/PlayerControl.scala | 9 +++ .../GlobalDefinitionsMiscellaneous.scala | 5 ++ .../serverobject/damage/Damageable.scala | 14 +++-- .../serverobject/dome/ForceDomeControl.scala | 60 +++++++++++++------ .../dome/ForceDomeDefinition.scala | 9 +++ .../serverobject/dome/ForceDomePhysics.scala | 2 +- .../vehicles/control/VehicleControl.scala | 11 +++- 7 files changed, 86 insertions(+), 24 deletions(-) diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 972106aa9..189d788d5 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -804,6 +804,15 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm } } + override protected def canChangeVulnerability(state: Damageable.PersonalVulnerability): Boolean = { + state match { + case Damageable.MakeInvulnerable => + player.LastDamage.exists { d => System.currentTimeMillis() - d.interaction.hitTime > 2500L } + case _ => + true + } + } + override protected def PerformDamage( target: Target, applyDamageTo: Output diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala index b85b9e872..1b1ddf536 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala @@ -966,6 +966,7 @@ object GlobalDefinitionsMiscellaneous { Vector3(83.05469f, -106.90625f, 0f) ) force_dome_amp_physics.ApplyProtectionTo = List(generator, manned_turret) + force_dome_amp_physics.GeneratorOffset = Vector3(0.148f, -21.867f, 17.623f) force_dome_comm_physics.Name = "force_dome_comm_physics" force_dome_comm_physics.UseRadius = 121.8149f @@ -978,6 +979,7 @@ object GlobalDefinitionsMiscellaneous { Vector3(-83.640625f, -89.859375f, 0f) ) force_dome_comm_physics.ApplyProtectionTo = List(generator, manned_turret) + force_dome_comm_physics.GeneratorOffset = Vector3(-19.481f, -4.25f, 12.649f) force_dome_cryo_physics.Name = "force_dome_cryo_physics" force_dome_cryo_physics.UseRadius = 127.9241f //127.7963f @@ -989,6 +991,7 @@ object GlobalDefinitionsMiscellaneous { Vector3(72.75476f, -103.47f, 0) ) force_dome_cryo_physics.ApplyProtectionTo = List(generator, implant_terminal_mech, manned_turret) + force_dome_cryo_physics.GeneratorOffset = Vector3(-20.709f, 5.387f, 19.466f) force_dome_dsp_physics.Name = "force_dome_dsp_physics" force_dome_dsp_physics.UseRadius = 175.8838f //175.7081f @@ -1001,6 +1004,7 @@ object GlobalDefinitionsMiscellaneous { Vector3(130.44531f, -93.28125f, 0f) ) force_dome_dsp_physics.ApplyProtectionTo = List(generator, manned_turret) + force_dome_dsp_physics.GeneratorOffset = Vector3(12.698f, 25.15f, 12.541f) force_dome_tech_physics.Name = "force_dome_tech_physics" force_dome_tech_physics.UseRadius = 150.1284f @@ -1016,5 +1020,6 @@ object GlobalDefinitionsMiscellaneous { Vector3(102.12191f, -114.65837f, 0f) ) force_dome_tech_physics.ApplyProtectionTo = List(generator, manned_turret) + force_dome_tech_physics.GeneratorOffset = Vector3(13.001f, -32.612f, 22.59f) } } diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala index 65d94d0d4..0b2e86597 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala @@ -10,6 +10,8 @@ import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.resolution.ResolutionCalculations +import scala.annotation.unused + /** * The base "control" `Actor` mixin for damage-handling code. * A valid entity requires health points and @@ -31,10 +33,10 @@ trait Damageable { * `orElse` onto the "control" `Actor` `receive`; or, * cite the `originalTakesDamage` protocol during inheritance overrides */ val takesDamage: Receive = { - case Damageable.MakeVulnerable => + case Damageable.MakeVulnerable if canChangeVulnerability(Damageable.MakeVulnerable) => isVulnerable = true - case Damageable.MakeInvulnerable => + case Damageable.MakeInvulnerable if canChangeVulnerability(Damageable.MakeInvulnerable) => isVulnerable = false case Vitality.Damage(damage_func) => @@ -43,16 +45,20 @@ trait Damageable { /** a duplicate of the core implementation for the default mixin hook, for use in overriding */ final val originalTakesDamage: Receive = { - case Damageable.MakeVulnerable => + case Damageable.MakeVulnerable if canChangeVulnerability(Damageable.MakeVulnerable) => isVulnerable = true - case Damageable.MakeInvulnerable => + case Damageable.MakeInvulnerable if canChangeVulnerability(Damageable.MakeInvulnerable) => isVulnerable = false case Vitality.Damage(damage_func) => PerformDamageIfVulnerable(DamageableObject, damage_func) } + protected def canChangeVulnerability(@unused state: Damageable.PersonalVulnerability): Boolean = { + true + } + /** * Assess if the target is vulnerable to damage. * If so, attempt damage calculations. diff --git a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeControl.scala b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeControl.scala index e959dd836..802a6ed31 100644 --- a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeControl.scala @@ -240,8 +240,7 @@ object ForceDomeControl { * where the second condition is one of two qualifications: * 1. within an angular perimeter boundary, and * 2a. below the base coordinate of the force dome or - * 2b. within a region above the base of the force dome represented by a literal "dome" (half of a sphere). - * @see `Zone.distanceCheck` + * 2b. within a region above the facility encapsulated by the force dome * @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 @@ -257,13 +256,36 @@ object ForceDomeControl { obj2: PlanetSideGameObject, @unused maxDistance: Float ): Boolean = { - val centerPos @ Vector3(centerX, centerY, centerZ) = obj1.Position - val Vector3(targetX, targetY, _) = obj2.Position.xy - centerPos.xy //deltas of segment of target to dome - lazy val checkForIntersection = segments.exists { case (point1, point2) => - //want targets within the perimeter; if there's an intersection, target is outside of the perimeter + val Vector3(centerX, centerY, centerZ) = obj1.Position + val Vector3(targetX, targetY, targetZ) = obj2.Position + lazy val insideOfThePerimeter = !segments.exists { case (point1, point2) => + //want targets within the perimeter; if there's any intersection, target is outside of the perimeter segmentIntersectionTestPerSegment(centerX, centerY, targetX, targetY, point1.x, point1.y, point2.x, point2.y) } - segments.nonEmpty && !checkForIntersection && (obj2.Position.z <= centerZ || Zone.distanceCheck(obj1, obj2, math.pow(obj1.Definition.UseRadius, 2).toFloat)) + segments.nonEmpty && insideOfThePerimeter && (targetZ <= centerZ || underForceDomeCeiling(obj1, obj2, targetZ)) + } + + /** + * The force dome is the top portion of an irregular prism. + * It's walls are similarly angled inwards, usually. + * There's no way to properly represent this without too much work. + * Just ensure the target is either below the dome or within a circular region + * defined by the base of the generator to the top of the extended stalk as the measurement of a hemisphere. + * @see `Zone.distanceCheck` + * @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 height target height that must be under the ceiling + * @return `true`, if target is detected within the force dome kill region + * `false`, otherwise + */ + private def underForceDomeCeiling(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, height: Float): Boolean = { + obj1 match { + case dome: ForceDomePhysics => + val generatorHeightOffsetZ = dome.Definition.GeneratorOffset.z + height < dome.Owner.Position.z + generatorHeightOffsetZ * 1.5f || Zone.distanceCheck(obj1, obj2, generatorHeightOffsetZ + 42f) + case _ => + false + } } /** @@ -294,21 +316,23 @@ object ForceDomeControl { ): 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 + val ax = targetX - pointX //delta-x of segment from center to target + val ay = targetY - pointY //delta-y of segment from center to target + val bx = segmentPoint1x - segmentPoint2x //delta-x of test segment + val by = segmentPoint1y - segmentPoint2y //delta-y of test segment + val cx = pointX - segmentPoint1x //delta-x of hypotenuse of triangle formed by center, test segment endpoint, and intersection point + val cy = pointY - segmentPoint1y //delta-y of hypotenuse of triangle formed by center, test segment endpoint, and intersection point + val alphaNumerator = by * cx - bx * cy //cross product of matrix [(by, bx)(cy, cx)] + val denominator = ay * bx - ax * by //cross product of matrix [(ay, ax)(cy, cx)] + val betaNumerator = ax * cy - ay * cx //cross product of matrix [(ax, ay)(cx, cy)] if ( - commonDenominator > 0 && - (alphaNumerator < 0 || alphaNumerator > commonDenominator || betaNumerator < 0 || betaNumerator > commonDenominator) + denominator > 0 && + (alphaNumerator < 0 || alphaNumerator > denominator || betaNumerator < 0 || betaNumerator > denominator) ) { false } else if ( - commonDenominator < 0 && - (alphaNumerator > 0 || alphaNumerator < commonDenominator || betaNumerator > 0 || betaNumerator < commonDenominator) + denominator < 0 && + (alphaNumerator > 0 || alphaNumerator < denominator || betaNumerator > 0 || betaNumerator < denominator) ) { false } else { diff --git a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala index efca36455..a1dedb00d 100644 --- a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomeDefinition.scala @@ -14,6 +14,8 @@ class ForceDomeDefinition(objectId: Int) * 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() + /** offset of the physical location of the force dome generator stalk */ + private var genOffset: Vector3 = Vector3.Zero def PerimeterOffsets: List[Vector3] = perimeter @@ -34,6 +36,13 @@ class ForceDomeDefinition(objectId: Int) protects = protect ApplyProtectionTo } + + def GeneratorOffset: Vector3 = genOffset + + def GeneratorOffset_=(offset: Vector3): Vector3 = { + genOffset = offset + GeneratorOffset + } } object ForceDomeDefinition { diff --git a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala index 1cd60c2a0..82dd187ae 100644 --- a/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala +++ b/src/main/scala/net/psforever/objects/serverobject/dome/ForceDomePhysics.scala @@ -11,7 +11,7 @@ class ForceDomePhysics(private val cfddef: ForceDomeDefinition) /** whether the dome is active or not */ private var energized: Boolean = false /** defined perimeter of this force dome on the floor; - * the walls created by this perimeter are angled inwards towards the facility, but that's not a consideration */ + * the walls created by this perimeter are angled inwards towards the facility vertically, but that's not a consideration here */ private var perimeter: List[(Vector3, Vector3)] = List() override def Position: Vector3 = Owner.Position diff --git a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala index d110fdc98..78c423197 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala @@ -15,7 +15,7 @@ import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObjec import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior} import net.psforever.objects.serverobject.damage.Damageable.Target -import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle} +import net.psforever.objects.serverobject.damage.{AggravatedBehavior, Damageable, DamageableVehicle} import net.psforever.objects.serverobject.environment._ import net.psforever.objects.serverobject.environment.interaction.common.Watery import net.psforever.objects.serverobject.environment.interaction.{InteractWithEnvironment, RespondsToZoneEnvironment} @@ -742,6 +742,15 @@ class VehicleControl(vehicle: Vehicle) } } + override protected def canChangeVulnerability(state: Damageable.PersonalVulnerability): Boolean = { + state match { + case Damageable.MakeInvulnerable => + vehicle.LastDamage.exists { d => System.currentTimeMillis() - d.interaction.hitTime > 2500L } + case _ => + true + } + } + override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = { StopInteractionSelfReportingNoReset() super.DestructionAwareness(target, cause)