mechanism for server-driven emp caused by projectiles with emp properties; finalization of geometry elements and tests for geometric tests

This commit is contained in:
Jason_DiDonato@yahoo.com 2021-02-08 00:20:17 -05:00
parent e41e7e7cfa
commit 6e81ee7e95
11 changed files with 706 additions and 52 deletions

View file

@ -5252,8 +5252,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
CheckForHitPositionDiscrepancy(projectile_guid, target.Position, target)
ResolveProjectileInteraction(projectile, resolution1, target, target.Position) match {
case Some(projectile) =>
HandleDealingDamage(target, projectile)
case Some(_projectile) =>
HandleDealingDamage(target, _projectile)
case None => ;
}
case _ => ;
@ -5264,13 +5264,25 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos) match {
case Some(projectile) =>
HandleDealingDamage(target, projectile)
case Some(_projectile) =>
HandleDealingDamage(target, _projectile)
case None => ;
}
case _ => ;
}
})
if (
projectile.profile.HasJammedEffectDuration ||
projectile.profile.JammerProjectile ||
projectile.profile.SympatheticExplosion
) {
Zone.causeSpecialEmp(
continent,
player,
explosion_pos,
GlobalDefinitions.special_emp.innateDamage.get
)
}
if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
//cleanup
val localIndex = projectile_guid.guid - Projectile.baseUID

View file

@ -82,7 +82,7 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D
mine,
DamageInteraction(
SourceEntry(mine),
TriggerUsedReason(PlayerSource(player), trigger),
TriggerUsedReason(PlayerSource(player), trigger.GUID),
mine.Position
).calculate()(mine),
damage = 0
@ -239,6 +239,10 @@ object ExplosiveDeployableControl {
val scalar = Vector3.ScalarProjection(dir, up)
val point1 = g1.pointOnOutside(dir).asVector3
val point2 = g2.pointOnOutside(Vector3.neg(dir)).asVector3
(scalar >= 0 || Vector3.MagnitudeSquared(up * scalar) < 0.35f) && Vector3.DistanceSquared(point1, point2) <= maxDistance
(scalar >= 0 || Vector3.MagnitudeSquared(up * scalar) < 0.35f) &&
math.min(
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3),
Vector3.DistanceSquared(point1, point2)
) <= maxDistance
}
}

View file

