woring Starfire damage calculations; projectiles have open-ended quality modifier

This commit is contained in:
FateJH 2020-08-10 08:20:12 -04:00
parent 66eb3b5b95
commit 80c1a34fb0
7 changed files with 106 additions and 58 deletions

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2020 PSForever
package net.psforever.objects.ballistics package net.psforever.objects.ballistics
import net.psforever.objects.equipment.TargetValidation import net.psforever.objects.equipment.TargetValidation
import net.psforever.objects.serverobject.aggravated.Aura 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], final case class AggravatedDamage(info: List[AggravatedInfo],
effect_type: Aura.Value, effect_type: Aura,
duration: Long, duration: Long,
max_factor: Float, max_factor: Float,
cumulative_damage_degrade: Boolean, cumulative_damage_degrade: Boolean,
@ -20,7 +20,7 @@ final case class AggravatedDamage(info: List[AggravatedInfo],
object AggravatedDamage { object AggravatedDamage {
def apply(info: AggravatedInfo, def apply(info: AggravatedInfo,
effect_type: Aura.Value, effect_type: Aura,
duration: Long, duration: Long,
max_factor: Float, max_factor: Float,
targets: List[TargetValidation]): AggravatedDamage = targets: List[TargetValidation]): AggravatedDamage =
@ -35,7 +35,7 @@ object AggravatedDamage {
) )
def apply(info: AggravatedInfo, def apply(info: AggravatedInfo,
effect_type: Aura.Value, effect_type: Aura,
duration: Long, duration: Long,
max_factor: Float, max_factor: Float,
vanu_aggravated: Boolean, vanu_aggravated: Boolean,

View file

@ -1,9 +1,16 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.aggravated package net.psforever.objects.serverobject.aggravated
object Aura extends Enumeration { sealed class Aura(val id: Int)
final val None = Value(0)
final val Plasma = Value(1) object Aura {
final val Comet = Value(2) final case object None extends Aura(id = 0)
final val Napalm = Value(4)
final val Fire = Value(8) 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)
} }

View file

@ -1,20 +1,21 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.aggravated package net.psforever.objects.serverobject.aggravated
import net.psforever.objects.serverobject.aggravated.{Aura => AuraEffect} import net.psforever.objects.serverobject.aggravated.{Aura => AuraEffect}
trait AuraContainer { 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) { if(effect != AuraEffect.None) {
aura = aura + effect aura = aura + effect
} }
Aura Aura
} }
def RemoveEffectFromAura(effect : AuraEffect.Value) : Set[AuraEffect.Value] = { def RemoveEffectFromAura(effect : AuraEffect) : Set[AuraEffect] = {
aura = aura - effect aura = aura - effect
Aura Aura
} }

View file

@ -6,7 +6,6 @@ import net.psforever.objects.ballistics._
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.vital.{DamageType, Vitality} import net.psforever.objects.vital.{DamageType, Vitality}
import net.psforever.types.Vector3
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
@ -15,16 +14,16 @@ import scala.concurrent.duration._
trait AuraEffectBehavior { trait AuraEffectBehavior {
_ : Actor with Damageable => _ : Actor with Damageable =>
private var activeEffectIndex: Long = 0 private var activeEffectIndex: Long = 0
private val effectToEntryId: mutable.HashMap[Aura.Value, List[Long]] = private val effectToEntryId: mutable.HashMap[Aura, List[Long]] =
mutable.HashMap.empty[Aura.Value, List[Long]] mutable.HashMap.empty[Aura, List[Long]]
private val entryIdToTimer: mutable.LongMap[Cancellable] = private val entryIdToTimer: mutable.LongMap[Cancellable] =
mutable.LongMap.empty[Cancellable] mutable.LongMap.empty[Cancellable]
private val entryIdToEntry: mutable.LongMap[AuraEffectBehavior.Entry] = private val entryIdToEntry: mutable.LongMap[AuraEffectBehavior.Entry] =
mutable.LongMap.empty[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) => case AuraEffectBehavior.Aggravate(id, 0, 0) =>
CancelEffectTimer(id) CancelEffectTimer(id)
PerformCleanupEffect(id) PerformCleanupEffect(id)
@ -33,8 +32,8 @@ trait AuraEffectBehavior {
RemoveEffectEntry(id) RemoveEffectEntry(id)
RetimeEvent(id, iteration = 0, Some(leftoverTime), leftoverTime = 0) RetimeEvent(id, iteration = 0, Some(leftoverTime), leftoverTime = 0)
case AuraEffectBehavior.Aggravate(id, iteration, leftover) => ; case AuraEffectBehavior.Aggravate(id, iteration, leftover) =>
RetimeEventAndPerformAggravation(id, iteration - 1, None, leftover) RetimeEventAndPerformAggravation(id, iteration, None, leftover)
} }
private def RetimeEvent( private def RetimeEvent(
@ -45,22 +44,13 @@ trait AuraEffectBehavior {
): Option[AuraEffectBehavior.Entry] = { ): Option[AuraEffectBehavior.Entry] = {
CancelEffectTimer(id) CancelEffectTimer(id)
entryIdToEntry.get(id) match { entryIdToEntry.get(id) match {
case Some(oldEntry) => case out @ Some(oldEntry) =>
val target = SourceEntry(AuraTargetObject)
val entry = PairIdWithAggravationEntry(
id,
oldEntry.effect,
oldEntry.retime,
oldEntry.data,
target,
target.Position - oldEntry.data.target.Position
)
entryIdToTimer += id -> context.system.scheduler.scheduleOnce( entryIdToTimer += id -> context.system.scheduler.scheduleOnce(
time.getOrElse(entry.retime) milliseconds, time.getOrElse(oldEntry.retime) milliseconds,
self, self,
AuraEffectBehavior.Aggravate(id, iteration, leftoverTime) AuraEffectBehavior.Aggravate(id, iteration, leftoverTime)
) )
Some(entry) out
case _ => case _ =>
PerformCleanupEffect(id) PerformCleanupEffect(id)
None None
@ -68,9 +58,9 @@ trait AuraEffectBehavior {
} }
private def RetimeEventAndPerformAggravation(id: Long, iteration: Int, time: Option[Long], leftoverTime: Long) : Unit = { 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) => case Some(entry) =>
PerformAggravation(entry) PerformAggravation(entry, iteration)
case _ => ; case _ => ;
} }
} }
@ -116,7 +106,7 @@ trait AuraEffectBehavior {
} }
private def CheckForUniqueUnqueuedProjectile(projectile : Projectile) : Boolean = { 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 = { private def SetupAggravationEntry(aggravation: AggravatedDamage, data: ResolvedProjectile) : Unit = {
@ -131,38 +121,50 @@ trait AuraEffectBehavior {
case None | Some(Nil) => effectToEntryId += effect -> List(id) case None | Some(Nil) => effectToEntryId += effect -> List(id)
case Some(list) => effectToEntryId -> (list :+ id) case Some(list) => effectToEntryId -> (list :+ id)
} }
//pair id with timer //setup timer data
val inflictionRate = 1000 //info.infliction_rate val tick = 1000 //each second
val iterations = (aggravation.duration / inflictionRate).toInt val duration = aggravation.duration
val leftoverTime = aggravation.duration - (iterations * inflictionRate) val iterations = (duration / tick).toInt
entryIdToTimer += id -> context.system.scheduler.scheduleOnce(inflictionRate milliseconds, self, AuraEffectBehavior.Aggravate(id, iterations, leftoverTime)) 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 //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 _ => ; case _ => ;
} }
} }
private def PairIdWithAggravationEntry( private def PairIdWithAggravationEntry(
id: Long, id: Long,
effect: Aura.Value, effect: Aura,
retime:Long, retime: Long,
data: ResolvedProjectile, data: ResolvedProjectile,
target: SourceEntry, target: SourceEntry,
offset: Vector3 powerOffset: List[Int]
): AuraEffectBehavior.Entry = { ): AuraEffectBehavior.Entry = {
val aggravatedDamageInfo = ResolvedProjectile( val aggravatedDamageInfo = ResolvedProjectile(
AuraEffectBehavior.burning(data.resolution), AuraEffectBehavior.burning(data.resolution),
data.projectile, data.projectile,
target, target,
data.damage_model, 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 entryIdToEntry += id -> entry
entry entry
} }
def RemoveEffectEntry(id: Long) : Aura.Value = { def RemoveEffectEntry(id: Long) : Aura = {
entryIdToEntry.remove(id) match { entryIdToEntry.remove(id) match {
case Some(entry) => case Some(entry) =>
entry.data.projectile.profile.Aggravated.get.effect_type 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 //remove and cancel timer
entryIdToTimer.remove(id) match { entryIdToTimer.remove(id) match {
case Some(timer) => timer.cancel case Some(timer) => timer.cancel
@ -220,15 +222,23 @@ trait AuraEffectBehavior {
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value)) zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value))
} }
private def PerformAggravation(entry: AuraEffectBehavior.Entry) : Unit = { private def PerformAggravation(entry: AuraEffectBehavior.Entry, tick: Int = 0) : Unit = {
TakesDamage.apply(Vitality.Damage(entry.data.damage_model.Calculate(entry.data))) 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 { object AuraEffectBehavior {
type Target = PlanetSideServerObject with Vitality with AuraContainer 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) private case class Aggravate(id: Long, iterations: Int, leftover: Long)

View file

@ -2321,7 +2321,7 @@ object GlobalDefinitions {
aphelion_starfire_projectile.Aggravated = AggravatedDamage( aphelion_starfire_projectile.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Direct, 0.25f, 250), AggravatedInfo(DamageType.Direct, 0.25f, 250),
Aura.None, Aura.None,
0, 2000,
0f, 0f,
true, true,
List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft)) List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft))
@ -2454,7 +2454,7 @@ object GlobalDefinitions {
comet_projectile.Aggravated = AggravatedDamage( comet_projectile.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Direct, 0.2f, 500), AggravatedInfo(DamageType.Direct, 0.2f, 500),
Aura.Comet, Aura.Comet,
0, 2000,
10f, 10f,
List( List(
TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player), TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player),
@ -3910,7 +3910,7 @@ object GlobalDefinitions {
starfire_projectile.Aggravated = AggravatedDamage( starfire_projectile.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Direct, 0.25f, 250), AggravatedInfo(DamageType.Direct, 0.25f, 250),
Aura.Comet, Aura.Comet,
3000, 2000,
0f, 0f,
true, true,
List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft)) List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft))
@ -3923,7 +3923,6 @@ object GlobalDefinitions {
starfire_projectile.Packet = projectileConverter starfire_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(starfire_projectile) ProjectileDefinition.CalculateDerivedFields(starfire_projectile)
starfire_projectile.Modifiers = List( starfire_projectile.Modifiers = List(
DamageModifiers.StarfireAggravated,
DamageModifiers.StarfireAggravatedBurn, DamageModifiers.StarfireAggravatedBurn,
DamageModifiers.RadialDegrade DamageModifiers.RadialDegrade
) )

View file

@ -1,6 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.ballistics package net.psforever.objects.ballistics
import java.util.concurrent.atomic.AtomicLong
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition} import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition}
import net.psforever.objects.entity.SimpleWorldEntity 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) * 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_origin where the projectile started
* @param shot_angle in which direction the projectile was aimed when it was discharged * @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; * @param fire_time when the weapon discharged was recorded;
* defaults to `System.nanoTime` * defaults to `System.nanoTime`
*/ */
@ -36,6 +41,8 @@ final case class Projectile(
attribute_to: Int, attribute_to: Int,
shot_origin: Vector3, shot_origin: Vector3,
shot_angle: Vector3, shot_angle: Vector3,
quality: Float = 1f,
id: Long = Projectile.idGenerator.getAndIncrement(),
fire_time: Long = System.nanoTime fire_time: Long = System.nanoTime
) extends PlanetSideGameObject { ) extends PlanetSideGameObject {
Position = shot_origin Position = shot_origin
@ -52,6 +59,28 @@ final case class Projectile(
val current: SimpleWorldEntity = new SimpleWorldEntity() val current: SimpleWorldEntity = new SimpleWorldEntity()
private var resolved: ProjectileResolution.Value = ProjectileResolution.Unresolved 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. * Mark the projectile as being "encountered" or "managed" at least once.
*/ */
@ -80,6 +109,8 @@ object Projectile {
*/ */
final val rangeUID: Int = 40150 final val rangeUID: Int = 40150
private val idGenerator: AtomicLong = new AtomicLong
/** /**
* Overloaded constructor for an `Unresolved` projectile. * Overloaded constructor for an `Unresolved` projectile.
* @param profile an explanation of the damage that can be performed by this discharge * @param profile an explanation of the damage that can be performed by this discharge

View file

@ -257,7 +257,7 @@ object DamageModifiers {
case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) => case (Some(aggravation), v : VehicleSource) if GlobalDefinitions.isFlightVehicle(v.Definition) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match { aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) => case Some(infos) =>
(damage * infos.degradation_percentage) toInt (math.floor(damage * infos.degradation_percentage) * data.projectile.quality) toInt
case _ => case _ =>
damage damage
} }