From 4508c1ae45096bb0213be831f5cb74d20a6727b8 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Fri, 15 Mar 2024 01:17:16 -0400 Subject: [PATCH] projectiles that, by definition, are allowed to damage targets through walls (radiation_cloud, with DamageThroughWalls) regardless of sidedness; any unflagged radiation_cloud must be on the side as its target; fixed chat bang-commands not being executed --- .../psforever/actors/session/ChatActor.scala | 10 +- .../WeaponAndProjectileOperations.scala | 101 ++++++++++++- .../InteractWithRadiationClouds.scala | 10 +- .../objects/ballistics/Projectile.scala | 8 +- .../definition/ProjectileDefinition.scala | 2 - .../objects/geometry/d3/Segment.scala | 10 ++ .../global/GlobalDefinitionsProjectile.scala | 7 +- .../serverobject/interior/Sidedness.scala | 6 + ...actWithRadiationCloudsSeatedInEntity.scala | 11 +- .../objects/vital/prop/DamageProperties.scala | 19 +++ .../net/psforever/objects/zones/Zone.scala | 140 +++++++++++++----- .../scala/net/psforever/types/Vector3.scala | 2 + 12 files changed, 266 insertions(+), 60 deletions(-) diff --git a/src/main/scala/net/psforever/actors/session/ChatActor.scala b/src/main/scala/net/psforever/actors/session/ChatActor.scala index bc6e96e26..be92f3603 100644 --- a/src/main/scala/net/psforever/actors/session/ChatActor.scala +++ b/src/main/scala/net/psforever/actors/session/ChatActor.scala @@ -224,6 +224,10 @@ class ChatActor( session.account.gm || Config.app.development.unprivilegedGmCommands.contains(message.messageType) (message.messageType, message.recipient.trim, message.contents.trim) match { + /** Messages starting with ! are custom chat commands */ + case (_, _, contents) if contents.startsWith("!") && + customCommandMessages(message, session, chatService, cluster, gmCommandAllowed) => () + case (CMT_FLY, recipient, contents) if gmCommandAllowed => val flying = contents match { case "on" => true @@ -899,10 +903,6 @@ class ChatActor( ZonePopulationUpdateMessage(4, 414, 138, 0, 138, 0, 138, 0, 138, contents.toInt) ) - /** Messages starting with ! are custom chat commands */ - case (_, _, contents) if contents.startsWith("!") && - customCommandMessages(message, session, chatService, cluster, gmCommandAllowed) => ; - case _ => log.warn(s"Unhandled chat message $message") } @@ -1126,7 +1126,7 @@ class ChatActor( ) true - } else if (contents.startsWith("!loc ")) { + } else if (contents.startsWith("!loc")) { val continent = session.zone val player = session.player val loc = diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index b6cdd5f30..1aa0e284e 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -3,6 +3,8 @@ package net.psforever.actors.session.support import akka.actor.{ActorContext, typed} import net.psforever.objects.definition.ProjectileDefinition +import net.psforever.objects.serverobject.doors.InteriorDoorPassage +import net.psforever.objects.serverobject.interior.Sidedness import net.psforever.objects.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior} import net.psforever.objects.zones.Zoning import net.psforever.objects.serverobject.turret.VanuSentry @@ -303,11 +305,11 @@ private[support] class WeaponAndProjectileOperations( //find target(s) (hit_info match { case Some(hitInfo) => - val hitPos = hitInfo.hit_pos + val hitPos = hitInfo.hit_pos sessionData.validObject(hitInfo.hitobject_guid, decorator = "Hit/hitInfo") match { case _ if projectile.profile == GlobalDefinitions.flail_projectile => val radius = projectile.profile.DamageRadius * projectile.profile.DamageRadius - val targets = Zone.findAllTargets(hitPos)(continent, player, projectile.profile) + val targets = Zone.findAllTargets(continent, player, hitPos, projectile.profile) .filter { target => Vector3.DistanceSquared(target.Position, hitPos) <= radius } @@ -361,7 +363,6 @@ private[support] class WeaponAndProjectileOperations( FindProjectileEntry(projectile_guid) match { case Some(projectile) => val profile = projectile.profile - projectile.Position = explosion_pos projectile.Velocity = projectile_vel val (resolution1, resolution2) = profile.Aggravated match { case Some(_) if profile.ProjectileDamageTypes.contains(DamageType.Aggravated) => @@ -372,7 +373,7 @@ private[support] class WeaponAndProjectileOperations( //direct_victim_uid sessionData.validObject(direct_victim_uid, decorator = "SplashHit/direct_victim") match { case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => - CheckForHitPositionDiscrepancy(projectile_guid, target.Position, target) + CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target) ResolveProjectileInteraction(projectile, resolution1, target, target.Position).collect { resprojectile => addShotsLanded(resprojectile.cause.attribution, shots = 1) sessionData.handleDealingDamage(target, resprojectile) @@ -563,6 +564,7 @@ private[support] class WeaponAndProjectileOperations( ProjectileQuality.Normal } val qualityprojectile = projectile.quality(initialQuality) + qualityprojectile.WhichSide = player.WhichSide projectiles(projectileIndex) = Some(qualityprojectile) if (projectile_info.ExistsOnRemoteClients) { log.trace( @@ -1048,19 +1050,22 @@ private[support] class WeaponAndProjectileOperations( GlobalDefinitions.getDamageProxy(projectile, hitPos) match { case Nil => Nil + case list if list.isEmpty => + Nil case list => HandleDamageProxySetupLittleBuddy(list, hitPos) + UpdateProjectileSidednessAfterHit(projectile, hitPos) + val projectileSide = projectile.WhichSide list.flatMap { proxy => if (proxy.profile.ExistsOnRemoteClients) { proxy.Position = hitPos + proxy.WhichSide = projectileSide continent.Projectile ! ZoneProjectile.Add(player.GUID, proxy) Nil } else if (proxy.tool_def == GlobalDefinitions.maelstrom) { //server-side maelstrom grenade target selection val radius = proxy.profile.LashRadius * proxy.profile.LashRadius - val targets = continent.blockMap - .sector(hitPos, proxy.profile.LashRadius) - .livePlayerList + val targets = Zone.findAllTargets(continent, hitPos, proxy.profile.LashRadius, { _.livePlayerList }) .filter { target => Vector3.DistanceSquared(target.Position, hitPos) <= radius } @@ -1498,6 +1503,88 @@ private[support] class WeaponAndProjectileOperations( } } + private def UpdateProjectileSidednessAfterHit(projectile: Projectile, hitPosition: Vector3): Unit = { + val origin = projectile.Position + val distance = Vector3.Magnitude(hitPosition - origin) + continent.blockMap + .sector(hitPosition, distance) + .environmentList + .collect { case o: InteriorDoorPassage => + val door = o.door + val intersectTest = quickLineSphereIntersectionPoints( + origin, + hitPosition, + door.Position, + door.Definition.geometryInteractionRadius.get + 0.1f + ) + (door, intersectTest) + } + .collect { case (door, intersectionTest) if intersectionTest.nonEmpty => + (door, Vector3.Magnitude(hitPosition - door.Position), intersectionTest) + } + .minByOption { case (_, dist, _) => dist } + .foreach { case (door, _, intersects) => + val strictly = if (Vector3.DotProduct(Vector3.Unit(hitPosition - door.Position), door.Outwards) > 0f) { + Sidedness.OutsideOf + } else { + Sidedness.InsideOf + } + projectile.WhichSide = if (intersects.size == 1) { + Sidedness.InBetweenSides(door, strictly) + } else { + strictly + } + } + } + + /** + * Does a line segment line intersect with a sphere?
+ * This most likely belongs in `Geometry` or `GeometryForm` or somehow in association with the `\objects\geometry\` package. + * @param start first point of the line segment + * @param end second point of the line segment + * @param center center of the sphere + * @param radius radius of the sphere + * @return list of all points of intersection, if any + * @see `Vector3.DistanceSquared` + * @see `Vector3.MagnitudeSquared` + */ + private def quickLineSphereIntersectionPoints( + start: Vector3, + end: Vector3, + center: Vector3, + radius: Float + ): Iterable[Vector3] = { + /* + Algorithm adapted from code found on https://paulbourke.net/geometry/circlesphere/index.html#linesphere, + because I kept messing up proper substitution of the line formula and the circle formula into the quadratic equation. + */ + val Vector3(cx, cy, cz) = center + val Vector3(sx, sy, sz) = start + val vector = end - start + //speed our way through a quadratic equation + val (a, b) = { + val Vector3(dx, dy, dz) = vector + ( + dx * dx + dy * dy + dz * dz, + 2f * (dx * (sx - cx) + dy * (sy - cy) + dz * (sz - cz)) + ) + } + val c = Vector3.MagnitudeSquared(center) + Vector3.MagnitudeSquared(start) - 2f * (cx * sx + cy * sy + cz * sz) - radius * radius + val result = b * b - 4 * a * c + if (result < 0f) { + //negative, no intersection + Seq() + } else if (result < 0.00001f) { + //zero-ish, one intersection point + Seq(start - vector * (b / (2f * a))) + } else { + //positive, two intersection points + val sqrt = math.sqrt(result).toFloat + val endStart = vector / (2f * a) + Seq(start + endStart * (sqrt - b), start + endStart * (b + sqrt) * -1f) + }.filter(p => Vector3.DistanceSquared(start, p) <= a) + } + override protected[session] def stop(): Unit = { if (player != null && player.HasGUID) { (prefire ++ shooting).foreach { guid => diff --git a/src/main/scala/net/psforever/objects/ballistics/InteractWithRadiationClouds.scala b/src/main/scala/net/psforever/objects/ballistics/InteractWithRadiationClouds.scala index 4c815c3f4..c0b5b39f0 100644 --- a/src/main/scala/net/psforever/objects/ballistics/InteractWithRadiationClouds.scala +++ b/src/main/scala/net/psforever/objects/ballistics/InteractWithRadiationClouds.scala @@ -29,7 +29,7 @@ class InteractWithRadiationClouds( */ private var skipTargets: List[PlanetSideGUID] = List() - def Type = RadiationInteraction + def Type: ZoneInteractionType = RadiationInteraction /** * Wander into a radiation cloud and suffer the consequences. @@ -40,12 +40,16 @@ class InteractWithRadiationClouds( target match { case t: Vitality => val position = target.Position + val targetList = List(target) //collect all projectiles in sector/range val projectiles = sector .projectileList .filter { cloud => - val radius = cloud.Definition.DamageRadius - cloud.Definition.radiation_cloud && Zone.distanceCheck(target, cloud, radius * radius) + val definition = cloud.Definition + val radius = definition.DamageRadius + definition.radiation_cloud && + Zone.allOnSameSide(cloud, definition, targetList).nonEmpty && + Zone.distanceCheck(target, cloud, radius * radius) } .distinct val notSkipped = projectiles.filterNot { t => skipTargets.contains(t.GUID) } diff --git a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index 476616ae7..4cf27207d 100644 --- a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.ballistics +import net.psforever.objects.serverobject.interior.TraditionalInteriorAware import net.psforever.objects.sourcing.SourceEntry import java.util.concurrent.atomic.AtomicLong @@ -53,7 +54,8 @@ final case class Projectile( id: Long = Projectile.idGenerator.getAndIncrement(), fire_time: Long = System.currentTimeMillis() ) extends PlanetSideGameObject - with BlockMapEntity { + with BlockMapEntity + with TraditionalInteriorAware { Position = shot_origin Orientation = shot_angle Velocity = shot_velocity.getOrElse { @@ -73,7 +75,8 @@ final case class Projectile( * Create a copy of this projectile with all the same information * save for the quality. * Used mainly for aggravated damage. - * It is important to note that the new projectile shares the (otherwise) exclusive id of the original. + * It is important to note that the new projectile shares the (otherwise) exclusive id of the original + * and that it is not added to a block map structure. * @param value the new quality * @return a new `Projectile` entity */ @@ -94,6 +97,7 @@ final case class Projectile( ) if(isMiss) projectile.Miss() else if(isResolved) projectile.Resolve() + projectile.WhichSide = this.WhichSide projectile } diff --git a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index c60a9f182..cd9708849 100644 --- a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -46,8 +46,6 @@ class ProjectileDefinition(objectId: Int) /** projectile takes the form of a type of "grenade"; * grenades arc with gravity rather than travel in a relatively straight path */ private var grenade_projectile: Boolean = false - /** radiation clouds create independent damage-dealing areas in a zone that last for the projectile's lifespan */ - var radiation_cloud: Boolean = false //derived calculations /** the calculated distance at which the projectile have traveled far enough to despawn (m); * typically handled as the projectile no longer performing damage; diff --git a/src/main/scala/net/psforever/objects/geometry/d3/Segment.scala b/src/main/scala/net/psforever/objects/geometry/d3/Segment.scala index ce80bcafc..e520e1558 100644 --- a/src/main/scala/net/psforever/objects/geometry/d3/Segment.scala +++ b/src/main/scala/net/psforever/objects/geometry/d3/Segment.scala @@ -57,6 +57,16 @@ object Segment { Segment(p, Point(p.x + d.x, p.y + d.y, p.z + d.z)) } + /** + * An overloaded constructor that uses individual coordinates. + * @param a origin + * @param b destination + * @return a `Segment` entity + */ + def apply(a: Vector3, b: Vector3): Segment = { + Segment(Point(a), Point(b)) + } + /** * An overloaded constructor that uses individual coordinates. * @param x the 'x' coordinate of the position diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala index 1997b8cd1..7b3e4f5de 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala @@ -1040,7 +1040,7 @@ object GlobalDefinitionsProjectile { maelstrom_grenade_damager.Name = "maelstrom_grenade_damager" maelstrom_grenade_damager.ProjectileDamageType = DamageType.Direct - //the maelstrom_grenade_damage is something of a broken entity atm + //the maelstrom_grenade_damage will be treated as a broken entity maelstrom_grenade_projectile.Name = "maelstrom_grenade_projectile" maelstrom_grenade_projectile.Damage0 = 32 @@ -1049,6 +1049,7 @@ object GlobalDefinitionsProjectile { maelstrom_grenade_projectile.LashRadius = 5f maelstrom_grenade_projectile.GrenadeProjectile = true maelstrom_grenade_projectile.ProjectileDamageType = DamageType.Direct + maelstrom_grenade_projectile.DamageThroughWalls = true maelstrom_grenade_projectile.InitialVelocity = 30 maelstrom_grenade_projectile.Lifespan = 2f maelstrom_grenade_projectile.DamageProxy = 464 //maelstrom_grenade_damager @@ -1063,6 +1064,7 @@ object GlobalDefinitionsProjectile { maelstrom_grenade_projectile_contact.LashRadius = 5f maelstrom_grenade_projectile_contact.GrenadeProjectile = true maelstrom_grenade_projectile_contact.ProjectileDamageType = DamageType.Direct + maelstrom_grenade_projectile_contact.DamageThroughWalls = true maelstrom_grenade_projectile_contact.InitialVelocity = 30 maelstrom_grenade_projectile_contact.Lifespan = 15f maelstrom_grenade_projectile_contact.DamageProxy = 464 //maelstrom_grenade_damager @@ -1533,6 +1535,7 @@ object GlobalDefinitionsProjectile { radiator_cloud.DamageToHealthOnly = true radiator_cloud.radiation_cloud = true radiator_cloud.ProjectileDamageType = DamageType.Radiation + radiator_cloud.DamageThroughWalls = true //custom aggravated information radiator_cloud.ProjectileDamageTypeSecondary = DamageType.Aggravated radiator_cloud.Aggravated = AggravatedDamage( @@ -2017,6 +2020,7 @@ object GlobalDefinitionsProjectile { aphelion_plasma_cloud.DamageRadius = 3f aphelion_plasma_cloud.radiation_cloud = true aphelion_plasma_cloud.ProjectileDamageType = DamageType.Aggravated + //aphelion_plasma_cloud.DamageThroughWalls = true aphelion_plasma_cloud.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Splash, 0.5f, 1000), Aura.Napalm, @@ -2229,6 +2233,7 @@ object GlobalDefinitionsProjectile { peregrine_particle_cannon_radiation_cloud.DamageRadius = 3f peregrine_particle_cannon_radiation_cloud.radiation_cloud = true peregrine_particle_cannon_radiation_cloud.ProjectileDamageType = DamageType.Radiation + peregrine_particle_cannon_radiation_cloud.DamageThroughWalls = true peregrine_particle_cannon_radiation_cloud.Lifespan = 5.0f ProjectileDefinition.CalculateDerivedFields(peregrine_particle_cannon_radiation_cloud) peregrine_particle_cannon_radiation_cloud.registerAs = "rc-projectiles" diff --git a/src/main/scala/net/psforever/objects/serverobject/interior/Sidedness.scala b/src/main/scala/net/psforever/objects/serverobject/interior/Sidedness.scala index bec498331..91f00fb6d 100644 --- a/src/main/scala/net/psforever/objects/serverobject/interior/Sidedness.scala +++ b/src/main/scala/net/psforever/objects/serverobject/interior/Sidedness.scala @@ -6,6 +6,8 @@ import net.psforever.objects.serverobject.doors.Door sealed trait SidenessComparison sealed trait Sidedness { + def opposite: Sidedness + protected def value: SidenessComparison } @@ -23,14 +25,17 @@ object Sidedness { /* Immutable value containers */ case object InsideOf extends Inside with Sidedness { + def opposite: Sidedness = OutsideOf protected def value: SidenessComparison = IsInside } case object OutsideOf extends Outside with Sidedness { + def opposite: Sidedness = InsideOf protected def value: SidenessComparison = IsOutside } case object StrictlyBetweenSides extends Inside with Outside with Sidedness { + def opposite: Sidedness = this protected def value: SidenessComparison = IsBetween } @@ -39,6 +44,7 @@ object Sidedness { private val door: Door, private val strictly: Sidedness ) extends Inside with Outside with Sidedness { + def opposite: Sidedness = this protected def value: SidenessComparison = { if (door.isOpen) { IsBetween diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/InteractWithRadiationCloudsSeatedInEntity.scala b/src/main/scala/net/psforever/objects/serverobject/mount/InteractWithRadiationCloudsSeatedInEntity.scala index ac87785c9..4c9064cce 100644 --- a/src/main/scala/net/psforever/objects/serverobject/mount/InteractWithRadiationCloudsSeatedInEntity.scala +++ b/src/main/scala/net/psforever/objects/serverobject/mount/InteractWithRadiationCloudsSeatedInEntity.scala @@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.mount import net.psforever.objects.ballistics.{Projectile, ProjectileQuality} import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.vital.Vitality -import net.psforever.objects.vital.base.{DamageResolution, DamageType} +import net.psforever.objects.vital.base.DamageResolution import net.psforever.objects.vital.etc.RadiationReason import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.resistance.StandardResistanceProfile @@ -38,17 +38,16 @@ class InteractWithRadiationCloudsSeatedInEntity( */ override def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = { val position = target.Position + val targetList = List(target) //collect all projectiles in sector/range val projectiles = sector .projectileList .filter { cloud => val definition = cloud.Definition + val radius = definition.DamageRadius definition.radiation_cloud && - definition.AllDamageTypes.contains(DamageType.Radiation) && - { - val radius = definition.DamageRadius - Zone.distanceCheck(target, cloud, radius * radius) - } + Zone.allOnSameSide(cloud, definition, targetList).nonEmpty && + Zone.distanceCheck(target, cloud, radius * radius) } .distinct val notSkipped = projectiles.filterNot { t => skipTargets.contains(t.GUID) } diff --git a/src/main/scala/net/psforever/objects/vital/prop/DamageProperties.scala b/src/main/scala/net/psforever/objects/vital/prop/DamageProperties.scala index 3eb0cc623..97c3c77f9 100644 --- a/src/main/scala/net/psforever/objects/vital/prop/DamageProperties.scala +++ b/src/main/scala/net/psforever/objects/vital/prop/DamageProperties.scala @@ -42,6 +42,11 @@ trait DamageProperties private var charging: Option[ChargeDamage] = None /** a destroyed mine will detonate rather than fizzle-out */ private var sympathy: Boolean = false + /** radiation clouds create independent damage-dealing areas in a zone that last for the projectile's lifespan; + * not implicate a damage type (primary or secondary or aggravated) of `Radiation` */ + private var radiationCloud: Boolean = false + /** server damage is applied when comparing against sided-ness of the source and the target */ + private var damageThroughWalls: Boolean = false def UseDamage1Subtract: Boolean = useDamage1Subtract @@ -132,4 +137,18 @@ trait DamageProperties sympathy = chain SympatheticExplosion } + + def radiation_cloud: Boolean = radiationCloud + + def radiation_cloud_=(isCloud: Boolean): Boolean = { + radiationCloud = isCloud + radiation_cloud + } + + def DamageThroughWalls: Boolean = damageThroughWalls + + def DamageThroughWalls_=(through: Boolean): Boolean = { + damageThroughWalls = through + DamageThroughWalls + } } diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala index fbf0289f9..38eba94ac 100644 --- a/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -51,7 +51,7 @@ import net.psforever.objects.vital.etc.ExplodingEntityReason import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.prop.DamageWithPosition import net.psforever.objects.vital.Vitality -import net.psforever.objects.zones.blockmap.BlockMap +import net.psforever.objects.zones.blockmap.{BlockMap, SectorPopulation} import net.psforever.services.Service import net.psforever.zones.Zones @@ -1437,6 +1437,48 @@ object Zone { allAffectedTargets } + /** + * na + * @param source na + * @param damageProperties na + * @param targets na + * @return na + */ + def allOnSameSide( + source: PlanetSideGameObject, + damageProperties: DamageWithPosition, + targets: List[PlanetSideServerObject with Vitality] + ): List[PlanetSideServerObject with Vitality] = { + source match { + case awareSource: InteriorAware if !damageProperties.DamageThroughWalls => + allOnSameSide(awareSource.WhichSide, targets) + case _ if !damageProperties.DamageThroughWalls => + val sourcePosition = source.Position + targets + .sortBy(t => Vector3.DistanceSquared(sourcePosition, t.Position)) + .collectFirst { case awareSource: InteriorAware => allOnSameSide(awareSource.WhichSide, targets) } + .getOrElse(targets) + case _ => + targets + } + } + + /** + * na + * @param side na + * @param targets na + * @return na + */ + def allOnSameSide( + side: Sidedness, + targets: List[PlanetSideServerObject with Vitality] + ): List[PlanetSideServerObject with Vitality] = { + targets.flatMap { + case awareTarget: InteriorAware if !Sidedness.equals(side, awareTarget.WhichSide) => None + case anyTarget => Some(anyTarget) + } + } + /** * na * @see `DamageWithPosition` @@ -1451,9 +1493,13 @@ object Zone { source: PlanetSideGameObject with Vitality, damagePropertiesBySource: DamageWithPosition ): List[PlanetSideServerObject with Vitality] = { - allOnSameSide( + findAllTargets( + zone, source, - findAllTargets(zone, source.Position, damagePropertiesBySource).filter { target => target ne source } + source.Position, + damagePropertiesBySource, + damagePropertiesBySource.DamageRadius, + getAllTargets ) } @@ -1461,45 +1507,29 @@ object Zone { * na * @see `DamageWithPosition` * @see `Zone.blockMap.sector` + * @param zone the zone in which the explosion should occur * @param sourcePosition a custom position that is used as the origin of the explosion; * not necessarily related to source - * @param zone the zone in which the explosion should occur * @param source a game entity that is treated as the origin and is excluded from results * @param damagePropertiesBySource information about the effect/damage * @return a list of affected entities */ def findAllTargets( - sourcePosition: Vector3 - ) - ( zone: Zone, source: PlanetSideGameObject with Vitality, + sourcePosition: Vector3, damagePropertiesBySource: DamageWithPosition ): List[PlanetSideServerObject with Vitality] = { - allOnSameSide( + findAllTargets( + zone, source, - findAllTargets(zone, sourcePosition, damagePropertiesBySource).filter { target => target ne source } + sourcePosition, + damagePropertiesBySource, + damagePropertiesBySource.DamageRadius, + getAllTargets ) } - private def allOnSameSide( - source: PlanetSideGameObject with Vitality, - targets: List[PlanetSideServerObject with Vitality] - ): List[PlanetSideServerObject with Vitality] = { - source match { - case awareSource: InteriorAware => - val awareSide = awareSource.WhichSide - targets.flatMap { - case awareTarget: InteriorAware if !Sidedness.equals(awareSide, awareTarget.WhichSide) => - None - case anyTarget => - Some(anyTarget) - } - case _ => - targets - } - } - /** * na * @see `DamageWithPosition` @@ -1507,24 +1537,66 @@ object Zone { * @param zone the zone in which the explosion should occur * @param sourcePosition a position that is used as the origin of the explosion * @param damagePropertiesBySource information about the effect/damage + * @param getTargetsFromSector get this list of entities from a sector * @return a list of affected entities */ def findAllTargets( zone: Zone, + source: PlanetSideGameObject with Vitality, sourcePosition: Vector3, - damagePropertiesBySource: DamageWithPosition + damagePropertiesBySource: DamageWithPosition, + radius: Float, + getTargetsFromSector: SectorPopulation => List[PlanetSideServerObject with Vitality] ): List[PlanetSideServerObject with Vitality] = { - val sourcePositionXY = sourcePosition.xy - val sectors = zone.blockMap.sector(sourcePositionXY, damagePropertiesBySource.DamageRadius) + allOnSameSide( + source, + damagePropertiesBySource, + findAllTargets(zone, sourcePosition, radius, getTargetsFromSector).filter { target => target ne source } + ) + } + + def findAllTargets( + sector: SectorPopulation, + source: PlanetSideGameObject with Vitality, + damagePropertiesBySource: DamageWithPosition, + getTargetsFromSector: SectorPopulation => List[PlanetSideServerObject with Vitality] + ): List[PlanetSideServerObject with Vitality] = { + allOnSameSide( + source, + damagePropertiesBySource, + getTargetsFromSector(sector) + ) + } + + /** + * na + * @see `DamageWithPosition` + * @see `Zone.blockMap.sector` + * @param zone the zone in which the explosion should occur + * @param sourcePosition a position that is used as the origin of the explosion + * @param radius idistance + * @param getTargetsFromSector get this list of entities from a sector + * @return a list of affected entities + */ + def findAllTargets( + zone: Zone, + sourcePosition: Vector3, + radius: Float, + getTargetsFromSector: SectorPopulation => List[PlanetSideServerObject with Vitality] + ): List[PlanetSideServerObject with Vitality] = { + getTargetsFromSector(zone.blockMap.sector(sourcePosition.xy, radius)) + } + + def getAllTargets(sector: SectorPopulation): List[PlanetSideServerObject with Vitality] = { //collect all targets that can be damaged //players - val playerTargets = sectors.livePlayerList.filterNot { _.VehicleSeated.nonEmpty } + val playerTargets = sector.livePlayerList.filterNot { _.VehicleSeated.nonEmpty } //vehicles - val vehicleTargets = sectors.vehicleList.filterNot { v => v.Destroyed || v.MountedIn.nonEmpty } + val vehicleTargets = sector.vehicleList.filterNot { v => v.Destroyed || v.MountedIn.nonEmpty } //deployables - val deployableTargets = sectors.deployableList.filterNot { _.Destroyed } + val deployableTargets = sector.deployableList.filterNot { _.Destroyed } //amenities - val soiTargets = sectors.amenityList.collect { case amenity: Vitality if !amenity.Destroyed => amenity } + val soiTargets = sector.amenityList.collect { case amenity: Vitality if !amenity.Destroyed => amenity } //altogether ... playerTargets ++ vehicleTargets ++ deployableTargets ++ soiTargets } diff --git a/src/main/scala/net/psforever/types/Vector3.scala b/src/main/scala/net/psforever/types/Vector3.scala index fc2c4cab1..575053d65 100644 --- a/src/main/scala/net/psforever/types/Vector3.scala +++ b/src/main/scala/net/psforever/types/Vector3.scala @@ -101,6 +101,8 @@ final case class Vector3(x: Float, y: Float, z: Float) { object Vector3 { final val Zero: Vector3 = Vector3(0f, 0f, 0f) + def unapply(v: Vector3): Option[(Float, Float, Float)] = Some((v.x, v.y, v.z)) + private def closeToInsignificance(d: Float, epsilon: Float = 10f): Float = { val ulp = math.ulp(epsilon) math.signum(d) match {