moved aggravation damage into damage implementations of turrets and vehicles, rather than directly into ther immediate control agencies; revamp projectile quality modifiers; comet now does initial damage and one less tick of aggravation; the implementation progression of damageable entities is different now

This commit is contained in:
FateJH 2020-08-18 13:32:43 -04:00
parent 89d7aea633
commit fc89355acf
22 changed files with 570 additions and 315 deletions

View file

@ -0,0 +1,36 @@
//Copyright (c) 2020 PSForever
package net.psforever.objects.ballistics
/**
* Projectile quality is an external aspect of projectiles
* that is not dependent on hard-coded definitions of the entities
* used to compose the projectile such as the knowlegde of the emitting `Tool` (weapon).
* A flag or a damage modifier, depending on use.
* To the extent that it can be used as a numeric modifier,
* insists on defining a numeric modifier component rather to what it is trying to express.
* That numeric modifier does not have to be used for anything.
*/
sealed trait ProjectileQuality {
def mod: Float
}
/**
* Implement the numeric modifier with as one.
*/
sealed trait SameAsQuality extends ProjectileQuality {
def mod: Float = 1f
}
object ProjectileQuality {
/** Standard projectile quality. More of a flag than a modifier. */
case object Normal extends SameAsQuality
/** Quality that flags the first stage of aggravation (setup). */
case object AggravatesTarget extends SameAsQuality
/** The complete lack of quality. Even the numeric modifier is zeroed. */
case object Zeroed extends ProjectileQuality { def mod = 0f }
/** Assign a custom numeric qualifier value, usually to be applied to damage calculations. */
case class Modified(mod: Float) extends ProjectileQuality
}

View file

