Restoring Proxy Damage (#1323)
Some checks failed
Publish Docs / docs (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Test / test (push) Has been cancelled

* 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
This commit is contained in:
Fate-JH 2025-12-02 20:14:54 -05:00 committed by GitHub
parent 4d2639b54d
commit d00fa6a6bf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 60 additions and 59 deletions

View file

@ -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_ap false
add_property lasher_projectile_ap lasher_projectile true add_property lasher_projectile_ap lasher_projectile true
add_property lightgunship maxhealth 855 add_property lightgunship maxhealth 855
add_property maelstrom allowed false add_property maelstrom allowed true
add_property maelstrom equiptime 1000 add_property maelstrom equiptime 1000
add_property maelstrom holstertime 1000 add_property maelstrom holstertime 1000
add_property magcutter equiptime 250 add_property magcutter equiptime 250
@ -68,7 +68,7 @@ add_property mini_chaingun equiptime 750
add_property mini_chaingun holstertime 750 add_property mini_chaingun holstertime 750
add_property nano_dispenser equiptime 750 add_property nano_dispenser equiptime 750
add_property nano_dispenser holstertime 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 equiptime 600
add_property pellet_gun holstertime 600 add_property pellet_gun holstertime 600
add_property peregrine_flight requirement_award0 false add_property peregrine_flight requirement_award0 false

View file

@ -132,7 +132,10 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
} }
//... //...
if (list.isEmpty) { 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)
}
} }
} }

View file

