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 a1b39ce3..e28a2544 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala @@ -1,4 +1,4 @@ -// Copyright (c) 2017 PSForever +// Copyright (c) 2020 PSForever package net.psforever.objects.ballistics import net.psforever.objects.equipment.TargetValidation import net.psforever.objects.serverobject.aggravated.Aura @@ -11,7 +11,7 @@ final case class AggravatedInfo(damage_type: DamageType.Value, } final case class AggravatedDamage(info: List[AggravatedInfo], - effect_type: Aura.Value, + effect_type: Aura, duration: Long, max_factor: Float, cumulative_damage_degrade: Boolean, @@ -20,7 +20,7 @@ final case class AggravatedDamage(info: List[AggravatedInfo], object AggravatedDamage { def apply(info: AggravatedInfo, - effect_type: Aura.Value, + effect_type: Aura, duration: Long, max_factor: Float, targets: List[TargetValidation]): AggravatedDamage = @@ -35,7 +35,7 @@ object AggravatedDamage { ) def apply(info: AggravatedInfo, - effect_type: Aura.Value, + effect_type: Aura, duration: Long, max_factor: Float, vanu_aggravated: Boolean, 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 index c6883468..3283e1fc 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/Aura.scala @@ -1,9 +1,16 @@ +// Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.aggravated -object Aura extends Enumeration { - final val None = Value(0) - final val Plasma = Value(1) - final val Comet = Value(2) - final val Napalm = Value(4) - final val Fire = Value(8) +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/AuraContainer.scala b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala index e2d617d6..e57f878b 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraContainer.scala @@ -1,20 +1,21 @@ +// Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.aggravated import net.psforever.objects.serverobject.aggravated.{Aura => AuraEffect} trait AuraContainer { - private var aura : Set[AuraEffect.Value] = Set.empty[AuraEffect.Value] + private var aura : Set[AuraEffect] = Set.empty[AuraEffect] - def Aura : Set[AuraEffect.Value] = aura + def Aura : Set[AuraEffect] = aura - def AddEffectToAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { + def AddEffectToAura(effect : AuraEffect) : Set[AuraEffect] = { if(effect != AuraEffect.None) { aura = aura + effect } Aura } - def RemoveEffectFromAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { + def RemoveEffectFromAura(effect : AuraEffect) : Set[AuraEffect] = { aura = aura - effect Aura } 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 index 8bd09b18..1a2cc3da 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/aggravated/AuraEffectBehavior.scala @@ -6,7 +6,6 @@ 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 net.psforever.types.Vector3 import scala.collection.mutable import scala.concurrent.ExecutionContext.Implicits.global @@ -15,16 +14,16 @@ import scala.concurrent.duration._ trait AuraEffectBehavior { _ : Actor with Damageable => private var activeEffectIndex: Long = 0 - private val effectToEntryId: mutable.HashMap[Aura.Value, List[Long]] = - mutable.HashMap.empty[Aura.Value, List[Long]] - private val entryIdToTimer: mutable.LongMap[Cancellable] = + 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 + def AuraTargetObject: AuraEffectBehavior.Target - val auraBehavior : Receive = { + val auraBehavior: Receive = { case AuraEffectBehavior.Aggravate(id, 0, 0) => CancelEffectTimer(id) PerformCleanupEffect(id) @@ -33,8 +32,8 @@ trait AuraEffectBehavior { RemoveEffectEntry(id) RetimeEvent(id, iteration = 0, Some(leftoverTime), leftoverTime = 0) - case AuraEffectBehavior.Aggravate(id, iteration, leftover) => ; - RetimeEventAndPerformAggravation(id, iteration - 1, None, leftover) + case AuraEffectBehavior.Aggravate(id, iteration, leftover) => + RetimeEventAndPerformAggravation(id, iteration, None, leftover) } private def RetimeEvent( @@ -45,22 +44,13 @@ trait AuraEffectBehavior { ): Option[AuraEffectBehavior.Entry] = { CancelEffectTimer(id) entryIdToEntry.get(id) match { - case Some(oldEntry) => - val target = SourceEntry(AuraTargetObject) - val entry = PairIdWithAggravationEntry( - id, - oldEntry.effect, - oldEntry.retime, - oldEntry.data, - target, - target.Position - oldEntry.data.target.Position - ) + case out @ Some(oldEntry) => entryIdToTimer += id -> context.system.scheduler.scheduleOnce( - time.getOrElse(entry.retime) milliseconds, + time.getOrElse(oldEntry.retime) milliseconds, self, AuraEffectBehavior.Aggravate(id, iteration, leftoverTime) ) - Some(entry) + out case _ => PerformCleanupEffect(id) None @@ -68,9 +58,9 @@ trait AuraEffectBehavior { } private def RetimeEventAndPerformAggravation(id: Long, iteration: Int, time: Option[Long], leftoverTime: Long) : Unit = { - RetimeEvent(id, iteration, time, leftoverTime) match { + RetimeEvent(id, iteration - 1, time, leftoverTime) match { case Some(entry) => - PerformAggravation(entry) + PerformAggravation(entry, iteration) case _ => ; } } @@ -116,7 +106,7 @@ trait AuraEffectBehavior { } private def CheckForUniqueUnqueuedProjectile(projectile : Projectile) : Boolean = { - !entryIdToEntry.values.exists { entry => entry.data.projectile eq projectile } + !entryIdToEntry.values.exists { entry => entry.data.projectile.id == projectile.id } } private def SetupAggravationEntry(aggravation: AggravatedDamage, data: ResolvedProjectile) : Unit = { @@ -131,38 +121,50 @@ trait AuraEffectBehavior { case None | Some(Nil) => effectToEntryId += effect -> List(id) case Some(list) => effectToEntryId -> (list :+ id) } - //pair id with timer - val inflictionRate = 1000 //info.infliction_rate - val iterations = (aggravation.duration / inflictionRate).toInt - val leftoverTime = aggravation.duration - (iterations * inflictionRate) - entryIdToTimer += id -> context.system.scheduler.scheduleOnce(inflictionRate milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations, leftoverTime)) + //setup timer data + val tick = 1000 //each second + val duration = aggravation.duration + val iterations = (duration / tick).toInt + val leftoverTime = duration - (iterations * tick) + //quality per tick + val totalPower = (duration.toFloat / info.infliction_rate).toInt - 1 + val averagePowerPerTick = math.max(1, totalPower.toFloat / iterations).toInt + val lastTickRemainder = totalPower - averagePowerPerTick * iterations + val qualityPerTick: List[Int] = if (lastTickRemainder > 0) { + 0 +: List.fill[Int](iterations - 1)(averagePowerPerTick) :+ (lastTickRemainder + averagePowerPerTick) + } + else { + 0 +: List.fill[Int](iterations)(averagePowerPerTick) + } //pair id with entry - PairIdWithAggravationEntry(id, effect, inflictionRate, data, data.target, Vector3.Zero) + 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.Value, - retime:Long, + effect: Aura, + retime: Long, data: ResolvedProjectile, target: SourceEntry, - offset: Vector3 + powerOffset: List[Int] ): AuraEffectBehavior.Entry = { val aggravatedDamageInfo = ResolvedProjectile( AuraEffectBehavior.burning(data.resolution), data.projectile, target, data.damage_model, - data.hit_pos + offset + data.hit_pos ) - val entry = AuraEffectBehavior.Entry(id, effect, retime, aggravatedDamageInfo) + val entry = AuraEffectBehavior.Entry(id, effect, retime, aggravatedDamageInfo, powerOffset) entryIdToEntry += id -> entry entry } - def RemoveEffectEntry(id: Long) : Aura.Value = { + def RemoveEffectEntry(id: Long) : Aura = { entryIdToEntry.remove(id) match { case Some(entry) => entry.data.projectile.profile.Aggravated.get.effect_type @@ -174,7 +176,7 @@ trait AuraEffectBehavior { } } - def CleanupEffect(id: Long) : Aura.Value = { + def CleanupEffect(id: Long) : Aura = { //remove and cancel timer entryIdToTimer.remove(id) match { case Some(timer) => timer.cancel @@ -220,15 +222,23 @@ trait AuraEffectBehavior { zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) } - private def PerformAggravation(entry: AuraEffectBehavior.Entry) : Unit = { - TakesDamage.apply(Vitality.Damage(entry.data.damage_model.Calculate(entry.data))) + private def PerformAggravation(entry: AuraEffectBehavior.Entry, tick: Int = 0) : Unit = { + val data = entry.data + val info = ResolvedProjectile( + data.resolution, + data.projectile.quality(entry.qualityPerTick(tick).toFloat), + data.target, + data.damage_model, + data.hit_pos + ) + TakesDamage.apply(Vitality.Damage(info.damage_model.Calculate(info))) } } object AuraEffectBehavior { type Target = PlanetSideServerObject with Vitality with AuraContainer - private case class Entry(id: Long, effect: Aura.Value, retime: Long, data: ResolvedProjectile) + private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Int]) private case class Aggravate(id: Long, iterations: Int, leftover: Long) diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 462046fb..f8171f01 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -2321,7 +2321,7 @@ object GlobalDefinitions { aphelion_starfire_projectile.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Direct, 0.25f, 250), Aura.None, - 0, + 2000, 0f, true, List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft)) @@ -2454,7 +2454,7 @@ object GlobalDefinitions { comet_projectile.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Direct, 0.2f, 500), Aura.Comet, - 0, + 2000, 10f, List( TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player), @@ -3910,7 +3910,7 @@ object GlobalDefinitions { starfire_projectile.Aggravated = AggravatedDamage( AggravatedInfo(DamageType.Direct, 0.25f, 250), Aura.Comet, - 3000, + 2000, 0f, true, List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft)) @@ -3923,7 +3923,6 @@ object GlobalDefinitions { starfire_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(starfire_projectile) starfire_projectile.Modifiers = List( - DamageModifiers.StarfireAggravated, DamageModifiers.StarfireAggravatedBurn, DamageModifiers.RadialDegrade ) diff --git a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index 57b6de22..d40d9de8 100644 --- a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -1,6 +1,8 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.ballistics +import java.util.concurrent.atomic.AtomicLong + import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition} import net.psforever.objects.entity.SimpleWorldEntity @@ -25,6 +27,9 @@ import net.psforever.types.Vector3 * if not, then it is a type of vehicle (and owner should have a positive `seated` field) * @param shot_origin where the projectile started * @param shot_angle in which direction the projectile was aimed when it was discharged + * @param quality na + * @param id an exclusive identifier for this projectile; + * normally generated internally, but can be manually set * @param fire_time when the weapon discharged was recorded; * defaults to `System.nanoTime` */ @@ -36,6 +41,8 @@ final case class Projectile( attribute_to: Int, shot_origin: Vector3, shot_angle: Vector3, + quality: Float = 1f, + id: Long = Projectile.idGenerator.getAndIncrement(), fire_time: Long = System.nanoTime ) extends PlanetSideGameObject { Position = shot_origin @@ -52,6 +59,28 @@ final case class Projectile( val current: SimpleWorldEntity = new SimpleWorldEntity() private var resolved: ProjectileResolution.Value = ProjectileResolution.Unresolved + /** + * Create a copy of this projectile with all the same information + * save for the quality. + * Used mainly for aggravated damage. + * It is important to note that the new projectile shares the (otherwise) exclusive id of the original. + * @param value the new quality + * @return a new `Projectile` entity + */ + def quality(value: Float): Projectile = + Projectile( + profile, + tool_def, + fire_mode, + owner, + attribute_to, + shot_origin, + shot_angle, + value, + id, + fire_time + ) + /** * Mark the projectile as being "encountered" or "managed" at least once. */ @@ -80,6 +109,8 @@ object Projectile { */ final val rangeUID: Int = 40150 + private val idGenerator: AtomicLong = new AtomicLong + /** * Overloaded constructor for an `Unresolved` projectile. * @param profile an explanation of the damage that can be performed by this discharge 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 7b508fb8..1ae9dbc9 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala @@ -257,7 +257,7 @@ object DamageModifiers { case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) => aggravation.info.find(_.damage_type == DamageType.Direct) match { case Some(infos) => - (damage * infos.degradation_percentage) toInt + (math.floor(damage * infos.degradation_percentage) * data.projectile.quality) toInt case _ => damage }