diff --git a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala index 812fd4072..56ed5fca4 100644 --- a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala +++ b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala @@ -7,11 +7,12 @@ import net.psforever.objects.ce._ import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition} import net.psforever.objects.definition.converter.SmallDeployableConverter import net.psforever.objects.equipment.JammableUnit +import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity} import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.vital.resolution.ResolutionCalculations.Output -import net.psforever.objects.vital.SimpleResolutions +import net.psforever.objects.vital.{SimpleResolutions, Vitality} import net.psforever.objects.vital.etc.TriggerUsedReason import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.projectile.ProjectileReason @@ -96,13 +97,33 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D val originalHealth = mine.Health val cause = applyDamageTo(mine) val damage = originalHealth - mine.Health - if (Damageable.CanDamageOrJammer(mine, damage, cause.interaction)) { + if (CanDetonate(mine, damage, cause.interaction)) { ExplosiveDeployableControl.DamageResolution(mine, cause, damage) } else { mine.Health = originalHealth } } } + + /** + * A supplement for checking target susceptibility + * to account for sympathetic explosives even if there is no damage. + * This does not supercede other underlying checks or undo prior damage checks. + * @see `Damageable.CanDamageOrJammer` + * @see `DamageProperties.SympatheticExplosives` + * @param obj the entity being damaged + * @param damage the amount of damage + * @param data historical information about the damage + * @return `true`, if the target can be affected; + * `false`, otherwise + */ + def CanDetonate(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = { + if (damage == 0 && data.cause.source.SympatheticExplosion) { + Damageable.CanDamageOrJammer(mine, damage = 1, data) + } else { + Damageable.CanDamageOrJammer(mine, damage, data) + } + } } object ExplosiveDeployableControl { diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 18d126666..62373100a 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -24,6 +24,7 @@ import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, Turr import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, SeatArmorRestriction, UtilityType} import net.psforever.objects.vital.base.DamageType import net.psforever.objects.vital.damage._ +import net.psforever.objects.vital.etc.ExplodingRadialDegrade import net.psforever.objects.vital.projectile._ import net.psforever.objects.vital.prop.DamageWithPosition import net.psforever.objects.vital.{ComplexDeployableResolutions, MaxResolutions, SimpleResolutions} @@ -5626,7 +5627,7 @@ object GlobalDefinitions { Damage1 = 225 DamageRadius = 5 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } fury.DrownAtMaxDepth = true fury.MaxDepth = 1.3f @@ -5657,7 +5658,7 @@ object GlobalDefinitions { Damage1 = 225 DamageRadius = 5 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } quadassault.DrownAtMaxDepth = true quadassault.MaxDepth = 1.3f @@ -5688,7 +5689,7 @@ object GlobalDefinitions { Damage1 = 225 DamageRadius = 5 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } quadstealth.DrownAtMaxDepth = true quadstealth.MaxDepth = 1.25f @@ -5721,7 +5722,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 8 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } two_man_assault_buggy.DrownAtMaxDepth = true two_man_assault_buggy.MaxDepth = 1.5f @@ -5756,7 +5757,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 8 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } skyguard.DrownAtMaxDepth = true skyguard.MaxDepth = 1.5f @@ -5795,7 +5796,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 10 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } threemanheavybuggy.DrownAtMaxDepth = true threemanheavybuggy.MaxDepth = 1.83f @@ -5829,7 +5830,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 8 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } twomanheavybuggy.DrownAtMaxDepth = true twomanheavybuggy.MaxDepth = 1.95f @@ -5863,7 +5864,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 10 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } twomanhoverbuggy.DrownAtMaxDepth = true twomanhoverbuggy.UnderwaterLifespan(suffocation = 45000L, recovery = 5000L) //but the thresher hovers over water, so ...? @@ -5903,7 +5904,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } mediumtransport.DrownAtMaxDepth = false mediumtransport.MaxDepth = 1.2f @@ -5947,7 +5948,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } battlewagon.DrownAtMaxDepth = true battlewagon.MaxDepth = 1.2f @@ -5988,7 +5989,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } thunderer.DrownAtMaxDepth = true thunderer.MaxDepth = 1.2f @@ -6029,7 +6030,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } aurora.DrownAtMaxDepth = true aurora.MaxDepth = 1.2f @@ -6093,7 +6094,7 @@ object GlobalDefinitions { Damage1 = 450 DamageRadius = 15 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } apc_tr.DrownAtMaxDepth = true apc_tr.MaxDepth = 3 @@ -6157,7 +6158,7 @@ object GlobalDefinitions { Damage1 = 450 DamageRadius = 15 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } apc_nc.DrownAtMaxDepth = true apc_nc.MaxDepth = 3 @@ -6221,7 +6222,7 @@ object GlobalDefinitions { Damage1 = 450 DamageRadius = 15 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } apc_vs.DrownAtMaxDepth = true apc_vs.MaxDepth = 3 @@ -6253,7 +6254,7 @@ object GlobalDefinitions { Damage1 = 375 DamageRadius = 10 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } lightning.DrownAtMaxDepth = true lightning.MaxDepth = 1.38f @@ -6290,7 +6291,7 @@ object GlobalDefinitions { Damage1 = 375 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } prowler.DrownAtMaxDepth = true prowler.MaxDepth = 3 @@ -6323,7 +6324,7 @@ object GlobalDefinitions { Damage1 = 375 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } vanguard.DrownAtMaxDepth = true vanguard.MaxDepth = 2.7f @@ -6358,7 +6359,7 @@ object GlobalDefinitions { Damage1 = 375 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } magrider.DrownAtMaxDepth = true magrider.MaxDepth = 2 @@ -6391,7 +6392,7 @@ object GlobalDefinitions { Damage1 = 450 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } ant.DrownAtMaxDepth = true ant.MaxDepth = 2 @@ -6427,7 +6428,7 @@ object GlobalDefinitions { Damage1 = 450 DamageRadius = 15 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } ams.DrownAtMaxDepth = true ams.MaxDepth = 3 @@ -6463,7 +6464,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 10 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } router.DrownAtMaxDepth = true router.MaxDepth = 2 @@ -6499,7 +6500,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 10 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } switchblade.DrownAtMaxDepth = true switchblade.MaxDepth = 2 @@ -6533,7 +6534,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 10 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } flail.DrownAtMaxDepth = true flail.MaxDepth = 2 @@ -6567,7 +6568,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 10 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } mosquito.DrownAtMaxDepth = true mosquito.MaxDepth = 2 //flying vehicles will automatically disable @@ -6601,7 +6602,7 @@ object GlobalDefinitions { Damage1 = 375 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } lightgunship.DrownAtMaxDepth = true lightgunship.MaxDepth = 2 //flying vehicles will automatically disable @@ -6634,7 +6635,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 10 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } wasp.DrownAtMaxDepth = true wasp.MaxDepth = 2 //flying vehicles will automatically disable @@ -6675,7 +6676,7 @@ object GlobalDefinitions { Damage1 = 375 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } liberator.DrownAtMaxDepth = true liberator.MaxDepth = 2 //flying vehicles will automatically disable @@ -6717,7 +6718,7 @@ object GlobalDefinitions { Damage1 = 375 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } vulture.DrownAtMaxDepth = true vulture.MaxDepth = 2 //flying vehicles will automatically disable @@ -6791,7 +6792,7 @@ object GlobalDefinitions { Damage1 = 450 DamageRadius = 30 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } dropship.DrownAtMaxDepth = true dropship.MaxDepth = 2 @@ -6844,7 +6845,7 @@ object GlobalDefinitions { Damage1 = 450 DamageRadius = 30 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } galaxy_gunship.DrownAtMaxDepth = true galaxy_gunship.MaxDepth = 2 @@ -6885,7 +6886,7 @@ object GlobalDefinitions { Damage1 = 450 DamageRadius = 30 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } lodestar.DrownAtMaxDepth = true lodestar.MaxDepth = 2 @@ -6927,7 +6928,7 @@ object GlobalDefinitions { Damage1 = 150 DamageRadius = 12 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } phantasm.DrownAtMaxDepth = true phantasm.MaxDepth = 2 @@ -6969,7 +6970,7 @@ object GlobalDefinitions { Damage4 = 1850 DamageRadius = 5.1f DamageAtEdge = 0.1f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } he_mine.Name = "he_mine" @@ -6990,7 +6991,7 @@ object GlobalDefinitions { Damage4 = 1600 DamageRadius = 6.6f DamageAtEdge = 0.25f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } jammer_mine.Name = "jammer_mine" @@ -7022,7 +7023,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 8 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } spitfire_cloaked.Name = "spitfire_cloaked" @@ -7044,7 +7045,7 @@ object GlobalDefinitions { Damage1 = 75 DamageRadius = 8 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } spitfire_aa.Name = "spitfire_aa" @@ -7066,7 +7067,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 8 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } motionalarmsensor.Name = "motionalarmsensor" @@ -7100,7 +7101,7 @@ object GlobalDefinitions { Damage1 = 10 DamageRadius = 8 DamageAtEdge = 0.2f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } val fieldTurretConverter = new FieldTurretConverter @@ -7127,7 +7128,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 8 DamageAtEdge = 0.1f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } portable_manned_turret_nc.Name = "portable_manned_turret_nc" @@ -7153,7 +7154,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 8 DamageAtEdge = 0.1f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } portable_manned_turret_tr.Name = "portable_manned_turret_tr" @@ -7179,7 +7180,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 8 DamageAtEdge = 0.1f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } portable_manned_turret_vs.Name = "portable_manned_turret_vs" @@ -7205,7 +7206,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 8 DamageAtEdge = 0.1f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } deployable_shield_generator.Name = "deployable_shield_generator" @@ -7668,7 +7669,7 @@ object GlobalDefinitions { Damage1 = 300 DamageRadius = 5 DamageAtEdge = 0.1f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade } vanu_sentry_turret.Name = "vanu_sentry_turret" @@ -7770,7 +7771,7 @@ object GlobalDefinitions { DamageRadius = 15.75f DamageRadiusMin = 14 DamageAtEdge = 0.00002f - Modifiers = RadialDegrade + Modifiers = ExplodingRadialDegrade //damage is 99999 at 14m, dropping rapidly to ~1 at 15.75m } } diff --git a/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala b/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala index 5413f325f..0a2d21205 100644 --- a/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala +++ b/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala @@ -18,6 +18,8 @@ final case class PlayerSource( position: Vector3, orientation: Vector3, velocity: Option[Vector3], + crouching: Boolean, + jumping: Boolean, modifiers: ResistanceProfile ) extends SourceEntry { override def Name = name @@ -48,6 +50,8 @@ object PlayerSource { tplayer.Position, tplayer.Orientation, tplayer.Velocity, + tplayer.Crouching, + tplayer.Jumping, ExoSuitDefinition.Select(tplayer.ExoSuit, tplayer.Faction) ) } diff --git a/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala b/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala index da18c0985..f99de8847 100644 --- a/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala @@ -3,15 +3,17 @@ package net.psforever.objects.definition import net.psforever.objects.avatar.Avatars import net.psforever.objects.definition.converter.AvatarConverter +import net.psforever.objects.geometry.GeometryForm import net.psforever.objects.vital.VitalityDefinition /** - * The definition for game objects that look like other people, and also for players. - * @param objectId the object's identifier number + * The definition for game objects that look like players. + * @param objectId the object type number */ class AvatarDefinition(objectId: Int) extends ObjectDefinition(objectId) with VitalityDefinition { Avatars(objectId) //let throw NoSuchElementException Packet = AvatarDefinition.converter + Geometry = GeometryForm.representPlayerByCylinder(radius = 1.6f) } object AvatarDefinition { diff --git a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala index 29e7609cd..5378b5224 100644 --- a/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ObjectDefinition.scala @@ -3,6 +3,7 @@ package net.psforever.objects.definition import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.definition.converter.{ObjectCreateConverter, PacketConverter} +import net.psforever.objects.geometry.{Geometry3D, GeometryForm} import net.psforever.types.OxygenState /** @@ -76,5 +77,27 @@ abstract class ObjectDefinition(private val objectId: Int) extends BasicDefiniti UnderwaterLifespan() } + private var serverSplashTargetsCentroid: Boolean = false + + def ServerSplashTargetsCentroid: Boolean = serverSplashTargetsCentroid + + def ServerSplashTargetsCentroid_=(splash: Boolean): Boolean = { + serverSplashTargetsCentroid = splash + ServerSplashTargetsCentroid + } + + private var serverGeometry: Any => Geometry3D = GeometryForm.representByPoint() + + def Geometry: Any => Geometry3D = if (ServerSplashTargetsCentroid) { + serverGeometry + } else { + GeometryForm.representByPoint() + } + + def Geometry_=(func: Any => Geometry3D): Any => Geometry3D = { + serverGeometry = func + Geometry + } + def ObjectId: Int = objectId } diff --git a/src/main/scala/net/psforever/objects/geometry/Closest.scala b/src/main/scala/net/psforever/objects/geometry/Closest.scala index 397a7d247..4b3dc6ebb 100644 --- a/src/main/scala/net/psforever/objects/geometry/Closest.scala +++ b/src/main/scala/net/psforever/objects/geometry/Closest.scala @@ -6,9 +6,9 @@ import net.psforever.types.Vector3 object Closest { object Distance { def apply(point : Vector3, seg : Segment2D) : Float = { - val segdx = seg.bx - seg.ax - val segdy = seg.by - seg.ay - ((point.x - seg.ax) * segdx + (point.y - seg.ay) * segdy) / + val segdx = seg.p2.x - seg.p1.x + val segdy = seg.p2.y - seg.p1.y + ((point.x - seg.p1.x) * segdx + (point.y - seg.p1.y) * segdy) / Vector3.MagnitudeSquared(Vector3(segdx, segdy, 0)) } @@ -18,7 +18,7 @@ object Closest { } else { math.abs( Vector3.DotProduct( - Vector3(line2.x - line1.x, line2.y - line1.y, 0), + Vector3(line2.p.x - line1.p.x, line2.p.y - line1.p.y, 0), Vector3(-1/line1.d.y, 1/line1.d.x, 0) ) ) @@ -29,10 +29,10 @@ object Closest { if (Intersection.Test(seg1, seg2)) { //intersecting line segments 0f } else { - val v1a = Vector3(seg1.ax, seg1.ay, 0) - val v2a = Vector3(seg2.ax, seg2.ay, 0) - val v1b = Vector3(seg1.bx, seg1.by, 0) - val v2b = Vector3(seg2.bx, seg2.by, 0) + val v1a = Vector3(seg1.p1.x, seg1.p1.y, 0) + val v2a = Vector3(seg2.p1.x, seg2.p1.y, 0) + val v1b = Vector3(seg1.p2.x, seg1.p2.y, 0) + val v2b = Vector3(seg2.p2.x, seg2.p2.y, 0) math.min( apply(v1a, seg2), math.min( @@ -47,7 +47,7 @@ object Closest { } def apply(c1: Circle, c2 : Circle): Float = { - math.max(0, Vector3.Magnitude(Vector3(c1.x - c2.x, c1.y - c2.y, 0)) - c1.radius - c2.radius) + math.max(0, Vector3.Magnitude(Vector3(c1.p.x - c2.p.x, c1.p.y - c2.p.y, 0)) - c1.radius - c2.radius) } /** @@ -62,12 +62,12 @@ object Closest { val cross = Vector3.CrossProduct(line1.d, line2.d) if(cross != Vector3.Zero) { math.abs( - Vector3.DotProduct(cross, Vector3(line1.x - line2.x, line1.y - line2.y, line1.z - line2.z)) + Vector3.DotProduct(cross, Vector3(line1.p.x - line2.p.x, line1.p.y - line2.p.y, line1.p.z - line2.p.z)) ) / Vector3.Magnitude(cross) } else { // lines are parallel or coincidental // construct a right triangle with one leg on line1 and the hypotenuse between the line's known points - val hypotenuse = Vector3(line2.x - line1.x, line2.y - line1.y, line2.z - line1.z) + val hypotenuse = Vector3(line2.p.x - line1.p.x, line2.p.y - line1.p.y, line2.p.z - line1.p.z) val legOnLine1 = line1.d * Vector3.DotProduct(hypotenuse, line1.d) Vector3.Magnitude(hypotenuse - legOnLine1) } @@ -82,7 +82,7 @@ object Closest { } def apply(s1: Sphere, s2 : Sphere): Float = { - math.max(0, Vector3.Magnitude(Vector3(s1.x - s2.x, s1.y - s2.y, s1.z - s2.z)) - s1.radius - s2.radius) + math.max(0, Vector3.Magnitude(Vector3(s1.p.x - s2.p.x, s1.p.y - s2.p.y, s1.p.z - s2.p.z)) - s1.radius - s2.radius) } } @@ -97,9 +97,9 @@ object Closest { def apply(c1 : Circle, c2 : Circle): Option[Segment2D] = { val distance = Distance(c1, c2) if (distance > 0) { - val c1x = c1.x - val c1y = c1.y - val v = Vector3.Unit(Vector3(c2.x - c1x, c2.y - c1y, 0f)) + val c1x = c1.p.x + val c1y = c1.p.y + val v = Vector3.Unit(Vector3(c2.p.x - c1x, c2.p.y - c1y, 0f)) val c1d = v * c1.radius val c2d = v * c2.radius Some( @@ -122,8 +122,8 @@ object Closest { * `None`, if the lines intersect with each other */ def apply(line1 : Line3D, line2 : Line3D): Option[Segment3D] = { - val p1 = Vector3(line1.x, line1.y, line1.z) - val p3 = Vector3(line2.x, line2.y, line2.z) + val p1 = Vector3(line1.p.x, line1.p.y, line1.p.z) + val p3 = Vector3(line2.p.x, line2.p.y, line2.p.z) val p13 = p1 - p3 // vector between point on first line and point on second line val p43 = line2.d val p21 = line1.d @@ -141,12 +141,12 @@ object Closest { if (p21 == p13u || p21 == Vector3.neg(p13u)) { //coincidental lines overlap / intersect None } else { //parallel lines - val connecting = Vector3(line2.x - line1.x, line2.y - line1.y, line2.z - line1.z) + val connecting = Vector3(line2.p.x - line1.p.x, line2.p.y - line1.p.y, line2.p.z - line1.p.z) val legOnLine1 = line1.d * Vector3.DotProduct(connecting, line1.d) val v = connecting - legOnLine1 Some(Segment3D( - line1.x, line1.y, line1.z, - line1.x + v.x, line1.y + v.y, line1.z + v.z + line1.p.x, line1.p.y, line1.p.z, + line1.p.x + v.x, line1.p.y + v.y, line1.p.z + v.z )) } } else { @@ -169,25 +169,25 @@ object Closest { def apply(line1 : Segment3D, line2 : Segment3D): Option[Segment3D] = { val uline1 = Vector3.Unit(line1.d) val uline2 = Vector3.Unit(line2.d) - apply(Line3D(line1.ax, line1.ay, line1.az, uline1), Line3D(line2.ax, line2.ay, line2.az, uline2)) match { + apply(Line3D(line1.p1.x, line1.p1.y, line1.p1.z, uline1), Line3D(line2.p1.x, line2.p1.y, line2.p1.z, uline2)) match { case Some(seg: Segment3D) => // common skew lines and parallel lines - val sega = Vector3(seg.ax, seg.ay, seg.az) - val p1 = Vector3(line1.ax, line1.ay, line1.az) + val sega = Vector3(seg.p1.x, seg.p1.y, seg.p1.z) + val p1 = Vector3(line1.p1.x, line1.p1.y, line1.p1.z) val d1 = sega - p1 val out1 = if (!Geometry.equalVectors(Vector3.Unit(d1), uline1)) { //clamp seg.a(xyz) to segment line1's bounds p1 } else if (Vector3.MagnitudeSquared(d1) > Vector3.MagnitudeSquared(line1.d)) { - Vector3(line1.bx, line1.by, line1.bz) + Vector3(line1.p2.x, line1.p2.y, line1.p2.z) } else { sega } - val segb = Vector3(seg.bx, seg.by, seg.bz) - val p2 = Vector3(line2.ax, line2.ay, line2.az) + val segb = Vector3(seg.p2.x, seg.p2.y, seg.p2.z) + val p2 = Vector3(line2.p1.x, line2.p1.y, line2.p1.z) val d2 = segb - p2 val out2 = if (!Geometry.equalVectors(Vector3.Unit(d2), uline2)) { //clamp seg.b(xyz) to segment line2's bounds p2 } else if (Vector3.MagnitudeSquared(d2) > Vector3.MagnitudeSquared(line2.d)) { - Vector3(line2.bx, line2.by, line2.bz) + Vector3(line2.p2.x, line2.p2.y, line2.p2.z) } else { segb } @@ -196,19 +196,19 @@ object Closest { out2.x, out2.y, out2.z )) case None => - val connectingU = Vector3.Unit(Vector3(line2.ax - line1.ax, line2.ay - line1.ay, line2.az - line1.az)) + val connectingU = Vector3.Unit(Vector3(line2.p1.x - line1.p1.x, line2.p1.y - line1.p1.y, line2.p1.z - line1.p1.z)) if (uline1 == connectingU || uline1 == Vector3.neg(connectingU)) { // coincidental line segments - val line1a = Vector3(line1.ax, line1.ay, line1.az) - val line1b = Vector3(line1.bx, line1.by, line1.bz) - val line2a = Vector3(line2.ax, line2.ay, line2.az) - val line2b = Vector3(line2.bx, line2.by, line2.bz) + val line1a = Vector3(line1.p1.x, line1.p1.y, line1.p1.z) + val line1b = Vector3(line1.p2.x, line1.p2.y, line1.p2.z) + val line2a = Vector3(line2.p1.x, line2.p1.y, line2.p1.z) + val line2b = Vector3(line2.p2.x, line2.p2.y, line2.p2.z) if (Vector3.Unit(line2a - line1a) != Vector3.Unit(line2b - line1a) || Vector3.Unit(line2a - line1b) != Vector3.Unit(line2b - line1b) || Vector3.Unit(line1a - line2a) != Vector3.Unit(line1b - line2a) || Vector3.Unit(line1a - line2b) != Vector3.Unit(line1b - line2b)) { Some(Segment3D( - line1.ax, line1.ay, line1a.z, - line1.ax, line1.ay, line1a.z + line1.p1.x, line1.p1.y, line1a.z, + line1.p1.x, line1.p1.y, line1a.z )) // overlap regions } else { @@ -245,10 +245,10 @@ object Closest { def apply(s1 : Sphere, s2 : Sphere): Option[Segment3D] = { val distance = Distance(s1, s2) if (distance > 0) { - val s1x = s1.x - val s1y = s1.y - val s1z = s1.z - val v = Vector3.Unit(Vector3(s2.x - s1x, s2.y - s1y, s2.z - s1z)) + val s1x = s1.p.x + val s1y = s1.p.y + val s1z = s1.p.z + val v = Vector3.Unit(Vector3(s2.p.x - s1x, s2.p.y - s1y, s2.p.z - s1z)) val s1d = v * s1.radius val s2d = v * (s1.radius + distance) Some(Segment3D(s1x + s1d.x, s1y + s1d.y, s1y + s1d.y, s1x + s2d.x, s1y + s2d.y, s1y + s2d.y)) @@ -258,8 +258,8 @@ object Closest { } def apply(line : Line3D, sphere : Sphere): Option[Segment3D] = { - val sphereAsPoint = Vector3(sphere.x, sphere.y, sphere.z) - val lineAsPoint = Vector3(line.x, line.y, line.z) + val sphereAsPoint = Vector3(sphere.p.x, sphere.p.y, sphere.p.z) + val lineAsPoint = Vector3(line.p.x, line.p.y, line.p.z) val direct = sphereAsPoint - lineAsPoint val projectionOfDirect = line.d * Vector3.DotProduct(direct, line.d) val heightFromProjection = projectionOfDirect - direct diff --git a/src/main/scala/net/psforever/objects/geometry/Geometry.scala b/src/main/scala/net/psforever/objects/geometry/Geometry.scala index 1a679d02d..3cd47c061 100644 --- a/src/main/scala/net/psforever/objects/geometry/Geometry.scala +++ b/src/main/scala/net/psforever/objects/geometry/Geometry.scala @@ -3,6 +3,30 @@ package net.psforever.objects.geometry import net.psforever.types.Vector3 +trait PrimitiveGeometry { + def center: Point + + def pointOnOutside(line: Line) : Point = pointOnOutside(line.d) + + def pointOnOutside(v: Vector3) : Point +} + +trait Geometry2D extends PrimitiveGeometry { + def center: Point2D + + def pointOnOutside(v: Vector3): Point2D = center +} + +trait Geometry3D extends PrimitiveGeometry { + def center: Point3D + + def pointOnOutside(v: Vector3): Point3D = center +} + +trait Point { + def asVector3: Vector3 +} + trait Slope { def d: Vector3 @@ -15,52 +39,186 @@ trait Line extends Slope { mag - 0.05f < 1f && mag + 0.05f > 1f }, "not a unit vector") + def p: Point + def length: Float = Float.PositiveInfinity } trait Segment extends Slope { + def p1: Point + + def p2: Point + def length: Float = Vector3.Magnitude(d) + + def asLine: PrimitiveGeometry } -final case class Line2D(x: Float, y: Float, d: Vector3) extends Line +final case class Point2D(x: Float, y: Float) extends Geometry2D with Point { + def center: Point2D = this + + def asVector3: Vector3 = Vector3(x, y, 0) +} + +object Point2D { + def apply(): Point2D = Point2D(0, 0) + + def apply(v: Vector3): Point2D = Point2D(v.x, v.y) +} + +final case class Ray2D(p: Point2D, d: Vector3) extends Geometry2D with Line { + def center: Point2D = p +} + +object Ray2D { + def apply(x: Float, y: Float, d: Vector3): Ray2D = Ray2D(Point2D(x, y), d) +} + +final case class Line2D(p: Point2D, d: Vector3) extends Geometry2D with Line { + def center: Point2D = p +} object Line2D { + def apply(ax: Float, ay: Float, d: Vector3): Line2D = { + Line2D(Point2D(ax, ay), d) + } + def apply(ax: Float, ay: Float, bx: Float, by: Float): Line2D = { - Line2D(ax, ay, Vector3.Unit(Vector3(bx-ax, by-ay, 0))) + Line2D(Point2D(ax, ay), Vector3.Unit(Vector3(bx-ax, by-ay, 0))) + } + + def apply(p1: Point2D, p2: Point2D): Line2D = { + Line2D(p1, Vector3.Unit(Vector3(p2.x-p1.x, p2.y-p1.y, 0))) } } -final case class Segment2D(ax: Float, ay: Float, bx: Float, by: Float) extends Segment { - def d: Vector3 = Vector3(bx - ax, by - ay, 0) +final case class Segment2D(p1: Point2D, p2: Point2D) extends Geometry2D with Segment { + def center: Point2D = Point2D(d * 0.5f) + + def d: Vector3 = p2.asVector3 - p1.asVector3 + + def asLine: Line2D = Line2D(p1, Vector3.Unit(d)) } object Segment2D { - def apply(x: Float, y: Float, z: Float, d: Vector3): Segment2D = { + def apply(ax: Float, ay: Float, bx: Float, by: Float): Segment2D = { + Segment2D(Point2D(ax, ay), Point2D(bx, by)) + } + + def apply(x: Float, y: Float, d: Vector3): Segment2D = { Segment2D(x, y, x + d.x, y + d.y) } } -final case class Circle(x: Float, y: Float, radius: Float) +final case class Circle(p: Point2D, radius: Float) extends Geometry2D { + def center : Point2D = p -object Circle { - def apply(radius: Float): Circle = Circle(0f, 0f, radius) -} - -final case class Line3D(x: Float, y: Float, z: Float, d: Vector3) extends Line - -final case class Segment3D(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float) extends Segment { - def d: Vector3 = Vector3(bx - ax, by - ay, bz - az) -} - -object Segment3D { - def apply(x: Float, y: Float, z: Float, d: Vector3): Segment3D = { - Segment3D(x, y, z, z+d.x, y+d.y, z+d.z) + override def pointOnOutside(v: Vector3) : Point2D = { + val slope = Vector3.Unit(v) + val pointOnRim = p.asVector3 + slope * radius + Point2D(pointOnRim.x, pointOnRim.y) } } -final case class Sphere(x: Float, y: Float, z: Float, radius: Float) +object Circle { + def apply(radius: Float): Circle = Circle(Point2D(), radius) -final case class Cylinder(circle: Circle, z: Float, height: Float) + def apply(x: Float, y: Float, radius: Float): Circle = Circle(Point2D(x, y), radius) +} + + +final case class Point3D(x: Float, y: Float, z: Float) extends Geometry3D with Point { + def center: Point3D = this + + def asVector3: Vector3 = Vector3(x, y, z) +} + +object Point3D { + def apply(): Point3D = Point3D(0,0,0) + + def apply(v: Vector3): Point3D = Point3D(v.x, v.y, v.z) +} + +final case class Ray3D(p: Point3D, d: Vector3) extends Geometry3D with Line { + def center: Point3D = p +} + +object Ray3D { + def apply(x: Float, y: Float, z: Float, d: Vector3): Ray3D = Ray3D(Point3D(x,y,z), d) +} + +final case class Line3D(p: Point3D, d: Vector3) extends Geometry3D with Line { + def center: Point3D = p +} + +object Line3D { + def apply(x: Float, y: Float, z: Float, d: Vector3): Line3D = { + Line3D(Point3D(x,y,z), d) + } + + 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))) + } + + def apply(p1: Point3D, p2: Point3D): Line3D = { + Line3D(p1, Vector3.Unit(Vector3(p2.x-p1.x, p2.y-p1.y, p2.z-p1.z))) + } +} + +final case class Segment3D(p1: Point3D, p2: Point3D) extends Geometry3D with Segment { + def center: Point3D = Point3D(d * 0.5f) + + def d: Vector3 = p2.asVector3 - p1.asVector3 + + def asLine: Line3D = Line3D(p1, Vector3.Unit(d)) +} + +object Segment3D { + def apply(ax: Float, ay: Float, az: Float, bx: Float, by: Float, bz: Float): Segment3D = { + Segment3D(Point3D(ax, ay, az), Point3D(bx, by, bz)) + } + + 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)) + } +} + +final case class Sphere(p: Point3D, radius: Float) extends Geometry3D { + def center: Point3D = p + + 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) + } +} + +object Sphere { + def apply(radius: Float): Sphere = Sphere(Point3D(), radius) + + def apply(x: Float, y: Float, z: Float, radius: Float): Sphere = Sphere(Point3D(x,y,z), radius) + + def apply(v: Vector3, radius: Float): Sphere = Sphere(Point3D(v), radius) +} + +final case class Cylinder(circle: Circle, z: Float, height: Float) extends Geometry3D { + def center: Point3D = Point3D(circle.p.x, circle.p.y, z + height * 0.5f) + + override def pointOnOutside(v: Vector3): Point3D = { + val centerVector = center.asVector3 + val slope = Vector3.Unit(v) + val mult = circle.radius / math.sqrt(slope.x * slope.x + slope.y * slope.y) + val pointOnRim = centerVector + slope * mult.toFloat + val point = if (z >= pointOnRim.z && pointOnRim.z <= height) { //side + pointOnRim + } else { //top or base + val rise = height * 0.5f / slope.z + centerVector + slope * rise + } + Point3D(point.x, point.y, point.z) + } +} object Cylinder { def apply(x: Float, y: Float, z: Float, radius: Float, height: Float): Cylinder = { @@ -68,10 +226,6 @@ object Cylinder { } } -object Sphere { - def apply(p: Vector3, radius: Float): Sphere = Sphere(p.x, p.y, p.z, radius) -} - object Geometry { def equalFloats(value1: Float, value2: Float, off: Float = 0.001f): Boolean = { val diff = value1 - value2 diff --git a/src/main/scala/net/psforever/objects/geometry/GeometryForm.scala b/src/main/scala/net/psforever/objects/geometry/GeometryForm.scala new file mode 100644 index 000000000..3b053916e --- /dev/null +++ b/src/main/scala/net/psforever/objects/geometry/GeometryForm.scala @@ -0,0 +1,113 @@ +// Copyright (c) 2021 PSForever +package net.psforever.objects.geometry + +import net.psforever.objects.ballistics.{PlayerSource, SourceEntry} +import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player} +import net.psforever.types.ExoSuitType + +object GeometryForm { + /** this point can not be used for purposes of geometric representation */ + lazy val invalidPoint: Point3D = Point3D(Float.MinValue, Float.MinValue, Float.MinValue) + /** this circle can not be used for purposes of geometric representation */ + lazy val invalidCircle: Circle = Circle(Point2D(invalidPoint.asVector3), 0) + /** this cylinder can not be used for purposes of geometric representation */ + lazy val invalidCylinder: Cylinder = Cylinder(invalidCircle, Float.MinValue, 0) + + /** + * The geometric representation is the entity's centroid. + * @param o the entity + * @return the representation + */ + def representByPoint()(o: Any): Geometry3D = { + o match { + case p: PlanetSideGameObject => Point3D(p.Position) + case s: SourceEntry => Point3D(s.Position) + case _ => invalidPoint + } + } + + /** + * The geometric representation is the a sphere around the entity's centroid. + * @param radius how wide a hemisphere is + * @param o the entity + * @return the representation + */ + def representBySphere(radius: Float)(o: Any): Geometry3D = { + o match { + case p: PlanetSideGameObject => Sphere(p.Position, radius) + case s: SourceEntry => Sphere(s.Position, radius) + case _ => Sphere(invalidPoint, radius) + } + } + + /** + * The geometric representation is the a cylinder around the entity's base. + * @param radius half the distance across + * @param height how tall the cylinder is (the distance of the top to the base) + * @param o the entity + * @return the representation + */ + def representByCylinder(radius: Float, height: Float)(o: Any): Geometry3D = { + o match { + case p: PlanetSideGameObject => Cylinder(Circle(p.Position.x, p.Position.y, radius), p.Position.z, height) + case s: SourceEntry => Cylinder(Circle(s.Position.x, s.Position.y, radius), s.Position.z, height) + case _ => invalidCylinder + } + } + + /** + * The geometric representation is the a cylinder around the entity's base + * if the target represents a player entity. + * @param radius a measure of the player's bulk + * @param o the entity + * @return the representation + */ + def representPlayerByCylinder(radius: Float)(o: Any): Geometry3D = { + o match { + case p: Player => + val radialOffset = if(p.ExoSuit == ExoSuitType.MAX) 0.25f else 0f + Cylinder( + Circle(p.Position.x, p.Position.y, radius + radialOffset), + p.Position.z, + GlobalDefinitions.MaxDepth(p) + ) + case p: PlayerSource => + val radialOffset = if(p.ExoSuit == ExoSuitType.MAX) 0.25f else 0f + Cylinder( + Circle(p.Position.x, p.Position.y, radius + radialOffset), + p.Position.z, + GlobalDefinitions.avatar.MaxDepth + ) + case _ => + invalidCylinder + } + } + + /** + * The geometric representation is the a cylinder around the entity's base + * as if the target is displaced from the ground at an expected (fixed?) distance. + * @param radius half the distance across + * @param height how tall the cylinder is (the distance of the top to the base) + * @param hoversAt how far off the base coordinates the actual cylinder begins + * @param o the entity + * @return the representation + */ + def representHoveringEntityByCylinder(radius: Float, height: Float, hoversAt: Float)(o: Any): Geometry3D = { + o match { + case p: PlanetSideGameObject => + Cylinder( + Circle(p.Position.x, p.Position.y, radius), + p.Position.z + hoversAt, + height + ) + case s: SourceEntry => + Cylinder( + Circle(s.Position.x, s.Position.y, radius), + s.Position.z + hoversAt, + height + ) + case _ => + invalidCylinder + } + } +} diff --git a/src/main/scala/net/psforever/objects/geometry/Intersection.scala b/src/main/scala/net/psforever/objects/geometry/Intersection.scala index a2e2834c7..cda1f31c9 100644 --- a/src/main/scala/net/psforever/objects/geometry/Intersection.scala +++ b/src/main/scala/net/psforever/objects/geometry/Intersection.scala @@ -13,7 +13,7 @@ object Intersection { def apply(line1: Line2D, line2: Line2D): Boolean = { line1.d != line2.d || { //parallel or antiparallel? - val u = Vector3.Unit(Vector3(line2.x - line1.x, line2.y - line1.y, 0)) + val u = Vector3.Unit(Vector3(line2.p.x - line1.p.x, line2.p.y - line1.p.y, 0)) u == Vector3.Zero || line1.d == u || line1.d == Vector3.neg(u) } } @@ -59,14 +59,14 @@ object Intersection { */ def apply(line1: Segment2D, line2: Segment2D): Boolean = { //setup - val ln1ax = line1.ax - val ln1ay = line1.ay - val ln1bx = line1.bx - val ln1by = line1.by - val ln2ax = line2.ax - val ln2ay = line2.ay - val ln2bx = line2.bx - val ln2by = line2.by + val ln1ax = line1.p1.x + val ln1ay = line1.p1.y + val ln1bx = line1.p2.x + val ln1by = line1.p2.y + val ln2ax = line2.p1.x + val ln2ay = line2.p1.y + val ln2bx = line2.p2.x + val ln2by = line2.p2.y val ln1_ln2a = orientationOfPoints(ln1ax, ln1ay, ln1bx, ln1by, ln2ax, ln2ay) val ln1_ln2b = orientationOfPoints(ln1ax, ln1ay, ln1bx, ln1by, ln2bx, ln2by) val ln2_ln1a = orientationOfPoints(ln2ax, ln2ay, ln2bx, ln2by, ln1ax, ln1ay) @@ -93,7 +93,7 @@ object Intersection { } def apply(c1: Circle, c2 : Circle): Boolean = { - Vector3.Magnitude(Vector3(c1.x - c2.x, c1.y - c2.y, 0)) <= c1.radius + c2.radius + Vector3.Magnitude(Vector3(c1.p.x - c2.p.x, c1.p.y - c2.p.y, 0)) <= c1.radius + c2.radius } /** @@ -111,9 +111,9 @@ object Intersection { def apply(s1: Sphere, s2 : Sphere): Boolean = { Vector3.Magnitude( Vector3( - s1.x - s2.x, - s1.y - s2.y, - s1.z - s2.z + s1.p.x - s2.p.x, + s1.p.y - s2.p.y, + s1.p.z - s2.p.z ) ) <= s1.radius + s2.radius } @@ -128,17 +128,17 @@ object Intersection { val cylinderCircleRadius = cylinderCircle.radius val cylinderTop = cylinder.z + cylinder.height val sphereRadius = sphere.radius - val sphereBase = sphere.z - sphereRadius - val sphereTop = sphere.z + sphereRadius - if (apply(cylinderCircle, Circle(sphere.x, sphere.y, sphereRadius)) && + val sphereBase = sphere.p.z - sphereRadius + val sphereTop = sphere.p.z + sphereRadius + if (apply(cylinderCircle, Circle(sphere.p.x, sphere.p.y, sphereRadius)) && ((sphereTop >= cylinder.z && sphereBase <= cylinderTop) || (cylinderTop >= sphereBase && cylinder.z <= sphereTop))) { // potential intersection ... - val sphereAsPoint = Vector3(sphere.x, sphere.y, sphere.z) - val cylinderAsPoint = Vector3(cylinderCircle.x, cylinderCircle.y, cylinder.z) + val sphereAsPoint = Vector3(sphere.p.x, sphere.p.y, sphere.p.z) + val cylinderAsPoint = Vector3(cylinderCircle.p.x, cylinderCircle.p.y, cylinder.z) val segmentFromCylinderToSphere = sphereAsPoint - cylinderAsPoint val segmentFromCylinderToSphereXY = segmentFromCylinderToSphere.xy - if ((cylinder.z <= sphere.z && sphere.z <= cylinderTop) || + if ((cylinder.z <= sphere.p.z && sphere.p.z <= cylinderTop) || Vector3.MagnitudeSquared(segmentFromCylinderToSphereXY) <= cylinderCircleRadius * cylinderCircleRadius) { true // top or bottom of sphere, or widest part of the sphere, must interact with the cylinder } else { diff --git a/src/main/scala/net/psforever/objects/vital/etc/ExplodingEntityReason.scala b/src/main/scala/net/psforever/objects/vital/etc/ExplodingEntityReason.scala index 7e652ff13..af266474b 100644 --- a/src/main/scala/net/psforever/objects/vital/etc/ExplodingEntityReason.scala +++ b/src/main/scala/net/psforever/objects/vital/etc/ExplodingEntityReason.scala @@ -5,10 +5,11 @@ import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.ballistics.SourceEntry import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.vital.{Vitality, VitalityDefinition} -import net.psforever.objects.vital.base.{DamageReason, DamageResolution} -import net.psforever.objects.vital.interaction.DamageResult +import net.psforever.objects.vital.base.{DamageModifiers, DamageReason, DamageResolution} +import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.prop.DamageWithPosition import net.psforever.objects.vital.resolution.DamageAndResistance +import net.psforever.objects.zones.Zone /** * A wrapper for a "damage source" in damage calculations @@ -48,3 +49,48 @@ final case class ExplodingEntityReason( /** the entity that exploded is the source of the damage */ override def attribution: Int = definition.ObjectId } + +object ExplodingDamageModifiers { + trait Mod extends DamageModifiers.Mod { + def calculate(damage : Int, data : DamageInteraction, cause : DamageReason) : Int = { + cause match { + case o: ExplodingEntityReason => calculate(damage, data, o) + case _ => damage + } + } + + def calculate(damage : Int, data : DamageInteraction, cause : ExplodingEntityReason) : Int + } +} + +/** + * A variation of the normal radial damage degradation + * that uses the geometric representations of the exploding entity and of the affected target + * in its calculations that determine the distance between them. + * @see `DamageModifierFunctions.RadialDegrade` + */ +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( + 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) { + //damage - (damage * profile.DamageAtEdge * (distance - radiusMin) / (radius - radiusMin)).toInt + val base = withPosition.DamageAtEdge + val radi = radius - radiusMin + (damage * ((1 - base) * ((radi - (distance - radiusMin)) / radi) + base)).toInt + } else { + 0 + } + case _ => + damage + } + } +} diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala index 8622ca769..b61a414f8 100644 --- a/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -38,6 +38,7 @@ import akka.actor.typed import net.psforever.actors.session.AvatarActor import net.psforever.actors.zone.ZoneActor import net.psforever.objects.avatar.Avatar +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 @@ -1175,7 +1176,7 @@ object Zone { * 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`. * @see `causeExplosion` - * @see `Vector3.DistanceSquare` + * @see `ObjectDefinition.Geometry` * @param obj1 a game entity * @param obj2 a game entity * @param maxDistance the square of the maximum distance permissible between game entities @@ -1183,7 +1184,35 @@ object Zone { * @return `true`, if the target entities are near to each other; * `false`, otherwise */ - private def distanceCheck(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float): Boolean = { - Vector3.DistanceSquared(obj1.Position, obj2.Position) <= maxDistance + def distanceCheck(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float): Boolean = { + distanceCheck(obj1.Definition.Geometry(obj1), obj2.Definition.Geometry(obj2), maxDistance) + } + /** + * Two game entities are considered "near" each other if they are within a certain distance of one another. + * @param g1 the geometric representation of a game entity + * @param g2 the geometric representation of a game entity + * @param maxDistance the square of the maximum distance permissible between game entities + * before they are no longer considered "near" + * @return `true`, if the target entities are near to each other; + * `false`, otherwise + */ + def distanceCheck(g1: Geometry3D, g2: Geometry3D, maxDistance: Float): Boolean = { + distanceCheck(g1, g2) <= maxDistance + } + /** + * Two game entities are considered "near" each other if they are within a certain distance of one another. + * @see `PrimitiveGeometry.pointOnOutside` + * @see `Vector3.DistanceSquared` + * @see `Vector3.neg` + * @see `Vector3.Unit` + * @param g1 the geometric representation of a game entity + * @param g2 the geometric representation of a game entity + * @return the crude distance between the two geometric representations + */ + def distanceCheck(g1: Geometry3D, g2: Geometry3D): Float = { + val dir = Vector3.Unit(g2.center.asVector3 - g1.center.asVector3) + val point1 = g1.pointOnOutside(dir).asVector3 + val point2 = g2.pointOnOutside(Vector3.neg(dir)).asVector3 + Vector3.DistanceSquared(point1, point2) } }