diff --git a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala index 81c4fedb0..cf4451151 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala @@ -1,7 +1,7 @@ // Copyright (c) 2020 PSForever package net.psforever.objects.ballistics import net.psforever.objects.equipment.TargetValidation -import net.psforever.objects.serverobject.aggravated.Aura +import net.psforever.objects.serverobject.aura.Aura import net.psforever.objects.vital.DamageType final case class AggravatedTiming(duration: Long, ticks: Option[Int]) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala new file mode 100644 index 000000000..0e1feff3f --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AggravatedBehavior.scala @@ -0,0 +1,223 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.serverobject.aggravated + +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.vital.{DamageType, Vitality} + +import scala.collection.mutable +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ + +trait AggravatedBehavior { + _ : Actor with Damageable => + private val entryIdToEntry: mutable.LongMap[AggravatedBehavior.Entry] = + mutable.LongMap.empty[AggravatedBehavior.Entry] + private val aggravationToTimer: mutable.LongMap[Cancellable] = + mutable.LongMap.empty[Cancellable] + + def AggravatedObject: AggravatedBehavior.Target + + def TryAggravationEffect(data: ResolvedProjectile): Option[AggravatedDamage] = { + data.projectile.profile.Aggravated match { + case Some(damage) + if data.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) && + damage.effect_type != Aura.Nothing && + damage.targets.exists(validation => validation.test(AggravatedObject)) => + TryAggravationEffect(damage, data) + case _ => + None + } + } + + private def TryAggravationEffect(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)) { + SetupAggravationEntry(aggravation, data) + Some(aggravation) + } + else { + None + } + } + else { + None + } + } + + private def CheckForUniqueUnqueuedProjectile(projectile : Projectile): Boolean = { + !entryIdToEntry.values.exists { entry => entry.data.projectile.id == projectile.id } + } + + private def SetupAggravationEntry(aggravation: AggravatedDamage, data: ResolvedProjectile): Boolean = { + val effect = aggravation.effect_type + aggravation.info.find(_.damage_type == AggravatedBehavior.basicDamageType(data.resolution)) match { + case Some(info) => + val timing = aggravation.timing + val duration = timing.duration + //setup effect + val id = data.projectile.id + //setup timer data + val (tick: Long, iterations: Int) = timing.ticks match { + case Some(n) if n < 1 => + val rate = info.infliction_rate + (rate, (duration / rate).toInt) + case Some(ticks) => + (duration / ticks, ticks) + case None => + (1000L, (duration / 1000).toInt) + } + //val leftoverTime = duration - (tick * iterations) + //quality per tick + val totalPower = (duration.toFloat / info.infliction_rate).toInt - 1 + val averagePowerPerTick = totalPower.toFloat / iterations + val lastTickRemainder = totalPower - averagePowerPerTick * iterations + val qualityPerTick: List[Float] = if (lastTickRemainder > 0) { + 0f +: List.fill[Float](iterations - 1)(averagePowerPerTick) :+ (lastTickRemainder + averagePowerPerTick) + } + else { + 0f +: List.fill[Float](iterations)(averagePowerPerTick) + } + //pair id with entry + 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)) + true + case _ => + false + } + } + + private def PairIdWithAggravationEntry( + id: Long, + effect: Aura, + retime: Long, + data: ResolvedProjectile, + target: SourceEntry, + powerOffset: List[Float] + ): AggravatedBehavior.Entry = { + val aggravatedDamageInfo = ResolvedProjectile( + AggravatedBehavior.burning(data.resolution), + data.projectile, + target, + data.damage_model, + data.hit_pos + ) + val entry = AggravatedBehavior.Entry(id, effect, retime, aggravatedDamageInfo, powerOffset) + entryIdToEntry += id -> entry + entry + } + + val aggravatedBehavior: Receive = { + case AggravatedBehavior.Aggravate(id, 0) => + AggravationCleanup(id) + + case AggravatedBehavior.Aggravate(id, iteration) => + RetimeEventAndPerformAggravation(id, iteration, None) + } + + private def RetimeEventAndPerformAggravation(id: Long, iteration: Int, time: Option[Long]) : Unit = { + RetimeAggravation(id, iteration - 1, time) match { + case Some(entry) => + PerformAggravation(entry, iteration) + case _ => ; + } + } + + private def RetimeAggravation( + id: Long, + iteration: Int, + time: Option[Long] + ): Option[AggravatedBehavior.Entry] = { + CleanupAggravationTimer(id) + entryIdToEntry.get(id) match { + case out @ Some(oldEntry) => + aggravationToTimer += id -> context.system.scheduler.scheduleOnce( + time.getOrElse(oldEntry.retime) milliseconds, + self, + AggravatedBehavior.Aggravate(id, iteration) + ) + out + case _ => + AggravationCleanup(id) + None + } + } + + def RemoveAggravatedEntry(id: Long): Aura = { + entryIdToEntry.remove(id) match { + case Some(entry) => + entry.data.projectile.profile.Aggravated.get.effect_type + case _ => + Aura.Nothing + } + } + + def CleanupAggravationTimer(id: Long): Unit = { + //remove and cancel timer + aggravationToTimer.remove(id) match { + case Some(timer) => timer.cancel + case _ => ; + } + } + + def AggravationCleanup(id: Long): Unit = { + RemoveAggravatedEntry(id) + CleanupAggravationTimer(id) + } + + def EndAllAggravation(): Unit = { + entryIdToEntry.clear + aggravationToTimer.values.foreach { _.cancel } + aggravationToTimer.clear + } + + 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.target, + model, + data.hit_pos + ) + TakesDamage.apply(Vitality.Damage(model.Calculate(aggravatedProjectileData))) + } +} + +object AggravatedBehavior { + type Target = AuraEffectBehavior.Target with Vitality + + private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Float]) + + private case class Aggravate(id: Long, iterations: Int) + + private def burning(resolution: ProjectileResolution.Value): ProjectileResolution.Value = { + resolution match { + case ProjectileResolution.AggravatedDirect => ProjectileResolution.AggravatedDirectBurn + case ProjectileResolution.AggravatedSplash => ProjectileResolution.AggravatedSplashBurn + case _ => resolution + } + } + + private def basicDamageType(resolution: ProjectileResolution.Value): DamageType.Value = { + resolution match { + case ProjectileResolution.AggravatedDirect | ProjectileResolution.AggravatedDirectBurn => + DamageType.Direct + case ProjectileResolution.AggravatedSplash | ProjectileResolution.AggravatedSplashBurn => + DamageType.Splash + case _ => + DamageType.None + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala deleted file mode 100644 index 3283e1fc8..000000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2020 PSForever -package net.psforever.objects.serverobject.aggravated - -sealed class Aura(val id: Int) - -object Aura { - final case object None extends Aura(id = 0) - - final case object Plasma extends Aura(id = 1) - - final case object Comet extends Aura(id = 2) - - final case object Napalm extends Aura(id = 4) - - final case object Fire extends Aura(id = 8) -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala deleted file mode 100644 index 9f7745259..000000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala +++ /dev/null @@ -1,272 +0,0 @@ -// Copyright (c) 2020 PSForever -package net.psforever.objects.serverobject.aggravated - -import akka.actor.{Actor, Cancellable} -import net.psforever.objects.ballistics._ -import net.psforever.objects.serverobject.PlanetSideServerObject -import net.psforever.objects.serverobject.damage.Damageable -import net.psforever.objects.vital.{DamageType, Vitality} - -import scala.collection.mutable -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration._ - -trait AuraEffectBehavior { - _ : Actor with Damageable => - private var activeEffectIndex: Long = 0 - private val effectToEntryId: mutable.HashMap[Aura, List[Long]] = - mutable.HashMap.empty[Aura, List[Long]] - private val entryIdToTimer: mutable.LongMap[Cancellable] = - mutable.LongMap.empty[Cancellable] - private val entryIdToEntry: mutable.LongMap[AuraEffectBehavior.Entry] = - mutable.LongMap.empty[AuraEffectBehavior.Entry] - - def AuraTargetObject: AuraEffectBehavior.Target - - val auraBehavior: Receive = { - case AuraEffectBehavior.Aggravate(id, 0, 0) => - CancelEffectTimer(id) - PerformCleanupEffect(id) - - case AuraEffectBehavior.Aggravate(id, 0, leftoverTime) => - RemoveEffectEntry(id) - RetimeEvent(id, iteration = 0, Some(leftoverTime), leftoverTime = 0) - - case AuraEffectBehavior.Aggravate(id, iteration, leftover) => - RetimeEventAndPerformAggravation(id, iteration, None, leftover) - } - - private def RetimeEvent( - id: Long, - iteration: Int, - time: Option[Long], - leftoverTime: Long - ): Option[AuraEffectBehavior.Entry] = { - CancelEffectTimer(id) - entryIdToEntry.get(id) match { - case out @ Some(oldEntry) => - entryIdToTimer += id -> context.system.scheduler.scheduleOnce( - time.getOrElse(oldEntry.retime) milliseconds, - self, - AuraEffectBehavior.Aggravate(id, iteration, leftoverTime) - ) - out - case _ => - PerformCleanupEffect(id) - None - } - } - - private def RetimeEventAndPerformAggravation(id: Long, iteration: Int, time: Option[Long], leftoverTime: Long) : Unit = { - RetimeEvent(id, iteration - 1, time, leftoverTime) match { - case Some(entry) => - PerformAggravation(entry, iteration) - case _ => ; - } - } - - def CancelEffectTimer(id: Long) : Unit = { - entryIdToTimer.remove(id) match { - case Some(timer) => timer.cancel - case _ => ; - } - } - - def PerformCleanupEffect(id: Long) : Unit = { - CleanupEffect(id) match { - case Aura.None => ; - case _ => UpdateAggravatedEffect(AuraTargetObject) - } - } - - def TryAggravationEffect(data: ResolvedProjectile) : Unit = { - data.projectile.profile.Aggravated match { - case Some(damage) - if data.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) && - damage.effect_type != Aura.None && //TODO aphelion starfire - damage.targets.exists(validation => validation.test(AuraTargetObject)) => - TryAggravationEffect(damage, data) - case _ => ; - } - } - - private def TryAggravationEffect(aggravation: AggravatedDamage, data: ResolvedProjectile) : Unit = { - val effect = aggravation.effect_type - val obj = AuraTargetObject - if(CheckForUniqueUnqueuedProjectile(data.projectile)) { - val auraEffects = obj.Aura - if(auraEffects.contains(effect) && aggravation.cumulative_damage_degrade) { - SetupAggravationEntry(aggravation, data) - } - else if(obj.AddEffectToAura(effect).diff(auraEffects).contains(effect)) { - SetupAggravationEntry(aggravation, data) - UpdateAggravatedEffect(obj) - } - } - } - - private def CheckForUniqueUnqueuedProjectile(projectile : Projectile) : Boolean = { - !entryIdToEntry.values.exists { entry => entry.data.projectile.id == projectile.id } - } - - private def SetupAggravationEntry(aggravation: AggravatedDamage, data: ResolvedProjectile) : Unit = { - val effect = aggravation.effect_type - aggravation.info.find(_.damage_type == AuraEffectBehavior.basicDamageType(data.resolution)) match { - case Some(info) => - //get unused id - val id = activeEffectIndex - activeEffectIndex += 1 - //pair aura effect with id - effectToEntryId.get(effect) match { - case None | Some(Nil) => effectToEntryId += effect -> List(id) - case Some(list) => effectToEntryId -> (list :+ id) - } - //setup timer data - val timing = aggravation.timing - val duration = timing.duration - val (tick: Long, iterations: Int) = timing.ticks match { - case Some(n) if n < 1 => - val rate = info.infliction_rate - (rate, (duration / rate).toInt) - case Some(ticks) => - (duration / ticks, ticks) - case None => - (1000, (duration / 1000).toInt) - } - val leftoverTime = duration - (tick * iterations) - //quality per tick - val totalPower = (duration.toFloat / info.infliction_rate).toInt - 1 - val averagePowerPerTick = totalPower.toFloat / iterations - val lastTickRemainder = totalPower - averagePowerPerTick * iterations - val qualityPerTick: List[Float] = if (lastTickRemainder > 0) { - 0f +: List.fill[Float](iterations - 1)(averagePowerPerTick) :+ (lastTickRemainder + averagePowerPerTick) - } - else { - 0f +: List.fill[Float](iterations)(averagePowerPerTick) - } - //pair id with entry - PairIdWithAggravationEntry(id, effect, tick, data, data.target, qualityPerTick) - //pair id with timer - entryIdToTimer += id -> context.system.scheduler.scheduleOnce(tick milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations, leftoverTime)) - case _ => ; - } - } - - private def PairIdWithAggravationEntry( - id: Long, - effect: Aura, - retime: Long, - data: ResolvedProjectile, - target: SourceEntry, - powerOffset: List[Float] - ): AuraEffectBehavior.Entry = { - val aggravatedDamageInfo = ResolvedProjectile( - AuraEffectBehavior.burning(data.resolution), - data.projectile, - target, - data.damage_model, - data.hit_pos - ) - val entry = AuraEffectBehavior.Entry(id, effect, retime, aggravatedDamageInfo, powerOffset) - entryIdToEntry += id -> entry - entry - } - - def RemoveEffectEntry(id: Long) : Aura = { - entryIdToEntry.remove(id) match { - case Some(entry) => - entry.data.projectile.profile.Aggravated.get.effect_type - case None => - effectToEntryId.find { case (_, values) => values.contains(id) } match { - case Some((effect, _)) => effect - case _ => Aura.None - } - } - } - - def CleanupEffect(id: Long) : Aura = { - //remove and cancel timer - entryIdToTimer.remove(id) match { - case Some(timer) => timer.cancel - case _ => ; - } - //remove entry and cache effect - val out = RemoveEffectEntry(id) - //remove id and, if now unsupported, effect - (effectToEntryId.get(out) match { - case Some(list) => (out, list.filterNot(_ == id)) - case _ => (Aura.None, Nil) - }) match { - case (Aura.None, _) => - Aura.None - case (effect, Nil) => - AuraTargetObject.RemoveEffectFromAura(effect) - effectToEntryId.remove(effect) - effect - case (effect, list) => - effectToEntryId += effect -> list - Aura.None - } - } - - def EndAllEffects() : Unit = { - entryIdToEntry.clear - entryIdToTimer.values.foreach { _.cancel } - entryIdToTimer.clear - effectToEntryId.clear - val obj = AuraTargetObject - obj.Aura.foreach { obj.RemoveEffectFromAura } - } - - def EndAllEffectsAndUpdate() : Unit = { - EndAllEffects() - UpdateAggravatedEffect(AuraTargetObject) - } - - def UpdateAggravatedEffect(target: AuraEffectBehavior.Target) : Unit = { - import services.avatar.{AvatarAction, AvatarServiceMessage} - val zone = target.Zone - val value = target.Aura.foldLeft(0)(_ + _.id) - zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) - } - - private def PerformAggravation(entry: AuraEffectBehavior.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.target, - model, - data.hit_pos - ) - TakesDamage.apply(Vitality.Damage(model.Calculate(aggravatedProjectileData))) - } -} - -object AuraEffectBehavior { - type Target = PlanetSideServerObject with Vitality with AuraContainer - - private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Float]) - - private case class Aggravate(id: Long, iterations: Int, leftover: Long) - - private def burning(resolution: ProjectileResolution.Value): ProjectileResolution.Value = { - resolution match { - case ProjectileResolution.AggravatedDirect => ProjectileResolution.AggravatedDirectBurn - case ProjectileResolution.AggravatedSplash => ProjectileResolution.AggravatedSplashBurn - case _ => resolution - } - } - - private def basicDamageType(resolution: ProjectileResolution.Value): DamageType.Value = { - resolution match { - case ProjectileResolution.AggravatedDirect | ProjectileResolution.AggravatedDirectBurn => - DamageType.Direct - case ProjectileResolution.AggravatedSplash | ProjectileResolution.AggravatedSplashBurn => - DamageType.Splash - case _ => - DamageType.None - } - } -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aura/Aura.scala b/common/src/main/scala/net/psforever/objects/serverobject/aura/Aura.scala new file mode 100644 index 000000000..f78867e0d --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/Aura.scala @@ -0,0 +1,33 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.serverobject.aura + +/** + * An effect that can be emitted by a target game object entity. + */ +sealed class Aura() + +/** + * Visual effects emitted by a target, usually a `Player` entity. + * Most often paired with aggravated damage. + * Unrelated to the effects emitted by obtaining and transporting + * the lattice logic unit, or a facility module, or the rabbit ball. + */ +object Aura { + /** Since `None` is an actual effect, the "no effect" default is repurposed as "Nothing". */ + final case object Nothing extends Aura + + /** Conferred by the `aphelion_starfire_projectile`. */ + final case object None extends Aura + + /** A green emission. */ + final case object Plasma extends Aura + + /** A purple emission. */ + final case object Comet extends Aura + + /** A white and yellow starburst emission. */ + final case object Napalm extends Aura + + /** A red and orange emission. */ + final case object Fire extends Aura +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraContainer.scala similarity index 54% rename from common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala rename to common/src/main/scala/net/psforever/objects/serverobject/aura/AuraContainer.scala index e57f878b9..78d420697 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraContainer.scala @@ -1,8 +1,11 @@ -// Copyright (c) 2020 PSForever -package net.psforever.objects.serverobject.aggravated +package net.psforever.objects.serverobject.aura -import net.psforever.objects.serverobject.aggravated.{Aura => AuraEffect} +import net.psforever.objects.serverobject.aura.{Aura => AuraEffect} +/** + * An entity that can display specific special effects that decorate its model. + * These animations confer information about the nature of some status that is affecting the target entity. + */ trait AuraContainer { private var aura : Set[AuraEffect] = Set.empty[AuraEffect] 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 new file mode 100644 index 000000000..5c528dc86 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/aura/AuraEffectBehavior.scala @@ -0,0 +1,153 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.serverobject.aura + +import akka.actor.{Actor, Cancellable} +import net.psforever.objects.serverobject.PlanetSideServerObject + +import scala.collection.mutable +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ + +trait AuraEffectBehavior { + _ : Actor => + private var activeEffectIndex: Long = 0 + private val effectToEntryId: mutable.HashMap[Aura, List[Long]] = + mutable.HashMap.empty[Aura, List[Long]] + private val effectIdToTimer: mutable.LongMap[Cancellable] = + mutable.LongMap.empty[Cancellable] + + def AuraTargetObject: AuraEffectBehavior.Target + + val auraBehavior: Receive = { + case AuraEffectBehavior.StartEffect(effect, duration) => + StartAuraEffect(effect, duration) + + case AuraEffectBehavior.EndEffect(Some(id), None) => + EndAuraEffect(id) + + case AuraEffectBehavior.EndEffect(None, Some(effect)) => + EndAuraEffect(effect) + + case AuraEffectBehavior.EndAllEffects() => + EndAllEffectsAndUpdate() + } + + final def GetUnusedEffectId: Long = { + val id = activeEffectIndex + activeEffectIndex += 1 + id + } + + def StartAuraEffect(effect: Aura, duration: Long): Long = { + StartAuraEffect(GetUnusedEffectId, effect, duration) + } + + def StartAuraEffect(id: Long, effect: Aura, duration: Long): Long = { + //pair aura effect with id + effectToEntryId.get(effect) match { + case None | Some(Nil) => effectToEntryId += effect -> List(id) + case Some(list) => effectToEntryId -> (list :+ id) + } + //pair id with timer + effectIdToTimer += id -> context.system.scheduler.scheduleOnce(duration milliseconds, self, AuraEffectBehavior.EndEffect(id)) + //update visuals + UpdateAuraEffect(AuraTargetObject) + id + } + + def EndAuraEffect(id: Long): Unit = { + EndActiveEffect(id) match { + case Aura.Nothing => ; + case effect => + CancelEffectTimer(id) + val obj = AuraTargetObject + obj.RemoveEffectFromAura(effect) + UpdateAuraEffect(obj) + } + } + + def EndActiveEffect(id: Long): Aura = { + effectToEntryId.find { case (_, ids) => ids.contains(id) } match { + case Some((effect, ids)) if ids.size == 1 => + effectToEntryId.remove(effect) + effect + case Some((effect, ids)) => + effectToEntryId += effect -> ids.filterNot(_ == id) + Aura.Nothing + case None => + Aura.Nothing + } + } + + def CancelEffectTimer(id: Long) : Unit = { + effectIdToTimer.remove(id) match { + case Some(timer) => timer.cancel + case _ => ; + } + } + + def EndAuraEffect(effect: Aura): Unit = { + effectToEntryId.remove(effect) match { + case Some(idList) => + idList.foreach { id => + val obj = AuraTargetObject + CancelEffectTimer(id) + obj.RemoveEffectFromAura(effect) + UpdateAuraEffect(obj) + } + case _ => ; + } + } + + def EndAllEffects() : Unit = { + effectIdToTimer.values.foreach { _.cancel } + effectIdToTimer.clear + effectToEntryId.clear + val obj = AuraTargetObject + obj.Aura.foreach { obj.RemoveEffectFromAura } + } + + def EndAllEffectsAndUpdate() : Unit = { + EndAllEffects() + UpdateAuraEffect(AuraTargetObject) + } + + def UpdateAuraEffect(target: AuraEffectBehavior.Target) : Unit = { + import services.avatar.{AvatarAction, AvatarServiceMessage} + val zone = target.Zone + val value = target.Aura.foldLeft(0)(_ + AuraEffectBehavior.effectToAttributeValue(_)) + zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) + } + + def TestForEffect(id: Long): Aura = { + effectToEntryId.find { case (_, ids) => ids.contains(id) } match { + case Some((effect, _)) => effect + case _ => Aura.Nothing + } + } +} + +object AuraEffectBehavior { + type Target = PlanetSideServerObject with AuraContainer + + final case class StartEffect(effect: Aura, duration: Long) + + final case class EndEffect(id: Option[Long], aura: Option[Aura]) + + object EndEffect { + def apply(id: Long): EndEffect = EndEffect(Some(id), None) + + def apply(aura: Aura): EndEffect = EndEffect(None, Some(aura)) + } + + final case class EndAllEffects() + + private def effectToAttributeValue(effect: Aura): Int = effect match { + case Aura.None => 0 + case Aura.Plasma => 1 + case Aura.Comet => 2 + case Aura.Napalm => 4 + case Aura.Fire => 8 + case _ => Int.MinValue + } +} diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala b/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala deleted file mode 100644 index 7e460c58d..000000000 --- a/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala +++ /dev/null @@ -1,176 +0,0 @@ -// Copyright (c) 2017 PSForever -//package net.psforever.objects.vital -// -//import net.psforever.objects.vital.StandardAmenityDamage.None -//import net.psforever.objects.vital.StandardDeployableDamage.None -//import net.psforever.objects.vital.damage._ -//import net.psforever.objects.vital.damage.DamageCalculations._ -//import net.psforever.objects.vital.projectile.ProjectileCalculations -// -///** -// * A protected super class for calculating "no damage." -// * Used for `NoDamage` but also for the base of `*LashDamage` calculation objects -// * to maintain the polymorphic identity of `DamageCalculations`. -// */ -//protected class NoDamageBase -// extends DamageCalculations( -// DamageCalculations.NoDamage, -// DamageWithModifiers(NoDamageAgainst), -// TooFar -// ) -// -//object NoDamage extends NoDamageBase -// -//object InfantryHitDamage -// extends DamageCalculations( -// DirectHitDamageWithDegrade, -// DamageWithModifiers(DamageAgainstExoSuit), -// DistanceBetweenTargetandSource -// ) -// -//object MaxHitDamage -// extends DamageCalculations( -// DirectHitDamageWithDegrade, -// DamageWithModifiers(DamageAgainstMaxSuit), -// DistanceBetweenTargetandSource -// ) -// -//object VehicleHitDamage -// extends DamageCalculations( -// DirectHitDamageWithDegrade, -// DamageWithModifiers(DamageAgainstVehicle), -// DistanceBetweenTargetandSource -// ) -// -//object AircraftHitDamage -// extends DamageCalculations( -// DirectHitDamageWithDegrade, -// DamageWithModifiers(DamageAgainstAircraft), -// DistanceBetweenTargetandSource -// ) -// -//object InfantrySplashDamage -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstExoSuit), -// DistanceFromExplosionToTarget -// ) -// -//object MaxSplashDamage -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstMaxSuit), -// DistanceFromExplosionToTarget -// ) -// -//object VehicleSplashDamage -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstVehicle), -// DistanceFromExplosionToTarget -// ) -// -//object AircraftSplashDamage -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstAircraft), -// DistanceFromExplosionToTarget -// ) -// -//object InfantrySplashDamageDirect -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstAircraft), -// NoDistance -// ) -// -//object InfantryLashDamage -// extends DamageCalculations( -// LashDamage, -// DamageWithModifiers(DamageAgainstExoSuit), -// DistanceBetweenTargetandSource -// ) -// -//object MaxLashDamage -// extends DamageCalculations( -// LashDamage, -// DamageWithModifiers(DamageAgainstMaxSuit), -// DistanceBetweenTargetandSource -// ) -// -//object VehicleLashDamage -// extends DamageCalculations( -// LashDamage, -// DamageWithModifiers(DamageAgainstVehicle), -// DistanceBetweenTargetandSource -// ) -// -//object AircraftLashDamage -// extends DamageCalculations( -// LashDamage, -// DamageWithModifiers(DamageAgainstAircraft), -// DistanceBetweenTargetandSource -// ) -// -//object AmenityHitDamage -// extends DamageCalculations( -// DirectHitDamageWithDegrade, -// DamageWithModifiers(DamageAgainstVehicle), -// DistanceBetweenTargetandSource -// ) -// -//object AmenitySplashDamage -// extends DamageCalculations( -// SplashDamageWithRadialDegrade, -// DamageWithModifiers(DamageAgainstVehicle), -// DistanceFromExplosionToTarget -// ) -// -//object NoDamageSelection extends DamageSelection { -// def Direct = None -// def Splash = None -// def Lash = None -// def Aggravated : ProjectileCalculations.Form = None -//} -// -//object StandardInfantryDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = InfantryHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = InfantrySplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = InfantryLashDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = InfantrySplashDamage.Calculate -//} -// -//object StandardMaxDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = MaxHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = MaxSplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = MaxLashDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = None -//} -// -//object StandardVehicleDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = VehicleHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = VehicleSplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = VehicleLashDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = None -//} -// -//object StandardAircraftDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = AircraftHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = AircraftSplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = AircraftLashDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = None -//} -// -//object StandardDeployableDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = VehicleHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = VehicleSplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = NoDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = None -//} -// -//object StandardAmenityDamage extends DamageSelection { -// def Direct : ProjectileCalculations.Form = AmenityHitDamage.Calculate -// def Splash : ProjectileCalculations.Form = AmenitySplashDamage.Calculate -// def Lash : ProjectileCalculations.Form = NoDamage.Calculate -// def Aggravated : ProjectileCalculations.Form = None -//} diff --git a/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala b/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala deleted file mode 100644 index 8b15c5006..000000000 --- a/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) 2017 PSForever -//package net.psforever.objects.vital.damage -// -//import net.psforever.objects.ballistics.ResolvedProjectile -//import net.psforever.objects.vital.{DamageType, NoDamage} -//import net.psforever.objects.vital.projectile.ProjectileCalculations -// -///** -// * Maintain information about three primary forms of damage calculation -// * and a means to test which calculation is valid in a given situation. -// */ -//trait DamageSelection { -// final def None: ProjectileCalculations.Form = NoDamage -// -// def Direct : ProjectileCalculations.Form -// def Splash : ProjectileCalculations.Form -// def Lash : ProjectileCalculations.Form -// def Aggravated : ProjectileCalculations.Form -// -// def apply(data : ResolvedProjectile) : ProjectileCalculations.Form = data.projectile.profile.ProjectileDamageType match { -// case DamageType.Direct => Direct -// case DamageType.Splash => Splash -// case DamageType.Lash => Lash -// case DamageType.Aggravated => Aggravated -// case _ => None -// } -// -// def apply(res : DamageType.Value) : ProjectileCalculations.Form = res match { -// case DamageType.Direct => Direct -// case DamageType.Splash => Splash -// case DamageType.Lash => Lash -// case DamageType.Aggravated => Aggravated -// case _ => None -// } -//} diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index e9295cb13..af02396e2 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -8,7 +8,7 @@ import net.psforever.objects.definition._ import net.psforever.objects.definition.converter._ import net.psforever.objects.equipment._ import net.psforever.objects.inventory.InventoryTile -import net.psforever.objects.serverobject.aggravated.Aura +import net.psforever.objects.serverobject.aura.Aura import net.psforever.objects.serverobject.doors.DoorDefinition import net.psforever.objects.serverobject.generator.GeneratorDefinition import net.psforever.objects.serverobject.implantmech.ImplantTerminalMechDefinition diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index 23b329e11..d7585f018 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -14,7 +14,7 @@ import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinity -import net.psforever.objects.serverobject.aggravated.AuraContainer +import net.psforever.objects.serverobject.aura.AuraContainer import net.psforever.objects.vital.resistance.ResistanceProfile import net.psforever.objects.vital.{DamageResistanceModel, Vitality} import net.psforever.objects.zones.ZoneAware diff --git a/src/main/scala/net/psforever/objects/Vehicle.scala b/src/main/scala/net/psforever/objects/Vehicle.scala index b927791f9..b1a472021 100644 --- a/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/src/main/scala/net/psforever/objects/Vehicle.scala @@ -7,7 +7,7 @@ import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinity -import net.psforever.objects.serverobject.aggravated.AuraContainer +import net.psforever.objects.serverobject.aura.AuraContainer import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.structures.AmenityOwner diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 99b02a39b..ef7b8225c 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -8,7 +8,8 @@ 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.AuraEffectBehavior +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.{CommonMessages, PlanetSideServerObject} @@ -32,6 +33,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm with JammableBehavior with Damageable with ContainableBehavior + with AggravatedBehavior with AuraEffectBehavior { def JammableObject = player @@ -39,6 +41,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm def ContainerObject = player + def AggravatedObject = player + def AuraTargetObject = player private[this] val log = org.log4s.getLogger(player.Name) @@ -58,11 +62,13 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm lockerControlAgent ! akka.actor.PoisonPill player.avatar.locker.Actor = Default.Actor EndAllEffects() + EndAllAggravation() } def receive: Receive = jammableBehavior .orElse(takesDamage) + .orElse(aggravatedBehavior) .orElse(auraBehavior) .orElse(containerBehavior) .orElse { @@ -554,7 +560,11 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm if (Damageable.CanJammer(target, cause)) { TryJammerEffectActivate(target, cause) } - TryAggravationEffect(cause) + TryAggravationEffect(cause) match { + case Some(aggravation) => + StartAuraEffect(aggravation.effect_type, aggravation.timing.duration) + case _ => ; + } } else { DestructionAwareness(target, Some(cause)) } @@ -609,6 +619,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm target.Die //aura effects cancel EndAllEffects() + //aggravation cancel + EndAllAggravation() //unjam CancelJammeredSound(target) CancelJammeredStatus(target) diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index c92689bcb..5f6dee3dc 100644 --- a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -10,7 +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.AuraEffectBehavior +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 @@ -54,11 +55,12 @@ class VehicleControl(vehicle: Vehicle) with JammableMountedWeapons with ContainableBehavior with AntTransferBehavior + with AggravatedBehavior with AuraEffectBehavior { //make control actors belonging to utilities when making control actor belonging to vehicle vehicle.Utilities.foreach({ case (_, util) => util.Setup }) - + def MountableObject = vehicle def CargoObject = vehicle @@ -79,6 +81,8 @@ class VehicleControl(vehicle: Vehicle) def AuraTargetObject = vehicle + def AggravatedObject = vehicle + if(vehicle.Definition == GlobalDefinitions.ant) { findChargeTargetFunc = Vehicles.FindANTChargingSource findDischargeTargetFunc = Vehicles.FindANTDischargingTarget @@ -101,6 +105,7 @@ class VehicleControl(vehicle: Vehicle) util().Actor = Default.Actor } EndAllEffects() + EndAllAggravation() } def Enabled: Receive = @@ -109,6 +114,7 @@ class VehicleControl(vehicle: Vehicle) .orElse(cargoBehavior) .orElse(jammableBehavior) .orElse(takesDamage) + .orElse(aggravatedBehavior) .orElse(auraBehavior) .orElse(canBeRepairedByNanoDispenser) .orElse(containerBehavior) @@ -592,7 +598,11 @@ class VehicleControl(vehicle: Vehicle) cause: ResolvedProjectile, amount: Int ): Unit = { - TryAggravationEffect(cause) + TryAggravationEffect(cause) match { + case Some(aggravation) => + StartAuraEffect(aggravation.effect_type, aggravation.timing.duration) + case _ => ; + } super.DamageAwareness(target, cause, amount) } @@ -602,6 +612,8 @@ class VehicleControl(vehicle: Vehicle) ): Unit = { //aura effects cancel EndAllEffects() + //aggravation cancel + EndAllAggravation() super.DestructionAwareness(target, cause) } } diff --git a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index b77e183b4..a3a7256ab 100644 --- a/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -255,7 +255,7 @@ object GamePacketOpcode extends Enumeration { // 0x68 case 0x68 => game.DroppodFreefallingMessage.decode case 0x69 => game.AvatarFirstTimeEventMessage.decode - case 0x6a => noDecoder(AggravatedDamageMessage) + case 0x6a => game.AggravatedDamageMessage.decode case 0x6b => game.TriggerSoundMessage.decode case 0x6c => game.LootItemMessage.decode case 0x6d => game.VehicleSubStateMessage.decode diff --git a/src/test/scala/objects/DamageModelTests.scala b/src/test/scala/objects/DamageModelTests.scala index 7e6935cde..8da2e8fc8 100644 --- a/src/test/scala/objects/DamageModelTests.scala +++ b/src/test/scala/objects/DamageModelTests.scala @@ -451,8 +451,8 @@ class DamageModelTests extends Specification { ) val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile) func(tplayer) - tplayer.Health mustEqual 54 - tplayer.Armor mustEqual 46 + tplayer.Health mustEqual 65 + tplayer.Armor mustEqual 35 } "resolve infantry targets in a specific way" in {