From d00fa6a6bfc73371399ef6615739a9938ff55ba7 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Tue, 2 Dec 2025 20:14:54 -0500 Subject: [PATCH] Restoring Proxy Damage (#1323) * adjusted target selection to account for volumetric geometry, hopefully collecting more targets than before; additionally, utilize previously allocated local sector to reduce the target scope; combined mappings to reduce passing over those same results * reactivating maelstrom availability; restore damage dealing potential to the maelstrom grenades; changing damage profile to 'no radial degrade' * reactivating oicw availability; no more discrepancy checks on secondary projectiles; code for maelstrom chain lashing * might have actually fixed oicw little buddies --- .../resources/overrides/game_objects0.adb.lst | 4 +- .../csr/WeaponAndProjectileLogic.scala | 10 ++- .../normal/WeaponAndProjectileLogic.scala | 14 +-- .../WeaponAndProjectileOperations.scala | 85 +++++++++---------- .../global/GlobalDefinitionsProjectile.scala | 6 +- 5 files changed, 60 insertions(+), 59 deletions(-) diff --git a/server/src/main/resources/overrides/game_objects0.adb.lst b/server/src/main/resources/overrides/game_objects0.adb.lst index 5c86f5d4b..6a7a893ba 100644 --- a/server/src/main/resources/overrides/game_objects0.adb.lst +++ b/server/src/main/resources/overrides/game_objects0.adb.lst @@ -55,7 +55,7 @@ add_property lasher holstertime 750 add_property lasher_projectile_ap lasher_projectile_ap false add_property lasher_projectile_ap lasher_projectile true add_property lightgunship maxhealth 855 -add_property maelstrom allowed false +add_property maelstrom allowed true add_property maelstrom equiptime 1000 add_property maelstrom holstertime 1000 add_property magcutter equiptime 250 @@ -68,7 +68,7 @@ add_property mini_chaingun equiptime 750 add_property mini_chaingun holstertime 750 add_property nano_dispenser equiptime 750 add_property nano_dispenser holstertime 750 -add_property oicw allowed false +add_property oicw allowed true add_property pellet_gun equiptime 600 add_property pellet_gun holstertime 600 add_property peregrine_flight requirement_award0 false diff --git a/src/main/scala/net/psforever/actors/session/csr/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/csr/WeaponAndProjectileLogic.scala index 3b2d780d2..b8c0b6485 100644 --- a/src/main/scala/net/psforever/actors/session/csr/WeaponAndProjectileLogic.scala +++ b/src/main/scala/net/psforever/actors/session/csr/WeaponAndProjectileLogic.scala @@ -132,7 +132,10 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit } //... if (list.isEmpty) { - ops.handleProxyDamage(pkt.projectile_guid, pkt.hit_info.map(_.hit_pos).getOrElse(Vector3.Zero)) + ops.handleProxyDamage(pkt.projectile_guid, pkt.hit_info.map(_.hit_pos).getOrElse(Vector3.Zero)).foreach { + case (target, proxy, hitPos, _) => + ops.resolveProjectileInteraction(target, proxy, DamageResolution.Hit, hitPos) + } } } } @@ -182,7 +185,10 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit } } //... - ops.handleProxyDamage(pkt.projectile_uid, pkt.projectile_pos) + ops.handleProxyDamage(pkt.projectile_uid, pkt.projectile_pos).foreach { + case (target, proxy, hitPos, _) => + ops.resolveProjectileInteraction(target, proxy, DamageResolution.Splash, hitPos) + } } } diff --git a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala index 785cb06c2..6ce96411b 100644 --- a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala +++ b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala @@ -8,8 +8,8 @@ import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObjec import net.psforever.objects.{BoomerDeployable, BoomerTrigger, Player, SpecialEmp, Tool, Vehicle} import net.psforever.objects.vital.base.{DamageResolution, DamageType} import net.psforever.objects.zones.{Zone, ZoneProjectile} -import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, OrbitalStrikeWaypointMessage, ProjectileStateMessage, ReloadMessage, SplashHitMessage, TriggerEffectMessage, TriggeredEffectLocation, UplinkRequest, UplinkRequestType, UplinkResponse, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage} -import net.psforever.types.{ValidPlanetSideGUID, Vector3} +import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage} +import net.psforever.types.Vector3 object WeaponAndProjectileLogic { def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = { @@ -141,18 +141,19 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit } def handleDirectHit(pkt: HitMessage): Unit = { + val projectileGuid = pkt.projectile_guid val list = ops.composeDirectDamageInformation(pkt) .collect { case (target, projectile, hitPos, _) => - ops.checkForHitPositionDiscrepancy(projectile.GUID, hitPos, target) + ops.checkForHitPositionDiscrepancy(projectileGuid, hitPos, target) ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, hitPos) projectile } //... if (list.isEmpty) { - ops.handleProxyDamage(pkt.projectile_guid, pkt.hit_info.map(_.hit_pos).getOrElse(Vector3.Zero)).foreach { + ops.handleProxyDamage(projectileGuid, pkt.hit_info.map(_.hit_pos).getOrElse(Vector3.Zero)).foreach { case (target, proxy, hitPos, _) => - ops.checkForHitPositionDiscrepancy(proxy.GUID, hitPos, target) + ops.resolveProjectileInteraction(target, proxy, DamageResolution.Hit, hitPos) } } } @@ -179,7 +180,6 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit } others.foreach { case (target, _, hitPos, _) => - ops.checkForHitPositionDiscrepancy(projectileGuid, hitPos, target) ops.resolveProjectileInteraction(target, projectile, resolution2, hitPos) } //... @@ -206,7 +206,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit //... ops.handleProxyDamage(pkt.projectile_uid, pkt.projectile_pos).foreach { case (target, proxy, hitPos, _) => - ops.checkForHitPositionDiscrepancy(proxy.GUID, hitPos, target) + ops.resolveProjectileInteraction(target, proxy, DamageResolution.Splash, hitPos) } } 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 047983bd9..280a282c3 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -9,6 +9,7 @@ import net.psforever.objects.ballistics.ProjectileQuality import net.psforever.objects.definition.{ProjectileDefinition, SpecialExoSuitDefinition} import net.psforever.objects.entity.SimpleWorldEntity import net.psforever.objects.equipment.{ChargeFireModeDefinition, Equipment, FireModeSwitch} +import net.psforever.objects.geometry.d3.{Point, VolumetricGeometry} import net.psforever.objects.guid.{GUIDTask, TaskBundle, TaskWorkflow} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.affinity.FactionAffinity @@ -662,14 +663,9 @@ class WeaponAndProjectileOperations( projectileGuid: PlanetSideGUID, explosionPosition: Vector3 ): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = { - val proxyList = FindProjectileEntry(projectileGuid) + FindProjectileEntry(projectileGuid) .map(projectile => resolveDamageProxy(projectile, projectile.GUID, explosionPosition)) .getOrElse(Nil) - proxyList.collectFirst { - case (_, proxy, _, _) if proxy.profile == GlobalDefinitions.oicw_little_buddy => - performLittleBuddyExplosion(proxyList.map(_._2)) - } - proxyList } /** @@ -695,35 +691,47 @@ class WeaponAndProjectileOperations( case list => setupDamageProxyLittleBuddy(list, hitPos) WeaponAndProjectileOperations.updateProjectileSidednessAfterHit(continent, projectile, hitPos) - val projectileSide = projectile.WhichSide list.flatMap { proxy => - if (proxy.profile.ExistsOnRemoteClients) { - proxy.Position = hitPos - proxy.WhichSide = projectileSide + if (proxy.profile == GlobalDefinitions.oicw_little_buddy) { + proxy.WhichSide = projectile.WhichSide + continent.Projectile ! ZoneProjectile.Add(player.GUID, proxy) + queueLittleBuddyExplosion(proxy) + Nil + } else if (proxy.profile.ExistsOnRemoteClients) { + proxy.WhichSide = projectile.WhichSide 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 = Zone.findAllTargets(continent, hitPos, proxy.profile.LashRadius, { _.livePlayerList }) - .filter { target => - Vector3.DistanceSquared(target.Position, hitPos) <= radius + //for convenience purposes, all resulting chain lashing is handled here and resolves in one pass + proxy.WhichSide = Sidedness.StrictlyBetweenSides + val radiusSquared = proxy.profile.LashRadius * proxy.profile.LashRadius + var availableTargets = sessionLogic.localSector.livePlayerList + var unresolvedChainLashHits: Seq[VolumetricGeometry] = Seq(Point(hitPos)) + var uniqueChainLashTargets: Seq[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile)] = Seq() + while (unresolvedChainLashHits.nonEmpty) { + val newChainLashTargets = unresolvedChainLashHits.flatMap { availableCarrier => + val proxyCopy = proxy.copy(shot_origin = availableCarrier.center.asVector3) + val (hits, misses) = availableTargets.partition { target => Zone.distanceCheck(availableCarrier, target, radiusSquared) } + availableTargets = misses + hits.map(t => (t, proxyCopy)) } - //chainlash is separated from the actual damage application for convenience + uniqueChainLashTargets = uniqueChainLashTargets ++ newChainLashTargets + unresolvedChainLashHits = newChainLashTargets.map { case (t, _) => t.Definition.Geometry(t) } + } + val (guidRefs, outputRefs) = uniqueChainLashTargets.map { case (target, proxyCopy) => + (target.GUID, (target, proxyCopy, proxyCopy.shot_origin, target.Position)) + }.unzip + //chain lash effect continent.AvatarEvents ! AvatarServiceMessage( continent.id, AvatarAction.SendResponse( PlanetSideGUID(0), - ChainLashMessage( - hitPos, - projectile.profile.ObjectId, - targets.map { _.GUID } - ) + ChainLashMessage(hitPos, projectile.profile.ObjectId, guidRefs.toList) ) ) - targets.map { target => - (target, proxy, hitPos, target.Position) - } + //chain lash target output + outputRefs.toList } else { Nil } @@ -784,28 +792,12 @@ class WeaponAndProjectileOperations( } } - private def performLittleBuddyExplosion(listOfProjectiles: List[Projectile]): Boolean = { - val listOfLittleBuddies: List[Projectile] = listOfProjectiles.filter { _.tool_def == GlobalDefinitions.oicw } - val size: Int = listOfLittleBuddies.size - if (size > 0) { - val desiredDownwardsProjectiles: Int = 2 - val firstHalf: Int = math.min(size, desiredDownwardsProjectiles) //number that fly straight down + private def queueLittleBuddyExplosion(proxy: Projectile): Boolean = { + if (proxy.profile == GlobalDefinitions.oicw_little_buddy) { val speed: Float = 144f //speed (packet discovered) val dist: Float = 25 //distance (client defined) - //downwards projectiles - var i: Int = 0 - listOfLittleBuddies.take(firstHalf).foreach { proxy => - val dir = proxy.Velocity.map(_ / speed).getOrElse(Vector3.Zero) - queueLittleBuddyDamage(proxy, dir, dist) - i += 1 - } - //flared out projectiles - i = 0 - listOfLittleBuddies.drop(firstHalf).foreach { proxy => - val dir = proxy.Velocity.map(_ / speed).getOrElse(Vector3.Zero) - queueLittleBuddyDamage(proxy, dir, dist) - i += 1 - } + val dir = proxy.Velocity.map(_ / speed).getOrElse(Vector3.Zero) + queueLittleBuddyDamage(proxy, dir, dist) true } else { false @@ -1569,7 +1561,8 @@ object WeaponAndProjectileOperations { def updateProjectileSidednessAfterHit(zone: Zone, projectile: Projectile, hitPosition: Vector3): Unit = { val origin = projectile.Position val distance = Vector3.Magnitude(hitPosition - origin) - zone.blockMap + zone + .blockMap .sector(hitPosition, distance) .environmentList .collect { case o: InteriorDoorPassage => @@ -1658,13 +1651,15 @@ object WeaponAndProjectileOperations { * @param source a game object that represents the source of the explosion * @param owner who or what to accredit damage from the explosion to; * clarifies a normal `SourceEntry(source)` accreditation + * @return a list of affected entities + */ def detonateLittleBuddy( zone: Zone, source: PlanetSideGameObject with FactionAffinity with Vitality, proxy: Projectile, owner: SourceEntry - )(): Unit = { + )(): List[PlanetSideServerObject] = { Zone.serverSideDamage(zone, source, littleBuddyExplosionDamage(owner, proxy.id, source.Position)) } diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala index be9f4e581..71687b2d8 100644 --- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala +++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala @@ -12,7 +12,7 @@ import net.psforever.objects.definition.converter.{ import net.psforever.objects.equipment.{ArmorSiphonRepairHost, EffectTarget, TargetValidation} import net.psforever.objects.serverobject.aura.Aura import net.psforever.objects.vital.base.DamageType -import net.psforever.objects.vital.damage.{RadialDegrade, StandardDamageProfile} +import net.psforever.objects.vital.damage.{RadialDegrade, SameHit, StandardDamageProfile} import net.psforever.objects.vital.etc.{ ArmorSiphonMaxDistanceCutoff, ExplosionDamagesOnlyAbove, @@ -1054,7 +1054,7 @@ object GlobalDefinitionsProjectile { maelstrom_grenade_projectile.Lifespan = 2f maelstrom_grenade_projectile.DamageProxy = 464 //maelstrom_grenade_damager ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile) - maelstrom_grenade_projectile.Modifiers = RadialDegrade + maelstrom_grenade_projectile.Modifiers = SameHit maelstrom_grenade_projectile_contact.Name = "maelstrom_grenade_projectile_contact" // TODO for later, maybe : set_resource_parent maelstrom_grenade_projectile_contact game_objects maelstrom_grenade_projectile @@ -1069,7 +1069,7 @@ object GlobalDefinitionsProjectile { maelstrom_grenade_projectile_contact.Lifespan = 15f maelstrom_grenade_projectile_contact.DamageProxy = 464 //maelstrom_grenade_damager ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile_contact) - maelstrom_grenade_projectile_contact.Modifiers = RadialDegrade + maelstrom_grenade_projectile_contact.Modifiers = SameHit maelstrom_stream_projectile.Name = "maelstrom_stream_projectile" maelstrom_stream_projectile.Damage0 = 15