@ -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.{BoomerDeployable, BoomerTrigger, Player, SpecialEmp, Tool, Vehicle}
import net.psforever.objects.vital.base.{DamageResolution, DamageType} import net.psforever.objects.vital.base.{DamageResolution, DamageType}
import net.psforever.objects.zones.{Zone, ZoneProjectile} 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.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.{ValidPlanetSideGUID, Vector3} import net.psforever.types.Vector3
object WeaponAndProjectileLogic { object WeaponAndProjectileLogic {
def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = { def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = {
@ -141,18 +141,19 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
} }
def handleDirectHit(pkt: HitMessage): Unit = { def handleDirectHit(pkt: HitMessage): Unit = {
val projectileGuid = pkt.projectile_guid
val list = ops.composeDirectDamageInformation(pkt) val list = ops.composeDirectDamageInformation(pkt)
.collect { .collect {
case (target, projectile, hitPos, _) => case (target, projectile, hitPos, _) =>
ops.checkForHitPositionDiscrepancy(projectile.GUID, hitPos, target) ops.checkForHitPositionDiscrepancy(projectileGuid, hitPos, target)
ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, hitPos) ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, hitPos)
projectile projectile
} }
//... //...
if (list.isEmpty) { 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, _) => 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 { others.foreach {
case (target, _, hitPos, _) => case (target, _, hitPos, _) =>
ops.checkForHitPositionDiscrepancy(projectileGuid, hitPos, target)
ops.resolveProjectileInteraction(target, projectile, resolution2, hitPos) 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 { ops.handleProxyDamage(pkt.projectile_uid, pkt.projectile_pos).foreach {
case (target, proxy, hitPos, _) => case (target, proxy, hitPos, _) =>
ops.checkForHitPositionDiscrepancy(proxy.GUID, hitPos, target) ops.resolveProjectileInteraction(target, proxy, DamageResolution.Splash, hitPos)
} }
} }

View file

@ -9,6 +9,7 @@ import net.psforever.objects.ballistics.ProjectileQuality
import net.psforever.objects.definition.{ProjectileDefinition, SpecialExoSuitDefinition} import net.psforever.objects.definition.{ProjectileDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.entity.SimpleWorldEntity import net.psforever.objects.entity.SimpleWorldEntity
import net.psforever.objects.equipment.{ChargeFireModeDefinition, Equipment, FireModeSwitch} 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.guid.{GUIDTask, TaskBundle, TaskWorkflow}
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
@ -662,14 +663,9 @@ class WeaponAndProjectileOperations(
projectileGuid: PlanetSideGUID, projectileGuid: PlanetSideGUID,
explosionPosition: Vector3 explosionPosition: Vector3
): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = { ): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = {
val proxyList = FindProjectileEntry(projectileGuid) FindProjectileEntry(projectileGuid)
.map(projectile => resolveDamageProxy(projectile, projectile.GUID, explosionPosition)) .map(projectile => resolveDamageProxy(projectile, projectile.GUID, explosionPosition))
.getOrElse(Nil) .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 => case list =>
setupDamageProxyLittleBuddy(list, hitPos) setupDamageProxyLittleBuddy(list, hitPos)
WeaponAndProjectileOperations.updateProjectileSidednessAfterHit(continent, projectile, hitPos) WeaponAndProjectileOperations.updateProjectileSidednessAfterHit(continent, projectile, hitPos)
val projectileSide = projectile.WhichSide
list.flatMap { proxy => list.flatMap { proxy =>
if (proxy.profile.ExistsOnRemoteClients) { if (proxy.profile == GlobalDefinitions.oicw_little_buddy) {
proxy.Position = hitPos proxy.WhichSide = projectile.WhichSide
proxy.WhichSide = projectileSide 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) 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 //for convenience purposes, all resulting chain lashing is handled here and resolves in one pass
val targets = Zone.findAllTargets(continent, hitPos, proxy.profile.LashRadius, { _.livePlayerList }) proxy.WhichSide = Sidedness.StrictlyBetweenSides
.filter { target => val radiusSquared = proxy.profile.LashRadius * proxy.profile.LashRadius
Vector3.DistanceSquared(target.Position, hitPos) <= radius 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.AvatarEvents ! AvatarServiceMessage(
continent.id, continent.id,
AvatarAction.SendResponse( AvatarAction.SendResponse(
PlanetSideGUID(0), PlanetSideGUID(0),
ChainLashMessage( ChainLashMessage(hitPos, projectile.profile.ObjectId, guidRefs.toList)
hitPos,
projectile.profile.ObjectId,
targets.map { _.GUID }
)
) )
) )
targets.map { target => //chain lash target output
(target, proxy, hitPos, target.Position) outputRefs.toList
}
} else { } else {
Nil Nil
} }
@ -784,28 +792,12 @@ class WeaponAndProjectileOperations(
} }
} }
private def performLittleBuddyExplosion(listOfProjectiles: List[Projectile]): Boolean = { private def queueLittleBuddyExplosion(proxy: Projectile): Boolean = {
val listOfLittleBuddies: List[Projectile] = listOfProjectiles.filter { _.tool_def == GlobalDefinitions.oicw } if (proxy.profile == GlobalDefinitions.oicw_little_buddy) {
val size: Int = listOfLittleBuddies.size
if (size > 0) {
val desiredDownwardsProjectiles: Int = 2
val firstHalf: Int = math.min(size, desiredDownwardsProjectiles) //number that fly straight down
val speed: Float = 144f //speed (packet discovered) val speed: Float = 144f //speed (packet discovered)
val dist: Float = 25 //distance (client defined) val dist: Float = 25 //distance (client defined)
//downwards projectiles val dir = proxy.Velocity.map(_ / speed).getOrElse(Vector3.Zero)
var i: Int = 0 queueLittleBuddyDamage(proxy, dir, dist)
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
}
true true
} else { } else {
false false
@ -1569,7 +1561,8 @@ object WeaponAndProjectileOperations {
def updateProjectileSidednessAfterHit(zone: Zone, projectile: Projectile, hitPosition: Vector3): Unit = { def updateProjectileSidednessAfterHit(zone: Zone, projectile: Projectile, hitPosition: Vector3): Unit = {
val origin = projectile.Position val origin = projectile.Position
val distance = Vector3.Magnitude(hitPosition - origin) val distance = Vector3.Magnitude(hitPosition - origin)
zone.blockMap zone
.blockMap
.sector(hitPosition, distance) .sector(hitPosition, distance)
.environmentList .environmentList
.collect { case o: InteriorDoorPassage => .collect { case o: InteriorDoorPassage =>
@ -1658,13 +1651,15 @@ object WeaponAndProjectileOperations {
* @param source a game object that represents the source of the explosion * @param source a game object that represents the source of the explosion
* @param owner who or what to accredit damage from the explosion to; * @param owner who or what to accredit damage from the explosion to;
* clarifies a normal `SourceEntry(source)` accreditation * clarifies a normal `SourceEntry(source)` accreditation
* @return a list of affected entities
*/ */
def detonateLittleBuddy( def detonateLittleBuddy(
zone: Zone, zone: Zone,
source: PlanetSideGameObject with FactionAffinity with Vitality, source: PlanetSideGameObject with FactionAffinity with Vitality,
proxy: Projectile, proxy: Projectile,
owner: SourceEntry owner: SourceEntry
)(): Unit = { )(): List[PlanetSideServerObject] = {
Zone.serverSideDamage(zone, source, littleBuddyExplosionDamage(owner, proxy.id, source.Position)) Zone.serverSideDamage(zone, source, littleBuddyExplosionDamage(owner, proxy.id, source.Position))
} }

View file

@ -12,7 +12,7 @@ import net.psforever.objects.definition.converter.{
import net.psforever.objects.equipment.{ArmorSiphonRepairHost, EffectTarget, TargetValidation} import net.psforever.objects.equipment.{ArmorSiphonRepairHost, EffectTarget, TargetValidation}
import net.psforever.objects.serverobject.aura.Aura import net.psforever.objects.serverobject.aura.Aura
import net.psforever.objects.vital.base.DamageType 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.{ import net.psforever.objects.vital.etc.{
ArmorSiphonMaxDistanceCutoff, ArmorSiphonMaxDistanceCutoff,
ExplosionDamagesOnlyAbove, ExplosionDamagesOnlyAbove,
@ -1054,7 +1054,7 @@ object GlobalDefinitionsProjectile {
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
ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile) ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile)
maelstrom_grenade_projectile.Modifiers = RadialDegrade maelstrom_grenade_projectile.Modifiers = SameHit
maelstrom_grenade_projectile_contact.Name = "maelstrom_grenade_projectile_contact" 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 // 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.Lifespan = 15f
maelstrom_grenade_projectile_contact.DamageProxy = 464 //maelstrom_grenade_damager maelstrom_grenade_projectile_contact.DamageProxy = 464 //maelstrom_grenade_damager
ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile_contact) 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.Name = "maelstrom_stream_projectile"
maelstrom_stream_projectile.Damage0 = 15 maelstrom_stream_projectile.Damage0 = 15