From fc89355acf247c6c225d3e1cc48696a2faefe96d Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 18 Aug 2020 13:32:43 -0400 Subject: [PATCH] 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 --- .../ballistics/ProjectileQuality.scala | 36 +++ .../aura/AuraEffectBehavior.scala | 16 +- .../AggravatedBehavior.scala | 43 ++-- .../actors/session/SessionActor.scala | 15 +- .../objects/ExplosiveDeployable.scala | 28 ++- .../psforever/objects/GlobalDefinitions.scala | 7 +- .../objects/ShieldGeneratorDeployable.scala | 15 +- .../psforever/objects/TurretDeployable.scala | 5 + .../objects/avatar/PlayerControl.scala | 209 +++++++++++------- .../objects/ballistics/Projectile.scala | 10 +- .../net/psforever/objects/ce/Deployable.scala | 2 +- .../serverobject/damage/Damageable.scala | 34 ++- .../damage/DamageableEntity.scala | 53 ++--- .../damage/DamageableMountable.scala | 14 +- .../damage/DamageableVehicle.scala | 205 ++++++++++------- .../damage/DamageableWeaponTurret.scala | 81 ++++++- .../generator/GeneratorControl.scala | 8 +- .../ImplantTerminalMechControl.scala | 8 +- .../turret/FacilityTurretControl.scala | 5 + .../objects/vehicles/VehicleControl.scala | 41 +--- .../vital/damage/DamageModifiers.scala | 34 ++- .../game/DamageWithPositionMessage.scala | 16 +- 22 files changed, 570 insertions(+), 315 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala rename common/src/main/scala/net/psforever/objects/serverobject/{aggravated => damage}/AggravatedBehavior.scala (82%) diff --git a/common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala b/common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala new file mode 100644 index 000000000..8fe9d38cd --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/ballistics/ProjectileQuality.scala @@ -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 +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala index 5c528dc86..6dc233a51 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala @@ -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]) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala similarity index 82% rename from common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala rename to common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala index 0e1feff3f..e2a477e96 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala @@ -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]) diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index 23e5d2832..5d4dae490 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -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)) } } diff --git a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala index 66c261abd..252d3e0c4 100644 --- a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala +++ b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala @@ -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 } + } } } diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index af02396e2..8d20e52d1 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -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 diff --git a/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala b/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala index feb1f1293..75a375ec8 100644 --- a/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala +++ b/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala @@ -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 = { diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala index 2c3b5e8dd..fd9f85697 100644 --- a/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -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) diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index ef7b8225c..e9a3b9568 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -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)) + ) + } } /** diff --git a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index d40d9de8f..a00b79e42 100644 --- a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -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. diff --git a/src/main/scala/net/psforever/objects/ce/Deployable.scala b/src/main/scala/net/psforever/objects/ce/Deployable.scala index 6448daf85..56736d35b 100644 --- a/src/main/scala/net/psforever/objects/ce/Deployable.scala +++ b/src/main/scala/net/psforever/objects/ce/Deployable.scala @@ -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 } diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala index 8fcde16c6..e36b3c95c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala @@ -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 { diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala index e45917476..a2db8e3c9 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala @@ -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 } } diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala index cc3e5a8bb..abfb7922e 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala @@ -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) => diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala index c3d26a3dc..ceea8af0c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala @@ -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) +} diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala index b7fab97a5..f4ff4b2c5 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala @@ -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) } diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala index 799b339fc..e4258dbaa 100644 --- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala @@ -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 = { diff --git a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala index ec5b7346e..2bbb5ed46 100644 --- a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala @@ -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 = { diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala index 44a85f831..a7e3436c9 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -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) diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index 5f6dee3dc..355698d47 100644 --- a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -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 { diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala index d544e5260..e1be38de0 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -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 } diff --git a/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala b/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala index 8f2a09e18..3e6406b4f 100644 --- a/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala +++ b/src/main/scala/net/psforever/packet/game/DamageWithPositionMessage.scala @@ -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.
+ * 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.
*
* 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 + } + ) }