redid combat engineering explosive logic

This commit is contained in:
Fate-JH 2024-06-03 14:41:13 -04:00
parent fa9ba7e7da
commit 38f25f5bcc
7 changed files with 202 additions and 166 deletions

View file

@ -5,7 +5,6 @@ import akka.actor.{ActorContext, Props}
import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.objects.vital.Vitality
@ -39,7 +38,8 @@ class BoomerDeployable(cdef: ExplosiveDeployableDefinition)
}
}
class BoomerDeployableDefinition(private val objectId: Int) extends ExplosiveDeployableDefinition(objectId) {
class BoomerDeployableDefinition(private val objectId: Int)
extends ExplosiveDeployableDefinition(objectId) {
override def Initialize(obj: Deployable, context: ActorContext): Unit = {
obj.Actor =
context.actorOf(Props(classOf[BoomerDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
@ -119,6 +119,6 @@ class BoomerDeployableControl(mine: BoomerDeployable)
* `false`, otherwise
*/
override def CanDetonate(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = {
!mine.Destroyed && (data.cause.isInstanceOf[TriggerUsedReason] || Damageable.CanJammer(obj, data))
super.CanDetonate(obj, damage, data) || data.cause.isInstanceOf[TriggerUsedReason]
}
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2018 PSForever
package net.psforever.objects
import akka.actor.{Actor, ActorContext, ActorRef, Props}
import akka.actor.Actor
import net.psforever.objects.ce._
import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.definition.converter.SmallDeployableConverter
@ -11,8 +11,6 @@ import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, SourceEntry}
import net.psforever.objects.vital.etc.TrippedMineReason
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
import net.psforever.objects.vital.{SimpleResolutions, Vitality}
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
@ -23,6 +21,7 @@ import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.annotation.unused
import scala.concurrent.duration._
class ExplosiveDeployable(cdef: ExplosiveDeployableDefinition)
@ -36,7 +35,7 @@ object ExplosiveDeployable {
final case class TriggeredBy(obj: PlanetSideServerObject)
}
class ExplosiveDeployableDefinition(private val objectId: Int)
abstract class ExplosiveDeployableDefinition(private val objectId: Int)
extends DeployableDefinition(objectId) {
Name = "explosive_deployable"
DeployCategory = DeployableCategory.Mines
@ -45,6 +44,8 @@ class ExplosiveDeployableDefinition(private val objectId: Int)
private var detonateOnJamming: Boolean = true
private var stability: Boolean = false
var triggerRadius: Float = 0f
def DetonateOnJamming: Boolean = detonateOnJamming
@ -54,15 +55,11 @@ class ExplosiveDeployableDefinition(private val objectId: Int)
DetonateOnJamming
}
override def Initialize(obj: Deployable, context: ActorContext): Unit = {
obj.Actor =
context.actorOf(Props(classOf[MineDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
}
}
def Stable: Boolean = stability
object ExplosiveDeployableDefinition {
def apply(dtype: DeployedItem.Value): ExplosiveDeployableDefinition = {
new ExplosiveDeployableDefinition(dtype.id)
def Stable_=(stableState: Boolean): Boolean = {
stability = stableState
Stable
}
}
@ -98,19 +95,10 @@ abstract class ExplosiveDeployableControl(mine: ExplosiveDeployable)
}
}
def Interaction(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = {
!mine.Destroyed && (if (damage == 0 && data.cause.source.SympatheticExplosion) {
Damageable.CanDamageOrJammer(obj, damage = 1, data)
} else {
Damageable.CanDamageOrJammer(obj, damage, data)
})
}
final def HandleDamage(target: ExplosiveDeployable, cause: DamageResult, damage: Int): Unit = {
target.LogActivity(cause)
if (CanDetonate(target, damage, cause.interaction)) {
ExplosiveDeployableControl.explodes(target, cause)
ExplosiveDeployableControl.DestructionAwareness(target, cause)
ExplosiveDeployableControl.doExplosion(target, cause)
} else if (target.Health == 0) {
ExplosiveDeployableControl.DestructionAwareness(target, cause)
} else {
@ -118,6 +106,17 @@ abstract class ExplosiveDeployableControl(mine: ExplosiveDeployable)
}
}
def Interaction(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = {
val actualDamage: Int = if (!mine.Definition.Stable && data.cause.source.SympatheticExplosion) {
math.max(damage, 1)
} else {
damage
}
!mine.Destroyed &&
Damageable.adversarialOrHackableChecks(obj, data) &&
(CanDetonate(obj, actualDamage, data) || Damageable.CanDamage(obj, actualDamage, data))
}
/**
* A supplement for checking target susceptibility
* to account for sympathetic explosives even if there is no damage.
@ -130,21 +129,22 @@ abstract class ExplosiveDeployableControl(mine: ExplosiveDeployable)
* @return `true`, if the target can be affected;
* `false`, otherwise
*/
def CanDetonate(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = {
!mine.Destroyed && (if (damage == 0 && data.cause.source.SympatheticExplosion) {
Damageable.CanDamageOrJammer(mine, damage = 1, data)
} else {
Damageable.CanDamageOrJammer(mine, damage, data)
})
}
def explode(target: ExplosiveDeployable, cause: DamageResult): Unit = {
ExplosiveDeployableControl.explodes(target, cause)
ExplosiveDeployableControl.DestructionAwareness(target, cause)
def CanDetonate(obj: Vitality with FactionAffinity, @unused damage: Int, data: DamageInteraction): Boolean = {
val sourceDef = data.cause.source
val mineDef = mine.Definition
val explodeFromSympathy: Boolean = sourceDef.SympatheticExplosion && !mineDef.Stable
val explodeFromJammer: Boolean = ExplosiveDeployableControl.CanJammer(mine, data)
!mine.Destroyed && (explodeFromSympathy || explodeFromJammer)
}
}
object ExplosiveDeployableControl {
def CanJammer(mine: ExplosiveDeployable, data: DamageInteraction): Boolean = {
Damageable.adversarialOrHackableChecks(mine, data) &&
data.cause.source.AdditionalEffect &&
mine.Definition.DetonateOnJamming
}
/**
* na
* @param target na
@ -152,22 +152,23 @@ object ExplosiveDeployableControl {
* @param damage na
*/
def DamageAwareness(target: ExplosiveDeployable, cause: DamageResult, damage: Int): Unit = {
if (!target.Jammed && Damageable.CanJammer(target, cause.interaction)) {
if ( {
target.Jammed = cause.interaction.cause match {
case o: ProjectileReason =>
val radius = o.projectile.profile.DamageRadius
Vector3.DistanceSquared(cause.interaction.hitPos, cause.interaction.target.Position) < radius * radius
case _ =>
true
if (
!target.Jammed &&
CanJammer(target, cause.interaction) &&
{
target.Jammed = cause.interaction.cause match {
case o: ProjectileReason =>
val radius = o.projectile.profile.DamageRadius
Vector3.DistanceSquared(cause.interaction.hitPos, cause.interaction.target.Position) < radius * radius
case _ =>
true
}
}
) {
if (target.Definition.DetonateOnJamming) {
explodes(target, cause)
}
) {
if (target.Definition.DetonateOnJamming) {
explodes(target, cause)
}
DestructionAwareness(target, cause)
}
DestructionAwareness(target, cause)
}
}
@ -190,6 +191,11 @@ object ExplosiveDeployableControl {
)
}
def doExplosion(target: ExplosiveDeployable, cause: DamageResult): Unit = {
explodes(target, cause)
DestructionAwareness(target, cause)
}
/**
* na
* @param target na
@ -272,112 +278,3 @@ object ExplosiveDeployableControl {
) <= maxDistance
}
}
class MineDeployableControl(mine: ExplosiveDeployable)
extends ExplosiveDeployableControl(mine) {
def receive: Receive =
commonMineBehavior
.orElse {
case ExplosiveDeployable.TriggeredBy(obj) =>
setTriggered(Some(obj), delay = 200)
case MineDeployableControl.Triggered() =>
explodes(testForTriggeringTarget(
mine,
mine.Definition.innateDamage.map { _.DamageRadius }.getOrElse(mine.Definition.triggerRadius)
))
case _ => ()
}
override def finalizeDeployable(callback: ActorRef): Unit = {
super.finalizeDeployable(callback)
//initial triggering upon build
setTriggered(testForTriggeringTarget(mine, mine.Definition.triggerRadius), delay = 1000)
}
def testForTriggeringTarget(mine: ExplosiveDeployable, range: Float): Option[PlanetSideServerObject] = {
val position = mine.Position
val faction = mine.Faction
val range2 = range * range
val sector = mine.Zone.blockMap.sector(position, range)
(sector.livePlayerList ++ sector.vehicleList)
.find { thing => thing.Faction != faction && Vector3.DistanceSquared(thing.Position, position) < range2 }
}
def setTriggered(instigator: Option[PlanetSideServerObject], delay: Long): Unit = {
instigator
.collect {
case _ if isConstructed.contains(true) && setup.isCancelled =>
//re-use the setup timer here
import scala.concurrent.ExecutionContext.Implicits.global
setup = context.system.scheduler.scheduleOnce(delay milliseconds, self, MineDeployableControl.Triggered())
}
}
def explodes(instigator: Option[PlanetSideServerObject]): Unit = {
instigator
.collect {
case _ =>
//explosion
mine.Destroyed = true
ExplosiveDeployableControl.DamageAwareness(
mine,
DamageInteraction(
SourceEntry(mine),
MineDeployableControl.trippedMineReason(mine),
mine.Position
).calculate()(mine),
damage = 0
)
}
.orElse {
//reset
setup = Default.Cancellable
None
}
}
}
object MineDeployableControl {
private case class Triggered()
def trippedMineReason(mine: ExplosiveDeployable): TrippedMineReason = {
lazy val deployableSource = DeployableSource(mine)
val zone = mine.Zone
val ownerName = mine.OwnerName
val blame = zone
.Players
.find(a => ownerName.contains(a.name))
.collect { a =>
val name = a.name
assignBlameToFrom(name, zone.LivePlayers)
.orElse(assignBlameToFrom(name, zone.Corpses))
.getOrElse {
val player = PlayerSource(name, mine.Faction, mine.Position) //might report minor inconsistencies, e.g., exo-suit type
player.copy(unique = player.unique.copy(charId = a.id), progress = a.scorecard.CurrentLife)
}
}
.getOrElse(deployableSource)
TrippedMineReason(deployableSource, blame)
}
/**
* Find a player with a given name from this list of possible players.
* If the player is seated, attach a shallow copy of the mounting information.
* @param name player name
* @param blameList possible players in which to find the player name
* @return discovered player as a reference, or `None` if not found
*/
private def assignBlameToFrom(name: String, blameList: List[Player]): Option[SourceEntry] = {
blameList
.find(_.Name.equals(name))
.map { player =>
PlayerSource
.mountableAndSeat(player)
.map { case (mount, seat) => PlayerSource.inSeat(player, mount, seat) }
.getOrElse { PlayerSource(player) }
}
}
}

View file

@ -1019,9 +1019,9 @@ object GlobalDefinitions {
*/
val boomer: BoomerDeployableDefinition = BoomerDeployableDefinition(DeployedItem.boomer)
val he_mine: ExplosiveDeployableDefinition = ExplosiveDeployableDefinition(DeployedItem.he_mine)
val he_mine: MineDeployableDefinition = MineDeployableDefinition(DeployedItem.he_mine)
val jammer_mine: ExplosiveDeployableDefinition = ExplosiveDeployableDefinition(DeployedItem.jammer_mine)
val jammer_mine: MineDeployableDefinition = MineDeployableDefinition(DeployedItem.jammer_mine)
val spitfire_turret: TurretDeployableDefinition = TurretDeployableDefinition(DeployedItem.spitfire_turret)

View file

@ -0,0 +1,137 @@
// Copyright (c) 2024 PSForever
package net.psforever.objects
import akka.actor.{ActorContext, ActorRef, Props}
import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, SourceEntry}
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.etc.TrippedMineReason
import net.psforever.objects.vital.interaction.DamageInteraction
import net.psforever.types.Vector3
import scala.concurrent.duration._
class MineDeployableDefinition(private val objectId: Int)
extends ExplosiveDeployableDefinition(objectId) {
override def Initialize(obj: Deployable, context: ActorContext): Unit = {
obj.Actor =
context.actorOf(Props(classOf[MineDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
}
}
object MineDeployableDefinition {
def apply(dtype: DeployedItem.Value): MineDeployableDefinition = {
new MineDeployableDefinition(dtype.id)
}
}
class MineDeployableControl(mine: ExplosiveDeployable)
extends ExplosiveDeployableControl(mine) {
def receive: Receive =
commonMineBehavior
.orElse {
case ExplosiveDeployable.TriggeredBy(obj) =>
setTriggered(Some(obj), delay = 200)
case MineDeployableControl.Triggered() =>
explodes(testForTriggeringTarget(
mine,
mine.Definition.innateDamage.map { _.DamageRadius }.getOrElse(mine.Definition.triggerRadius)
))
case _ => ()
}
override def finalizeDeployable(callback: ActorRef): Unit = {
super.finalizeDeployable(callback)
//initial triggering upon build
setTriggered(testForTriggeringTarget(mine, mine.Definition.triggerRadius), delay = 1000)
}
def testForTriggeringTarget(mine: ExplosiveDeployable, range: Float): Option[PlanetSideServerObject] = {
val position = mine.Position
val faction = mine.Faction
val range2 = range * range
val sector = mine.Zone.blockMap.sector(position, range)
(sector.livePlayerList ++ sector.vehicleList)
.find { thing => thing.Faction != faction && Vector3.DistanceSquared(thing.Position, position) < range2 }
}
def setTriggered(instigator: Option[PlanetSideServerObject], delay: Long): Unit = {
instigator
.collect {
case _ if isConstructed.contains(true) && setup.isCancelled =>
//re-use the setup timer here
import scala.concurrent.ExecutionContext.Implicits.global
setup = context.system.scheduler.scheduleOnce(delay milliseconds, self, MineDeployableControl.Triggered())
}
}
override def CanDetonate(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = {
super.CanDetonate(obj, damage, data) || data.cause.isInstanceOf[TrippedMineReason]
}
def explodes(instigator: Option[PlanetSideServerObject]): Unit = {
//reset
setup = Default.Cancellable
instigator
.collect {
case _ =>
//explosion
HandleDamage(
mine,
DamageInteraction(
SourceEntry(mine),
MineDeployableControl.trippedMineReason(mine),
mine.Position
).calculate()(mine),
damage = 0
)
}
}
}
object MineDeployableControl {
private case class Triggered()
def trippedMineReason(mine: ExplosiveDeployable): TrippedMineReason = {
lazy val deployableSource = DeployableSource(mine)
val zone = mine.Zone
val ownerName = mine.OwnerName
val blame = zone
.Players
.find(a => ownerName.contains(a.name))
.collect { a =>
val name = a.name
assignBlameToFrom(name, zone.LivePlayers)
.orElse(assignBlameToFrom(name, zone.Corpses))
.getOrElse {
val player = PlayerSource(name, mine.Faction, mine.Position) //might report minor inconsistencies, e.g., exo-suit type
player.copy(unique = player.unique.copy(charId = a.id), progress = a.scorecard.CurrentLife)
}
}
.getOrElse(deployableSource)
TrippedMineReason(deployableSource, blame)
}
/**
* Find a player with a given name from this list of possible players.
* If the player is seated, attach a shallow copy of the mounting information.
* @param name player name
* @param blameList possible players in which to find the player name
* @return discovered player as a reference, or `None` if not found
*/
private def assignBlameToFrom(name: String, blameList: List[Player]): Option[SourceEntry] = {
blameList
.find(_.Name.equals(name))
.map { player =>
PlayerSource
.mountableAndSeat(player)
.map { case (mount, seat) => PlayerSource.inSeat(player, mount, seat) }
.getOrElse { PlayerSource(player) }
}
}
}

View file

@ -42,6 +42,7 @@ object GlobalDefinitionsDeployable {
boomer.DeployTime = Duration.create(1000, "ms")
boomer.deployAnimation = DeployAnimation.Standard
boomer.interference = InterferenceRange(main = 0.2f)
boomer.Stable = true
boomer.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.Splash
SympatheticExplosion = true
@ -58,7 +59,7 @@ object GlobalDefinitionsDeployable {
he_mine.Name = "he_mine"
he_mine.Descriptor = "Mines"
he_mine.MaxHealth = 50
he_mine.MaxHealth = 25
he_mine.Damageable = true
he_mine.DamageableByFriendlyFire = false
he_mine.Repairable = false
@ -91,12 +92,12 @@ object GlobalDefinitionsDeployable {
jammer_mine.interference = InterferenceRange(main = 7f, sharedGroupId = 1, shared = 7f, deployables = 0.1f)
jammer_mine.DetonateOnJamming = false
jammer_mine.triggerRadius = 3f
jammer_mine.Stable = true
jammer_mine.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.Splash
Damage0 = 0
DamageRadius = 10f
DamageAtEdge = 1.0f
AdditionalEffect = true
JammedEffectDuration += TargetValidation(
EffectTarget.Category.Player,
EffectTarget.Validation.Player

View file

@ -94,12 +94,12 @@ object Damageable {
* `false`, otherwise
*/
def CanJammer(obj: Vitality with FactionAffinity, data: DamageInteraction): Boolean = {
data.cause.source.HasJammedEffectDuration &&
(data.cause.source.HasJammedEffectDuration || data.cause.source.AdditionalEffect) &&
obj.isInstanceOf[JammableUnit] &&
adversarialOrHackableChecks(obj, data)
}
private def adversarialOrHackableChecks(obj: Vitality with FactionAffinity, data: DamageInteraction): Boolean = {
def adversarialOrHackableChecks(obj: Vitality with FactionAffinity, data: DamageInteraction): Boolean = {
(data.adversarial match {
case Some(adversarial) => adversarial.attacker.Faction != adversarial.defender.Faction
case None => true

View file

@ -34,7 +34,8 @@ trait DamageProperties
* also used to produce staged projectiles */
private var damageProxy: List[Int] = Nil
/** na;
* currently used with jammer properties only */
* currently used with jammer properties only;
* used sepcifically to indicate jammering effect targets explosive deployables */
private var additionalEffect: Boolean = false
/** confers aggravated damage burn to its target */
private var aggravatedDamage: Option[AggravatedDamage] = None