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

This commit is contained in:
Fate-JH 2024-03-15 01:17:16 -04:00
parent b5d60a7f9e
commit 4508c1ae45
12 changed files with 266 additions and 60 deletions

View file

@ -224,6 +224,10 @@ class ChatActor(
session.account.gm || Config.app.development.unprivilegedGmCommands.contains(message.messageType) session.account.gm || Config.app.development.unprivilegedGmCommands.contains(message.messageType)
(message.messageType, message.recipient.trim, message.contents.trim) match { (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 => case (CMT_FLY, recipient, contents) if gmCommandAllowed =>
val flying = contents match { val flying = contents match {
case "on" => true case "on" => true
@ -899,10 +903,6 @@ class ChatActor(
ZonePopulationUpdateMessage(4, 414, 138, 0, 138, 0, 138, 0, 138, contents.toInt) 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 _ => case _ =>
log.warn(s"Unhandled chat message $message") log.warn(s"Unhandled chat message $message")
} }
@ -1126,7 +1126,7 @@ class ChatActor(
) )
true true
} else if (contents.startsWith("!loc ")) { } else if (contents.startsWith("!loc")) {
val continent = session.zone val continent = session.zone
val player = session.player val player = session.player
val loc = val loc =

View file

@ -3,6 +3,8 @@ package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed} import akka.actor.{ActorContext, typed}
import net.psforever.objects.definition.ProjectileDefinition 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.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior}
import net.psforever.objects.zones.Zoning import net.psforever.objects.zones.Zoning
import net.psforever.objects.serverobject.turret.VanuSentry import net.psforever.objects.serverobject.turret.VanuSentry
@ -303,11 +305,11 @@ private[support] class WeaponAndProjectileOperations(
//find target(s) //find target(s)
(hit_info match { (hit_info match {
case Some(hitInfo) => case Some(hitInfo) =>
val hitPos = hitInfo.hit_pos val hitPos = hitInfo.hit_pos
sessionData.validObject(hitInfo.hitobject_guid, decorator = "Hit/hitInfo") match { sessionData.validObject(hitInfo.hitobject_guid, decorator = "Hit/hitInfo") match {
case _ if projectile.profile == GlobalDefinitions.flail_projectile => case _ if projectile.profile == GlobalDefinitions.flail_projectile =>
val radius = projectile.profile.DamageRadius * projectile.profile.DamageRadius 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 => .filter { target =>
Vector3.DistanceSquared(target.Position, hitPos) <= radius Vector3.DistanceSquared(target.Position, hitPos) <= radius
} }
@ -361,7 +363,6 @@ private[support] class WeaponAndProjectileOperations(
FindProjectileEntry(projectile_guid) match { FindProjectileEntry(projectile_guid) match {
case Some(projectile) => case Some(projectile) =>
val profile = projectile.profile val profile = projectile.profile
projectile.Position = explosion_pos
projectile.Velocity = projectile_vel projectile.Velocity = projectile_vel
val (resolution1, resolution2) = profile.Aggravated match { val (resolution1, resolution2) = profile.Aggravated match {
case Some(_) if profile.ProjectileDamageTypes.contains(DamageType.Aggravated) => case Some(_) if profile.ProjectileDamageTypes.contains(DamageType.Aggravated) =>
@ -372,7 +373,7 @@ private[support] class WeaponAndProjectileOperations(
//direct_victim_uid //direct_victim_uid
sessionData.validObject(direct_victim_uid, decorator = "SplashHit/direct_victim") match { sessionData.validObject(direct_victim_uid, decorator = "SplashHit/direct_victim") match {
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => 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 => ResolveProjectileInteraction(projectile, resolution1, target, target.Position).collect { resprojectile =>
addShotsLanded(resprojectile.cause.attribution, shots = 1) addShotsLanded(resprojectile.cause.attribution, shots = 1)
sessionData.handleDealingDamage(target, resprojectile) sessionData.handleDealingDamage(target, resprojectile)
@ -563,6 +564,7 @@ private[support] class WeaponAndProjectileOperations(
ProjectileQuality.Normal ProjectileQuality.Normal
} }
val qualityprojectile = projectile.quality(initialQuality) val qualityprojectile = projectile.quality(initialQuality)
qualityprojectile.WhichSide = player.WhichSide
projectiles(projectileIndex) = Some(qualityprojectile) projectiles(projectileIndex) = Some(qualityprojectile)
if (projectile_info.ExistsOnRemoteClients) { if (projectile_info.ExistsOnRemoteClients) {
log.trace( log.trace(
@ -1048,19 +1050,22 @@ private[support] class WeaponAndProjectileOperations(
GlobalDefinitions.getDamageProxy(projectile, hitPos) match { GlobalDefinitions.getDamageProxy(projectile, hitPos) match {
case Nil => case Nil =>
Nil Nil
case list if list.isEmpty =>
Nil
case list => case list =>
HandleDamageProxySetupLittleBuddy(list, hitPos) HandleDamageProxySetupLittleBuddy(list, hitPos)
UpdateProjectileSidednessAfterHit(projectile, hitPos)
val projectileSide = projectile.WhichSide
list.flatMap { proxy => list.flatMap { proxy =>
if (proxy.profile.ExistsOnRemoteClients) { if (proxy.profile.ExistsOnRemoteClients) {
proxy.Position = hitPos proxy.Position = hitPos
proxy.WhichSide = projectileSide
continent.Projectile ! ZoneProjectile.Add(player.GUID, proxy) continent.Projectile ! ZoneProjectile.Add(player.GUID, proxy)
Nil Nil
} else if (proxy.tool_def == GlobalDefinitions.maelstrom) { } else if (proxy.tool_def == GlobalDefinitions.maelstrom) {
//server-side maelstrom grenade target selection //server-side maelstrom grenade target selection
val radius = proxy.profile.LashRadius * proxy.profile.LashRadius val radius = proxy.profile.LashRadius * proxy.profile.LashRadius
val targets = continent.blockMap val targets = Zone.findAllTargets(continent, hitPos, proxy.profile.LashRadius, { _.livePlayerList })
.sector(hitPos, proxy.profile.LashRadius)
.livePlayerList
.filter { target => .filter { target =>
Vector3.DistanceSquared(target.Position, hitPos) <= radius 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?<br>
* 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 = { override protected[session] def stop(): Unit = {
if (player != null && player.HasGUID) { if (player != null && player.HasGUID) {
(prefire ++ shooting).foreach { guid => (prefire ++ shooting).foreach { guid =>

View file

@ -29,7 +29,7 @@ class InteractWithRadiationClouds(
*/ */
private var skipTargets: List[PlanetSideGUID] = List() private var skipTargets: List[PlanetSideGUID] = List()
def Type = RadiationInteraction def Type: ZoneInteractionType = RadiationInteraction
/** /**
* Wander into a radiation cloud and suffer the consequences. * Wander into a radiation cloud and suffer the consequences.
@ -40,12 +40,16 @@ class InteractWithRadiationClouds(
target match { target match {
case t: Vitality => case t: Vitality =>
val position = target.Position val position = target.Position
val targetList = List(target)
//collect all projectiles in sector/range //collect all projectiles in sector/range
val projectiles = sector val projectiles = sector
.projectileList .projectileList
.filter { cloud => .filter { cloud =>
val radius = cloud.Definition.DamageRadius val definition = cloud.Definition
cloud.Definition.radiation_cloud && Zone.distanceCheck(target, cloud, radius * radius) val radius = definition.DamageRadius
definition.radiation_cloud &&
Zone.allOnSameSide(cloud, definition, targetList).nonEmpty &&
Zone.distanceCheck(target, cloud, radius * radius)
} }
.distinct .distinct
val notSkipped = projectiles.filterNot { t => skipTargets.contains(t.GUID) } val notSkipped = projectiles.filterNot { t => skipTargets.contains(t.GUID) }

View file

@ -1,6 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.ballistics package net.psforever.objects.ballistics
import net.psforever.objects.serverobject.interior.TraditionalInteriorAware
import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.sourcing.SourceEntry
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
@ -53,7 +54,8 @@ final case class Projectile(
id: Long = Projectile.idGenerator.getAndIncrement(), id: Long = Projectile.idGenerator.getAndIncrement(),
fire_time: Long = System.currentTimeMillis() fire_time: Long = System.currentTimeMillis()
) extends PlanetSideGameObject ) extends PlanetSideGameObject
with BlockMapEntity { with BlockMapEntity
with TraditionalInteriorAware {
Position = shot_origin Position = shot_origin
Orientation = shot_angle Orientation = shot_angle
Velocity = shot_velocity.getOrElse { Velocity = shot_velocity.getOrElse {
@ -73,7 +75,8 @@ final case class Projectile(
* Create a copy of this projectile with all the same information * Create a copy of this projectile with all the same information
* save for the quality. * save for the quality.
* Used mainly for aggravated damage. * 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 * @param value the new quality
* @return a new `Projectile` entity * @return a new `Projectile` entity
*/ */
@ -94,6 +97,7 @@ final case class Projectile(
) )
if(isMiss) projectile.Miss() if(isMiss) projectile.Miss()
else if(isResolved) projectile.Resolve() else if(isResolved) projectile.Resolve()
projectile.WhichSide = this.WhichSide
projectile projectile
} }

View file

@ -46,8 +46,6 @@ class ProjectileDefinition(objectId: Int)
/** projectile takes the form of a type of "grenade"; /** projectile takes the form of a type of "grenade";
* grenades arc with gravity rather than travel in a relatively straight path */ * grenades arc with gravity rather than travel in a relatively straight path */
private var grenade_projectile: Boolean = false 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 //derived calculations
/** the calculated distance at which the projectile have traveled far enough to despawn (m); /** the calculated distance at which the projectile have traveled far enough to despawn (m);
* typically handled as the projectile no longer performing damage; * typically handled as the projectile no longer performing damage;

View file

@ -57,6 +57,16 @@ object Segment {
Segment(p, Point(p.x + d.x, p.y + d.y, p.z + d.z)) 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. * An overloaded constructor that uses individual coordinates.
* @param x the 'x' coordinate of the position * @param x the 'x' coordinate of the position

View file

@ -1040,7 +1040,7 @@ object GlobalDefinitionsProjectile {
maelstrom_grenade_damager.Name = "maelstrom_grenade_damager" maelstrom_grenade_damager.Name = "maelstrom_grenade_damager"
maelstrom_grenade_damager.ProjectileDamageType = DamageType.Direct 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.Name = "maelstrom_grenade_projectile"
maelstrom_grenade_projectile.Damage0 = 32 maelstrom_grenade_projectile.Damage0 = 32
@ -1049,6 +1049,7 @@ object GlobalDefinitionsProjectile {
maelstrom_grenade_projectile.LashRadius = 5f maelstrom_grenade_projectile.LashRadius = 5f
maelstrom_grenade_projectile.GrenadeProjectile = true maelstrom_grenade_projectile.GrenadeProjectile = true
maelstrom_grenade_projectile.ProjectileDamageType = DamageType.Direct maelstrom_grenade_projectile.ProjectileDamageType = DamageType.Direct
maelstrom_grenade_projectile.DamageThroughWalls = true
maelstrom_grenade_projectile.InitialVelocity = 30 maelstrom_grenade_projectile.InitialVelocity = 30
maelstrom_grenade_projectile.Lifespan = 2f maelstrom_grenade_projectile.Lifespan = 2f
maelstrom_grenade_projectile.DamageProxy = 464 //maelstrom_grenade_damager maelstrom_grenade_projectile.DamageProxy = 464 //maelstrom_grenade_damager
@ -1063,6 +1064,7 @@ object GlobalDefinitionsProjectile {
maelstrom_grenade_projectile_contact.LashRadius = 5f maelstrom_grenade_projectile_contact.LashRadius = 5f
maelstrom_grenade_projectile_contact.GrenadeProjectile = true maelstrom_grenade_projectile_contact.GrenadeProjectile = true
maelstrom_grenade_projectile_contact.ProjectileDamageType = DamageType.Direct maelstrom_grenade_projectile_contact.ProjectileDamageType = DamageType.Direct
maelstrom_grenade_projectile_contact.DamageThroughWalls = true
maelstrom_grenade_projectile_contact.InitialVelocity = 30 maelstrom_grenade_projectile_contact.InitialVelocity = 30
maelstrom_grenade_projectile_contact.Lifespan = 15f maelstrom_grenade_projectile_contact.Lifespan = 15f
maelstrom_grenade_projectile_contact.DamageProxy = 464 //maelstrom_grenade_damager maelstrom_grenade_projectile_contact.DamageProxy = 464 //maelstrom_grenade_damager
@ -1533,6 +1535,7 @@ object GlobalDefinitionsProjectile {
radiator_cloud.DamageToHealthOnly = true radiator_cloud.DamageToHealthOnly = true
radiator_cloud.radiation_cloud = true radiator_cloud.radiation_cloud = true
radiator_cloud.ProjectileDamageType = DamageType.Radiation radiator_cloud.ProjectileDamageType = DamageType.Radiation
radiator_cloud.DamageThroughWalls = true
//custom aggravated information //custom aggravated information
radiator_cloud.ProjectileDamageTypeSecondary = DamageType.Aggravated radiator_cloud.ProjectileDamageTypeSecondary = DamageType.Aggravated
radiator_cloud.Aggravated = AggravatedDamage( radiator_cloud.Aggravated = AggravatedDamage(
@ -2017,6 +2020,7 @@ object GlobalDefinitionsProjectile {
aphelion_plasma_cloud.DamageRadius = 3f aphelion_plasma_cloud.DamageRadius = 3f
aphelion_plasma_cloud.radiation_cloud = true aphelion_plasma_cloud.radiation_cloud = true
aphelion_plasma_cloud.ProjectileDamageType = DamageType.Aggravated aphelion_plasma_cloud.ProjectileDamageType = DamageType.Aggravated
//aphelion_plasma_cloud.DamageThroughWalls = true
aphelion_plasma_cloud.Aggravated = AggravatedDamage( aphelion_plasma_cloud.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Splash, 0.5f, 1000), AggravatedInfo(DamageType.Splash, 0.5f, 1000),
Aura.Napalm, Aura.Napalm,
@ -2229,6 +2233,7 @@ object GlobalDefinitionsProjectile {
peregrine_particle_cannon_radiation_cloud.DamageRadius = 3f peregrine_particle_cannon_radiation_cloud.DamageRadius = 3f
peregrine_particle_cannon_radiation_cloud.radiation_cloud = true peregrine_particle_cannon_radiation_cloud.radiation_cloud = true
peregrine_particle_cannon_radiation_cloud.ProjectileDamageType = DamageType.Radiation peregrine_particle_cannon_radiation_cloud.ProjectileDamageType = DamageType.Radiation
peregrine_particle_cannon_radiation_cloud.DamageThroughWalls = true
peregrine_particle_cannon_radiation_cloud.Lifespan = 5.0f peregrine_particle_cannon_radiation_cloud.Lifespan = 5.0f
ProjectileDefinition.CalculateDerivedFields(peregrine_particle_cannon_radiation_cloud) ProjectileDefinition.CalculateDerivedFields(peregrine_particle_cannon_radiation_cloud)
peregrine_particle_cannon_radiation_cloud.registerAs = "rc-projectiles" peregrine_particle_cannon_radiation_cloud.registerAs = "rc-projectiles"

View file

@ -6,6 +6,8 @@ import net.psforever.objects.serverobject.doors.Door
sealed trait SidenessComparison sealed trait SidenessComparison
sealed trait Sidedness { sealed trait Sidedness {
def opposite: Sidedness
protected def value: SidenessComparison protected def value: SidenessComparison
} }
@ -23,14 +25,17 @@ object Sidedness {
/* Immutable value containers */ /* Immutable value containers */
case object InsideOf extends Inside with Sidedness { case object InsideOf extends Inside with Sidedness {
def opposite: Sidedness = OutsideOf
protected def value: SidenessComparison = IsInside protected def value: SidenessComparison = IsInside
} }
case object OutsideOf extends Outside with Sidedness { case object OutsideOf extends Outside with Sidedness {
def opposite: Sidedness = InsideOf
protected def value: SidenessComparison = IsOutside protected def value: SidenessComparison = IsOutside
} }
case object StrictlyBetweenSides extends Inside with Outside with Sidedness { case object StrictlyBetweenSides extends Inside with Outside with Sidedness {
def opposite: Sidedness = this
protected def value: SidenessComparison = IsBetween protected def value: SidenessComparison = IsBetween
} }
@ -39,6 +44,7 @@ object Sidedness {
private val door: Door, private val door: Door,
private val strictly: Sidedness private val strictly: Sidedness
) extends Inside with Outside with Sidedness { ) extends Inside with Outside with Sidedness {
def opposite: Sidedness = this
protected def value: SidenessComparison = { protected def value: SidenessComparison = {
if (door.isOpen) { if (door.isOpen) {
IsBetween IsBetween

View file

@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.mount
import net.psforever.objects.ballistics.{Projectile, ProjectileQuality} import net.psforever.objects.ballistics.{Projectile, ProjectileQuality}
import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.Vitality 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.etc.RadiationReason
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.resistance.StandardResistanceProfile import net.psforever.objects.vital.resistance.StandardResistanceProfile
@ -38,17 +38,16 @@ class InteractWithRadiationCloudsSeatedInEntity(
*/ */
override def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = { override def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
val position = target.Position val position = target.Position
val targetList = List(target)
//collect all projectiles in sector/range //collect all projectiles in sector/range
val projectiles = sector val projectiles = sector
.projectileList .projectileList
.filter { cloud => .filter { cloud =>
val definition = cloud.Definition val definition = cloud.Definition
val radius = definition.DamageRadius
definition.radiation_cloud && definition.radiation_cloud &&
definition.AllDamageTypes.contains(DamageType.Radiation) && Zone.allOnSameSide(cloud, definition, targetList).nonEmpty &&
{ Zone.distanceCheck(target, cloud, radius * radius)
val radius = definition.DamageRadius
Zone.distanceCheck(target, cloud, radius * radius)
}
} }
.distinct .distinct
val notSkipped = projectiles.filterNot { t => skipTargets.contains(t.GUID) } val notSkipped = projectiles.filterNot { t => skipTargets.contains(t.GUID) }

View file

@ -42,6 +42,11 @@ trait DamageProperties
private var charging: Option[ChargeDamage] = None private var charging: Option[ChargeDamage] = None
/** a destroyed mine will detonate rather than fizzle-out */ /** a destroyed mine will detonate rather than fizzle-out */
private var sympathy: Boolean = false 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 def UseDamage1Subtract: Boolean = useDamage1Subtract
@ -132,4 +137,18 @@ trait DamageProperties
sympathy = chain sympathy = chain
SympatheticExplosion 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
}
} }

View file

@ -51,7 +51,7 @@ import net.psforever.objects.vital.etc.ExplodingEntityReason
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
import net.psforever.objects.vital.prop.DamageWithPosition import net.psforever.objects.vital.prop.DamageWithPosition
import net.psforever.objects.vital.Vitality 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.services.Service
import net.psforever.zones.Zones import net.psforever.zones.Zones
@ -1437,6 +1437,48 @@ object Zone {
allAffectedTargets 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 * na
* @see `DamageWithPosition` * @see `DamageWithPosition`
@ -1451,9 +1493,13 @@ object Zone {
source: PlanetSideGameObject with Vitality, source: PlanetSideGameObject with Vitality,
damagePropertiesBySource: DamageWithPosition damagePropertiesBySource: DamageWithPosition
): List[PlanetSideServerObject with Vitality] = { ): List[PlanetSideServerObject with Vitality] = {
allOnSameSide( findAllTargets(
zone,
source, source,
findAllTargets(zone, source.Position, damagePropertiesBySource).filter { target => target ne source } source.Position,
damagePropertiesBySource,
damagePropertiesBySource.DamageRadius,
getAllTargets
) )
} }
@ -1461,45 +1507,29 @@ object Zone {
* na * na
* @see `DamageWithPosition` * @see `DamageWithPosition`
* @see `Zone.blockMap.sector` * @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; * @param sourcePosition a custom position that is used as the origin of the explosion;
* not necessarily related to source * 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 source a game entity that is treated as the origin and is excluded from results
* @param damagePropertiesBySource information about the effect/damage * @param damagePropertiesBySource information about the effect/damage
* @return a list of affected entities * @return a list of affected entities
*/ */
def findAllTargets( def findAllTargets(
sourcePosition: Vector3
)
(
zone: Zone, zone: Zone,
source: PlanetSideGameObject with Vitality, source: PlanetSideGameObject with Vitality,
sourcePosition: Vector3,
damagePropertiesBySource: DamageWithPosition damagePropertiesBySource: DamageWithPosition
): List[PlanetSideServerObject with Vitality] = { ): List[PlanetSideServerObject with Vitality] = {
allOnSameSide( findAllTargets(
zone,
source, 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 * na
* @see `DamageWithPosition` * @see `DamageWithPosition`
@ -1507,24 +1537,66 @@ object Zone {
* @param zone the zone in which the explosion should occur * @param zone the zone in which the explosion should occur
* @param sourcePosition a position that is used as the origin of the explosion * @param sourcePosition a position that is used as the origin of the explosion
* @param damagePropertiesBySource information about the effect/damage * @param damagePropertiesBySource information about the effect/damage
* @param getTargetsFromSector get this list of entities from a sector
* @return a list of affected entities * @return a list of affected entities
*/ */
def findAllTargets( def findAllTargets(
zone: Zone, zone: Zone,
source: PlanetSideGameObject with Vitality,
sourcePosition: Vector3, sourcePosition: Vector3,
damagePropertiesBySource: DamageWithPosition damagePropertiesBySource: DamageWithPosition,
radius: Float,
getTargetsFromSector: SectorPopulation => List[PlanetSideServerObject with Vitality]
): List[PlanetSideServerObject with Vitality] = { ): List[PlanetSideServerObject with Vitality] = {
val sourcePositionXY = sourcePosition.xy allOnSameSide(
val sectors = zone.blockMap.sector(sourcePositionXY, damagePropertiesBySource.DamageRadius) 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 //collect all targets that can be damaged
//players //players
val playerTargets = sectors.livePlayerList.filterNot { _.VehicleSeated.nonEmpty } val playerTargets = sector.livePlayerList.filterNot { _.VehicleSeated.nonEmpty }
//vehicles //vehicles
val vehicleTargets = sectors.vehicleList.filterNot { v => v.Destroyed || v.MountedIn.nonEmpty } val vehicleTargets = sector.vehicleList.filterNot { v => v.Destroyed || v.MountedIn.nonEmpty }
//deployables //deployables
val deployableTargets = sectors.deployableList.filterNot { _.Destroyed } val deployableTargets = sector.deployableList.filterNot { _.Destroyed }
//amenities //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 ... //altogether ...
playerTargets ++ vehicleTargets ++ deployableTargets ++ soiTargets playerTargets ++ vehicleTargets ++ deployableTargets ++ soiTargets
} }

View file

@ -101,6 +101,8 @@ final case class Vector3(x: Float, y: Float, z: Float) {
object Vector3 { object Vector3 {
final val Zero: Vector3 = Vector3(0f, 0f, 0f) 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 = { private def closeToInsignificance(d: Float, epsilon: Float = 10f): Float = {
val ulp = math.ulp(epsilon) val ulp = math.ulp(epsilon)
math.signum(d) match { math.signum(d) match {