mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-02-24 17:13:34 +00:00
merge rebase hell
This commit is contained in:
parent
bb3c0f5d91
commit
89a7f180dd
11 changed files with 3 additions and 2 deletions
|
|
@ -77,6 +77,7 @@ import net.psforever.services.vehicle.{VehicleAction, VehicleResponse, VehicleSe
|
|||
import net.psforever.services.{InterstellarClusterService, RemoverActor, Service, ServiceManager}
|
||||
import net.psforever.types._
|
||||
import net.psforever.util.{Config, DefinitionUtil}
|
||||
import net.psforever.zones.Zones
|
||||
|
||||
object SessionActor {
|
||||
|
||||
|
|
|
|||
|
|
@ -929,7 +929,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
}
|
||||
|
||||
def UpdateAuraEffect(target: AuraEffectBehavior.Target) : Unit = {
|
||||
import services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
val zone = target.Zone
|
||||
val value = target.Aura.foldLeft(0)(_ + PlayerControl.auraEffectToAttributeValue(_))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,178 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.ballistics
|
||||
import net.psforever.objects.equipment.TargetValidation
|
||||
import net.psforever.objects.serverobject.aura.Aura
|
||||
import net.psforever.objects.vital.DamageType
|
||||
|
||||
/**
|
||||
* In what manner of pacing the aggravated damage ticks are applied.
|
||||
* @param duration for how long the over-all effect is applied
|
||||
* @param ticks a custom number of damage applications,
|
||||
* as opposed to whatever calculations normally estimate the number of applications
|
||||
*/
|
||||
final case class AggravatedTiming(duration: Long, ticks: Option[Int])
|
||||
|
||||
object AggravatedTiming {
|
||||
/**
|
||||
* Overloaded constructor that only defines the duration.
|
||||
* @param duration for how long the over-all effect lasts
|
||||
* @return an `AggravatedTiming` object
|
||||
*/
|
||||
def apply(duration: Long): AggravatedTiming = AggravatedTiming(duration, None)
|
||||
|
||||
/**
|
||||
* Overloaded constructor.
|
||||
* @param duration for how long the over-all effect lasts
|
||||
* @param ticks a custom number of damage applications
|
||||
* @return an `AggravatedTiming` object
|
||||
*/
|
||||
def apply(duration: Long, ticks: Int): AggravatedTiming = AggravatedTiming(duration, Some(ticks))
|
||||
}
|
||||
|
||||
/**
|
||||
* Aggravation damage has components that are mainly divided by the `DamageType` they inflict.
|
||||
* Only `Direct` and `Splash` are valid damage types, however.
|
||||
* @param damage_type the type of damage
|
||||
* @param degradation_percentage by how much the damage is degraded
|
||||
* @param infliction_rate how often the damage is inflicted (ms)
|
||||
*/
|
||||
final case class AggravatedInfo(damage_type: DamageType.Value,
|
||||
degradation_percentage: Float,
|
||||
infliction_rate: Long) {
|
||||
assert(damage_type == DamageType.Direct || damage_type == DamageType.Splash, s"aggravated damage is an unsupported type - $damage_type")
|
||||
}
|
||||
|
||||
/**
|
||||
* Information related to the aggravated damage.
|
||||
* @param info the specific kinds of aggravation damage available
|
||||
* @param effect_type what effect is exhibited by this aggravated damage
|
||||
* @param timing the timing for the damage application
|
||||
* @param max_factor na (if the target is a mechanized assault exo-suit?)
|
||||
* @param cumulative_damage_degrade na (can multiple instances of this type of aggravated damage apply to the same target at once?)
|
||||
* @param vanu_aggravated na (search me)
|
||||
* @param targets validation information indicating whether a certain entity is applicable for aggravation
|
||||
*/
|
||||
final case class AggravatedDamage(info: List[AggravatedInfo],
|
||||
effect_type: Aura,
|
||||
timing: AggravatedTiming,
|
||||
max_factor: Float,
|
||||
cumulative_damage_degrade: Boolean,
|
||||
vanu_aggravated: Boolean,
|
||||
targets: List[TargetValidation])
|
||||
|
||||
object AggravatedDamage {
|
||||
/**
|
||||
* Overloaded constructor.
|
||||
* @param info the specific kinds of aggravation damage available
|
||||
* @param effect_type what effect is exhibited by this aggravated damage
|
||||
* @param timing the timing for the damage application
|
||||
* @param max_factor na
|
||||
* @param targets validation information indicating whether a certain entity is applicable for aggravation
|
||||
*/
|
||||
def apply(info: AggravatedInfo,
|
||||
effect_type: Aura,
|
||||
timing: AggravatedTiming,
|
||||
max_factor: Float,
|
||||
targets: List[TargetValidation]): AggravatedDamage =
|
||||
AggravatedDamage(
|
||||
List(info),
|
||||
effect_type,
|
||||
timing,
|
||||
max_factor,
|
||||
cumulative_damage_degrade = true,
|
||||
vanu_aggravated = false,
|
||||
targets
|
||||
)
|
||||
|
||||
/**
|
||||
* Overloaded constructor.
|
||||
* @param info the specific kinds of aggravation damage available
|
||||
* @param effect_type what effect is exhibited by this aggravated damage
|
||||
* @param timing the timing for the damage application
|
||||
* @param max_factor na
|
||||
* @param vanu_aggravated na
|
||||
* @param targets validation information indicating whether a certain entity is applicable for aggravation
|
||||
*/
|
||||
def apply(info: AggravatedInfo,
|
||||
effect_type: Aura,
|
||||
timing: AggravatedTiming,
|
||||
max_factor: Float,
|
||||
vanu_aggravated: Boolean,
|
||||
targets: List[TargetValidation]): AggravatedDamage =
|
||||
AggravatedDamage(
|
||||
List(info),
|
||||
effect_type,
|
||||
timing,
|
||||
max_factor,
|
||||
cumulative_damage_degrade = true,
|
||||
vanu_aggravated,
|
||||
targets
|
||||
)
|
||||
|
||||
/**
|
||||
* Overloaded constructor.
|
||||
* @param info the specific kinds of aggravation damage available
|
||||
* @param effect_type what effect is exhibited by this aggravated damage
|
||||
* @param duration for how long the over-all effect is applied
|
||||
* @param max_factor na
|
||||
* @param targets validation information indicating whether a certain entity is applicable for aggravation
|
||||
*/
|
||||
def apply(info: AggravatedInfo,
|
||||
effect_type: Aura,
|
||||
duration: Long,
|
||||
max_factor: Float,
|
||||
targets: List[TargetValidation]): AggravatedDamage =
|
||||
AggravatedDamage(
|
||||
List(info),
|
||||
effect_type,
|
||||
AggravatedTiming(duration),
|
||||
max_factor,
|
||||
cumulative_damage_degrade = true,
|
||||
vanu_aggravated = false,
|
||||
targets
|
||||
)
|
||||
|
||||
/**
|
||||
* Overloaded constructor.
|
||||
* @param info the specific kinds of aggravation damage available
|
||||
* @param effect_type what effect is exhibited by this aggravated damage
|
||||
* @param duration for how long the over-all effect is applied
|
||||
* @param max_factor na
|
||||
* @param vanu_aggravated na
|
||||
* @param targets validation information indicating whether a certain entity is applicable for aggravation
|
||||
*/
|
||||
def apply(info: AggravatedInfo,
|
||||
effect_type: Aura,
|
||||
duration: Long,
|
||||
max_factor: Float,
|
||||
vanu_aggravated: Boolean,
|
||||
targets: List[TargetValidation]): AggravatedDamage =
|
||||
AggravatedDamage(
|
||||
List(info),
|
||||
effect_type,
|
||||
AggravatedTiming(duration),
|
||||
max_factor,
|
||||
cumulative_damage_degrade = true,
|
||||
vanu_aggravated,
|
||||
targets
|
||||
)
|
||||
|
||||
def burning(resolution: ProjectileResolution.Value): ProjectileResolution.Value = {
|
||||
resolution match {
|
||||
case ProjectileResolution.AggravatedDirect => ProjectileResolution.AggravatedDirectBurn
|
||||
case ProjectileResolution.AggravatedSplash => ProjectileResolution.AggravatedSplashBurn
|
||||
case _ => resolution
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 the value 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 (initial damage). */
|
||||
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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package net.psforever.objects.serverobject.aura
|
||||
|
||||
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]
|
||||
|
||||
def Aura : Set[AuraEffect] = aura
|
||||
|
||||
def AddEffectToAura(effect : AuraEffect) : Set[AuraEffect] = {
|
||||
if(effect != AuraEffect.None) {
|
||||
aura = aura + effect
|
||||
}
|
||||
Aura
|
||||
}
|
||||
|
||||
def RemoveEffectFromAura(effect : AuraEffect) : Set[AuraEffect] = {
|
||||
aura = aura - effect
|
||||
Aura
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,186 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.aura
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.Default
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* A mixin that governs the addition, display, and removal of aura particle effects
|
||||
* on a target with control agency.
|
||||
* @see `Aura`
|
||||
* @see `AuraContainer`
|
||||
* @see `PlayerControl`
|
||||
*/
|
||||
trait AuraEffectBehavior {
|
||||
_ : Actor =>
|
||||
/** active aura effects are monotonic, but the timer will be updated for continuing and cancelling effects as well<br>
|
||||
* only effects that are initialized to this mapping are approved for display on this target<br>
|
||||
* key - aura effect; value - the timer for that effect
|
||||
* @see `ApplicableEffect`
|
||||
*/
|
||||
private val effectToTimer: mutable.HashMap[Aura, AuraEffectBehavior.Entry] = mutable.HashMap.empty[Aura, AuraEffectBehavior.Entry]
|
||||
|
||||
def AuraTargetObject: AuraEffectBehavior.Target
|
||||
|
||||
val auraBehavior: Receive = {
|
||||
case AuraEffectBehavior.StartEffect(effect, duration) =>
|
||||
StartAuraEffect(effect, duration)
|
||||
|
||||
case AuraEffectBehavior.EndEffect(effect) =>
|
||||
EndAuraEffectAndUpdate(effect)
|
||||
|
||||
case AuraEffectBehavior.EndAllEffects() =>
|
||||
EndAllEffectsAndUpdate()
|
||||
}
|
||||
|
||||
/**
|
||||
* Only pre-apporved aura effects will be emitted by this target.
|
||||
* @param effect the aura effect
|
||||
*/
|
||||
def ApplicableEffect(effect: Aura): Unit = {
|
||||
//create entry
|
||||
effectToTimer += effect -> AuraEffectBehavior.Entry()
|
||||
}
|
||||
|
||||
/**
|
||||
* An aura particle effect is to be emitted by the target.
|
||||
* If the effect was not previously applied to the target in an ongoing manner,
|
||||
* animate it appropriately.
|
||||
* @param effect the effect to be emitted
|
||||
* @param duration for how long the effect will be emitted
|
||||
* @return the active effect index number
|
||||
*/
|
||||
def StartAuraEffect(effect: Aura, duration: Long): Unit = {
|
||||
val obj = AuraTargetObject
|
||||
val auraEffectsBefore = obj.Aura.size
|
||||
if(StartAuraTimer(effect, duration) && obj.AddEffectToAura(effect).size > auraEffectsBefore) {
|
||||
//new effect; update visuals
|
||||
UpdateAuraEffect(AuraTargetObject)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* As long as the effect has been approved for this target,
|
||||
* the timer will either start if it is stopped or has never been started,
|
||||
* or the timer will stop and be recreated with the new duration if is currently running for a shoreter amount of time.
|
||||
* @param effect the effect to be emitted
|
||||
* @param duration for how long the effect will be emitted
|
||||
* @return `true`, if the timer was started or restarted;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
private def StartAuraTimer(effect: Aura, duration: Long): Boolean = {
|
||||
//pair aura effect with entry
|
||||
(effectToTimer.get(effect) match {
|
||||
case Some(timer) if timer.start + timer.duration < System.currentTimeMillis() + duration =>
|
||||
timer.cancel()
|
||||
Some(effect)
|
||||
case _ =>
|
||||
None
|
||||
}) match {
|
||||
case None =>
|
||||
false
|
||||
case Some(_) =>
|
||||
//retime
|
||||
effectToTimer(effect) = AuraEffectBehavior.Entry(
|
||||
duration,
|
||||
context.system.scheduler.scheduleOnce(duration milliseconds, self, AuraEffectBehavior.EndEffect(effect))
|
||||
)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the target entity from emitting the aura particle effect, if it currently is.
|
||||
* @param effect the target effect
|
||||
* @return `true`, if the effect was being emitted but has been stopped
|
||||
* `false`, if the effect was not approved or is not being emitted
|
||||
*/
|
||||
def EndAuraEffect(effect: Aura): Boolean = {
|
||||
effectToTimer.get(effect) match {
|
||||
case Some(timer) if !timer.isCancelled =>
|
||||
timer.cancel()
|
||||
//effectToTimer(effect) = Default.Cancellable
|
||||
AuraTargetObject.RemoveEffectFromAura(effect)
|
||||
true
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the target entity from emitting all aura particle effects.
|
||||
*/
|
||||
def EndAllEffects() : Unit = {
|
||||
effectToTimer.keysIterator.foreach { effect =>
|
||||
effectToTimer(effect).cancel()
|
||||
//effectToTimer(effect) = Default.Cancellable
|
||||
}
|
||||
val obj = AuraTargetObject
|
||||
obj.Aura.foreach { obj.RemoveEffectFromAura }
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the target entity from emitting the aura particle effect, if it currently is.
|
||||
* If the effect has been stopped, animate the new particle effect state.
|
||||
*/
|
||||
def EndAuraEffectAndUpdate(effect: Aura) : Unit = {
|
||||
if(EndAuraEffect(effect)) {
|
||||
UpdateAuraEffect(AuraTargetObject)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop the target entity from emitting all aura particle effects.
|
||||
* Animate the new particle effect state.
|
||||
*/
|
||||
def EndAllEffectsAndUpdate() : Unit = {
|
||||
EndAllEffects()
|
||||
UpdateAuraEffect(AuraTargetObject)
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the target entity emitting the aura effect?
|
||||
* @param effect the effect being tested
|
||||
* @return `true`, if the effect is currently being emitted;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def TestForEffect(effect: Aura): Boolean = {
|
||||
effectToTimer.get(effect) match {
|
||||
case None => false
|
||||
case Some(timer) => timer.isCancelled
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An override callback to display aura effects emitted.
|
||||
* @param target the entity from which the aura effects are being emitted
|
||||
*/
|
||||
def UpdateAuraEffect(target: AuraEffectBehavior.Target) : Unit
|
||||
}
|
||||
|
||||
object AuraEffectBehavior {
|
||||
type Target = PlanetSideServerObject with AuraContainer
|
||||
|
||||
case class Entry(duration: Long, timer: Cancellable) extends Cancellable {
|
||||
val start: Long = System.currentTimeMillis()
|
||||
|
||||
override def isCancelled : Boolean = timer.isCancelled
|
||||
|
||||
override def cancel(): Boolean = timer.cancel()
|
||||
}
|
||||
|
||||
object Entry {
|
||||
def apply(): Entry = Entry(0, Default.Cancellable)
|
||||
}
|
||||
|
||||
final case class StartEffect(effect: Aura, duration: Long)
|
||||
|
||||
final case class EndEffect(aura: Aura)
|
||||
|
||||
final case class EndAllEffects()
|
||||
}
|
||||
|
|
@ -0,0 +1,209 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.damage
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.serverobject.aura.Aura
|
||||
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]
|
||||
/** 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 TryAggravationEffectActivate(data: ResolvedProjectile): Option[AggravatedDamage] = {
|
||||
val projectile = data.projectile
|
||||
projectile.profile.Aggravated match {
|
||||
case Some(damage)
|
||||
if projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) &&
|
||||
damage.info.exists(_.damage_type == AggravatedDamage.basicDamageType(data.resolution)) &&
|
||||
damage.effect_type != Aura.Nothing &&
|
||||
(projectile.quality == ProjectileQuality.AggravatesTarget ||
|
||||
damage.targets.exists(validation => validation.test(AggravatedObject))) =>
|
||||
TryAggravationEffectActivate(damage, data)
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
private def TryAggravationEffectActivate(
|
||||
aggravation: AggravatedDamage,
|
||||
data: ResolvedProjectile
|
||||
): Option[AggravatedDamage] = {
|
||||
val effect = aggravation.effect_type
|
||||
if(CheckForUniqueUnqueuedProjectile(data.projectile)) {
|
||||
val sameEffect = entryIdToEntry.values.filter(entry => entry.effect == effect)
|
||||
if(sameEffect.isEmpty || sameEffect.nonEmpty && aggravation.cumulative_damage_degrade) {
|
||||
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 == AggravatedDamage.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)
|
||||
}
|
||||
//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))
|
||||
ongoingAggravated = true
|
||||
true
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private def PairIdWithAggravationEntry(
|
||||
id: Long,
|
||||
effect: Aura,
|
||||
retime: Long,
|
||||
data: ResolvedProjectile,
|
||||
target: SourceEntry,
|
||||
powerOffset: List[Float]
|
||||
): AggravatedBehavior.Entry = {
|
||||
val aggravatedDamageInfo = ResolvedProjectile(
|
||||
AggravatedDamage.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) =>
|
||||
ongoingAggravated = entryIdToEntry.nonEmpty
|
||||
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()
|
||||
}
|
||||
|
||||
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(ProjectileQuality.Modified(entry.qualityPerTick(tick))),
|
||||
data.target,
|
||||
model,
|
||||
data.hit_pos
|
||||
)
|
||||
takesDamage.apply(Vitality.Damage(model.Calculate(aggravatedProjectileData)))
|
||||
}
|
||||
}
|
||||
|
||||
object AggravatedBehavior {
|
||||
type Target = Damageable.Target
|
||||
|
||||
private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Float])
|
||||
|
||||
private case class Aggravate(id: Long, iterations: Int)
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import scodec.Codec
|
||||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
* Dispatched from the server to cause a damage reaction from a specific target.
|
||||
* Infantry targets should be the primary target of this packet, as indicated by their identifier.
|
||||
* Infantry targets display their flinch animation.
|
||||
* All targets yelp in agony.
|
||||
* Infantry targets use their assigned voice.
|
||||
* Non-infantry targets use the "grizzled"(?) voice.
|
||||
* @param guid the target entity's global unique identifier
|
||||
* @param damage the amount of damsge being simulated
|
||||
*/
|
||||
final case class AggravatedDamageMessage(guid : PlanetSideGUID,
|
||||
damage : Long)
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = AggravatedDamageMessage
|
||||
def opcode = GamePacketOpcode.AggravatedDamageMessage
|
||||
def encode = AggravatedDamageMessage.encode(this)
|
||||
}
|
||||
|
||||
object AggravatedDamageMessage extends Marshallable[AggravatedDamageMessage] {
|
||||
implicit val codec : Codec[AggravatedDamageMessage] = (
|
||||
("guid" | PlanetSideGUID.codec) ::
|
||||
("damage" | uint32L)
|
||||
).as[AggravatedDamageMessage]
|
||||
}
|
||||
29
src/test/scala/game/AggravatedDamageMessageTest.scala
Normal file
29
src/test/scala/game/AggravatedDamageMessageTest.scala
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package game
|
||||
|
||||
import net.psforever.packet._
|
||||
import net.psforever.packet.game._
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
import org.specs2.mutable._
|
||||
import scodec.bits._
|
||||
|
||||
class AggravatedDamageMessageTest extends Specification {
|
||||
val string = hex"6a350a0e000000"
|
||||
|
||||
"decode" in {
|
||||
PacketCoding.DecodePacket(string).require match {
|
||||
case AggravatedDamageMessage(guid,unk) =>
|
||||
guid mustEqual PlanetSideGUID(2613)
|
||||
unk mustEqual 14
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"encode" in {
|
||||
val msg = AggravatedDamageMessage(PlanetSideGUID(2613), 14)
|
||||
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
|
||||
|
||||
pkt mustEqual string
|
||||
}
|
||||
}
|
||||
285
src/test/scala/objects/AuraTest.scala
Normal file
285
src/test/scala/objects/AuraTest.scala
Normal file
|
|
@ -0,0 +1,285 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package objects
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Props}
|
||||
import akka.testkit.TestProbe
|
||||
import base.ActorTest
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.aura.AuraEffectBehavior.Target
|
||||
import net.psforever.objects.serverobject.aura.{Aura, AuraContainer, AuraEffectBehavior}
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
import org.specs2.mutable.Specification
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class AuraContainerTest extends Specification {
|
||||
"AuraContainer" should {
|
||||
"have no default effects" in {
|
||||
new AuraTest.Entity().Aura.isEmpty mustEqual true
|
||||
}
|
||||
|
||||
"add effects" in {
|
||||
val obj = new AuraTest.Entity()
|
||||
obj.Aura.size mustEqual 0
|
||||
obj.Aura.contains(Aura.Plasma) mustEqual false
|
||||
obj.AddEffectToAura(Aura.Plasma)
|
||||
obj.Aura.size mustEqual 1
|
||||
obj.Aura.contains(Aura.Plasma) mustEqual true
|
||||
}
|
||||
|
||||
"do nothing if adding repeated effects" in {
|
||||
val obj = new AuraTest.Entity()
|
||||
obj.Aura.size mustEqual 0
|
||||
obj.AddEffectToAura(Aura.Plasma)
|
||||
obj.Aura.size mustEqual 1
|
||||
obj.AddEffectToAura(Aura.Plasma)
|
||||
obj.Aura.size mustEqual 1
|
||||
}
|
||||
|
||||
"remove effects" in {
|
||||
val obj = new AuraTest.Entity()
|
||||
obj.Aura.size mustEqual 0
|
||||
obj.Aura.contains(Aura.Plasma) mustEqual false
|
||||
obj.AddEffectToAura(Aura.Plasma)
|
||||
obj.Aura.size mustEqual 1
|
||||
obj.Aura.contains(Aura.Plasma) mustEqual true
|
||||
obj.RemoveEffectFromAura(Aura.Plasma)
|
||||
obj.Aura.size mustEqual 0
|
||||
obj.Aura.contains(Aura.Plasma) mustEqual false
|
||||
}
|
||||
|
||||
"do nothing if no effects" in {
|
||||
val obj = new AuraTest.Entity()
|
||||
obj.Aura.size mustEqual 0
|
||||
obj.Aura.contains(Aura.Plasma) mustEqual false
|
||||
obj.RemoveEffectFromAura(Aura.Plasma)
|
||||
obj.Aura.size mustEqual 0
|
||||
obj.Aura.contains(Aura.Plasma) mustEqual false
|
||||
}
|
||||
|
||||
"do nothing if trying to remove wrong effect" in {
|
||||
val obj = new AuraTest.Entity()
|
||||
obj.Aura.size mustEqual 0
|
||||
obj.Aura.contains(Aura.Plasma) mustEqual false
|
||||
obj.Aura.contains(Aura.Fire) mustEqual false
|
||||
obj.AddEffectToAura(Aura.Plasma)
|
||||
obj.Aura.size mustEqual 1
|
||||
obj.Aura.contains(Aura.Plasma) mustEqual true
|
||||
obj.Aura.contains(Aura.Fire) mustEqual false
|
||||
obj.RemoveEffectFromAura(Aura.Fire)
|
||||
obj.Aura.size mustEqual 1
|
||||
obj.Aura.contains(Aura.Plasma) mustEqual true
|
||||
obj.Aura.contains(Aura.Fire) mustEqual false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuraEffectBehaviorInitTest extends ActorTest {
|
||||
val obj = new AuraTest.Entity()
|
||||
|
||||
"AuraEffectBehavior" should {
|
||||
"init" in {
|
||||
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, ActorRef.noSender), "aura-test-actor")
|
||||
expectNoMessage(500 milliseconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuraEffectBehaviorStartEffectTest extends ActorTest {
|
||||
val obj = new AuraTest.Entity()
|
||||
val updateProbe = new TestProbe(system)
|
||||
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
|
||||
|
||||
"AuraEffectBehavior" should {
|
||||
"start effect (ends naturally)" in {
|
||||
assert(obj.Aura.isEmpty)
|
||||
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
|
||||
val msg1 = updateProbe.receiveOne(100 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case AuraTest.DoUpdateAuraEffect() => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(obj.Aura.contains(Aura.Plasma))
|
||||
expectNoMessage(2000 milliseconds)
|
||||
assert(obj.Aura.contains(Aura.Plasma))
|
||||
val msg2 = updateProbe.receiveOne(750 milliseconds)
|
||||
assert(
|
||||
msg2 match {
|
||||
case AuraTest.DoUpdateAuraEffect() => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(obj.Aura.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuraEffectBehaviorStartLongerEffectTest extends ActorTest {
|
||||
val obj = new AuraTest.Entity()
|
||||
val updateProbe = new TestProbe(system)
|
||||
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
|
||||
|
||||
"AuraEffectBehavior" should {
|
||||
"replace a shorter effect with a longer one" in {
|
||||
assert(obj.Aura.isEmpty)
|
||||
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 500)
|
||||
val msg1 = updateProbe.receiveOne(100 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case AuraTest.DoUpdateAuraEffect() => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(obj.Aura.contains(Aura.Plasma))
|
||||
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
|
||||
updateProbe.expectNoMessage(2000 milliseconds)
|
||||
//first effect has not ended naturally (yet)
|
||||
assert(obj.Aura.contains(Aura.Plasma))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuraEffectBehaviorNoRedundantStartEffectTest extends ActorTest {
|
||||
val obj = new AuraTest.Entity()
|
||||
val updateProbe = new TestProbe(system)
|
||||
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
|
||||
|
||||
"AuraEffectBehavior" should {
|
||||
"not start an effect if already active" in {
|
||||
assert(obj.Aura.isEmpty)
|
||||
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
|
||||
val msg1 = updateProbe.receiveOne(100 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case AuraTest.DoUpdateAuraEffect() => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(obj.Aura.contains(Aura.Plasma))
|
||||
expectNoMessage(1000 milliseconds) //wait for half of the effect's duration
|
||||
assert(obj.Aura.contains(Aura.Plasma))
|
||||
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
|
||||
updateProbe.expectNoMessage(1500 milliseconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuraEffectBehaviorNoOverrideStartEffectTest extends ActorTest {
|
||||
val obj = new AuraTest.Entity()
|
||||
val updateProbe = new TestProbe(system)
|
||||
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
|
||||
|
||||
"AuraEffectBehavior" should {
|
||||
"not replace a long-running effect with a short-running effect" in {
|
||||
assert(obj.Aura.isEmpty)
|
||||
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
|
||||
val msg1 = updateProbe.receiveOne(100 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case AuraTest.DoUpdateAuraEffect() => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(obj.Aura.contains(Aura.Plasma))
|
||||
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 500)
|
||||
updateProbe.expectNoMessage(1500 milliseconds)
|
||||
//effect has not ended naturally
|
||||
assert(obj.Aura.contains(Aura.Plasma))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuraEffectBehaviorNoStartUnsupportedEffectTest extends ActorTest {
|
||||
val obj = new AuraTest.Entity()
|
||||
val updateProbe = new TestProbe(system)
|
||||
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor") //supports Plasma only
|
||||
|
||||
"AuraEffectBehavior" should {
|
||||
"not start an effect that is not approved" in {
|
||||
assert(obj.Aura.isEmpty)
|
||||
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Fire, 2500)
|
||||
assert(obj.Aura.isEmpty)
|
||||
updateProbe.expectNoMessage(2000 milliseconds)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
class AuraEffectBehaviorEndEarlyTest extends ActorTest {
|
||||
val obj = new AuraTest.Entity()
|
||||
val updateProbe = new TestProbe(system)
|
||||
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
|
||||
|
||||
"AuraEffectBehavior" should {
|
||||
"start effect (ends early)" in {
|
||||
assert(obj.Aura.isEmpty)
|
||||
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
|
||||
val msg1 = updateProbe.receiveOne(100 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case AuraTest.DoUpdateAuraEffect() => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(obj.Aura.contains(Aura.Plasma))
|
||||
obj.Actor ! AuraEffectBehavior.EndEffect(Aura.Plasma)
|
||||
val msg2 = updateProbe.receiveOne(100 milliseconds)
|
||||
assert(
|
||||
msg2 match {
|
||||
case AuraTest.DoUpdateAuraEffect() => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(obj.Aura.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class AuraEffectBehaviorEndNothingTest extends ActorTest {
|
||||
val obj = new AuraTest.Entity()
|
||||
val updateProbe = new TestProbe(system)
|
||||
obj.Actor = system.actorOf(Props(classOf[AuraTest.Agency], obj, updateProbe.ref), "aura-test-actor")
|
||||
|
||||
"AuraEffectBehavior" should {
|
||||
"can not end an effect that is not supported (hence, not started)" in {
|
||||
assert(obj.Aura.isEmpty)
|
||||
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Plasma, 2500)
|
||||
val msg1 = updateProbe.receiveOne(100 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case AuraTest.DoUpdateAuraEffect() => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(obj.Aura.size == 1)
|
||||
obj.Actor ! AuraEffectBehavior.EndEffect(Aura.Fire)
|
||||
updateProbe.expectNoMessage(1000 milliseconds)
|
||||
assert(obj.Aura.size == 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object AuraTest {
|
||||
class Agency(obj: AuraEffectBehavior.Target, updateRef: ActorRef) extends Actor with AuraEffectBehavior {
|
||||
def AuraTargetObject : Target = obj
|
||||
ApplicableEffect(Aura.Plasma)
|
||||
|
||||
def receive: Receive = auraBehavior.orElse {
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
def UpdateAuraEffect(target : Target) : Unit = {
|
||||
updateRef ! DoUpdateAuraEffect()
|
||||
}
|
||||
}
|
||||
|
||||
class Entity extends PlanetSideServerObject with AuraContainer {
|
||||
def Faction = PlanetSideEmpire.NEUTRAL
|
||||
def Definition = null
|
||||
}
|
||||
|
||||
final case class DoUpdateAuraEffect()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue