Merge pull request #1328 from Fate-JH/rad-poisoning
Some checks failed
Publish Docs / docs (push) Has been cancelled
Publish Docker Image / docker (push) Has been cancelled
Test / test (push) Has been cancelled

Lead Plating
This commit is contained in:
Fate-JH 2026-01-06 18:35:47 -05:00 committed by GitHub
commit 8f1badb862
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
45 changed files with 386 additions and 328 deletions

View file

@ -74,7 +74,7 @@ add_property pulsar equiptime 600
add_property pulsar holstertime 600 add_property pulsar holstertime 600
add_property punisher equiptime 600 add_property punisher equiptime 600
add_property punisher holstertime 600 add_property punisher holstertime 600
add_property radiator allowed false add_property radiator allowed true
add_property r_shotgun equiptime 750 add_property r_shotgun equiptime 750
add_property r_shotgun holstertime 750 add_property r_shotgun holstertime 750
add_property remote_electronics_kit equiptime 500 add_property remote_electronics_kit equiptime 500

View file

@ -127,14 +127,14 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
val list = ops.composeDirectDamageInformation(pkt) val list = ops.composeDirectDamageInformation(pkt)
if (!player.spectator) { if (!player.spectator) {
list.foreach { list.foreach {
case (target, projectile, _, _) => case (target, projectile, _, targetPos) =>
ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, target.Position) ops.resolveProjectileInteractionAndProxy(target, projectile, DamageResolution.Hit, targetPos)
} }
//... //...
if (list.isEmpty) { if (list.isEmpty) {
ops.handleProxyDamage(pkt.projectile_guid, pkt.hit_info.map(_.hit_pos).getOrElse(Vector3.Zero)).foreach { ops.handleProxyDamage(pkt.projectile_guid, pkt.hit_info.map(_.hit_pos).getOrElse(Vector3.Zero)).foreach {
case (target, proxy, hitPos, _) => case (target, proxy, _, targetPos) =>
ops.resolveProjectileInteraction(target, proxy, DamageResolution.Hit, hitPos) ops.resolveProjectileInteraction(target, proxy, DamageResolution.Hit, targetPos)
} }
} }
} }
@ -157,12 +157,12 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
//... //...
val (direct, others) = list.partition { case (_, _, hitPos, targetPos) => hitPos == targetPos } val (direct, others) = list.partition { case (_, _, hitPos, targetPos) => hitPos == targetPos }
direct.foreach { direct.foreach {
case (target, _, _, _) => case (target, _, _, targetPos) =>
ops.resolveProjectileInteraction(target, projectile, resolution1, target.Position) ops.resolveProjectileInteractionAndProxy(target, projectile, resolution1, targetPos)
} }
others.foreach { others.foreach {
case (target, _, _, _) => case (target, _, _, targetPos) =>
ops.resolveProjectileInteraction(target, projectile, resolution2, target.Position) ops.resolveProjectileInteraction(target, projectile, resolution2, targetPos)
} }
//... //...
if ( if (
@ -183,11 +183,11 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
if (profile.ExistsOnRemoteClients && projectile.HasGUID) { if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
continent.Projectile ! ZoneProjectile.Remove(projectileGuid) continent.Projectile ! ZoneProjectile.Remove(projectileGuid)
} }
} } else {
//... ops.handleProxyDamage(pkt.projectile_uid, pkt.projectile_pos).foreach {
ops.handleProxyDamage(pkt.projectile_uid, pkt.projectile_pos).foreach { case (target, proxy, _, targetPos) =>
case (target, proxy, hitPos, _) => ops.resolveProjectileInteraction(target, proxy, DamageResolution.Splash, targetPos)
ops.resolveProjectileInteraction(target, proxy, DamageResolution.Splash, hitPos) }
} }
} }
} }

View file

@ -144,9 +144,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
val projectileGuid = pkt.projectile_guid 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, targetPos) =>
ops.checkForHitPositionDiscrepancy(projectileGuid, hitPos, target) ops.checkForHitPositionDiscrepancy(projectileGuid, hitPos, targetPos)
ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, hitPos) ops.resolveProjectileInteractionAndProxy(target, projectile, DamageResolution.Hit, hitPos)
projectile projectile
} }
//... //...
@ -174,9 +174,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
//... //...
val (direct, others) = list.partition { case (_, _, hitPos, targetPos) => hitPos == targetPos } val (direct, others) = list.partition { case (_, _, hitPos, targetPos) => hitPos == targetPos }
direct.foreach { direct.foreach {
case (target, _, hitPos, _) => case (target, _, hitPos, targetPos) =>
ops.checkForHitPositionDiscrepancy(projectileGuid, hitPos, target) ops.checkForHitPositionDiscrepancy(projectileGuid, hitPos, targetPos)
ops.resolveProjectileInteraction(target, projectile, resolution1, hitPos) ops.resolveProjectileInteractionAndProxy(target, projectile, resolution1, hitPos)
} }
others.foreach { others.foreach {
case (target, _, hitPos, _) => case (target, _, hitPos, _) =>
@ -202,19 +202,19 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
//cleanup //cleanup
continent.Projectile ! ZoneProjectile.Remove(projectile.GUID) continent.Projectile ! ZoneProjectile.Remove(projectile.GUID)
} }
} } else {
//... 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.resolveProjectileInteraction(target, proxy, DamageResolution.Splash, hitPos)
ops.resolveProjectileInteraction(target, proxy, DamageResolution.Splash, hitPos) }
} }
} }
def handleLashHit(pkt: LashMessage): Unit = { def handleLashHit(pkt: LashMessage): Unit = {
val list = ops.composeLashDamageInformation(pkt) val list = ops.composeLashDamageInformation(pkt)
list.foreach { list.foreach {
case (target, projectile, hitPos, _) => case (target, projectile, hitPos, targetPos) =>
ops.checkForHitPositionDiscrepancy(projectile.GUID, hitPos, target) ops.checkForHitPositionDiscrepancy(projectile.GUID, hitPos, targetPos)
ops.resolveProjectileInteraction(target, projectile, DamageResolution.Lash, hitPos) ops.resolveProjectileInteraction(target, projectile, DamageResolution.Lash, hitPos)
} }
} }
@ -223,8 +223,8 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
val list = ops.composeAIDamageInformation(pkt) val list = ops.composeAIDamageInformation(pkt)
if (ops.confirmAIDamageTarget(pkt, list.map(_._1))) { if (ops.confirmAIDamageTarget(pkt, list.map(_._1))) {
list.foreach { list.foreach {
case (target, projectile, hitPos, _) => case (target, projectile, hitPos, targetPos) =>
ops.checkForHitPositionDiscrepancy(pkt.attacker_guid, hitPos, target) ops.checkForHitPositionDiscrepancy(pkt.attacker_guid, hitPos, targetPos)
ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, hitPos) ops.resolveProjectileInteraction(target, projectile, DamageResolution.Hit, hitPos)
} }
} }

View file

@ -513,6 +513,7 @@ class WeaponAndProjectileOperations(
hit_info match { hit_info match {
case Some(hitInfo) => case Some(hitInfo) =>
val hitPos = hitInfo.hit_pos val hitPos = hitInfo.hit_pos
projectile.Position = hitPos
sessionLogic.validObject(hitInfo.hitobject_guid, decorator = "Hit/hitInfo") match { sessionLogic.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
@ -522,7 +523,7 @@ class WeaponAndProjectileOperations(
.map(target => (target, projectile, hitPos, target.Position)) .map(target => (target, projectile, hitPos, target.Position))
case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
List((target, projectile, hitInfo.shot_origin, hitPos)) List((target, projectile, hitPos, target.Position))
case None => case None =>
Nil Nil
@ -585,7 +586,9 @@ class WeaponAndProjectileOperations(
FindProjectileEntry(projectile_guid) FindProjectileEntry(projectile_guid)
.flatMap { .flatMap {
projectile => projectile =>
sessionLogic //projectile may still be moving, and may lash other targets in the future when in a different position
projectile.Position = hit_pos
sessionLogic
.validObject(victim_guid, decorator = "LashHit/victim_guid") .validObject(victim_guid, decorator = "LashHit/victim_guid")
.collect { .collect {
case target: PlanetSideGameObject with FactionAffinity with Vitality => case target: PlanetSideGameObject with FactionAffinity with Vitality =>
@ -668,6 +671,18 @@ class WeaponAndProjectileOperations(
.getOrElse(Nil) .getOrElse(Nil)
} }
private def handleProxyDamage(
projectile: Projectile,
explosionPosition: Vector3
): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = {
val proxyList = resolveDamageProxy(projectile, projectile.GUID, explosionPosition)
proxyList.collectFirst {
case (_, proxy, _, _) if proxy.profile == GlobalDefinitions.oicw_little_buddy =>
queueLittleBuddyExplosion(proxy)
}
proxyList
}
/** /**
* Take a projectile that was introduced into the game world and * Take a projectile that was introduced into the game world and
* determine if it generates a secondary damage projectile or * determine if it generates a secondary damage projectile or
@ -698,12 +713,14 @@ class WeaponAndProjectileOperations(
queueLittleBuddyExplosion(proxy) queueLittleBuddyExplosion(proxy)
Nil Nil
} else if (proxy.profile.ExistsOnRemoteClients) { } else if (proxy.profile.ExistsOnRemoteClients) {
proxy.Position = hitPos
proxy.WhichSide = projectile.WhichSide 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
//for convenience purposes, all resulting chain lashing is handled here and resolves in one pass //for convenience purposes, all resulting chain lashing is handled here and resolves in one pass
proxy.Position = hitPos
proxy.WhichSide = Sidedness.StrictlyBetweenSides proxy.WhichSide = Sidedness.StrictlyBetweenSides
val radiusSquared = proxy.profile.LashRadius * proxy.profile.LashRadius val radiusSquared = proxy.profile.LashRadius * proxy.profile.LashRadius
var availableTargets = sessionLogic.localSector.livePlayerList var availableTargets = sessionLogic.localSector.livePlayerList
@ -812,6 +829,26 @@ class WeaponAndProjectileOperations(
context.system.scheduler.scheduleOnce(500.milliseconds) { explosionFunc() } context.system.scheduler.scheduleOnce(500.milliseconds) { explosionFunc() }
} }
/**
* Find a projectile with the given globally unique identifier and mark it as a resolved shot.
* A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle.
* Check if we are required to deal with damage proxy management as well.
* @param projectile projectile
* @param resolution resolution status to promote the projectile
* @return package that contains information about the damage
*/
def resolveProjectileInteractionAndProxy(
target: PlanetSideGameObject with FactionAffinity with Vitality,
projectile: Projectile,
resolution: DamageResolution.Value,
hitPosition: Vector3
): Option[DamageInteraction] = {
if (projectile.profile.DamageProxyOnDirectHit.exists(_.test(target))) {
handleProxyDamage(projectile, hitPosition)
}
resolveProjectileInteraction(target, projectile, resolution, hitPosition)
}
/** /**
* Find a projectile with the given globally unique identifier and mark it as a resolved shot. * Find a projectile with the given globally unique identifier and mark it as a resolved shot.
* A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle. * A `Resolved` shot has either encountered an obstacle or is being cleaned up for not finding an obstacle.
@ -1445,15 +1482,23 @@ class WeaponAndProjectileOperations(
} }
def checkForHitPositionDiscrepancy( def checkForHitPositionDiscrepancy(
projectile_guid: PlanetSideGUID, projectileGuid: PlanetSideGUID,
hitPos: Vector3, hitPosition: Vector3,
target: PlanetSideGameObject with Vitality target: PlanetSideGameObject with Vitality
): Unit = { ): Unit = {
val hitPositionDiscrepancy = Vector3.DistanceSquared(hitPos, target.Position) checkForHitPositionDiscrepancy(projectileGuid, hitPosition, target.Position)
}
def checkForHitPositionDiscrepancy(
projectileGuid: PlanetSideGUID,
hitPosition: Vector3,
targetPosition: Vector3
): Unit = {
val hitPositionDiscrepancy = Vector3.DistanceSquared(hitPosition, targetPosition)
if (hitPositionDiscrepancy > Config.app.antiCheat.hitPositionDiscrepancyThreshold) { if (hitPositionDiscrepancy > Config.app.antiCheat.hitPositionDiscrepancyThreshold) {
// If the target position on the server does not match the position where the projectile landed within reason there may be foul play // If the target position on the server does not match the position where the projectile landed within reason there may be foul play
log.warn( log.warn(
s"${player.Name}'s shot #${projectile_guid.guid} has hit discrepancy with target. Target: ${target.Position}, Reported: $hitPos, Distance: $hitPositionDiscrepancy / ${math.sqrt(hitPositionDiscrepancy).toFloat}; suspect" s"${player.Name}'s shot #${projectileGuid.guid} has hit discrepancy with target. Target: $targetPosition, Reported: $hitPosition, Distance: $hitPositionDiscrepancy / ${math.sqrt(hitPositionDiscrepancy).toFloat}; suspect"
) )
} }
} }

View file

@ -20,7 +20,8 @@ import net.psforever.objects.vital.damage.DamageProfile
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.resolution.DamageResistanceModel import net.psforever.objects.vital.resolution.DamageResistanceModel
import net.psforever.objects.zones.blockmap.BlockMapEntity import net.psforever.objects.zones.blockmap.BlockMapEntity
import net.psforever.objects.zones.{InteractsWithZone, ZoneAware, Zoning} import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.objects.zones.{ZoneAware, Zoning}
import net.psforever.types._ import net.psforever.types._
import scala.annotation.tailrec import scala.annotation.tailrec

View file

@ -18,7 +18,7 @@ import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.vital.interaction.DamageResult import net.psforever.objects.vital.interaction.DamageResult
import net.psforever.objects.vital.resistance.StandardResistanceProfile import net.psforever.objects.vital.resistance.StandardResistanceProfile
import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance} import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance}
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.packet.game.TriggeredSound import net.psforever.packet.game.TriggeredSound
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}

View file

@ -20,8 +20,8 @@ import net.psforever.objects.vehicles.interaction.{TriggerOnVehicleRule, WithLav
import net.psforever.objects.vital.resistance.StandardResistanceProfile import net.psforever.objects.vital.resistance.StandardResistanceProfile
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.DamageResistanceModel import net.psforever.objects.vital.resolution.DamageResistanceModel
import net.psforever.objects.zones.InteractsWithZone
import net.psforever.objects.zones.blockmap.BlockMapEntity import net.psforever.objects.zones.blockmap.BlockMapEntity
import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.PlanetSideGamePacket
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}

View file

@ -898,7 +898,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
//initial damage for aggravation, but never treat as "aggravated" //initial damage for aggravation, but never treat as "aggravated"
false false
case _ => case _ =>
cause.interaction.cause.source.Aggravated.nonEmpty target.VehicleSeated.isEmpty && cause.interaction.cause.source.Aggravated.nonEmpty
} }
//log historical event (always) //log historical event (always)
target.LogActivity(cause) target.LogActivity(cause)

View file

@ -5,7 +5,7 @@ import net.psforever.objects.serverobject.doors.{Door, InteriorDoorPassage}
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment, interaction} import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment, interaction}
import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment} import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment}
import net.psforever.objects.serverobject.interior.{Sidedness, TraditionalInteriorAware} import net.psforever.objects.serverobject.interior.{Sidedness, TraditionalInteriorAware}
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import scala.annotation.unused import scala.annotation.unused

View file

@ -5,7 +5,7 @@ import net.psforever.objects.serverobject.environment.interaction.{InteractionWi
import net.psforever.objects.{Player, Vehicle, Vehicles} import net.psforever.objects.{Player, Vehicle, Vehicles}
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, GantryDenialField, PieceOfEnvironment, interaction} import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, GantryDenialField, PieceOfEnvironment, interaction}
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.packet.game.{ChatMsg, PlayerStateShiftMessage, ShiftState} import net.psforever.packet.game.{ChatMsg, PlayerStateShiftMessage, ShiftState}
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}

View file

@ -8,7 +8,7 @@ import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.environment.EnvironmentReason import net.psforever.objects.vital.environment.EnvironmentReason
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import scala.concurrent.duration._ import scala.concurrent.duration._

View file

@ -6,7 +6,7 @@ import net.psforever.objects.serverobject.environment.interaction.{InteractionWi
import net.psforever.objects.serverobject.environment.interaction.common.Watery import net.psforever.objects.serverobject.environment.interaction.common.Watery
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment, interaction} import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment, interaction}
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.types.OxygenState import net.psforever.types.OxygenState

View file

@ -7,83 +7,38 @@ import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.DamageResolution 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.zones.blockmap.SectorPopulation import net.psforever.objects.zones.interaction.{InteractsWithZone, RadiationCloudInteraction, ZoneInteractionType}
import net.psforever.objects.zones.{InteractsWithZone, Zone, ZoneInteraction, ZoneInteractionType}
import net.psforever.types.PlanetSideGUID
case object RadiationInteraction extends ZoneInteractionType case object RadiationInteraction extends ZoneInteractionType
/** /**
* This game entity may infrequently test whether it may interact with radiation cloud projectiles * This game entity may infrequently test whether it may interact with radiation cloud projectiles
* that may be emitted in the game environment for a limited amount of time. * that may be emitted in the game environment for a limited amount of time.
*/ * Since the target entity is a player character, it gets tested for its interaction
*/
class InteractWithRadiationClouds( class InteractWithRadiationClouds(
val range: Float, val range: Float,
private val user: Option[Player] private val user: Option[Player]
) extends ZoneInteraction { ) extends RadiationCloudInteraction {
/**
* radiation clouds that, though detected, are skipped from affecting the target;
* in between interaction tests, a memory of the clouds that were tested last are retained and
* are excluded from being tested this next time;
* clouds that are detected a second time are cleared from the list and are available to be tested next time
*/
private var skipTargets: List[PlanetSideGUID] = List()
def Type: ZoneInteractionType = RadiationInteraction def Type: ZoneInteractionType = RadiationInteraction
/** def performInteractionWithTarget(projectiles: List[Projectile], target: InteractsWithZone): Unit = {
* Wander into a radiation cloud and suffer the consequences. if (projectiles.nonEmpty) {
* @param sector the portion of the block map being tested val position = target.Position
* @param target the fixed element in this test projectiles
*/ .foreach { projectile =>
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = { target.Actor ! Vitality.Damage(
target match { DamageInteraction(
case t: Vitality => SourceEntry(target),
val position = target.Position RadiationReason(
val targetList = List(target) ProjectileQuality.modifiers(projectile, DamageResolution.Radiation, target, target.Position, user),
//collect all projectiles in sector/range target.DamageModel,
val projectiles = sector RadiationCloudInteraction.RadiationShieldingFrom(target)
.projectileList ),
.filter { cloud => position
val definition = cloud.Definition ).calculate()
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) }
skipTargets = notSkipped.map { _.GUID }
if (notSkipped.nonEmpty) {
//isolate one of each type of projectile
notSkipped
.foldLeft(Nil: List[Projectile]) {
(acc, next) => if (acc.exists { _.profile == next.profile }) acc else next :: acc
}
.foreach { projectile =>
t.Actor ! Vitality.Damage(
DamageInteraction(
SourceEntry(target),
RadiationReason(
ProjectileQuality.modifiers(projectile, DamageResolution.Radiation, t, t.Position, user),
t.DamageModel,
0f
),
position
).calculate()
)
}
} }
case _ => ;
} }
} }
/**
* Any radiation clouds blocked from being tested should be cleared.
* All that can be done is blanking our retained previous effect targets.
* @param target the fixed element in this test
*/
def resetInteraction(target: InteractsWithZone): Unit = {
skipTargets = List()
}
} }

View file

@ -3,7 +3,7 @@ package net.psforever.objects.ce
import net.psforever.objects.geometry.d3.VolumetricGeometry import net.psforever.objects.geometry.d3.VolumetricGeometry
import net.psforever.objects.zones.blockmap.SectorPopulation import net.psforever.objects.zones.blockmap.SectorPopulation
import net.psforever.objects.zones.{InteractsWithZone, ZoneInteraction, ZoneInteractionType} import net.psforever.objects.zones.interaction.{InteractsWithZone, ZoneInteraction, ZoneInteractionType}
import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable} import net.psforever.objects.{BoomerDeployable, ExplosiveDeployable}
import net.psforever.types.PlanetSideGUID import net.psforever.types.PlanetSideGUID

View file

@ -5,8 +5,8 @@ import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior} import net.psforever.objects.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior}
import net.psforever.objects.zones.blockmap.SectorPopulation import net.psforever.objects.zones.blockmap.SectorPopulation
import net.psforever.objects.zones.{InteractsWithZone, ZoneInteraction, ZoneInteractionType}
import net.psforever.objects.sourcing.SourceUniqueness import net.psforever.objects.sourcing.SourceUniqueness
import net.psforever.objects.zones.interaction.{InteractsWithZone, ZoneInteraction, ZoneInteractionType}
import net.psforever.types.Vector3 import net.psforever.types.Vector3
case object TurretInteraction extends ZoneInteractionType case object TurretInteraction extends ZoneInteractionType

View file

@ -135,6 +135,8 @@ class SpecialExoSuitDefinition(private val suitType: ExoSuitType.Value) extends
override def Use: ExoSuitDefinition = { override def Use: ExoSuitDefinition = {
val obj = new SpecialExoSuitDefinition(SuitType) val obj = new SpecialExoSuitDefinition(SuitType)
obj.Name = Name
obj.Descriptor = Descriptor
obj.Permissions = Permissions obj.Permissions = Permissions
obj.MaxArmor = MaxArmor obj.MaxArmor = MaxArmor
obj.MaxCapacitor = MaxCapacitor obj.MaxCapacitor = MaxCapacitor
@ -150,6 +152,7 @@ class SpecialExoSuitDefinition(private val suitType: ExoSuitType.Value) extends
obj.ResistanceDirectHit = ResistanceDirectHit obj.ResistanceDirectHit = ResistanceDirectHit
obj.ResistanceSplash = ResistanceSplash obj.ResistanceSplash = ResistanceSplash
obj.ResistanceAggravated = ResistanceAggravated obj.ResistanceAggravated = ResistanceAggravated
obj.RadiationShielding = RadiationShielding
obj.DamageUsing = DamageUsing obj.DamageUsing = DamageUsing
obj.ResistUsing = ResistUsing obj.ResistUsing = ResistUsing
obj.Model = Model obj.Model = Model

View file

@ -17,7 +17,7 @@ object EffectTarget {
* Arbitrary, but useful. * Arbitrary, but useful.
*/ */
object Category extends Enumeration { object Category extends Enumeration {
val Aircraft, Deployable, Equipment, Player, Turret, Vehicle = Value val Aircraft, Deployable, Equipment, Player, Turret, Vehicle, All = Value
} }
object Validation { object Validation {
@ -26,6 +26,9 @@ object EffectTarget {
//noinspection ScalaUnusedSymbol //noinspection ScalaUnusedSymbol
def Invalid(target: PlanetSideGameObject): Boolean = false def Invalid(target: PlanetSideGameObject): Boolean = false
//noinspection ScalaUnusedSymbol
def Valid(target: PlanetSideGameObject): Boolean = true
def Medical(target: PlanetSideGameObject): Boolean = { def Medical(target: PlanetSideGameObject): Boolean = {
target match { target match {
case p: Player => case p: Player =>

View file

@ -4,20 +4,14 @@ package net.psforever.objects.global
import net.psforever.objects.GlobalDefinitions import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedInfo, AggravatedTiming, ChargeDamage} import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedInfo, AggravatedTiming, ChargeDamage}
import net.psforever.objects.definition.ProjectileDefinition import net.psforever.objects.definition.ProjectileDefinition
import net.psforever.objects.definition.converter.{ import net.psforever.objects.definition.converter.{LittleBuddyProjectileConverter, ProjectileConverter, RadiationCloudConverter}
LittleBuddyProjectileConverter,
ProjectileConverter,
RadiationCloudConverter
}
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, SameHit, 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
InfantryAggravatedRadiation,
InfantryAggravatedRadiationBurn
} }
import net.psforever.objects.vital.projectile._ import net.psforever.objects.vital.projectile._
@ -1053,6 +1047,9 @@ object GlobalDefinitionsProjectile {
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
maelstrom_grenade_projectile.DamageProxyOnDirectHit = List(
TargetValidation(EffectTarget.Category.All, EffectTarget.Validation.Valid)
)
ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile) ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile)
maelstrom_grenade_projectile.Modifiers = SameHit maelstrom_grenade_projectile.Modifiers = SameHit
@ -1068,6 +1065,9 @@ object GlobalDefinitionsProjectile {
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
maelstrom_grenade_projectile_contact.DamageProxyOnDirectHit = List(
TargetValidation(EffectTarget.Category.All, EffectTarget.Validation.Valid)
)
ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile_contact) ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile_contact)
maelstrom_grenade_projectile_contact.Modifiers = SameHit maelstrom_grenade_projectile_contact.Modifiers = SameHit
@ -1529,7 +1529,7 @@ object GlobalDefinitionsProjectile {
ProjectileDefinition.CalculateDerivedFields(quasar_projectile) ProjectileDefinition.CalculateDerivedFields(quasar_projectile)
radiator_cloud.Name = "radiator_cloud" radiator_cloud.Name = "radiator_cloud"
radiator_cloud.Damage0 = 1 //2 radiator_cloud.Damage0 = 2
radiator_cloud.DamageAtEdge = 1.0f radiator_cloud.DamageAtEdge = 1.0f
radiator_cloud.DamageRadius = 5f radiator_cloud.DamageRadius = 5f
radiator_cloud.DamageToHealthOnly = true radiator_cloud.DamageToHealthOnly = true
@ -1539,7 +1539,7 @@ object GlobalDefinitionsProjectile {
//custom aggravated information //custom aggravated information
radiator_cloud.ProjectileDamageTypeSecondary = DamageType.Aggravated radiator_cloud.ProjectileDamageTypeSecondary = DamageType.Aggravated
radiator_cloud.Aggravated = AggravatedDamage( radiator_cloud.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Splash, 1f, 80), AggravatedInfo(DamageType.Splash, 0f, 80),
Aura.None, Aura.None,
AggravatedTiming(250, 2), AggravatedTiming(250, 2),
0f, 0f,
@ -1552,12 +1552,7 @@ object GlobalDefinitionsProjectile {
radiator_cloud.ExistsOnRemoteClients = true radiator_cloud.ExistsOnRemoteClients = true
radiator_cloud.Packet = radCloudConverter radiator_cloud.Packet = radCloudConverter
//radiator_cloud.Geometry = GeometryForm.representProjectileBySphere() //radiator_cloud.Geometry = GeometryForm.representProjectileBySphere()
radiator_cloud.Modifiers = List( radiator_cloud.Modifiers = MaxDistanceCutoff
MaxDistanceCutoff,
InfantryAggravatedRadiation,
InfantryAggravatedRadiationBurn,
ShieldAgainstRadiation
)
radiator_grenade_projectile.Name = "radiator_grenade_projectile" // Todo : Radiator damages ? radiator_grenade_projectile.Name = "radiator_grenade_projectile" // Todo : Radiator damages ?
radiator_grenade_projectile.GrenadeProjectile = true //not really, but technically yes radiator_grenade_projectile.GrenadeProjectile = true //not really, but technically yes
@ -1565,6 +1560,9 @@ object GlobalDefinitionsProjectile {
radiator_grenade_projectile.InitialVelocity = 30 radiator_grenade_projectile.InitialVelocity = 30
radiator_grenade_projectile.Lifespan = 3f radiator_grenade_projectile.Lifespan = 3f
radiator_grenade_projectile.DamageProxy = 717 //radiator_cloud radiator_grenade_projectile.DamageProxy = 717 //radiator_cloud
radiator_grenade_projectile.DamageProxyOnDirectHit = List(
TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player)
)
ProjectileDefinition.CalculateDerivedFields(radiator_grenade_projectile) ProjectileDefinition.CalculateDerivedFields(radiator_grenade_projectile)
radiator_sticky_projectile.Name = "radiator_sticky_projectile" radiator_sticky_projectile.Name = "radiator_sticky_projectile"
@ -1574,6 +1572,9 @@ object GlobalDefinitionsProjectile {
radiator_sticky_projectile.InitialVelocity = 30 radiator_sticky_projectile.InitialVelocity = 30
radiator_sticky_projectile.Lifespan = 4f radiator_sticky_projectile.Lifespan = 4f
radiator_sticky_projectile.DamageProxy = 717 //radiator_cloud radiator_sticky_projectile.DamageProxy = 717 //radiator_cloud
radiator_sticky_projectile.DamageProxyOnDirectHit = List(
TargetValidation(EffectTarget.Category.All, EffectTarget.Validation.Valid)
)
ProjectileDefinition.CalculateDerivedFields(radiator_sticky_projectile) ProjectileDefinition.CalculateDerivedFields(radiator_sticky_projectile)
reaver_rocket_projectile.Name = "reaver_rocket_projectile" reaver_rocket_projectile.Name = "reaver_rocket_projectile"
@ -2040,12 +2041,7 @@ object GlobalDefinitionsProjectile {
aphelion_plasma_cloud.ExistsOnRemoteClients = true aphelion_plasma_cloud.ExistsOnRemoteClients = true
aphelion_plasma_cloud.Packet = radCloudConverter aphelion_plasma_cloud.Packet = radCloudConverter
//aphelion_plasma_cloud.Geometry = GeometryForm.representProjectileBySphere() //aphelion_plasma_cloud.Geometry = GeometryForm.representProjectileBySphere()
aphelion_plasma_cloud.Modifiers = List( //TODO placeholder values aphelion_plasma_cloud.Modifiers = MaxDistanceCutoff
MaxDistanceCutoff,
InfantryAggravatedRadiation,
InfantryAggravatedRadiationBurn,
ShieldAgainstRadiation
)
aphelion_plasma_rocket_projectile.Name = "aphelion_plasma_rocket_projectile" aphelion_plasma_rocket_projectile.Name = "aphelion_plasma_rocket_projectile"
//has property aggravated_damage_max_factor, but it's the aphelion_plasma_cloud that performs aggravated damage //has property aggravated_damage_max_factor, but it's the aphelion_plasma_cloud that performs aggravated damage
@ -2060,6 +2056,9 @@ object GlobalDefinitionsProjectile {
aphelion_plasma_rocket_projectile.DamageRadius = 3f aphelion_plasma_rocket_projectile.DamageRadius = 3f
aphelion_plasma_rocket_projectile.ProjectileDamageType = DamageType.Splash aphelion_plasma_rocket_projectile.ProjectileDamageType = DamageType.Splash
//aphelion_plasma_rocket_projectile.DamageProxy = 96 //aphelion_plama_cloud //aphelion_plasma_rocket_projectile.DamageProxy = 96 //aphelion_plama_cloud
aphelion_plasma_rocket_projectile.DamageProxyOnDirectHit = List(
TargetValidation(EffectTarget.Category.All, EffectTarget.Validation.Valid)
)
aphelion_plasma_rocket_projectile.InitialVelocity = 75 aphelion_plasma_rocket_projectile.InitialVelocity = 75
aphelion_plasma_rocket_projectile.Lifespan = 5f aphelion_plasma_rocket_projectile.Lifespan = 5f
ProjectileDefinition.CalculateDerivedFields(aphelion_plasma_rocket_projectile) ProjectileDefinition.CalculateDerivedFields(aphelion_plasma_rocket_projectile)
@ -2225,6 +2224,9 @@ object GlobalDefinitionsProjectile {
peregrine_particle_cannon_projectile.DamageRadius = 3f peregrine_particle_cannon_projectile.DamageRadius = 3f
peregrine_particle_cannon_projectile.ProjectileDamageType = DamageType.Splash peregrine_particle_cannon_projectile.ProjectileDamageType = DamageType.Splash
//peregrine_particle_cannon_projectile.DamageProxy = 655 //peregrine_particle_cannon_radiation_cloud //peregrine_particle_cannon_projectile.DamageProxy = 655 //peregrine_particle_cannon_radiation_cloud
peregrine_particle_cannon_projectile.DamageProxyOnDirectHit = List(
TargetValidation(EffectTarget.Category.All, EffectTarget.Validation.Valid)
)
peregrine_particle_cannon_projectile.InitialVelocity = 500 peregrine_particle_cannon_projectile.InitialVelocity = 500
peregrine_particle_cannon_projectile.Lifespan = .6f peregrine_particle_cannon_projectile.Lifespan = .6f
ProjectileDefinition.CalculateDerivedFields(peregrine_particle_cannon_projectile) ProjectileDefinition.CalculateDerivedFields(peregrine_particle_cannon_projectile)
@ -2243,10 +2245,7 @@ object GlobalDefinitionsProjectile {
peregrine_particle_cannon_radiation_cloud.ExistsOnRemoteClients = true peregrine_particle_cannon_radiation_cloud.ExistsOnRemoteClients = true
peregrine_particle_cannon_radiation_cloud.Packet = radCloudConverter peregrine_particle_cannon_radiation_cloud.Packet = radCloudConverter
//peregrine_particle_cannon_radiation_cloud.Geometry = GeometryForm.representProjectileBySphere() //peregrine_particle_cannon_radiation_cloud.Geometry = GeometryForm.representProjectileBySphere()
peregrine_particle_cannon_radiation_cloud.Modifiers = List( peregrine_particle_cannon_radiation_cloud.Modifiers = MaxDistanceCutoff
MaxDistanceCutoff,
ShieldAgainstRadiation
)
peregrine_rocket_pod_projectile.Name = "peregrine_rocket_pod_projectile" peregrine_rocket_pod_projectile.Name = "peregrine_rocket_pod_projectile"
peregrine_rocket_pod_projectile.Damage0 = 30 peregrine_rocket_pod_projectile.Damage0 = 30

View file

@ -1634,6 +1634,7 @@ object GlobalDefinitionsVehicle {
droppod.Packet = new DroppodConverter() droppod.Packet = new DroppodConverter()
droppod.DeconstructionTime = Some(5 seconds) droppod.DeconstructionTime = Some(5 seconds)
droppod.DestroyedModel = None //the adb calls out a droppod; the cyclic nature of this confounds me droppod.DestroyedModel = None //the adb calls out a droppod; the cyclic nature of this confounds me
droppod.RadiationShielding = 1.0f
droppod.DamageUsing = DamageCalculations.AgainstAircraft droppod.DamageUsing = DamageCalculations.AgainstAircraft
droppod.DrownAtMaxDepth = false droppod.DrownAtMaxDepth = false
droppod.mass = 2500f droppod.mass = 2500f
@ -1667,6 +1668,7 @@ object GlobalDefinitionsVehicle {
orbital_shuttle.Packet = new OrbitalShuttleConverter orbital_shuttle.Packet = new OrbitalShuttleConverter
orbital_shuttle.DeconstructionTime = None orbital_shuttle.DeconstructionTime = None
orbital_shuttle.DestroyedModel = None orbital_shuttle.DestroyedModel = None
orbital_shuttle.RadiationShielding = 1.0f
orbital_shuttle.DamageUsing = DamageCalculations.AgainstNothing orbital_shuttle.DamageUsing = DamageCalculations.AgainstNothing
orbital_shuttle.DrownAtMaxDepth = false orbital_shuttle.DrownAtMaxDepth = false
orbital_shuttle.mass = 25000f orbital_shuttle.mass = 25000f

View file

@ -5,6 +5,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment} import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment}
import net.psforever.objects.zones._ import net.psforever.objects.zones._
import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorGroup, SectorPopulation} import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorGroup, SectorPopulation}
import net.psforever.objects.zones.interaction.{InteractsWithZone, ZoneInteraction, ZoneInteractionType}
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import scala.collection.mutable import scala.collection.mutable

View file

@ -1,7 +1,7 @@
package net.psforever.objects.serverobject.environment.interaction package net.psforever.objects.serverobject.environment.interaction
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment} import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment}
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
trait InteractionWith { trait InteractionWith {
def attribute: EnvironmentTrait def attribute: EnvironmentTrait

View file

@ -4,7 +4,7 @@ package net.psforever.objects.serverobject.environment.interaction
import akka.actor.{Actor, ActorRef, Cancellable} import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.Default import net.psforever.objects.Default
import net.psforever.objects.serverobject.environment.EnvironmentTrait import net.psforever.objects.serverobject.environment.EnvironmentTrait
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.duration.FiniteDuration import scala.concurrent.duration.FiniteDuration

View file

@ -6,7 +6,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.environment.interaction.InteractWithEnvironment import net.psforever.objects.serverobject.environment.interaction.InteractWithEnvironment
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment} import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment}
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.types.{OxygenState, PlanetSideGUID} import net.psforever.types.{OxygenState, PlanetSideGUID}
trait Watery { trait Watery {

View file

@ -7,7 +7,7 @@ import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.etc.SuicideReason import net.psforever.objects.vital.etc.SuicideReason
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.vital.{IncarnationActivity, Vitality} import net.psforever.objects.vital.{IncarnationActivity, Vitality}
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import scala.annotation.unused import scala.annotation.unused

View file

@ -3,7 +3,7 @@ package net.psforever.objects.serverobject.environment.interaction.common
import net.psforever.objects.serverobject.environment._ import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.environment.interaction.InteractionWith import net.psforever.objects.serverobject.environment.interaction.InteractionWith
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import scala.annotation.unused import scala.annotation.unused

View file

@ -3,7 +3,7 @@ package net.psforever.objects.serverobject.interior
import net.psforever.objects.avatar.interaction.WithEntrance import net.psforever.objects.avatar.interaction.WithEntrance
import net.psforever.objects.serverobject.environment.interaction.InteractWithEnvironment import net.psforever.objects.serverobject.environment.interaction.InteractWithEnvironment
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import scala.annotation.unused import scala.annotation.unused

View file

@ -8,91 +8,43 @@ 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
import net.psforever.objects.zones.blockmap.SectorPopulation import net.psforever.objects.zones.interaction.{InteractsWithZone, RadiationCloudInteraction, ZoneInteractionType}
import net.psforever.objects.zones.{InteractsWithZone, Zone, ZoneInteraction}
import net.psforever.types.PlanetSideGUID
/** /**
* This game entity may infrequently test whether it may interact with radiation cloud projectiles * This game entity may infrequently test whether it may interact with radiation cloud projectiles
* that may be emitted in the game environment for a limited amount of time. * that may be emitted in the game environment for a limited amount of time.
* Since the entity in question is a vehicle, the occupants of the vehicle get tested their interaction. * Since the entity in question is mountable, its occupants get tested for their interaction.
*/ */
class InteractWithRadiationCloudsSeatedInEntity( class InteractWithRadiationCloudsSeatedInEntity(
private val obj: Mountable with StandardResistanceProfile, private val obj: Mountable with StandardResistanceProfile,
val range: Float val range: Float
) extends ZoneInteraction { ) extends RadiationCloudInteraction {
/** def Type: ZoneInteractionType = RadiationInMountableInteraction
* radiation clouds that, though detected, are skipped from affecting the target;
* in between interaction tests, a memory of the clouds that were tested last are retained and
* are excluded from being tested this next time;
* clouds that are detected a second time are cleared from the list and are available to be tested next time
*/
private var skipTargets: List[PlanetSideGUID] = List()
def Type: RadiationInMountableInteraction.type = RadiationInMountableInteraction def performInteractionWithTarget(projectiles: List[Projectile], target: InteractsWithZone): Unit = {
val mountedTargets = obj.Seats
/** .values
* Drive into a radiation cloud and all the vehicle's occupants suffer the consequences. .collect { case seat => seat.occupant }
* @param sector the portion of the block map being tested .flatten
* @param target the fixed element in this test if (projectiles.nonEmpty && mountedTargets.nonEmpty) {
*/ val position = target.Position
override def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = { val shielding = RadiationCloudInteraction.RadiationShieldingFrom(target)
val position = target.Position mountedTargets
val targetList = List(target) .flatMap(t => projectiles.map(p => (t, p)))
//collect all projectiles in sector/range .foreach { case (t, p) =>
val projectiles = sector t.Actor ! Vitality.Damage(
.projectileList DamageInteraction(
.filter { cloud => SourceEntry(t),
val definition = cloud.Definition RadiationReason(
val radius = definition.DamageRadius ProjectileQuality.modifiers(p, DamageResolution.Radiation, t, t.Position, None),
definition.radiation_cloud && t.DamageModel,
Zone.allOnSameSide(cloud, definition, targetList).nonEmpty && shielding
Zone.distanceCheck(target, cloud, radius * radius) ),
} position
.distinct ).calculate()
val notSkipped = projectiles.filterNot { t => skipTargets.contains(t.GUID) } )
skipTargets = notSkipped.map { _.GUID }
if (notSkipped.nonEmpty) {
(
//isolate one of each type of projectile
notSkipped
.foldLeft(Nil: List[Projectile]) {
(acc, next) => if (acc.exists { _.profile == next.profile }) acc else next :: acc
},
obj.Seats
.values
.collect { case seat => seat.occupant }
.flatten
) match {
case (uniqueProjectiles, targets) if uniqueProjectiles.nonEmpty && targets.nonEmpty =>
val shielding = obj.RadiationShielding
targets.foreach { t =>
uniqueProjectiles.foreach { p =>
t.Actor ! Vitality.Damage(
DamageInteraction(
SourceEntry(t),
RadiationReason(
ProjectileQuality.modifiers(p, DamageResolution.Radiation, t, t.Position, None),
t.DamageModel,
shielding
),
position
).calculate()
)
}
}
case _ => ()
} }
} }
} }
/**
* Any radiation clouds blocked from being tested should be cleared.
* All that can be done is blanking our retained previous effect targets.
* @param target the fixed element in this test
*/
def resetInteraction(target: InteractsWithZone): Unit = {
skipTargets = List()
}
} }

View file

@ -1,5 +1,5 @@
package net.psforever.objects.serverobject.mount package net.psforever.objects.serverobject.mount
import net.psforever.objects.zones.ZoneInteractionType import net.psforever.objects.zones.interaction.ZoneInteractionType
case object RadiationInMountableInteraction extends ZoneInteractionType case object RadiationInMountableInteraction extends ZoneInteractionType

View file

@ -2,9 +2,11 @@
package net.psforever.objects.serverobject.terminals.implant package net.psforever.objects.serverobject.terminals.implant
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.mount.{Mountable, Seat} import net.psforever.objects.serverobject.mount.{InteractWithRadiationCloudsSeatedInEntity, Mountable, Seat}
import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware
import net.psforever.objects.vital.resistance.StandardResistanceProfile
import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.packet.game.TriggeredSound import net.psforever.packet.game.TriggeredSound
import net.psforever.types.Vector3 import net.psforever.types.Vector3
@ -17,7 +19,10 @@ class ImplantTerminalMech(private val idef: ImplantTerminalMechDefinition)
extends Amenity extends Amenity
with Mountable with Mountable
with Hackable with Hackable
with StandardResistanceProfile
with InteractsWithZone
with CaptureTerminalAware { with CaptureTerminalAware {
interaction(new InteractWithRadiationCloudsSeatedInEntity(obj = this, range = 50f))
seats = Map(0 -> new Seat(idef.Seats.head._2)) seats = Map(0 -> new Seat(idef.Seats.head._2))
HackSound = TriggeredSound.HackTerminal HackSound = TriggeredSound.HackTerminal

View file

@ -3,17 +3,23 @@ package net.psforever.objects.serverobject.turret
import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.interior.Sidedness import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.objects.serverobject.mount.InteractWithRadiationCloudsSeatedInEntity
import net.psforever.objects.serverobject.structures.{Amenity, AmenityOwner, Building} import net.psforever.objects.serverobject.structures.{Amenity, AmenityOwner, Building}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware
import net.psforever.objects.serverobject.turret.auto.AutomatedTurret import net.psforever.objects.serverobject.turret.auto.AutomatedTurret
import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.resistance.StandardResistanceProfile
import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.types.Vector3 import net.psforever.types.Vector3
class FacilityTurret(tDef: FacilityTurretDefinition) class FacilityTurret(tDef: FacilityTurretDefinition)
extends Amenity extends Amenity
with AutomatedTurret with AutomatedTurret
with StandardResistanceProfile
with JammableUnit with JammableUnit
with InteractsWithZone
with CaptureTerminalAware { with CaptureTerminalAware {
interaction(new InteractWithRadiationCloudsSeatedInEntity(obj = this, range = 100f))
WeaponTurret.LoadDefinition(turret = this) WeaponTurret.LoadDefinition(turret = this)
WhichSide = Sidedness.OutsideOf WhichSide = Sidedness.OutsideOf

View file

@ -13,7 +13,8 @@ import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, SourceUniquene
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.interaction.DamageResult import net.psforever.objects.vital.interaction.DamageResult
import net.psforever.objects.zones.exp.ToDatabase import net.psforever.objects.zones.exp.ToDatabase
import net.psforever.objects.zones.{InteractsWithZone, Zone} import net.psforever.objects.zones.Zone
import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.objects.{Default, PlanetSideGameObject, Player} import net.psforever.objects.{Default, PlanetSideGameObject, Player}
import net.psforever.packet.game.{ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ObjectDetectedMessage} import net.psforever.packet.game.{ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ObjectDetectedMessage}
import net.psforever.services.Service import net.psforever.services.Service

View file

@ -4,24 +4,24 @@ package net.psforever.objects.vehicles
import net.psforever.objects.Vehicle import net.psforever.objects.Vehicle
import net.psforever.objects.serverobject.mount.{InteractWithRadiationCloudsSeatedInEntity, RadiationInMountableInteraction} import net.psforever.objects.serverobject.mount.{InteractWithRadiationCloudsSeatedInEntity, RadiationInMountableInteraction}
import net.psforever.objects.zones.blockmap.SectorPopulation import net.psforever.objects.zones.blockmap.SectorPopulation
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
/** /**
* This game entity may infrequently test whether it may interact with radiation cloud projectiles * This game entity may infrequently test whether it may interact with radiation cloud projectiles
* that may be emitted in the game environment for a limited amount of time. * that may be emitted in the game environment for a limited amount of time.
* Since the entity in question is a vehicle, the occupants of the vehicle get tested their interaction. * Since the entity in question is a vehicle, the occupants of the vehicle's mounted vehicles get tested for their interaction.
*/ */
class InteractWithRadiationCloudsSeatedInVehicle( class InteractWithRadiationCloudsSeatedInVehicle(
private val obj: Vehicle, private val obj: Vehicle,
override val range: Float override val range: Float
) extends InteractWithRadiationCloudsSeatedInEntity(obj, range) { ) extends InteractWithRadiationCloudsSeatedInEntity(obj, range) {
/** /**
* Drive into a radiation cloud and all the vehicle's occupants suffer the consequences. * Drive into a radiation cloud and all the vehicle's occupants suffer the consequences.
* @param sector the portion of the block map being tested * @param sector the portion of the block map being tested
* @param target the fixed element in this test * @param target the fixed element in this test
*/ */
override def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = { override def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
super.interaction(sector, target) super.interaction(sector, target) //vehicle is a mountable entity and must call down
obj.CargoHolds obj.CargoHolds
.values .values
.collect { .collect {
@ -30,7 +30,7 @@ class InteractWithRadiationCloudsSeatedInVehicle(
target target
.interaction() .interaction()
.find(_.Type == RadiationInMountableInteraction) .find(_.Type == RadiationInMountableInteraction)
.foreach(func => func.interaction(sector, target)) .foreach(func => func.interaction(sector, obj))
} }
} }
} }

View file

@ -288,11 +288,11 @@ class VehicleControl(vehicle: Vehicle)
final def Enabled: Receive = final def Enabled: Receive =
commonEnabledBehavior commonEnabledBehavior
.orElse { .orElse {
case VehicleControl.RadiationTick => case VehicleControl.RadiationTick if !passengerRadiationCloudTimer.isCancelled =>
vehicle.interaction().find { _.Type == RadiationInMountableInteraction } match { vehicle
case Some(func) => func.interaction(vehicle.getInteractionSector, vehicle) .interaction()
case _ => () .find(_.Type == RadiationInMountableInteraction)
} .foreach(_.interaction(vehicle.getInteractionSector, vehicle))
case _ => () case _ => ()
} }
@ -355,13 +355,12 @@ class VehicleControl(vehicle: Vehicle)
} }
def mountCleanup(mount_point: Int, user: Player): Unit = { def mountCleanup(mount_point: Int, user: Player): Unit = {
val obj = MountableObject vehicle.PassengerInSeat(user) match {
obj.PassengerInSeat(user) match { case Some(0) => //driver seat
case Some(seatNumber) =>
val vsrc = VehicleSource(vehicle) val vsrc = VehicleSource(vehicle)
user.LogActivity(VehicleMountActivity(vsrc, PlayerSource.inSeat(user, vsrc, seatNumber), vehicle.Zone.Number)) user.LogActivity(VehicleMountActivity(vsrc, PlayerSource.inSeat(user, vsrc, seatNumber = 0), vehicle.Zone.Number))
//if the driver mount, change ownership if that is permissible for this vehicle //if the driver mount, change ownership if that is permissible for this vehicle
if (seatNumber == 0 && !obj.OwnerName.contains(user.Name) && obj.Definition.CanBeOwned.nonEmpty) { if (!vehicle.OwnerName.contains(user.Name) && vehicle.Definition.CanBeOwned.nonEmpty) {
//whatever vehicle was previously owned //whatever vehicle was previously owned
vehicle.Zone.GUID(user.avatar.vehicle) match { vehicle.Zone.GUID(user.avatar.vehicle) match {
case Some(v: Vehicle) => case Some(v: Vehicle) =>
@ -370,13 +369,24 @@ class VehicleControl(vehicle: Vehicle)
user.avatar.vehicle = None user.avatar.vehicle = None
} }
GainOwnership(user) //gain new ownership GainOwnership(user) //gain new ownership
passengerRadiationCloudTimer.cancel()
} else { } else {
decaying = false decaying = false
decayTimer.cancel() decayTimer.cancel()
} }
passengerRadiationCloudTimer.cancel()
updateZoneInteractionProgressUI(user) updateZoneInteractionProgressUI(user)
case None => ;
case Some(seatNumber) => //literally any other seat
val vsrc = VehicleSource(vehicle)
user.LogActivity(VehicleMountActivity(vsrc, PlayerSource.inSeat(user, vsrc, seatNumber), vehicle.Zone.Number))
decaying = false
decayTimer.cancel()
if (!vehicle.Seats(0).isOccupied && passengerRadiationCloudTimer.isCancelled) {
StartRadiationSelfReporting()
}
updateZoneInteractionProgressUI(user)
case None => ()
} }
} }
@ -390,19 +400,17 @@ class VehicleControl(vehicle: Vehicle)
def dismountCleanup(seatBeingDismounted: Int, user: Player): Unit = { def dismountCleanup(seatBeingDismounted: Int, user: Player): Unit = {
val obj = MountableObject val obj = MountableObject
val allSeatsUnoccupied = !obj.Seats.values.exists(_.isOccupied)
// Reset velocity to zero when driver dismounts, to allow jacking/repair if vehicle was moving slightly before dismount // Reset velocity to zero when driver dismounts, to allow jacking/repair if vehicle was moving slightly before dismount
if (!obj.Seats(0).isOccupied) { if (!obj.Seats(0).isOccupied) {
obj.Velocity = Some(Vector3.Zero) obj.Velocity = Some(Vector3.Zero)
} }
if (seatBeingDismounted == 0) { if (allSeatsUnoccupied) {
passengerRadiationCloudTimer = context.system.scheduler.scheduleWithFixedDelay( passengerRadiationCloudTimer.cancel()
250.milliseconds, } else if (seatBeingDismounted == 0) {
250.milliseconds, StartRadiationSelfReporting()
self,
VehicleControl.RadiationTick
)
} }
if (!obj.Seats(seatBeingDismounted).isOccupied) { //seat was vacated if (!obj.Seats(seatBeingDismounted).isOccupied) { //this seat really was vacated
user.LogActivity(VehicleDismountActivity(VehicleSource(vehicle), PlayerSource(user), vehicle.Zone.Number)) user.LogActivity(VehicleDismountActivity(VehicleSource(vehicle), PlayerSource(user), vehicle.Zone.Number))
//we were only owning the vehicle while we sat in its driver seat //we were only owning the vehicle while we sat in its driver seat
val canBeOwned = obj.Definition.CanBeOwned val canBeOwned = obj.Definition.CanBeOwned
@ -411,9 +419,9 @@ class VehicleControl(vehicle: Vehicle)
} }
//are we already decaying? are we unowned? is no one seated anywhere? //are we already decaying? are we unowned? is no one seated anywhere?
if (!decaying && if (!decaying &&
obj.Definition.undergoesDecay && obj.Definition.undergoesDecay &&
obj.OwnerGuid.isEmpty && obj.OwnerGuid.isEmpty &&
obj.Seats.values.forall(!_.isOccupied)) { allSeatsUnoccupied) {
decaying = true decaying = true
decayTimer = context.system.scheduler.scheduleOnce( decayTimer = context.system.scheduler.scheduleOnce(
MountableObject.Definition.DeconstructionTime.getOrElse(5 minutes), MountableObject.Definition.DeconstructionTime.getOrElse(5 minutes),
@ -424,6 +432,16 @@ class VehicleControl(vehicle: Vehicle)
} }
} }
private def StartRadiationSelfReporting(): Unit = {
passengerRadiationCloudTimer.cancel()
passengerRadiationCloudTimer = context.system.scheduler.scheduleWithFixedDelay(
250.milliseconds,
250.milliseconds,
self,
VehicleControl.RadiationTick
)
}
def PrepareForDisabled(kickPassengers: Boolean) : Unit = { def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
val guid = vehicle.GUID val guid = vehicle.GUID
val zone = vehicle.Zone val zone = vehicle.Zone
@ -492,7 +510,7 @@ class VehicleControl(vehicle: Vehicle)
case Some(_) => case Some(_) =>
decaying = false decaying = false
decayTimer.cancel() decayTimer.cancel()
case None => ; case None => ()
} }
} }
@ -507,9 +525,9 @@ class VehicleControl(vehicle: Vehicle)
self.toString, self.toString,
AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectAttachMessage(obj.GUID, item.GUID, slot)) AvatarAction.SendResponse(Service.defaultPlayerGUID, ObjectAttachMessage(obj.GUID, item.GUID, slot))
) )
case None => ; case None => ()
} }
case _ => ; case _ => ()
} }
} }

View file

@ -5,7 +5,7 @@ import net.psforever.objects.Vehicle
import net.psforever.objects.avatar.interaction.WithEntrance import net.psforever.objects.avatar.interaction.WithEntrance
import net.psforever.objects.serverobject.doors.InteriorDoorPassage import net.psforever.objects.serverobject.doors.InteriorDoorPassage
import net.psforever.objects.serverobject.environment.PieceOfEnvironment import net.psforever.objects.serverobject.environment.PieceOfEnvironment
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
class WithEntranceInVehicle class WithEntranceInVehicle
extends WithEntrance() { extends WithEntrance() {

View file

@ -7,7 +7,7 @@ import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.environment.EnvironmentReason import net.psforever.objects.vital.environment.EnvironmentReason
import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import scala.concurrent.duration._ import scala.concurrent.duration._

View file

@ -8,7 +8,7 @@ import net.psforever.objects.serverobject.environment.interaction.common.Watery
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment, interaction} import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment, interaction}
import net.psforever.objects.vehicles.control.VehicleControl import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.types.OxygenState import net.psforever.types.OxygenState
import scala.annotation.unused import scala.annotation.unused

View file

@ -8,11 +8,12 @@ import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.vital.prop.DamageProperties import net.psforever.objects.vital.prop.DamageProperties
import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel} import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel}
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions} import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.interaction.InteractsWithZone
/** /**
* A wrapper for a "damage source" in damage calculations * A wrapper for a "damage source" in damage calculations
* that parameterizes information necessary to explain the environment being antagonistic. * that parameterizes information necessary to explain the environment being antagonistic.
*
* @see `DamageCalculations` * @see `DamageCalculations`
* @param body a representative of an element of the environment * @param body a representative of an element of the environment
* @param against for the purposes of damage, what kind of target is being acted upon * @param against for the purposes of damage, what kind of target is being acted upon

View file

@ -333,28 +333,6 @@ case object FlailDistanceDamageBoost extends ProjectileDamageModifiers.Mod {
} }
} }
/**
* If the damge is caused by a projectile that emits a field that permeates armor,
* determine by how much the traversed armor's shielding reduces the damage.
* Infantry take damage, reduced only if one is equipped with a mechanized assault exo-suit.
*/
case object ShieldAgainstRadiation extends ProjectileDamageModifiers.Mod {
def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = {
if (data.resolution == DamageResolution.Radiation) {
data.target match {
case p: PlayerSource if p.ExoSuit == ExoSuitType.MAX =>
damage - (damage * p.Modifiers.RadiationShielding).toInt
case _: PlayerSource =>
damage
case _ =>
0
}
} else {
damage
}
}
}
/** The Cerberus turret can not target any entities besides flying vehicles. /** The Cerberus turret can not target any entities besides flying vehicles.
* An exception to this rule, however, happens when retaliating against something that damaged it first. */ * An exception to this rule, however, happens when retaliating against something that damaged it first. */
case object CerberusTurretWrongTarget extends ProjectileDamageModifiers.Mod { case object CerberusTurretWrongTarget extends ProjectileDamageModifiers.Mod {

View file

@ -2,7 +2,7 @@
package net.psforever.objects.vital.prop package net.psforever.objects.vital.prop
import net.psforever.objects.ballistics.{AggravatedDamage, ChargeDamage} import net.psforever.objects.ballistics.{AggravatedDamage, ChargeDamage}
import net.psforever.objects.equipment.JammingUnit import net.psforever.objects.equipment.{JammingUnit, TargetValidation}
import net.psforever.objects.vital.base.{DamageModifiers, DamageType} import net.psforever.objects.vital.base.{DamageModifiers, DamageType}
import net.psforever.objects.vital.damage.StandardDamageProfile import net.psforever.objects.vital.damage.StandardDamageProfile
@ -37,6 +37,9 @@ trait DamageProperties
* usually corresponding to a projectile; * usually corresponding to a projectile;
* also used to produce staged projectiles */ * also used to produce staged projectiles */
private var damageProxy: List[Int] = Nil private var damageProxy: List[Int] = Nil
/** damage proxies are expected to activate upon general detonation
* this damage proxy will also activate upon direct hit of these specific types of targets */
private var damageProxyOnDirectHit: List[TargetValidation] = Nil
/** na; /** na;
* currently used with jammer properties only; * currently used with jammer properties only;
* used sepcifically to indicate jammering effect targets explosive deployables */ * used sepcifically to indicate jammering effect targets explosive deployables */
@ -118,6 +121,18 @@ trait DamageProperties
DamageProxy DamageProxy
} }
def DamageProxyOnDirectHit: List[TargetValidation] = damageProxyOnDirectHit
def DamageProxyOnDirectHit_=(elem: TargetValidation): List[TargetValidation] = {
damageProxyOnDirectHit = List(elem)
DamageProxyOnDirectHit
}
def DamageProxyOnDirectHit_=(list: List[TargetValidation]): List[TargetValidation] = {
damageProxyOnDirectHit = list
DamageProxyOnDirectHit
}
def AdditionalEffect: Boolean = additionalEffect def AdditionalEffect: Boolean = additionalEffect
def AdditionalEffect_=(effect: Boolean): Boolean = { def AdditionalEffect_=(effect: Boolean): Boolean = {

View file

@ -10,9 +10,9 @@ import net.psforever.objects.vital.damage.DamageProfile
* based on the assumption that the implementing object's `Definition` is the primary `ResistanceProfile`. * based on the assumption that the implementing object's `Definition` is the primary `ResistanceProfile`.
*/ */
trait StandardResistanceProfile extends ResistanceProfile { trait StandardResistanceProfile extends ResistanceProfile {
this: PlanetSideGameObject => _: PlanetSideGameObject =>
//actually check that this will work for this implementing class //actually check that this will work for this implementing class
assert(Definition.isInstanceOf[ResistanceProfile], s"$this object definition must extend ResistanceProfile") assert(Definition.isInstanceOf[ResistanceProfile], s"${this.getClass.getSimpleName} object definition must extend ResistanceProfile")
private val resistDef = Definition.asInstanceOf[ResistanceProfile] //cast only once private val resistDef = Definition.asInstanceOf[ResistanceProfile] //cast only once
def Subtract: DamageProfile = resistDef.Subtract def Subtract: DamageProfile = resistDef.Subtract
@ -23,5 +23,5 @@ trait StandardResistanceProfile extends ResistanceProfile {
def ResistanceAggravated: Int = resistDef.ResistanceDirectHit def ResistanceAggravated: Int = resistDef.ResistanceDirectHit
def RadiationShielding: Float = resistDef.ResistanceDirectHit.toFloat def RadiationShielding: Float = resistDef.RadiationShielding
} }

View file

@ -1,5 +1,5 @@
// Copyright (c) 2021 PSForever // Copyright (c) 2021 PSForever
package net.psforever.objects.zones package net.psforever.objects.zones.interaction
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
@ -72,39 +72,3 @@ trait InteractsWithZone
override def Definition: ObjectDefinition with VitalityDefinition override def Definition: ObjectDefinition with VitalityDefinition
} }
trait ZoneInteractionType
/**
* The basic behavior of an entity in a zone.
* @see `InteractsWithZone`
* @see `Zone`
*/
trait ZoneInteraction {
/**
* A categorical descriptor for this interaction.
*/
def Type: ZoneInteractionType
/**
* The anticipated (radial?) distance across which this interaction affects the zone's blockmap.
*/
def range: Float
/**
* The method by which zone interactions are tested.
* How a target tests this interaction with elements of the target's zone.
* @param sector the portion of the block map being tested
* @param target the fixed element in this test
*/
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit
/**
* Suspend any current interaction procedures.
* How the interactions are undone and stability restored to elements engaged with this target,
* even if only possible by small measure.
* Not all interactions can be reversed.
* @param target the fixed element in this test
*/
def resetInteraction(target: InteractsWithZone): Unit
}

View file

@ -0,0 +1,67 @@
// Copyright (c) 2021-2025 PSForever
package net.psforever.objects.zones.interaction
import net.psforever.objects.ballistics.Projectile
import net.psforever.objects.definition.ProjectileDefinition
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.zones.Zone
import net.psforever.objects.zones.blockmap.SectorPopulation
/**
* This game entity may infrequently test whether it may interact with radiation cloud projectiles
* that may be emitted in the game environment for a limited amount of time.
*/
trait RadiationCloudInteraction extends ZoneInteraction {
/**
* radiation clouds that, though detected, are skipped from affecting the target;
* in between interaction tests, a memory of the clouds that were tested last are retained and
* are excluded from being tested this next time;
* clouds that are detected a second time are cleared from the list and are available to be tested next time
*/
private var damageTypesToSkip: List[ProjectileDefinition] = List()
/**
* Wander into a radiation cloud and suffer the consequences.
* @param sector the portion of the block map being tested
* @param target the fixed element in this test
*/
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
performInteractionWithTarget(uniqueProjectileDamageToTargetInSector(sector, target), target)
}
/**
* Any radiation clouds blocked from being tested should be cleared.
* All that can be done is blanking our retained previous effect targets.
* @param target the fixed element in this test
*/
def resetInteraction(target: InteractsWithZone): Unit = {
damageTypesToSkip = List()
}
private def uniqueProjectileDamageToTargetInSector(sector: SectorPopulation, target: InteractsWithZone): List[Projectile] = {
lazy val targetList = List(target)
val projectiles = sector
.projectileList
.filter { cloud =>
val definition = cloud.Definition
val radius = definition.DamageRadius
definition.radiation_cloud &&
Zone.allOnSameSide(cloud, definition, targetList).nonEmpty &&
Zone.distanceCheck(target, cloud, radius * radius)
}
val projectilesToUse = projectiles.filterNot(p => damageTypesToSkip.contains(p.profile)).distinctBy(_.profile)
damageTypesToSkip = projectilesToUse.map(_.profile)
projectilesToUse
}
def performInteractionWithTarget(projectiles: List[Projectile], target: InteractsWithZone): Unit
}
object RadiationCloudInteraction {
def RadiationShieldingFrom(target: InteractsWithZone): Float = {
target match {
case profile: ResistanceProfile => profile.RadiationShielding
case _ => 0f
}
}
}

