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
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,

View file

@ -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)
}

View file

@ -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
}

View file

@ -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)

View file

@ -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
)

View file

@ -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

View file

@ -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
}