@ -972,6 +972,8 @@ object GlobalDefinitions {
val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
val special_emp = ExplosiveDeployableDefinition(DeployedItem.jammer_mine)
//this is only treated like a deployable
val internal_router_telepad_deployable = InternalTelepadDefinition() //objectId: 744
init_deployables()
@ -7293,6 +7295,22 @@ object GlobalDefinitions {
internal_router_telepad_deployable.DeployTime = Duration.create(1, "ms")
internal_router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
internal_router_telepad_deployable.Packet = new InternalTelepadDeployableConverter
special_emp.Name = "emp"
special_emp.MaxHealth = 1
special_emp.Damageable = false
special_emp.Repairable = false
special_emp.DeployCategory = DeployableCategory.Mines
special_emp.explodes = true
special_emp.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.Splash
SympatheticExplosion = true
Damage0 = 0
DamageAtEdge = 1.0f
DamageRadius = 5f
AdditionalEffect = true
Modifiers = MaxDistanceCutoff
}
}
/**
@ -7844,12 +7862,11 @@ object GlobalDefinitions {
generator.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One
Damage0 = 99999
//DamageRadius should be 14, but 14 is insufficient for hitting the whole chamber; hence, ...
DamageRadius = 15.75f
DamageRadiusMin = 14
DamageRadius = 14.5f
DamageAtEdge = 0.00002f
Modifiers = ExplodingRadialDegrade
//damage is 99999 at 14m, dropping rapidly to ~1 at 15.75m
//damage is 99999 at 14m, dropping rapidly to ~1 at 14.5m
}
generator.Geometry = GeometryForm.representByCylinder(radius = 1.2617f, height = 9.14063f)
}

View file

@ -6,7 +6,7 @@ import net.psforever.types.Vector3
object Geometry {
def equalFloats(value1: Float, value2: Float, off: Float = 0.001f): Boolean = {
val diff = value1 - value2
(diff >= 0 && diff <= off) || diff > -off
if (diff >= 0) diff <= off else diff > -off
}
def equalVectors(value1: Vector3, value2: Vector3, off: Float = 0.001f): Boolean = {

View file

@ -3,11 +3,22 @@ package net.psforever.objects.geometry
import net.psforever.types.Vector3
/**
* Basic interface for all geometry.
*/
trait PrimitiveGeometry {
/**
* The centroid of the geometry.
* @return a point
*/
def center: Point
def pointOnOutside(line: Line) : Point = pointOnOutside(line.d)
/**
* Find a point on the exterior of the geometry if a line was drawn outwards from the centroid.
* What counts as "the exterior" is limited to the complexity of the geometry.
* @param v the vector in the direction of the point on the exterior
* @return a point
*/
def pointOnOutside(v: Vector3) : Point
}
@ -17,43 +28,103 @@ trait PrimitiveGeometry {
// def pointOnOutside(v: Vector3): Point2D = center
//}
/**
* Basic interface of all three-dimensional geometry.
* For the only real requirement for a hree-dimensional geometric figure is that it has three components of position
* and an equal number of components demonstrating equal that said dimensionality.
*/
trait Geometry3D extends PrimitiveGeometry {
def center: Point3D
def pointOnOutside(v: Vector3): Point3D = center
}
/**
* Characteristics of a geometric figure with only three coordinates to define a position.
*/
trait Point {
/**
* Transform the point into the common interchangeable format for coordinates.
* They're very similar, anyway.
* @return a `Vector3` entity of the same denomination
*/
def asVector3: Vector3
}
/**
* Characteristics of a geometric figure defining a direction or a progressive change in coordinates.
*/
trait Slope {
/**
* The slope itself.
* @return a `Vector3` entity
*/
def d: Vector3
/**
* How long the slope goes on for.
* @return The length of the slope
*/
def length: Float
}
object Slope {
/**
* On occasions, the defined slope should have a length of one unit.
* It is a unit vector.
* @param v the input slope as a `Vector3` entity
* @throws `AssertionError` if the length is more or less than 1.
*/
def assertUnitVector(v: Vector3): Unit = {
assert({
val mag = Vector3.Magnitude(v)
mag - 0.05f < 1f && mag + 0.05f > 1f
}, "not a unit vector")
}
}
/**
* Characteristics of a geometric figure indicating an infinite slope - a mathematical line.
* The slope is always a unit vector.
* The point that assists to define the line is a constraint that the line must pass through.
*/
trait Line extends Slope {
assert({
val mag = Vector3.Magnitude(d)
mag - 0.05f < 1f && mag + 0.05f > 1f
}, "not a unit vector")
Slope.assertUnitVector(d)
def p: Point
/**
* The length of a mathematical line is infinite.
* @return The length of the slope
*/
def length: Float = Float.PositiveInfinity
}
/**
* Characteristics of a geometric figure that have two endpoints, defining a fixed-length slope.
*/
trait Segment extends Slope {
/** The first point, considered the "start". */
def p1: Point
/** The second point, considered the "end". */
def p2: Point
def length: Float = Vector3.Magnitude(d)
/**
* Transform the segment into a matheatical line of the same slope.
* @return
*/
def asLine: PrimitiveGeometry
}
/**
* The instance of a geometric coordinate position.
* @see `Vector3`
* @param x the 'x' coordinate of the position
* @param y the 'y' coordinate of the position
* @param z the 'z' coordinate of the position
*/
final case class Point3D(x: Float, y: Float, z: Float) extends Geometry3D with Point {
def center: Point3D = this
@ -61,39 +132,119 @@ final case class Point3D(x: Float, y: Float, z: Float) extends Geometry3D with P
}
object Point3D {
/**
* An overloaded constructor that assigns world origin coordinates.
* @return a `Point3D` entity
*/
def apply(): Point3D = Point3D(0,0,0)
/**
* An overloaded constructor that uses the same coordinates from a `Vector3` entity.
* @param v the entity with the corresponding points
* @return a `Point3D` entity
*/
def apply(v: Vector3): Point3D = Point3D(v.x, v.y, v.z)
}
/**
* The instance of a geometric coordinate position and a specific direction from that position.
* Rays are like mathematical lines in that they have infinite length;
* but, that infinite length is only expressed in a single direction,
* rather than proceeding in both a direction and its opposite direction from a target point.
* Infinity just be like that.
* Additionally, the point is not merely any point on the ray used to assist defining it
* and is instead considered the clearly-defined origin of the ray.
* @param p the point of origin
* @param d the direction
*/
final case class Ray3D(p: Point3D, d: Vector3) extends Geometry3D with Line {
def center: Point3D = p
}
object Ray3D {
/**
* An overloaded constructor that uses individual coordinates.
* @param x the 'x' coordinate of the position
* @param y the 'y' coordinate of the position
* @param z the 'z' coordinate of the position
* @param d the direction
* @return a `Ray3D` entity
*/
def apply(x: Float, y: Float, z: Float, d: Vector3): Ray3D = Ray3D(Point3D(x,y,z), d)
/**
* An overloaded constructor that uses a `Vector3` entity to express coordinates.
* @param v the coordinates of the position
* @param d the direction
* @return a `Ray3D` entity
*/
def apply(v: Vector3, d: Vector3): Ray3D = Ray3D(Point3D(v.x, v.y, v.z), d)
}
/**
* The instance of a geometric coordinate position and a specific direction from that position.
* Mathematical lines have infinite length and their slope is represented as a unit vector.
* The point is merely a point used to assist in defining the line.
* @param p the point of origin
* @param d the direction
*/
final case class Line3D(p: Point3D, d: Vector3) extends Geometry3D with Line {
def center: Point3D = p
}
object Line3D {
/**
* An overloaded constructor that uses individual coordinates.
* @param x the 'x' coordinate of the position
* @param y the 'y' coordinate of the position
* @param z the 'z' coordinate of the position
* @param d the direction
* @return a `Line3D` entity
*/
def apply(x: Float, y: Float, z: Float, d: Vector3): Line3D = {
Line3D(Point3D(x,y,z), d)
}
/**
* An overloaded constructor that uses a pair of individual coordinates
* and uses their difference to produce a unit vector to define a direction.
* @param ax the 'x' coordinate of the position
* @param ay the 'y' coordinate of the position
* @param az the 'z' coordinate of the position
* @param bx the 'x' coordinate of a destination position
* @param by the 'y' coordinate of a destination position
* @param bz the 'z' coordinate of a destination position
* @return a `Line3D` entity
*/
def apply(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float): Line3D = {
Line3D(Point3D(ax, ay, az), Vector3.Unit(Vector3(bx-ax, by-ay, bz-az)))
}
/**
* An overloaded constructor that uses a pair of points
* and uses their difference to produce a unit vector to define a direction.
* @param p1 the coordinates of the position
* @param p2 the coordinates of a destination position
* @return a `Line3D` entity
*/
def apply(p1: Point3D, p2: Point3D): Line3D = {
Line3D(p1, Vector3.Unit(Vector3(p2.x-p1.x, p2.y-p1.y, p2.z-p1.z)))
}
}
/**
* The instance of a limited span between two geometric coordinate positions, called "endpoints".
* Unlike mathematical lines, slope is treated the same as the vector leading from one point to the other
* and is the length of the segment.
* @param p1 a point
* @param p2 another point
*/
final case class Segment3D(p1: Point3D, p2: Point3D) extends Geometry3D with Segment {
def center: Point3D = Point3D(d * 0.5f)
/**
* The center point of a segment is a position that is equally in between both endpoints.
* @return a point
*/
def center: Point3D = Point3D((p2.asVector3 + p1.asVector3) * 0.5f)
def d: Vector3 = p2.asVector3 - p1.asVector3
@ -101,65 +252,182 @@ final case class Segment3D(p1: Point3D, p2: Point3D) extends Geometry3D with Seg
}
object Segment3D {
/**
* An overloaded constructor that uses a pair of individual coordinates
* and uses their difference to define a direction.
* @param ax the 'x' coordinate of the position
* @param ay the 'y' coordinate of the position
* @param az the 'z' coordinate of the position
* @param bx the 'x' coordinate of a destination position
* @param by the 'y' coordinate of a destination position
* @param bz the 'z' coordinate of a destination position
* @return a `Segment3D` entity
*/
def apply(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float): Segment3D = {
Segment3D(Point3D(ax, ay, az), Point3D(bx, by, bz))
}
/**
* An overloaded constructor.
* @param p the point of origin
* @param d the direction and distance (of the second point)
*/
def apply(p: Point3D, d: Vector3): Segment3D = {
Segment3D(p, Point3D(p.x + d.x, p.y + d.y, p.z + d.z))
}
/**
* An overloaded constructor that uses individual coordinates.
* @param x the 'x' coordinate of the position
* @param y the 'y' coordinate of the position
* @param z the 'z' coordinate of the position
* @param d the direction
* @return a `Segment3D` entity
*/
def apply(x: Float, y: Float, z: Float, d: Vector3): Segment3D = {
Segment3D(Point3D(x, y, z), Point3D(x + d.x, y + d.y, z + d.z))
}
}
/**
* The instance of a volumetric region that encapsulates all points within a certain distance of a central point.
* (That's what a sphere is.)
* A sphere has no real "top", "base", or "side" as all directions are described the same.
* @param p the point
* @param radius a distance that spans all points in any direction from the central point
*/
final case class Sphere(p: Point3D, radius: Float) extends Geometry3D {
def center: Point3D = p
/**
* Find a point on the exterior of the geometry if a line was drawn outwards from the centroid.
* All points that exist on the exterior of a sphere are on the surface of that sphere
* and are equally distant from the central point.
* @param v the vector in the direction of the point on the exterior
* @return a point
*/
override def pointOnOutside(v: Vector3): Point3D = {
val slope = Vector3.Unit(v)
val mult = radius / math.sqrt(slope.x * slope.x + slope.y * slope.y + slope.z * slope.z)
val pointOnSurface = center.asVector3 + slope * mult.toFloat
Point3D(pointOnSurface.x, pointOnSurface.y, pointOnSurface.z)
val mult = radius / Vector3.Magnitude(slope)
Point3D(center.asVector3 + slope * mult)
}
}
object Sphere {
/**
* An overloaded constructor that only defines the radius of the sphere
* and places it at the world origin.
* @param radius a distance around the world origin coordinates
* @return a `Sphere` entity
*/
def apply(radius: Float): Sphere = Sphere(Point3D(), radius)
/**
* An overloaded constructor that uses individual coordinates to define the central point.
* * @param x the 'x' coordinate of the position
* * @param y the 'y' coordinate of the position
* * @param z the 'z' coordinate of the position
* @param radius a distance around the world origin coordinates
* @return a `Sphere` entity
*/
def apply(x: Float, y: Float, z: Float, radius: Float): Sphere = Sphere(Point3D(x,y,z), radius)
/**
* An overloaded constructor that uses vector coordinates to define the central point.
* @param v the coordinates of the position
* @param radius a distance around the world origin coordinates
* @return a `Sphere` entity
*/
def apply(v: Vector3, radius: Float): Sphere = Sphere(Point3D(v), radius)
}
final case class Cylinder(position: Vector3, relativeUp: Vector3, radius: Float, height: Float) extends Geometry3D {
def center: Point3D = Point3D(position + relativeUp * height * 0.5f)
/**
* The instance of a volumetric region that encapsulates all points within a certain distance of a central point.
* The region is characterized by a regular circular cross-section when observed from above or below
* and a flat top and a flat base when viewed from the side.
* The "base" is where the origin point is defined (at the center of a circular cross-section)
* and the "top" is discovered a `height` from the base along what the cylinder considers its `relativeUp` direction.
* @param p the point
* @param relativeUp what the cylinder considers its "up" direction
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
* @param height the distance between the "base" and the "top"
*/
final case class Cylinder(p: Point3D, relativeUp: Vector3, radius: Float, height: Float) extends Geometry3D {
Slope.assertUnitVector(relativeUp)
/**
* The center point of a cylinder is halfway between the "top" and the "base" along the direction of `relativeUp`.
* @return a point
*/
def center: Point3D = Point3D(p.asVector3 + relativeUp * height * 0.5f)
/**
* Find a point on the exterior of the geometry if a line was drawn outwards from the centroid.
* A cylinder is composed of three clearly-defined regions on its exterior -
* two flat but circular surfaces that are the "top" and the "base"
* and a wrapped "sides" surface that defines all points connecting the "base" to the "top"
* along the `relativeUp` direction.
* The requested point may exist on any of these surfaces.
* @param v the vector in the direction of the point on the exterior
* @return a point
*/
override def pointOnOutside(v: Vector3): Point3D = {
val centerVector = center.asVector3
val slope = Vector3.Unit(v)
val acrossTopAndBase = slope - relativeUp
val pointOnSide = centerVector + slope * (radius / Vector3.Magnitude(acrossTopAndBase))
val pointOnBase = position + acrossTopAndBase * radius
val pointOnTop = pointOnBase + relativeUp * height
val fromPointOnTopToSide = Vector3.Unit(pointOnTop - pointOnSide)
val fromPointOnSideToBase = Vector3.Unit(pointOnSide - pointOnBase)
val target = if(fromPointOnTopToSide == Vector3.Zero ||
fromPointOnSideToBase == Vector3.Zero ||
Geometry.equalVectors(fromPointOnTopToSide, fromPointOnSideToBase)) {
//on side, including top rim or base rim
pointOnSide
val dotProdOfSlopeAndUp = Vector3.DotProduct(slope, relativeUp)
if (Geometry.equalFloats(dotProdOfSlopeAndUp, value2 = 1) || Geometry.equalFloats(dotProdOfSlopeAndUp, value2 = -1)) {
// very rare condition: 'slope' and 'relativeUp' are parallel or antiparallel
Point3D(centerVector + slope * height * 0.5f)
} else {
//on top or base
// the full equation would be 'centerVector + slope * (height * 0.5f / Vector3.Magnitude(relativeUp))'
// 'relativeUp` is already a unit vector (magnitude of 1)
centerVector + slope * height * 0.5f
val acrossTopAndBase = slope - relativeUp * dotProdOfSlopeAndUp
val pointOnSide = centerVector + slope * (radius / Vector3.Magnitude(acrossTopAndBase))
val pointOnBase = p.asVector3 + acrossTopAndBase * radius
val pointOnTop = pointOnBase + relativeUp * height
val fromPointOnTopToSide = Vector3.Unit(pointOnTop - pointOnSide)
val fromPointOnSideToBase = Vector3.Unit(pointOnSide - pointOnBase)
val target = if(Geometry.equalVectors(fromPointOnTopToSide, Vector3.Zero) ||
Geometry.equalVectors(fromPointOnSideToBase, Vector3.Zero) ||
Geometry.equalVectors(fromPointOnTopToSide, fromPointOnSideToBase)) {
//on side, including top rim or base rim
pointOnSide
} else {
//on top or base
// the full equation would be 'centerVector + slope * (height * 0.5f / Vector3.Magnitude(relativeUp))'
// 'relativeUp` is already a unit vector (magnitude of 1)
centerVector + slope * height * 0.5f
}
Point3D(target)
}
Point3D(target)
}
}
object Cylinder {
def apply(v: Vector3, radius: Float, height: Float): Cylinder = Cylinder(v, Vector3(0,0,1), radius, height)
/**
* An overloaded constructor where the 'relativeUp' of the cylinder is perpendicular to the xy-plane.
* @param p the point
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
* @param height the distance between the "base" and the "top"
* @return
*/
def apply(p: Point3D, radius: Float, height: Float): Cylinder = Cylinder(p, Vector3(0,0,1), radius, height)
def apply(p: Point3D, radius: Float, height: Float): Cylinder = Cylinder(p.asVector3, Vector3(0,0,1), radius, height)
/**
* An overloaded constructor where the origin point is expressed as a vector
* and the 'relativeUp' of the cylinder is perpendicular to the xy-plane.
* @param p the point
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
* @param height the distance between the "base" and the "top"
* @return
*/
def apply(p: Vector3, radius: Float, height: Float): Cylinder = Cylinder(Point3D(p), Vector3(0,0,1), radius, height)
def apply(p: Point3D, v: Vector3, radius: Float, height: Float): Cylinder = Cylinder(p.asVector3, v, radius, height)
/**
* An overloaded constructor the origin point is expressed as a vector.
* @param p the point
* @param v what the cylinder considers its "up" direction
* @param radius a distance expressed in all circular cross-sections along the `relativeUp` direction
* @param height the distance between the "base" and the "top"
* @return
*/
def apply(p: Vector3, v: Vector3, radius: Float, height: Float): Cylinder = Cylinder(Point3D(p), v, radius, height)
}

View file

@ -0,0 +1,48 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vital.etc
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
import net.psforever.objects.vital.prop.DamageWithPosition
import net.psforever.objects.vital.resolution.DamageAndResistance
/**
* A wrapper for a "damage source" in damage calculations
* that parameterizes information necessary to explain a server-driven electromagnetic pulse occurring.
* @see `VitalityDefinition.explodes`
* @see `VitalityDefinition.innateDamage`
* @see `Zone.causesSpecialEmp`
* @param entity the source of the explosive yield
* @param damageModel the model to be utilized in these calculations;
* typically, but not always, defined by the target
*/
final case class EmpReason(
entity: SourceEntry,
source: DamageWithPosition,
damageModel: DamageAndResistance,
override val attribution: Int
) extends DamageReason {
def resolution: DamageResolution.Value = DamageResolution.Splash
def same(test: DamageReason): Boolean = test match {
case eer: ExplodingEntityReason => eer.entity eq entity
case _ => false
}
/** lay the blame on that which caused this emp to occur */
def adversary: Option[SourceEntry] = Some(entity)
}
object EmpReason {
def apply(
owner: PlanetSideGameObject with FactionAffinity,
source: DamageWithPosition,
target: PlanetSideServerObject with Vitality
): EmpReason = {
EmpReason(SourceEntry(owner), source, target.DamageModel, owner.Definition.ObjectId)
}
}

View file

@ -73,12 +73,12 @@ case object ExplodingRadialDegrade extends ExplodingDamageModifiers.Mod {
def calculate(damage: Int, data: DamageInteraction, cause: ExplodingEntityReason): Int = {
cause.source match {
case withPosition: DamageWithPosition =>
val distance = math.sqrt(Zone.distanceCheck(
val radius = withPosition.DamageRadius
val radiusMin = withPosition.DamageRadiusMin
val distance = math.sqrt(Zone.distanceCheck(
cause.entity.Definition.asInstanceOf[ObjectDefinition].Geometry(cause.entity),
data.target.Definition.Geometry(data.target)
))
val radius = withPosition.DamageRadius
val radiusMin = withPosition.DamageRadiusMin
if (distance <= radiusMin) {
damage
} else if (distance <= radius) {

View file

@ -1,13 +1,14 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.vital.etc
import net.psforever.objects.BoomerTrigger
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.ballistics.{PlayerSource, SourceEntry}
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
import net.psforever.objects.vital.damage.DamageCalculations.AgainstExoSuit
import net.psforever.objects.vital.prop.DamageProperties
import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel}
import net.psforever.types.PlanetSideGUID
/**
* A wrapper for a "damage source" in damage calculations
@ -23,16 +24,16 @@ import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResist
* @see `DamageCalculations`
* @see `VitalityDefinition.DamageableByFriendlyFire`
* @param user the player who is holding the trigger
* @param item the trigger
* @param item_guid the trigger
*/
final case class TriggerUsedReason(user: PlayerSource, item: BoomerTrigger)
final case class TriggerUsedReason(user: PlayerSource, item_guid: PlanetSideGUID)
extends DamageReason {
def source: DamageProperties = TriggerUsedReason.triggered
def resolution: DamageResolution.Value = DamageResolution.Resolved
def same(test: DamageReason): Boolean = test match {
case tur: TriggerUsedReason => tur.item eq item
case tur: TriggerUsedReason => tur.item_guid == item_guid && tur.user.Name.equals(user.Name)
case _ => false
}
@ -43,7 +44,7 @@ final case class TriggerUsedReason(user: PlayerSource, item: BoomerTrigger)
/** while weird, the trigger was accredited as the method of death on Gemini Live;
* even though its icon looks like an misshapen AMS */
override def attribution: Int = item.Definition.ObjectId
override def attribution: Int = GlobalDefinitions.boomer_trigger.ObjectId
}
object TriggerUsedReason {

View file

@ -42,9 +42,10 @@ import net.psforever.objects.geometry.Geometry3D
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.vehicles.UtilityType
import net.psforever.objects.vital.etc.ExplodingEntityReason
import net.psforever.objects.vital.etc.{EmpReason, ExplodingEntityReason}
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.prop.DamageWithPosition
/**
* A server object representing the one-landmass planets as well as the individual subterranean caverns.<br>
@ -1172,6 +1173,64 @@ object Zone {
}
}
/**
* Allocates `Damageable` targets within the radius of a server-prepared electromagnetic pulse
* and informs those entities that they have affected by the aforementioned pulse.
* Targets within the effect radius within other rooms are affected, unlike with normal damage.
* The only affected target is Boomer deployables.
* @see `Amenity.Owner`
* @see `BoomerDeployable`
* @see `DamageInteraction`
* @see `DamageResult`
* @see `DamageWithPosition`
* @see `EmpReason`
* @see `Zone.DeployableList`
* @param zone the zone in which the emp should occur
* @param obj the entity that triggered the emp (information)
* @param sourcePosition where the emp physically originates
* @param effect characteristics of the emp produced
* @param detectionTest a custom test to determine if any given target is affected;
* defaults to an internal test for simple radial proximity
* @return a list of affected entities
*/
def causeSpecialEmp(
zone: Zone,
obj: PlanetSideServerObject with Vitality,
sourcePosition: Vector3,
effect: DamageWithPosition,
detectionTest: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = distanceCheck
): List[PlanetSideServerObject] = {
val proxy: ExplosiveDeployable = {
//construct a proxy unit to represent the pulse
val o = new ExplosiveDeployable(GlobalDefinitions.special_emp)
o.Owner = Some(obj.GUID)
o.OwnerName = obj match {
case p: Player => p.Name
case o: OwnableByPlayer => o.OwnerName.getOrElse("")
case _ => ""
}
o.Position = sourcePosition
o.Faction = obj.Faction
o
}
val radius = effect.DamageRadius * effect.DamageRadius
//only boomers can be affected (that's why it's special)
val allAffectedTargets = zone.DeployableList
.collect { case o: BoomerDeployable if !o.Destroyed && (o ne obj) && detectionTest(proxy, o, radius) => o }
//inform targets that they have suffered the effects of the emp
allAffectedTargets
.foreach { target =>
target.Actor ! Vitality.Damage(
DamageInteraction(
SourceEntry(target),
EmpReason(obj, effect, target),
sourcePosition
).calculate()
)
}
allAffectedTargets
}
/**
* Two game entities are considered "near" each other if they are within a certain distance of one another.
* A default function literal mainly used for `causesExplosion`.
@ -1197,6 +1256,7 @@ object Zone {
* `false`, otherwise
*/
def distanceCheck(g1: Geometry3D, g2: Geometry3D, maxDistance: Float): Boolean = {
Vector3.DistanceSquared(g1.center.asVector3, g2.center.asVector3) <= maxDistance ||
distanceCheck(g1, g2) <= maxDistance
}
/**

View file

@ -364,7 +364,7 @@ object PointOfInterest {
"anguta" -> Vector3(3999, 4170, 266),
"igaluk" -> Vector3(3241, 5658, 235),
"keelut" -> Vector3(3630, 1904, 265),
"nerrivik" -> Vector3(3522, 3703, 322),
"nerrivik" -> Vector3(3522, 3703, 222),
"pinga" -> Vector3(5938, 3545, 96),
"sedna" -> Vector3(3932, 5160, 232),
"tarqaq" -> Vector3(2980, 2155, 237),

View file

@ -0,0 +1,244 @@
// Copyright (c) 2021 PSForever
package objects
import net.psforever.objects.geometry._
import net.psforever.types.Vector3
import org.specs2.mutable.Specification
class GeometryTest extends Specification {
"Point3D" should {
"construct (1)" in {
Point3D(1,2,3.5f)
ok
}
"construct (2)" in {
Point3D() mustEqual Point3D(0,0,0)
}
"construct (3)" in {
Point3D(Vector3(1,2,3)) mustEqual Point3D(1,2,3)
}
"be its own center point" in {
val obj = Point3D(1,2,3.5f)
obj.center mustEqual obj
}
"define its own exterior" in {
val obj = Point3D(1,2,3.5f)
obj.pointOnOutside(Vector3(1,0,0)) mustEqual obj
obj.pointOnOutside(Vector3(0,1,0)) mustEqual obj
obj.pointOnOutside(Vector3(0,0,1)) mustEqual obj
}
"convert to Vector3" in {
val obj = Point3D(1,2,3.5f)
obj.asVector3 mustEqual Vector3(1,2,3.5f)
}
}
"Ray3D" should {
"construct (1)" in {
Ray3D(Point3D(1,2,3.5f), Vector3(1,0,0))
ok
}
"construct (2)" in {
Ray3D(1,2,3.5f, Vector3(1,0,0)) mustEqual Ray3D(Point3D(1,2,3.5f), Vector3(1,0,0))
}
"construct (3)" in {
Ray3D(Vector3(1,2,3.5f), Vector3(1,0,0)) mustEqual Ray3D(Point3D(1,2,3.5f), Vector3(1,0,0))
}
"have a unit vector as its direction vector" in {
Ray3D(1,2,3.5f, Vector3(1,1,1)) must throwA[AssertionError]
}
"have its target point as the center point" in {
val obj = Ray3D(1,2,3.5f, Vector3(1,0,0))
obj.center mustEqual Point3D(1,2,3.5f)
}
"define its own exterior" in {
val obj1 = Ray3D(1,2,3.5f, Vector3(1,0,0))
val obj2 = Point3D(1,2,3.5f)
obj1.pointOnOutside(Vector3(1,0,0)) mustEqual obj2
obj1.pointOnOutside(Vector3(0,1,0)) mustEqual obj2
obj1.pointOnOutside(Vector3(0,0,1)) mustEqual obj2
}
}
"Line3D" should {
"construct (1)" in {
Line3D(Point3D(1,2,3.5f), Vector3(1,0,0))
ok
}
"construct (2)" in {
Line3D(1,2,3.5f, Vector3(1,0,0))
ok
}
"construct (3)" in {
Line3D(1,2,3.5f, 2,2,3.5f) mustEqual Line3D(1,2,3.5f, Vector3(1,0,0))
}
"have a unit vector as its direction vector" in {
Line3D(1,2,3.5f, Vector3(1,1,1)) must throwA[AssertionError]
}
"have its target point as the center point" in {
val obj = Line3D(1,2,3.5f, Vector3(1,0,0))
obj.center mustEqual Point3D(1,2,3.5f)
}
"define its own exterior" in {
val obj1 = Line3D(1,2,3.5f, Vector3(1,0,0))
val obj2 = Point3D(1,2,3.5f)
obj1.pointOnOutside(Vector3(1,0,0)) mustEqual obj2
obj1.pointOnOutside(Vector3(0,1,0)) mustEqual obj2
obj1.pointOnOutside(Vector3(0,0,1)) mustEqual obj2
}
}
"Segment3D" should {
"construct (1)" in {
Segment3D(Point3D(1,2,3), Point3D(3,2,3))
ok
}
"construct (2)" in {
Segment3D(1,2,3, 3,2,3) mustEqual Segment3D(Point3D(1,2,3), Point3D(3,2,3))
ok
}
"construct (3)" in {
Segment3D(Point3D(1,2,3), Vector3(1,0,0)) mustEqual Segment3D(Point3D(1,2,3), Point3D(2,2,3))
}
"construct (4)" in {
Segment3D(1,2,3, Vector3(1,0,0)) mustEqual Segment3D(Point3D(1,2,3), Point3D(2,2,3))
}
"does not need to have unit vector as its direction vector" in {
val obj1 = Segment3D(1,2,3, Vector3(5,1,1))
val obj2 = Segment3D(Point3D(1,2,3), Point3D(6,3,4))
obj1 mustEqual obj2
obj1.d mustEqual obj2.d
}
"have a midway point between its two endpoints" in {
Segment3D(Point3D(1,2,3), Point3D(3,4,5)).center mustEqual Point3D(2,3,4)
}
"report the point on the outside as its center point" in {
val obj1 = Segment3D(Point3D(1,2,3), Point3D(3,4,5))
val obj2 = obj1.center
obj1.pointOnOutside(Vector3(1,0,0)) mustEqual obj2
obj1.pointOnOutside(Vector3(0,1,0)) mustEqual obj2
obj1.pointOnOutside(Vector3(0,0,1)) mustEqual obj2
}
}
"Sphere3D" should {
"construct (1)" in {
Sphere(Point3D(1,2,3), 3)
ok
}
"construct (2)" in {
Sphere(3) mustEqual Sphere(Point3D(0,0,0), 3)
ok
}
"construct (3)" in {
Sphere(1,2,3, 3) mustEqual Sphere(Point3D(1,2,3), 3)
}
"construct (4)" in {
Sphere(Vector3(1,2,3), 3) mustEqual Sphere(Point3D(1,2,3), 3)
}
"the center point is self-evident" in {
Sphere(Point3D(1,2,3), 3).center mustEqual Point3D(1,2,3)
}
"report the point on the outside depending on the requested direction" in {
val obj1 = Sphere(1,2,3, 3)
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustEqual Point3D( 4, 2,3) //east
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustEqual Point3D( 1, 5,3) //north
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustEqual Point3D( 1, 2,6) //up
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustEqual Point3D(-2, 2,3) //west
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustEqual Point3D( 1,-1,3) //south
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustEqual Point3D( 1, 2,0) //down
}
}
"Cylinder (normal)" should {
"construct (1)" in {
Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
ok
}
"construct (2)" in {
Cylinder(Point3D(1,2,3), 2, 3) mustEqual Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
}
"construct (3)" in {
Cylinder(Vector3(1,2,3), 2, 3) mustEqual Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
}
"construct (4)" in {
Cylinder(Vector3(1,2,3), Vector3(0,0,1), 2, 3) mustEqual Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
}
"report the center point as the center of the cylinder" in {
Cylinder(Point3D(1,2,3), 2, 3).center mustEqual Point3D(1,2,4.5f)
}
"the point on the outside is different depending on the requested direction" in {
val obj1 = Cylinder(Point3D(1,2,3), 2, 3)
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustEqual Point3D( 3, 2, 4.5f) //east
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustEqual Point3D( 1, 4, 4.5f) //north
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustEqual Point3D( 1, 2, 6f) //up
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustEqual Point3D(-1, 2, 4.5f) //west
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustEqual Point3D( 1, 0, 4.5f) //south
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustEqual Point3D( 1, 2, 3f) //down
}
}
"Cylinder (side tilt)" should {
"not require a specific direction to be relative up" in {
Cylinder(Point3D(1,2,3), Vector3(1,0,0), 2, 3)
ok
}
"require its specific relative up direction to be expressed as a unit vector" in {
Cylinder(Point3D(1,2,3), Vector3(4,0,0), 2, 3) must throwA[AssertionError]
}
"report the center point as the center of the cylinder, as if rotated about its base" in {
Cylinder(Point3D(1,2,3), Vector3(1,0,0), 2, 3).center mustEqual Point3D(2.5f, 2, 3)
}
"report the point on the outside as different depending on the requested direction and the relative up direction" in {
val obj1 = Cylinder(Point3D(1,2,3), Vector3(1,0,0), 2, 3)
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustEqual Point3D(4, 2, 3) //east
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustEqual Point3D(2.5f, 4, 3) //north
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustEqual Point3D(2.5f, 2, 5) //up
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustEqual Point3D(1, 2, 3) //west
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustEqual Point3D(2.5f, 0, 3) //south
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustEqual Point3D(2.5f, 2, 1) //down
val obj2 = Cylinder(Point3D(1,2,3), Vector3(0,0,1), 2, 3)
obj1.pointOnOutside(Vector3( 1, 0, 0)) mustNotEqual obj2.pointOnOutside(Vector3( 1, 0, 0))
obj1.pointOnOutside(Vector3( 0, 1, 0)) mustNotEqual obj2.pointOnOutside(Vector3( 1, 1, 0))
obj1.pointOnOutside(Vector3( 0, 0, 1)) mustNotEqual obj2.pointOnOutside(Vector3( 1, 0, 1))
obj1.pointOnOutside(Vector3(-1, 0, 0)) mustNotEqual obj2.pointOnOutside(Vector3(-1, 0, 0))
obj1.pointOnOutside(Vector3( 0,-1, 0)) mustNotEqual obj2.pointOnOutside(Vector3( 0,-1, 0))
obj1.pointOnOutside(Vector3( 0, 0,-1)) mustNotEqual obj2.pointOnOutside(Vector3( 0, 0,-1))
}
}
}