View file

@ -0,0 +1,40 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.zones.interaction
import net.psforever.objects.zones.blockmap.SectorPopulation
trait ZoneInteractionType
/**
* The basic behavior of an entity in a zone.
* @see `InteractsWithZone`
* @see `Zone`
*/
trait ZoneInteraction {
/**
* A categorical descriptor for this interaction.
*/
def Type: ZoneInteractionType
/**
* The anticipated (radial?) distance across which this interaction affects the zone's blockmap.
*/
def range: Float
/**
* The method by which zone interactions are tested.
* How a target tests this interaction with elements of the target's zone.
* @param sector the portion of the block map being tested
* @param target the fixed element in this test
*/
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit
/**
* Suspend any current interaction procedures.
* How the interactions are undone and stability restored to elements engaged with this target,
* even if only possible by small measure.
* Not all interactions can be reversed.
* @param target the fixed element in this test
*/
def resetInteraction(target: InteractsWithZone): Unit
}

View file

@ -10,7 +10,8 @@ import net.psforever.objects.serverobject.environment.interaction.InteractWithEn
import net.psforever.objects.serverobject.llu.CaptureFlag import net.psforever.objects.serverobject.llu.CaptureFlag
import net.psforever.objects.serverobject.structures.{Building, WarpGate} import net.psforever.objects.serverobject.structures.{Building, WarpGate}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
import net.psforever.objects.zones.{InteractsWithZone, Zone} import net.psforever.objects.zones.Zone
import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.services.{Service, ServiceManager} import net.psforever.services.{Service, ServiceManager}
import net.psforever.services.ServiceManager.{Lookup, LookupResult} import net.psforever.services.ServiceManager.{Lookup, LookupResult}

View file

@ -10,7 +10,8 @@ import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior}
import net.psforever.objects.serverobject.environment._ import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.environment.interaction.RespondsToZoneEnvironment import net.psforever.objects.serverobject.environment.interaction.RespondsToZoneEnvironment
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.zones.{InteractsWithZone, Zone, ZoneMap} import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire, PlanetSideGUID, Vector3}
import scala.concurrent.duration._ import scala.concurrent.duration._