@ -38,8 +38,18 @@ trait AuraEffectBehavior {
id
}
def StartAuraEffect(effect: Aura, duration: Long): Long = {
StartAuraEffect(GetUnusedEffectId, effect, duration)
def StartAuraEffect(effect: Aura, duration: Long): Option[Long] = {
val obj = AuraTargetObject
val auraEffects = obj.Aura
if (obj.Aura.contains(effect)) {
effectToEntryId.getOrElse(effect, List[Long](AuraEffectBehavior.InvalidEffectId)).headOption //grab an available active effect id
}
else if(obj.AddEffectToAura(effect).diff(auraEffects).contains(effect)) {
Some(StartAuraEffect(GetUnusedEffectId, effect, duration))
}
else {
None
}
}
def StartAuraEffect(id: Long, effect: Aura, duration: Long): Long = {
@ -130,6 +140,8 @@ trait AuraEffectBehavior {
object AuraEffectBehavior {
type Target = PlanetSideServerObject with AuraContainer
final val InvalidEffectId = -1
final case class StartEffect(effect: Aura, duration: Long)
final case class EndEffect(id: Option[Long], aura: Option[Aura])

View file

@ -1,10 +1,9 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.aggravated
package net.psforever.objects.serverobject.damage
import akka.actor.{Actor, Cancellable}
import net.psforever.objects.ballistics._
import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior}
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.aura.Aura
import net.psforever.objects.vital.{DamageType, Vitality}
import scala.collection.mutable
@ -17,31 +16,33 @@ trait AggravatedBehavior {
mutable.LongMap.empty[AggravatedBehavior.Entry]
private val aggravationToTimer: mutable.LongMap[Cancellable] =
mutable.LongMap.empty[Cancellable]
/** ongoing flag to indicate whether the target is being afflicted by any form of aggravated damage */
private var ongoingAggravated: Boolean = false
def AggravatedObject: AggravatedBehavior.Target
def TryAggravationEffect(data: ResolvedProjectile): Option[AggravatedDamage] = {
data.projectile.profile.Aggravated match {
def TryAggravationEffectActivate(data: ResolvedProjectile): Option[AggravatedDamage] = {
val projectile = data.projectile
projectile.profile.Aggravated match {
case Some(damage)
if data.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) &&
if projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) &&
damage.effect_type != Aura.Nothing &&
damage.targets.exists(validation => validation.test(AggravatedObject)) =>
TryAggravationEffect(damage, data)
(projectile.quality == ProjectileQuality.AggravatesTarget ||
damage.targets.exists(validation => validation.test(AggravatedObject))) =>
TryAggravationEffectActivate(damage, data)
case _ =>
None
}
}
private def TryAggravationEffect(aggravation: AggravatedDamage, data: ResolvedProjectile): Option[AggravatedDamage] = {
private def TryAggravationEffectActivate(
aggravation: AggravatedDamage,
data: ResolvedProjectile
): Option[AggravatedDamage] = {
val effect = aggravation.effect_type
val obj = AggravatedObject
if(CheckForUniqueUnqueuedProjectile(data.projectile)) {
val auraEffects = obj.Aura
if(auraEffects.contains(effect) && aggravation.cumulative_damage_degrade) {
SetupAggravationEntry(aggravation, data)
Some(aggravation)
}
else if(obj.AddEffectToAura(effect).diff(auraEffects).contains(effect)) {
val sameEffect = entryIdToEntry.values.filter(entry => entry.effect == effect)
if(sameEffect.isEmpty || sameEffect.nonEmpty && aggravation.cumulative_damage_degrade) {
SetupAggravationEntry(aggravation, data)
Some(aggravation)
}
@ -91,6 +92,7 @@ trait AggravatedBehavior {
PairIdWithAggravationEntry(id, effect, tick, data, data.target, qualityPerTick)
//pair id with timer
aggravationToTimer += id -> context.system.scheduler.scheduleOnce(tick milliseconds, self, AggravatedBehavior.Aggravate(id, iterations))
ongoingAggravated = true
true
case _ =>
false
@ -156,6 +158,7 @@ trait AggravatedBehavior {
def RemoveAggravatedEntry(id: Long): Aura = {
entryIdToEntry.remove(id) match {
case Some(entry) =>
ongoingAggravated = entryIdToEntry.nonEmpty
entry.data.projectile.profile.Aggravated.get.effect_type
case _ =>
Aura.Nothing
@ -181,22 +184,24 @@ trait AggravatedBehavior {
aggravationToTimer.clear
}
def AggravatedReaction: Boolean = ongoingAggravated
private def PerformAggravation(entry: AggravatedBehavior.Entry, tick: Int = 0): Unit = {
val data = entry.data
val model = data.damage_model
val aggravatedProjectileData = ResolvedProjectile(
data.resolution,
data.projectile.quality(entry.qualityPerTick(tick)),
data.projectile.quality(ProjectileQuality.Modified(entry.qualityPerTick(tick))),
data.target,
model,
data.hit_pos
)
TakesDamage.apply(Vitality.Damage(model.Calculate(aggravatedProjectileData)))
takesDamage.apply(Vitality.Damage(model.Calculate(aggravatedProjectileData)))
}
}
object AggravatedBehavior {
type Target = AuraEffectBehavior.Target with Vitality
type Target = Damageable.Target
private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Float])

View file

@ -7750,7 +7750,20 @@ class SessionActor extends Actor with MDCContextAware {
None
} else {
projectile.Resolve()
Some(ResolvedProjectile(resolution, projectile, SourceEntry(target), target.DamageModel, pos))
val outProjectile = if(projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)) {
val quality = projectile.profile.Aggravated match {
case Some(aggravation)
if aggravation.targets.exists(validation => validation.test(target)) =>
ProjectileQuality.AggravatesTarget
case _ =>
ProjectileQuality.Normal
}
projectile.quality(quality)
}
else {
projectile
}
Some(ResolvedProjectile(resolution, outProjectile, SourceEntry(target), target.DamageModel, pos))
}
}

View file

@ -9,7 +9,9 @@ import net.psforever.objects.definition.converter.SmallDeployableConverter
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.vital.{StandardResolutions, Vitality}
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
import net.psforever.objects.vital.StandardResolutions
import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideGUID, Vector3}
import net.psforever.services.Service
@ -63,18 +65,20 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D
case _ => ;
}
protected def TakesDamage: Receive = {
case Vitality.Damage(applyDamageTo) =>
if (mine.CanDamage) {
val originalHealth = mine.Health
val cause = applyDamageTo(mine)
val damage = originalHealth - mine.Health
if (Damageable.CanDamageOrJammer(mine, damage, cause)) {
ExplosiveDeployableControl.DamageResolution(mine, cause, damage)
} else {
mine.Health = originalHealth
}
override protected def PerformDamage(
target: Target,
applyDamageTo: Output
): Unit = {
if (mine.CanDamage) {
val originalHealth = mine.Health
val cause = applyDamageTo(mine)
val damage = originalHealth - mine.Health
if (Damageable.CanDamageOrJammer(mine, damage, cause)) {
ExplosiveDeployableControl.DamageResolution(mine, cause, damage)
} else {
mine.Health = originalHealth
}
}
}
}

View file

@ -2452,13 +2452,14 @@ object GlobalDefinitions {
comet_projectile.DamageRadius = 1.0f
comet_projectile.ProjectileDamageType = DamageType.Aggravated
comet_projectile.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Direct, 0.2f, 500),
AggravatedInfo(DamageType.Direct, 0.25f, 500), //originally, .2
Aura.Comet,
AggravatedTiming(2000, 4),
AggravatedTiming(2000, 3),
10f,
List(
TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player),
TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.Vehicle)
TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.Vehicle),
TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret)
)
)
comet_projectile.InitialVelocity = 80

View file

@ -44,7 +44,6 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
def JammableObject = gen
def DamageableObject = gen
def RepairableObject = gen
private var handleDamageToShields: Boolean = false
def receive: Receive =
jammableBehavior
@ -90,18 +89,20 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
target,
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"
)
handleDamageToShields = damageToShields > 0
HandleDamage(target, cause, damageToHealth)
HandleDamage(target, cause, (damageToHealth, damageToShields))
} else {
gen.Health = originalHealth
gen.Shields = originalShields
}
}
override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = {
super.DamageAwareness(target, cause, amount)
ShieldGeneratorControl.DamageAwareness(gen, cause, handleDamageToShields)
handleDamageToShields = false
override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = {
val (damageToHealth, damageToShields) = amount match {
case (a: Int, b: Int) => (a, b)
case _ => (0, 0)
}
super.DamageAwareness(target, cause, damageToHealth)
ShieldGeneratorControl.DamageAwareness(gen, cause, damageToShields > 0)
}
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {

View file

@ -75,6 +75,11 @@ class TurretControl(turret: TurretDeployable)
def DamageableObject = turret
def RepairableObject = turret
override def postStop(): Unit = {
super.postStop()
damageableWeaponTurretPostStop()
}
def receive: Receive =
checkBehavior
.orElse(jammableBehavior)

View file

@ -8,16 +8,17 @@ import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.{GridInventory, InventoryItem}
import net.psforever.objects.loadouts.Loadout
import net.psforever.objects.serverobject.aggravated.AggravatedBehavior
import net.psforever.objects.serverobject.aura.AuraEffectBehavior
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
import net.psforever.objects.vital.{PlayerSuicide, Vitality}
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.PlayerSuicide
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, Damageable}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.repair.Repairable
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vital._
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
@ -491,29 +492,31 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case _ => ;
}
protected def TakesDamage: Receive = {
case Vitality.Damage(applyDamageTo) =>
if (player.isAlive && !player.spectator) {
val originalHealth = player.Health
val originalArmor = player.Armor
val originalStamina = player.avatar.stamina
val originalCapacitor = player.Capacitor.toInt
val cause = applyDamageTo(player)
val health = player.Health
val armor = player.Armor
val stamina = player.avatar.stamina
val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor
val damageToStamina = originalStamina - stamina
val damageToCapacitor = originalCapacitor - capacitor
HandleDamage(player, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
if (damageToHealth > 0 || damageToArmor > 0 || damageToStamina > 0 || damageToCapacitor > 0) {
damageLog.info(
s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalStamina/$originalCapacitor, AFTER=$health/$armor/$stamina/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToStamina/$damageToCapacitor"
)
}
override protected def PerformDamage(
target: Target,
applyDamageTo: Output
): Unit = {
if (player.isAlive && !player.spectator) {
val originalHealth = player.Health
val originalArmor = player.Armor
val originalStamina = player.avatar.stamina
val originalCapacitor = player.Capacitor.toInt
val cause = applyDamageTo(player)
val health = player.Health
val armor = player.Armor
val stamina = player.avatar.stamina
val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor
val damageToStamina = originalStamina - stamina
val damageToCapacitor = originalCapacitor - capacitor
HandleDamage(player, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
if (damageToHealth > 0 || damageToArmor > 0 || damageToStamina > 0 || damageToCapacitor > 0) {
damageLog.info(
s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalStamina/$originalCapacitor, AFTER=$health/$armor/$stamina/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToStamina/$damageToCapacitor"
)
}
}
}
/**
@ -521,76 +524,110 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
* @param target na
*/
def HandleDamage(
target: Player,
cause: ResolvedProjectile,
damageToHealth: Int,
damageToArmor: Int,
damageToStamina: Int,
damageToCapacitor: Int
): Unit = {
val targetGUID = target.GUID
val zone = target.Zone
val zoneId = zone.id
val events = zone.AvatarEvents
val health = target.Health
target: Player,
cause: ResolvedProjectile,
damageToHealth: Int,
damageToArmor: Int,
damageToStamina: Int,
damageToCapacitor: Int
): Unit = {
//always do armor update
if (damageToArmor > 0) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
val zone = target.Zone
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
AvatarAction.PlanetsideAttributeToAll(target.GUID, 4, target.Armor)
)
}
if (health > 0) {
if (damageToCapacitor > 0) {
events ! AvatarServiceMessage(
target.Name,
AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong)
)
}
if (damageToHealth > 0 || damageToStamina > 0) {
target.History(cause)
if (damageToHealth > 0) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
}
if (damageToStamina > 0) {
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
}
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert damage source
DamageAwareness(target, cause)
}
//special effects
if (Damageable.CanJammer(target, cause)) {
TryJammerEffectActivate(target, cause)
}
TryAggravationEffect(cause) match {
case Some(aggravation) =>
StartAuraEffect(aggravation.effect_type, aggravation.timing.duration)
case _ => ;
}
//choose
if (target.Health > 0) {
DamageAwareness(target, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
} else {
DestructionAwareness(target, Some(cause))
}
}
/**
* na
* @param target na
* @param cause na
*/
def DamageAwareness(target: Player, cause: ResolvedProjectile): Unit = {
val zone = target.Zone
zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
cause.projectile.owner match {
case pSource: PlayerSource => //player damage
val name = pSource.Name
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
case Some(tplayer) => AvatarAction.HitHint(tplayer.GUID, target.GUID)
case None =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
def DamageAwareness(
target: Player,
cause: ResolvedProjectile,
damageToHealth: Int,
damageToArmor: Int,
damageToStamina: Int,
damageToCapacitor: Int
): Unit = {
val targetGUID = target.GUID
val zone = target.Zone
val zoneId = zone.id
val events = zone.AvatarEvents
val health = target.Health
var announceConfrontation = damageToArmor > 0
//special effects
if (Damageable.CanJammer(target, cause)) {
TryJammerEffectActivate(target, cause)
}
val aggravated: Boolean = TryAggravationEffectActivate(cause) match {
case Some(aggravation) =>
StartAuraEffect(aggravation.effect_type, aggravation.timing.duration)
announceConfrontation = true //useful if initial damage (to anything) is zero
//initial damage for aggravation, but never treat as "aggravated"
false
case _ =>
cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)
}
//log historical event
target.History(cause)
//stat changes
if (damageToCapacitor > 0) {
events ! AvatarServiceMessage(
target.Name,
AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong)
)
announceConfrontation = true
}
if (damageToStamina > 0) {
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
announceConfrontation = true
}
if (damageToHealth > 0) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
announceConfrontation = true
}
val countableDamage = damageToHealth + damageToArmor
if(announceConfrontation) {
if (!aggravated) {
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert to damage source
zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
cause.projectile.owner match {
case pSource: PlayerSource => //player damage
val name = pSource.Name
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
case Some(tplayer) =>
AvatarAction.HitHint(tplayer.GUID, target.GUID)
case None =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, pSource.Position))
}
case source =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, source.Position))
}
case source =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
)
}
)
else {
//general alert
zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, Vector3.Zero))
)
}
}
if (aggravated) {
events ! AvatarServiceMessage(
zoneId,
AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage))
)
}
}
/**

View file

@ -41,7 +41,7 @@ final case class Projectile(
attribute_to: Int,
shot_origin: Vector3,
shot_angle: Vector3,
quality: Float = 1f,
quality: ProjectileQuality = ProjectileQuality.Normal,
id: Long = Projectile.idGenerator.getAndIncrement(),
fire_time: Long = System.nanoTime
) extends PlanetSideGameObject {
@ -67,8 +67,8 @@ final case class Projectile(
* @param value the new quality
* @return a new `Projectile` entity
*/
def quality(value: Float): Projectile =
Projectile(
def quality(value: ProjectileQuality): Projectile = {
val projectile = Projectile(
profile,
tool_def,
fire_mode,
@ -80,6 +80,10 @@ final case class Projectile(
id,
fire_time
)
if(isMiss) projectile.Miss()
else if(isResolved) projectile.Resolve()
projectile
}
/**
* Mark the projectile as being "encountered" or "managed" at least once.

View file

@ -33,7 +33,7 @@ object Deployable {
def Includes(category: DeployableCategory.Value): List[DeployedItem.Value] = {
(for {
(ce, cat) <- deployablesToCategories
(ce: DeployedItem.Value, cat: DeployableCategory.Value) <- deployablesToCategories
if cat == category
} yield ce) toList
}

View file

@ -8,6 +8,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.ResolutionCalculations
/**
* The base "control" `Actor` mixin for damage-handling code.
@ -24,16 +25,35 @@ trait Damageable {
*/
def DamageableObject: Damageable.Target
/** the official mixin hook; `orElse` onto the "control" `Actor` `receive` */
final val takesDamage: Receive = TakesDamage
/** the official mixin hook;
* `orElse` onto the "control" `Actor` `receive`; or,
* cite the `originalTakesDamage` protocol during inheritance overrides */
val takesDamage: Receive = {
case Vitality.Damage(damage_func) =>
val obj = DamageableObject
if (obj.CanDamage) {
PerformDamage(obj, damage_func)
}
}
/** a duplicate of the core implementation for the default mixin hook, for use in overriding */
final val originalTakesDamage: Receive = {
case Vitality.Damage(damage_func) =>
val obj = DamageableObject
if (obj.CanDamage) {
PerformDamage(obj, damage_func)
}
}
/**
* Implementation of the mixin hook will be provided by a child class.
* Override this method only when directly implementing.
* @see `takesDamage`
* @see `DamageableAmenity.PerformDamage`
* Assess the vital statistics of the target, apply the damage, and determine if any of those statistics changed.
* By default, only take an interest in the change of "health".
* If implementing custom damage with no new message handling, override this method.
* @see `ResolutionCalculations.Output`
* @param target the entity to be damaged
* @param applyDamageTo the function that applies the damage to the target in a target-tailored fashion
*/
protected def TakesDamage: Receive
protected def PerformDamage(target: Damageable.Target, applyDamageTo: ResolutionCalculations.Output): Unit
}
object Damageable {

View file

@ -1,10 +1,8 @@
//Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.damage
import akka.actor.Actor.Receive
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.objects.zones.Zone
import net.psforever.types.PlanetSideGUID
@ -42,23 +40,6 @@ trait DamageableEntity extends Damageable {
DamageLog(s"${name.substring(slashPoint + 1, name.length - 1)}: $msg")
}
/**
* Catch the expected damage message and apply checks to the target.
* If adding custom message handling in an future child implementation,
* override this method and call `super.TakesDamage.orElse { ... }`.
* @see `Damageable.TakesDamage`
* @see `ResolutionCalcultions.Output`
* @see `Vitality.CanDamage`
* @see `Vitality.Damage`
*/
protected def TakesDamage: Receive = {
case Vitality.Damage(damage_func) =>
val obj = DamageableObject
if (obj.CanDamage) {
PerformDamage(obj, damage_func)
}
}
/**
* Assess the vital statistics of the target, apply the damage, and determine if any of those statistics changed.
* By default, only take an interest in the change of "health".
@ -108,7 +89,7 @@ trait DamageableEntity extends Damageable {
* @param cause historical information about the damage
* @param damage the amount of damage
*/
protected def HandleDamage(target: Damageable.Target, cause: ResolvedProjectile, damage: Int): Unit = {
protected def HandleDamage(target: Damageable.Target, cause: ResolvedProjectile, damage: Any): Unit = {
if (!target.Destroyed && target.Health <= target.Definition.DamageDestroysAt) {
DestructionAwareness(target, cause)
} else {
@ -122,8 +103,12 @@ trait DamageableEntity extends Damageable {
* @param cause historical information about the damage
* @param amount the amount of damage
*/
protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = {
DamageableEntity.DamageAwareness(target, cause, amount)
protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = {
amount match {
case value: Int =>
DamageableEntity.DamageAwareness(target, cause, value)
case _ => ;
}
}
/**
@ -162,16 +147,22 @@ object DamageableEntity {
if (Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause)
}
if (amount > 0) {
if (DamageToHealth(target, cause, amount)) {
target.Zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
}
}
def DamageToHealth(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Boolean = {
if (amount > 0 && !target.Destroyed) {
val zone = target.Zone
if (!target.Destroyed) {
val tguid = target.GUID
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health)
)
}
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
AvatarAction.PlanetsideAttributeToAll(target.GUID, 0, target.Health)
)
true
}
else {
false
}
}

View file

@ -26,8 +26,13 @@ object DamageableMountable {
* @see `Zone.LivePlayers`
* @param target the entity being damaged
* @param cause historical information about the damage
* @param countableDamage the amount of damage being done, translating to the intensity of the damage indicator
*/
def DamageAwareness(target: Damageable.Target with Mountable, cause: ResolvedProjectile): Unit = {
def DamageAwareness(
target: Damageable.Target with Mountable,
cause: ResolvedProjectile,
countableDamage: Int
): Unit = {
val zone = target.Zone
val events = zone.AvatarEvents
val occupants = target.Seats.values.collect {
@ -38,9 +43,10 @@ object DamageableMountable {
case pSource: PlayerSource => //player damage
val name = pSource.Name
(zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
case Some(player) => AvatarAction.HitHint(player.GUID, player.GUID)
case Some(player) =>
AvatarAction.HitHint(player.GUID, player.GUID)
case None =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, pSource.Position))
}) match {
case AvatarAction.HitHint(_, guid) =>
occupants.map { tplayer => (tplayer.Name, AvatarAction.HitHint(guid, tplayer.GUID)) }
@ -48,7 +54,7 @@ object DamageableMountable {
occupants.map { tplayer => (tplayer.Name, msg) }
}
case source => //object damage
val msg = AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
val msg = AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, source.Position))
occupants.map { tplayer => (tplayer.Name, msg) }
}).foreach {
case (channel, msg) =>

View file

@ -1,43 +1,55 @@
//Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.damage
import akka.actor.Actor.Receive
import akka.actor.Actor
import net.psforever.objects.{Vehicle, Vehicles}
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.DamageType
import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.DamageWithPositionMessage
import net.psforever.types.Vector3
import scala.concurrent.duration._
/**
* The "control" `Actor` mixin for damage-handling code for `Vehicle` objects.
*/
trait DamageableVehicle extends DamageableEntity {
trait DamageableVehicle
extends DamageableEntity
with AggravatedBehavior {
_ : Actor =>
/** vehicles (may) have shields; they need to be handled */
private var handleDamageToShields: Boolean = false
def damageableVehiclePostStop(): Unit = {
EndAllAggravation()
}
/** whether or not the vehicle has been damaged directly, report that damage has occurred */
private var reportDamageToVehicle: Boolean = false
def DamageableObject: Vehicle
def AggravatedObject : Vehicle = DamageableObject
override protected def TakesDamage: Receive =
super.TakesDamage.orElse {
case DamageableVehicle.Damage(cause, damage) =>
//cargo vehicles inherit feedback from carrier
reportDamageToVehicle = damage > 0
DamageAwareness(DamageableObject, cause, amount = 0)
override val takesDamage: Receive =
originalTakesDamage
.orElse(aggravatedBehavior)
.orElse {
case DamageableVehicle.Damage(cause, damage) =>
//cargo vehicles inherit feedback from carrier
reportDamageToVehicle = damage > 0
DamageAwareness(DamageableObject, cause, amount = 0)
case DamageableVehicle.Destruction(cause) =>
//cargo vehicles are destroyed when carrier is destroyed
val obj = DamageableObject
obj.Health = 0
obj.History(cause)
DestructionAwareness(obj, cause)
}
case DamageableVehicle.Destruction(cause) =>
//cargo vehicles are destroyed when carrier is destroyed
val obj = DamageableObject
obj.Health = 0
obj.History(cause)
DestructionAwareness(obj, cause)
}
/**
* Vehicles may have charged shields that absorb damage before the vehicle's own health is affected.
@ -62,53 +74,13 @@ trait DamageableVehicle extends DamageableEntity {
target,
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"
)
handleDamageToShields = damageToShields > 0
HandleDamage(target, cause, damageToHealth + damageToShields)
HandleDamage(target, cause, (damageToHealth, damageToShields))
} else {
obj.Health = originalHealth
obj.Shields = originalShields
}
}
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = {
val obj = DamageableObject
val handleShields = handleDamageToShields
handleDamageToShields = false
val handleReport = reportDamageToVehicle || amount > 0
reportDamageToVehicle = false
if (Damageable.CanDamageOrJammer(target, amount, cause)) {
super.DamageAwareness(target, cause, amount)
}
if (handleReport) {
DamageableMountable.DamageAwareness(obj, cause)
}
DamageableVehicle.DamageAwareness(obj, cause, amount, handleShields)
}
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {
super.DestructionAwareness(target, cause)
val obj = DamageableObject
DamageableMountable.DestructionAwareness(obj, cause)
DamageableVehicle.DestructionAwareness(obj, cause)
DamageableWeaponTurret.DestructionAwareness(obj, cause)
}
}
object DamageableVehicle {
/**
* Message for instructing the target's cargo vehicles about a damage source affecting their carrier.
* @param cause historical information about damage
*/
private case class Damage(cause: ResolvedProjectile, amount: Int)
/**
* Message for instructing the target's cargo vehicles that their carrier is destroyed,
* and they should be destroyed too.
* @param cause historical information about damage
*/
private case class Destruction(cause: ResolvedProjectile)
/**
* Most all vehicles and the weapons mounted to them can jam
* if the projectile that strikes (near) them has jammering properties.
@ -121,25 +93,76 @@ object DamageableVehicle {
* @see `VehicleServiceMessage`
* @param target the entity being destroyed
* @param cause historical information about the damage
* @param damage how much damage was performed
* @param damageToShields dispatch a shield strength update
* @param amount how much damage was performed
*/
def DamageAwareness(target: Vehicle, cause: ResolvedProjectile, damage: Int, damageToShields: Boolean): Unit = {
//alert cargo occupants to damage source
target.CargoHolds.values.foreach(hold => {
hold.Occupant match {
case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Damage(cause, damage + (if (damageToShields) 1 else 0))
case None => ;
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = {
val obj = DamageableObject
val zone = target.Zone
val events = zone.VehicleEvents
val targetGUID = target.GUID
val zoneId = zone.id
val vehicleChannel = s"${obj.Actor}"
val (damageToHealth, damageToShields, totalDamage) = amount match {
case (a: Int, b: Int) => (a, b, a+b)
case _ => (0, 0, 0)
}
var announceConfrontation: Boolean = reportDamageToVehicle || totalDamage > 0
val aggravated = TryAggravationEffectActivate(cause) match {
case Some(_) =>
announceConfrontation = true
false
case _ =>
cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)
}
reportDamageToVehicle = false
//log historical event
target.History(cause)
//damage
if (Damageable.CanDamageOrJammer(target, totalDamage, cause)) {
//jammering
if (Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause)
}
})
//shields
if (damageToShields) {
val zone = target.Zone
zone.VehicleEvents ! VehicleServiceMessage(
s"${target.Actor}",
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields)
)
//stat changes
if (damageToShields > 0) {
events ! VehicleServiceMessage(
vehicleChannel,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, obj.Shields)
)
announceConfrontation = true
}
if (damageToHealth > 0) {
events ! VehicleServiceMessage(
zoneId,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, obj.Health)
)
announceConfrontation = true
}
}
if (announceConfrontation) {
if (aggravated) {
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(totalDamage, Vector3.Zero))
obj.Seats.values
.collect { case seat if seat.Occupant.nonEmpty => seat.Occupant.get.Name }
.foreach { channel =>
events ! VehicleServiceMessage(channel, msg)
}
}
else {
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert to damage source
DamageableMountable.DamageAwareness(obj, cause, totalDamage)
}
//alert cargo occupants to damage source
obj.CargoHolds.values.foreach(hold => {
hold.Occupant match {
case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Damage(cause, totalDamage)
case None => ;
}
})
}
}
@ -164,10 +187,15 @@ object DamageableVehicle {
* @param target the entity being destroyed
* @param cause historical information about the damage
*/
def DestructionAwareness(target: Vehicle, cause: ResolvedProjectile): Unit = {
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {
super.DestructionAwareness(target, cause)
val obj = DamageableObject
DamageableMountable.DestructionAwareness(obj, cause)
val zone = target.Zone
//aggravation cancel
EndAllAggravation()
//cargo vehicles die with us
target.CargoHolds.values.foreach(hold => {
obj.CargoHolds.values.foreach(hold => {
hold.Occupant match {
case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Destruction(cause)
@ -175,10 +203,10 @@ object DamageableVehicle {
}
})
//special considerations for certain vehicles
Vehicles.BeforeUnloadVehicle(target, zone)
Vehicles.BeforeUnloadVehicle(obj, zone)
//shields
if (target.Shields > 0) {
target.Shields = 0
if (obj.Shields > 0) {
obj.Shields = 0
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0)
@ -186,5 +214,22 @@ object DamageableVehicle {
}
target.Actor ! Vehicle.Deconstruct(Some(1 minute))
target.ClearHistory()
DamageableWeaponTurret.DestructionAwareness(obj, cause)
}
}
object DamageableVehicle {
/**
* Message for instructing the target's cargo vehicles about a damage source affecting their carrier.
* @param cause historical information about damage
*/
private case class Damage(cause: ResolvedProjectile, amount: Int)
/**
* Message for instructing the target's cargo vehicles that their carrier is destroyed,
* and they should be destroyed too.
* @param cause historical information about damage
*/
private case class Destruction(cause: ResolvedProjectile)
}

View file

@ -1,30 +1,97 @@
//Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.damage
import akka.actor.Actor
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.turret.{TurretUpgrade, WeaponTurret}
import net.psforever.objects.vehicles.MountedWeapons
import net.psforever.objects.vital.DamageType
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.DamageWithPositionMessage
import net.psforever.types.Vector3
import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.support.TurretUpgrader
import net.psforever.services.vehicle.VehicleServiceMessage
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
/**
* The "control" `Actor` mixin for damage-handling code for `WeaponTurret` objects.
*/
trait DamageableWeaponTurret extends DamageableEntity {
def DamageableObject: Damageable.Target with WeaponTurret
trait DamageableWeaponTurret
extends DamageableEntity
with AggravatedBehavior {
_: Actor =>
override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = {
super.DamageAwareness(target, cause, amount)
if (amount > 0) {
DamageableMountable.DamageAwareness(DamageableObject, cause)
def damageableWeaponTurretPostStop(): Unit = {
EndAllAggravation()
}
def DamageableObject: Damageable.Target with WeaponTurret
def AggravatedObject: Damageable.Target with WeaponTurret = DamageableObject
override val takesDamage: Receive = originalTakesDamage.orElse(aggravatedBehavior)
override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = {
val obj = DamageableObject
val zone = target.Zone
val events = zone.VehicleEvents
val targetGUID = target.GUID
val zoneId = zone.id
val damageToHealth = amount match {
case a: Int => a
case _ => 0
}
var announceConfrontation: Boolean = damageToHealth > 0
val aggravated = TryAggravationEffectActivate(cause) match {
case Some(_) =>
announceConfrontation = true
false
case _ =>
cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)
}
//log historical event
target.History(cause)
//damage
if (Damageable.CanDamageOrJammer(target, damageToHealth, cause)) {
//jammering
if (Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause)
}
//stat changes
//TODO some turrets have shields
if (damageToHealth > 0) {
DamageableMountable.DamageAwareness(DamageableObject, cause, damageToHealth)
events ! VehicleServiceMessage(
zoneId,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, obj.Health)
)
announceConfrontation = true
}
}
if (announceConfrontation) {
if (aggravated) {
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(damageToHealth, Vector3.Zero))
obj.Seats.values
.collect { case seat if seat.Occupant.nonEmpty => seat.Occupant.get.Name }
.foreach { channel =>
events ! VehicleServiceMessage(channel, msg)
}
}
else {
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert to damage source
DamageableMountable.DamageAwareness(obj, cause, damageToHealth)
}
}
}
override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = {
super.DestructionAwareness(target, cause)
val obj = DamageableObject
EndAllAggravation()
DamageableWeaponTurret.DestructionAwareness(obj, cause)
DamageableMountable.DestructionAwareness(obj, cause)
}

View file

@ -88,9 +88,13 @@ class GeneratorControl(gen: Generator)
!imminentExplosion && super.WillAffectTarget(target, damage, cause)
}
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = {
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = {
super.DamageAwareness(target, cause, amount)
GeneratorControl.DamageAwareness(gen, cause, amount)
val damageTo = amount match {
case a: Int => a
case _ => 0
}
GeneratorControl.DamageAwareness(gen, cause, damageTo)
}
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {

View file

@ -72,9 +72,13 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
}
}
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = {
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = {
super.DamageAwareness(target, cause, amount)
DamageableMountable.DamageAwareness(DamageableObject, cause)
val damageTo = amount match {
case a: Int => a
case _ => 0
}
DamageableMountable.DamageAwareness(DamageableObject, cause, damageTo)
}
override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = {

View file

@ -44,6 +44,11 @@ class FacilityTurretControl(turret: FacilityTurret)
// Used for timing ammo recharge for vanu turrets in caves
var weaponAmmoRechargeTimer = Default.Cancellable
override def postStop(): Unit = {
super.postStop()
damageableWeaponTurretPostStop()
}
def receive: Receive =
checkBehavior
.orElse(jammableBehavior)

View file

@ -10,11 +10,8 @@ import net.psforever.objects.inventory.{GridInventory, InventoryItem}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.aggravated.AggravatedBehavior
import net.psforever.objects.serverobject.aura.AuraEffectBehavior
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.serverobject.damage.DamageableVehicle
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle}
import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject
import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
import net.psforever.objects.serverobject.hackable.GenericHackables
@ -55,8 +52,7 @@ class VehicleControl(vehicle: Vehicle)
with JammableMountedWeapons
with ContainableBehavior
with AntTransferBehavior
with AggravatedBehavior
with AuraEffectBehavior {
with AggravatedBehavior {
//make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach({ case (_, util) => util.Setup })
@ -79,10 +75,6 @@ class VehicleControl(vehicle: Vehicle)
def ChargeTransferObject = vehicle
def AuraTargetObject = vehicle
def AggravatedObject = vehicle
if(vehicle.Definition == GlobalDefinitions.ant) {
findChargeTargetFunc = Vehicles.FindANTChargingSource
findDischargeTargetFunc = Vehicles.FindANTDischargingTarget
@ -98,14 +90,13 @@ class VehicleControl(vehicle: Vehicle)
override def postStop(): Unit = {
super.postStop()
damageableVehiclePostStop()
decaying = false
decayTimer.cancel()
vehicle.Utilities.values.foreach { util =>
context.stop(util().Actor)
util().Actor = Default.Actor
}
EndAllEffects()
EndAllAggravation()
}
def Enabled: Receive =
@ -114,8 +105,6 @@ class VehicleControl(vehicle: Vehicle)
.orElse(cargoBehavior)
.orElse(jammableBehavior)
.orElse(takesDamage)
.orElse(aggravatedBehavior)
.orElse(auraBehavior)
.orElse(canBeRepairedByNanoDispenser)
.orElse(containerBehavior)
.orElse(antBehavior)
@ -592,30 +581,6 @@ class VehicleControl(vehicle: Vehicle)
}
out
}
override def DamageAwareness(
target: Target,
cause: ResolvedProjectile,
amount: Int
): Unit = {
TryAggravationEffect(cause) match {
case Some(aggravation) =>
StartAuraEffect(aggravation.effect_type, aggravation.timing.duration)
case _ => ;
}
super.DamageAwareness(target, cause, amount)
}
override def DestructionAwareness(
target: Target,
cause: ResolvedProjectile
): Unit = {
//aura effects cancel
EndAllEffects()
//aggravation cancel
EndAllAggravation()
super.DestructionAwareness(target, cause)
}
}
object VehicleControl {

View file

@ -2,7 +2,7 @@
package net.psforever.objects.vital.damage
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.ballistics.{PlayerSource, ProjectileResolution, ResolvedProjectile, VehicleSource}
import net.psforever.objects.ballistics._
import net.psforever.objects.vital.DamageType
import net.psforever.types.{ExoSuitType, Vector3}
@ -163,7 +163,8 @@ object DamageModifiers {
damage: Int,
data: ResolvedProjectile
): Int = {
if (data.resolution == resolution) {
if (data.resolution == resolution &&
data.projectile.quality == ProjectileQuality.AggravatesTarget) {
(data.projectile.profile.Aggravated, data.target) match {
case (Some(aggravation), p: PlayerSource) =>
val aggravatedDamage = aggravation.info.find(_.damage_type == damageType) match {
@ -230,7 +231,8 @@ object DamageModifiers {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (data.resolution == ProjectileResolution.AggravatedDirect) {
if (data.resolution == ProjectileResolution.AggravatedDirect &&
data.projectile.quality == ProjectileQuality.AggravatesTarget) {
(data.projectile.profile.Aggravated, data.target) match {
case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
@ -257,7 +259,7 @@ object DamageModifiers {
case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) =>
(math.floor(damage * infos.degradation_percentage) * data.projectile.quality) toInt
(math.floor(damage * infos.degradation_percentage) * data.projectile.quality.mod) toInt
case _ =>
damage
}
@ -274,8 +276,19 @@ object DamageModifiers {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (data.resolution == ProjectileResolution.AggravatedDirect) {
0
if (data.resolution == ProjectileResolution.AggravatedDirect &&
data.projectile.quality == ProjectileQuality.AggravatesTarget) {
data.projectile.profile.Aggravated match {
case Some(aggravation) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) =>
damage - (damage * infos.degradation_percentage) toInt
case _ =>
damage
}
case _ =>
damage
}
} else {
damage
}
@ -288,8 +301,13 @@ object DamageModifiers {
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (data.resolution == ProjectileResolution.AggravatedDirectBurn) {
data.projectile.profile.Aggravated match {
case Some(_) =>
(damage * data.projectile.quality) toInt
case Some(aggravation) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) =>
damage - (damage * infos.degradation_percentage) toInt
case _ =>
damage
}
case _ =>
0
}

View file

@ -5,10 +5,13 @@ import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacke
import net.psforever.types.Vector3
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
/**
* Dispatched by the server to indicate a source of damage affecting the player.
* Unlike `HitHint` the damage source is defined by an actual coordinate location rather than a physical target.<br>
* Unlike `HitHint` the damage source is defined by an actual coordinate location rather than a physical target.
* Setting the position to the world origin, however,
* can cause the damage tick mark to point towards the previous damaging entity in some situations.<br>
* <br>
* The player will be shown a fading, outwards drifting, red tick mark.
* The location will indicate a general direction towards the source.
@ -27,5 +30,14 @@ object DamageWithPositionMessage extends Marshallable[DamageWithPositionMessage]
implicit val codec: Codec[DamageWithPositionMessage] = (
("unk" | uint8L) ::
("pos" | Vector3.codec_pos)
).as[DamageWithPositionMessage]
).xmap[DamageWithPositionMessage] (
{
case unk :: pos :: HNil =>
DamageWithPositionMessage(math.min(0, math.max(unk, 255)), pos)
},
{
case DamageWithPositionMessage(unk, pos) =>
unk :: pos :: HNil
}
)
}