Aggravation (#529)

* introduction of aggravated damage properties and modification of damage pathways such that 'aggravated' is treated as a unique damage type

* initial work and tests for AggravatedDamageMessage

* wrote aggravated damage properties into the definitions of projectiles that convey them; basic workflow for aggravation from projectile damage is in place, but not tested

* merge rebase resolutions

* grenade projectile flag on projectile definition; better timing control on aura behavior and integration with player control

* timing and damage tuning around: standard, reinforced, max; plasma grenades, dragon

* work on starfire damage profile; moved aura behavior into own folder and creates an aura container object; extended aura behavior to vehicles; applied target-control to aggravation effect

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

* working comet calculations

* separated aggravation behavior and uara management behavior; moved files into packages and deleted old files

* moved aggravation damage into damage implementations of turrets and vehicles, rather than directly into ther immediate control agencies; revamp projectile quality modifiers; comet now does initial damage and one less tick of aggravation; the implementation progression of damageable entities is different now

* eliminated multiple timers for a single aura effect; added comments; added tests; fixed tests

* aura effects that will run for longer will not get replaced by aura effects that would end sooner

* merge rebase; accommodation for suppressing aura where no aura should be displayed; new radial degrade calculations

* test repairs

* merge rebase hell

* dragon fireball burn damage modification

* fireball burn effect should always be 1 as long as it does damage, despite radial degrade
This commit is contained in:
Fate-JH 2020-08-28 00:32:45 -04:00 committed by GitHub
commit de87845bfe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 2736 additions and 784 deletions

View file

@ -9,7 +9,9 @@ import net.psforever.objects.definition.converter.SmallDeployableConverter
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.vital.{StandardResolutions, Vitality}
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
import net.psforever.objects.vital.StandardResolutions
import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideGUID, Vector3}
import net.psforever.services.Service
@ -63,18 +65,20 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D
case _ => ;
}
protected def TakesDamage: Receive = {
case Vitality.Damage(applyDamageTo) =>
if (mine.CanDamage) {
val originalHealth = mine.Health
val cause = applyDamageTo(mine)
val damage = originalHealth - mine.Health
if (Damageable.CanDamageOrJammer(mine, damage, cause)) {
ExplosiveDeployableControl.DamageResolution(mine, cause, damage)
} else {
mine.Health = originalHealth
}
override protected def PerformDamage(
target: Target,
applyDamageTo: Output
): Unit = {
if (mine.CanDamage) {
val originalHealth = mine.Health
val cause = applyDamageTo(mine)
val damage = originalHealth - mine.Health
if (Damageable.CanDamageOrJammer(mine, damage, cause)) {
ExplosiveDeployableControl.DamageResolution(mine, cause, damage)
} else {
mine.Health = originalHealth
}
}
}
}

View file

@ -2,12 +2,13 @@
package net.psforever.objects
import net.psforever.objects.avatar.Certification
import net.psforever.objects.ballistics.Projectiles
import net.psforever.objects.ballistics.{AggravatedDamage, AggravatedInfo, AggravatedTiming, Projectiles}
import net.psforever.objects.ce.{DeployableCategory, DeployedItem}
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.aura.Aura
import net.psforever.objects.serverobject.doors.DoorDefinition
import net.psforever.objects.serverobject.generator.GeneratorDefinition
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMechDefinition
@ -207,6 +208,8 @@ object GlobalDefinitions {
val flail_projectile = ProjectileDefinition(Projectiles.flail_projectile)
val flamethrower_fire_cloud = ProjectileDefinition(Projectiles.flamethrower_projectile) //flamethrower_fire_cloud
val flamethrower_fireball = ProjectileDefinition(Projectiles.flamethrower_fireball)
val flamethrower_projectile = ProjectileDefinition(Projectiles.flamethrower_projectile)
@ -2037,6 +2040,7 @@ object GlobalDefinitions {
no_projectile.Name = "none"
ProjectileDefinition.CalculateDerivedFields(no_projectile)
no_projectile.Modifiers = Nil
bullet_105mm_projectile.Name = "105mmbullet_projectile"
bullet_105mm_projectile.Damage0 = 150
@ -2269,6 +2273,7 @@ object GlobalDefinitions {
ProjectileDefinition.CalculateDerivedFields(aphelion_laser_projectile)
aphelion_plasma_rocket_projectile.Name = "aphelion_plasma_rocket_projectile"
//has property aggravated_damage_max_factor, but it's the aphelion_plasma_cloud that performs aggravated damage
aphelion_plasma_rocket_projectile.Damage0 = 38
aphelion_plasma_rocket_projectile.Damage1 = 70
aphelion_plasma_rocket_projectile.Damage2 = 95
@ -2313,6 +2318,14 @@ object GlobalDefinitions {
aphelion_starfire_projectile.InitialVelocity = 45
aphelion_starfire_projectile.Lifespan = 7f
aphelion_starfire_projectile.ProjectileDamageType = DamageType.Aggravated
aphelion_starfire_projectile.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Direct, 0.25f, 250),
Aura.None,
2000,
0f,
true,
List(TargetValidation(EffectTarget.Category.Aircraft, EffectTarget.Validation.Aircraft))
)
aphelion_starfire_projectile.ExistsOnRemoteClients = true
aphelion_starfire_projectile.RemoteClientData = (39577, 249) //starfire_projectile data
aphelion_starfire_projectile.AutoLock = true
@ -2352,6 +2365,7 @@ object GlobalDefinitions {
chainblade_projectile.InitialVelocity = 100
chainblade_projectile.Lifespan = .02f
ProjectileDefinition.CalculateDerivedFields(chainblade_projectile)
chainblade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
colossus_100mm_projectile.Name = "colossus_100mm_projectile"
colossus_100mm_projectile.Damage0 = 58
@ -2437,9 +2451,24 @@ object GlobalDefinitions {
comet_projectile.DamageAtEdge = 0.45f
comet_projectile.DamageRadius = 1.0f
comet_projectile.ProjectileDamageType = DamageType.Aggravated
comet_projectile.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Direct, 0.25f, 500), //originally, .2
Aura.Comet,
AggravatedTiming(2000, 3),
10f,
List(
TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player),
TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.Vehicle),
TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret)
)
)
comet_projectile.InitialVelocity = 80
comet_projectile.Lifespan = 3.1f
ProjectileDefinition.CalculateDerivedFields(comet_projectile)
comet_projectile.Modifiers = List(
DamageModifiers.CometAggravated,
DamageModifiers.CometAggravatedBurn
)
dualcycler_projectile.Name = "dualcycler_projectile"
dualcycler_projectile.Damage0 = 18
@ -2457,6 +2486,7 @@ object GlobalDefinitions {
dynomite_projectile.Damage1 = 175
dynomite_projectile.DamageAtEdge = 0.1f
dynomite_projectile.DamageRadius = 10f
dynomite_projectile.GrenadeProjectile = true
dynomite_projectile.ProjectileDamageType = DamageType.Splash
dynomite_projectile.InitialVelocity = 30
dynomite_projectile.Lifespan = 3f
@ -2579,12 +2609,28 @@ object GlobalDefinitions {
flamethrower_fireball.Damage2 = 0
flamethrower_fireball.Damage3 = 20
flamethrower_fireball.Damage4 = 0
flamethrower_fireball.DamageToHealthOnly = true
flamethrower_fireball.DamageAtEdge = 0.15f
flamethrower_fireball.DamageRadius = 5f
flamethrower_fireball.ProjectileDamageType = DamageType.Aggravated
flamethrower_fireball.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.9f, 500), AggravatedInfo(DamageType.Splash, 0.9f, 500)),
Aura.Fire,
AggravatedTiming(5000, 10),
0.1f,
false,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
flamethrower_fireball.InitialVelocity = 15
flamethrower_fireball.Lifespan = 1.2f
ProjectileDefinition.CalculateDerivedFields(flamethrower_fireball)
flamethrower_fireball.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.InfantryAggravatedSplash,
DamageModifiers.RadialDegrade,
DamageModifiers.FireballAggravatedBurn
)
flamethrower_projectile.Name = "flamethrower_projectile"
flamethrower_projectile.Damage0 = 10
@ -2592,14 +2638,29 @@ object GlobalDefinitions {
flamethrower_projectile.Damage2 = 0
flamethrower_projectile.Damage3 = 4
flamethrower_projectile.Damage4 = 0
flamethrower_projectile.DamageToHealthOnly = true
flamethrower_projectile.Acceleration = -5
flamethrower_projectile.AccelerationUntil = 2f
flamethrower_projectile.ProjectileDamageType = DamageType.Aggravated
flamethrower_projectile.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.5f, 500)),
Aura.Fire,
AggravatedTiming(5000, 10),
0.5f,
false,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
flamethrower_projectile.DegradeDelay = 1.0f
flamethrower_projectile.DegradeMultiplier = 0.5f
flamethrower_projectile.InitialVelocity = 10
flamethrower_projectile.Lifespan = 2.0f
ProjectileDefinition.CalculateDerivedFields(flamethrower_projectile)
flamethrower_projectile.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.FireballAggravatedBurn,
DamageModifiers.MaxDistanceCutoff
)
flux_cannon_apc_projectile.Name = "flux_cannon_apc_projectile"
// TODO for later, maybe : set_resource_parent flux_cannon_apc_projectile game_objects flux_cannon_thresher_projectile
@ -2651,6 +2712,7 @@ object GlobalDefinitions {
forceblade_projectile.InitialVelocity = 100
forceblade_projectile.Lifespan = .02f
ProjectileDefinition.CalculateDerivedFields(forceblade_projectile)
forceblade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
frag_cartridge_projectile.Name = "frag_cartridge_projectile"
// TODO for later, maybe : set_resource_parent frag_cartridge_projectile game_objects frag_grenade_projectile
@ -2658,6 +2720,7 @@ object GlobalDefinitions {
frag_cartridge_projectile.Damage1 = 100
frag_cartridge_projectile.DamageAtEdge = 0.1f
frag_cartridge_projectile.DamageRadius = 7f
frag_cartridge_projectile.GrenadeProjectile = true
frag_cartridge_projectile.ProjectileDamageType = DamageType.Splash
frag_cartridge_projectile.InitialVelocity = 30
frag_cartridge_projectile.Lifespan = 15f
@ -2670,6 +2733,7 @@ object GlobalDefinitions {
frag_cartridge_projectile_b.Damage1 = 100
frag_cartridge_projectile_b.DamageAtEdge = 0.1f
frag_cartridge_projectile_b.DamageRadius = 5f
frag_cartridge_projectile_b.GrenadeProjectile = true
frag_cartridge_projectile_b.ProjectileDamageType = DamageType.Splash
frag_cartridge_projectile_b.InitialVelocity = 30
frag_cartridge_projectile_b.Lifespan = 2f
@ -2681,6 +2745,7 @@ object GlobalDefinitions {
frag_grenade_projectile.Damage1 = 100
frag_grenade_projectile.DamageAtEdge = 0.1f
frag_grenade_projectile.DamageRadius = 7f
frag_grenade_projectile.GrenadeProjectile = true
frag_grenade_projectile.ProjectileDamageType = DamageType.Splash
frag_grenade_projectile.InitialVelocity = 30
frag_grenade_projectile.Lifespan = 15f
@ -2693,6 +2758,7 @@ object GlobalDefinitions {
frag_grenade_projectile_enh.Damage1 = 100
frag_grenade_projectile_enh.DamageAtEdge = 0.1f
frag_grenade_projectile_enh.DamageRadius = 7f
frag_grenade_projectile_enh.GrenadeProjectile = true
frag_grenade_projectile_enh.ProjectileDamageType = DamageType.Splash
frag_grenade_projectile_enh.InitialVelocity = 30
frag_grenade_projectile_enh.Lifespan = 2f
@ -2743,6 +2809,7 @@ object GlobalDefinitions {
heavy_grenade_projectile.Damage4 = 66
heavy_grenade_projectile.DamageAtEdge = 0.1f
heavy_grenade_projectile.DamageRadius = 5f
heavy_grenade_projectile.GrenadeProjectile = true
heavy_grenade_projectile.ProjectileDamageType = DamageType.Splash
heavy_grenade_projectile.InitialVelocity = 75
heavy_grenade_projectile.Lifespan = 5f
@ -2828,6 +2895,7 @@ object GlobalDefinitions {
jammer_cartridge_projectile.Damage1 = 0
jammer_cartridge_projectile.DamageAtEdge = 1.0f
jammer_cartridge_projectile.DamageRadius = 10f
jammer_cartridge_projectile.GrenadeProjectile = true
jammer_cartridge_projectile.ProjectileDamageType = DamageType.Splash
jammer_cartridge_projectile.InitialVelocity = 30
jammer_cartridge_projectile.Lifespan = 15f
@ -2858,7 +2926,7 @@ object GlobalDefinitions {
EffectTarget.Validation.VehicleNotAMS
) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile)
jammer_cartridge_projectile.Modifiers = Nil
jammer_cartridge_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
jammer_cartridge_projectile_b.Name = "jammer_cartridge_projectile_b"
// TODO for later, maybe : set_resource_parent jammer_cartridge_projectile_b game_objects jammer_grenade_projectile_enh
@ -2866,6 +2934,7 @@ object GlobalDefinitions {
jammer_cartridge_projectile_b.Damage1 = 0
jammer_cartridge_projectile_b.DamageAtEdge = 1.0f
jammer_cartridge_projectile_b.DamageRadius = 10f
jammer_cartridge_projectile_b.GrenadeProjectile = true
jammer_cartridge_projectile_b.ProjectileDamageType = DamageType.Splash
jammer_cartridge_projectile_b.InitialVelocity = 30
jammer_cartridge_projectile_b.Lifespan = 2f
@ -2896,13 +2965,14 @@ object GlobalDefinitions {
EffectTarget.Validation.VehicleNotAMS
) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile_b)
jammer_cartridge_projectile_b.Modifiers = Nil
jammer_cartridge_projectile_b.Modifiers = DamageModifiers.MaxDistanceCutoff
jammer_grenade_projectile.Name = "jammer_grenade_projectile"
jammer_grenade_projectile.Damage0 = 0
jammer_grenade_projectile.Damage1 = 0
jammer_grenade_projectile.DamageAtEdge = 1.0f
jammer_grenade_projectile.DamageRadius = 10f
jammer_grenade_projectile.GrenadeProjectile = true
jammer_grenade_projectile.ProjectileDamageType = DamageType.Splash
jammer_grenade_projectile.InitialVelocity = 30
jammer_grenade_projectile.Lifespan = 15f
@ -2933,7 +3003,7 @@ object GlobalDefinitions {
EffectTarget.Validation.VehicleNotAMS
) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile)
jammer_grenade_projectile.Modifiers = Nil
jammer_grenade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
jammer_grenade_projectile_enh.Name = "jammer_grenade_projectile_enh"
// TODO for later, maybe : set_resource_parent jammer_grenade_projectile_enh game_objects jammer_grenade_projectile
@ -2941,6 +3011,7 @@ object GlobalDefinitions {
jammer_grenade_projectile_enh.Damage1 = 0
jammer_grenade_projectile_enh.DamageAtEdge = 1.0f
jammer_grenade_projectile_enh.DamageRadius = 10f
jammer_grenade_projectile_enh.GrenadeProjectile = true
jammer_grenade_projectile_enh.ProjectileDamageType = DamageType.Splash
jammer_grenade_projectile_enh.InitialVelocity = 30
jammer_grenade_projectile_enh.Lifespan = 3f
@ -2971,7 +3042,7 @@ object GlobalDefinitions {
EffectTarget.Validation.VehicleNotAMS
) -> 10000
ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile_enh)
jammer_grenade_projectile_enh.Modifiers = Nil
jammer_grenade_projectile_enh.Modifiers = DamageModifiers.MaxDistanceCutoff
katana_projectile.Name = "katana_projectile"
katana_projectile.Damage0 = 25
@ -3014,7 +3085,10 @@ object GlobalDefinitions {
lasher_projectile.LashRadius = 2.5f
lasher_projectile.Lifespan = 0.75f
ProjectileDefinition.CalculateDerivedFields(lasher_projectile)
lasher_projectile.Modifiers = List(DamageModifiers.DistanceDegrade, DamageModifiers.Lash)
lasher_projectile.Modifiers = List(
DamageModifiers.DistanceDegrade,
DamageModifiers.Lash
)
lasher_projectile_ap.Name = "lasher_projectile_ap"
lasher_projectile_ap.Damage0 = 12
@ -3029,7 +3103,10 @@ object GlobalDefinitions {
lasher_projectile_ap.LashRadius = 2.5f
lasher_projectile_ap.Lifespan = 0.75f
ProjectileDefinition.CalculateDerivedFields(lasher_projectile_ap)
lasher_projectile_ap.Modifiers = List(DamageModifiers.DistanceDegrade, DamageModifiers.Lash)
lasher_projectile_ap.Modifiers = List(
DamageModifiers.DistanceDegrade,
DamageModifiers.Lash
)
liberator_bomb_cluster_bomblet_projectile.Name = "liberator_bomb_cluster_bomblet_projectile"
liberator_bomb_cluster_bomblet_projectile.Damage0 = 75
@ -3071,6 +3148,7 @@ object GlobalDefinitions {
maelstrom_grenade_projectile.Damage1 = 60
maelstrom_grenade_projectile.DamageRadius = 20f
maelstrom_grenade_projectile.LashRadius = 5f
maelstrom_grenade_projectile.GrenadeProjectile = true
maelstrom_grenade_projectile.ProjectileDamageType = DamageType.Direct
maelstrom_grenade_projectile.InitialVelocity = 30
maelstrom_grenade_projectile.Lifespan = 2f
@ -3084,6 +3162,7 @@ object GlobalDefinitions {
maelstrom_grenade_projectile_contact.Damage1 = 60
maelstrom_grenade_projectile_contact.DamageRadius = 20f
maelstrom_grenade_projectile_contact.LashRadius = 5f
maelstrom_grenade_projectile_contact.GrenadeProjectile = true
maelstrom_grenade_projectile_contact.ProjectileDamageType = DamageType.Direct
maelstrom_grenade_projectile_contact.InitialVelocity = 30
maelstrom_grenade_projectile_contact.Lifespan = 15f
@ -3100,6 +3179,7 @@ object GlobalDefinitions {
maelstrom_stream_projectile.InitialVelocity = 200
maelstrom_stream_projectile.Lifespan = 0.2f
ProjectileDefinition.CalculateDerivedFields(maelstrom_stream_projectile)
maelstrom_stream_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
magcutter_projectile.Name = "magcutter_projectile"
// TODO for later, maybe : set_resource_parent magcutter_projectile game_objects melee_ammo_projectile
@ -3109,6 +3189,7 @@ object GlobalDefinitions {
magcutter_projectile.InitialVelocity = 100
magcutter_projectile.Lifespan = .02f
ProjectileDefinition.CalculateDerivedFields(magcutter_projectile)
magcutter_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
melee_ammo_projectile.Name = "melee_ammo_projectile"
melee_ammo_projectile.Damage0 = 25
@ -3117,6 +3198,7 @@ object GlobalDefinitions {
melee_ammo_projectile.InitialVelocity = 100
melee_ammo_projectile.Lifespan = .02f
ProjectileDefinition.CalculateDerivedFields(melee_ammo_projectile)
melee_ammo_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
meteor_common.Name = "meteor_common"
meteor_common.DamageAtEdge = .1f
@ -3208,6 +3290,7 @@ object GlobalDefinitions {
mine_sweeper_projectile.Damage1 = 0
mine_sweeper_projectile.DamageAtEdge = .33f
mine_sweeper_projectile.DamageRadius = 25f
mine_sweeper_projectile.GrenadeProjectile = true
mine_sweeper_projectile.ProjectileDamageType = DamageType.Splash
mine_sweeper_projectile.InitialVelocity = 30
mine_sweeper_projectile.Lifespan = 15f
@ -3219,6 +3302,7 @@ object GlobalDefinitions {
mine_sweeper_projectile_enh.Damage1 = 0
mine_sweeper_projectile_enh.DamageAtEdge = 0.33f
mine_sweeper_projectile_enh.DamageRadius = 25f
mine_sweeper_projectile_enh.GrenadeProjectile = true
mine_sweeper_projectile_enh.ProjectileDamageType = DamageType.Splash
mine_sweeper_projectile_enh.InitialVelocity = 30
mine_sweeper_projectile_enh.Lifespan = 3f
@ -3422,10 +3506,27 @@ object GlobalDefinitions {
plasma_cartridge_projectile.Damage1 = 15
plasma_cartridge_projectile.DamageAtEdge = 0.2f
plasma_cartridge_projectile.DamageRadius = 7f
plasma_cartridge_projectile.GrenadeProjectile = true
plasma_cartridge_projectile.ProjectileDamageType = DamageType.Aggravated
plasma_cartridge_projectile.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)),
Aura.Plasma,
AggravatedTiming(3000),
1.5f,
true,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
plasma_cartridge_projectile.InitialVelocity = 30
plasma_cartridge_projectile.Lifespan = 15f
ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile)
plasma_cartridge_projectile.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.InfantryAggravatedDirectBurn,
DamageModifiers.InfantryAggravatedSplash,
DamageModifiers.InfantryAggravatedSplashBurn,
DamageModifiers.RadialDegrade
)
plasma_cartridge_projectile_b.Name = "plasma_cartridge_projectile_b"
// TODO for later, maybe : set_resource_parent plasma_cartridge_projectile_b game_objects plasma_grenade_projectile_B
@ -3433,20 +3534,54 @@ object GlobalDefinitions {
plasma_cartridge_projectile_b.Damage1 = 15
plasma_cartridge_projectile_b.DamageAtEdge = 0.2f
plasma_cartridge_projectile_b.DamageRadius = 7f
plasma_cartridge_projectile_b.GrenadeProjectile = true
plasma_cartridge_projectile_b.ProjectileDamageType = DamageType.Aggravated
plasma_cartridge_projectile_b.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)),
Aura.Plasma,
AggravatedTiming(3000),
1.5f,
true,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
plasma_cartridge_projectile_b.InitialVelocity = 30
plasma_cartridge_projectile_b.Lifespan = 2f
ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile_b)
plasma_cartridge_projectile_b.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.InfantryAggravatedDirectBurn,
DamageModifiers.InfantryAggravatedSplash,
DamageModifiers.InfantryAggravatedSplashBurn,
DamageModifiers.RadialDegrade
)
plasma_grenade_projectile.Name = "plasma_grenade_projectile"
plasma_grenade_projectile.Damage0 = 40
plasma_grenade_projectile.Damage1 = 30
plasma_grenade_projectile.DamageAtEdge = 0.1f
plasma_grenade_projectile.DamageRadius = 7f
plasma_grenade_projectile.GrenadeProjectile = true
plasma_grenade_projectile.ProjectileDamageType = DamageType.Aggravated
plasma_grenade_projectile.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)),
Aura.Plasma,
AggravatedTiming(3000),
1.5f,
true,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
plasma_grenade_projectile.InitialVelocity = 30
plasma_grenade_projectile.Lifespan = 15f
ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile)
plasma_grenade_projectile.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.InfantryAggravatedDirectBurn,
DamageModifiers.InfantryAggravatedSplash,
DamageModifiers.InfantryAggravatedSplashBurn,
DamageModifiers.RadialDegrade
)
plasma_grenade_projectile_B.Name = "plasma_grenade_projectile_B"
// TODO for later, maybe : set_resource_parent plasma_grenade_projectile_B game_objects plasma_grenade_projectile
@ -3454,10 +3589,27 @@ object GlobalDefinitions {
plasma_grenade_projectile_B.Damage1 = 30
plasma_grenade_projectile_B.DamageAtEdge = 0.1f
plasma_grenade_projectile_B.DamageRadius = 7f
plasma_grenade_projectile_B.GrenadeProjectile = true
plasma_grenade_projectile_B.ProjectileDamageType = DamageType.Aggravated
plasma_grenade_projectile_B.Aggravated = AggravatedDamage(
List(AggravatedInfo(DamageType.Direct, 0.25f, 750), AggravatedInfo(DamageType.Splash, 0.25f, 1000)),
Aura.Plasma,
AggravatedTiming(3000),
1.5f,
true,
false,
List(TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player))
)
plasma_grenade_projectile_B.InitialVelocity = 30
plasma_grenade_projectile_B.Lifespan = 3f
ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile_B)
plasma_grenade_projectile_B.Modifiers = List(
DamageModifiers.InfantryAggravatedDirect,
DamageModifiers.InfantryAggravatedDirectBurn,
DamageModifiers.InfantryAggravatedSplash,
DamageModifiers.InfantryAggravatedSplashBurn,
DamageModifiers.RadialDegrade
)
pounder_projectile.Name = "pounder_projectile"
pounder_projectile.Damage0 = 31
@ -3467,6 +3619,7 @@ object GlobalDefinitions {
pounder_projectile.Damage4 = 132
pounder_projectile.DamageAtEdge = 0.1f
pounder_projectile.DamageRadius = 1f
pounder_projectile.GrenadeProjectile = true
pounder_projectile.ProjectileDamageType = DamageType.Splash
pounder_projectile.InitialVelocity = 120
pounder_projectile.Lifespan = 2.5f
@ -3482,6 +3635,7 @@ object GlobalDefinitions {
pounder_projectile_enh.Damage4 = 132
pounder_projectile_enh.DamageAtEdge = 0.1f
pounder_projectile_enh.DamageRadius = 1f
pounder_projectile_enh.GrenadeProjectile = true
pounder_projectile_enh.ProjectileDamageType = DamageType.Splash
pounder_projectile_enh.InitialVelocity = 120
pounder_projectile_enh.Lifespan = 3.2f
@ -3533,6 +3687,7 @@ object GlobalDefinitions {
ProjectileDefinition.CalculateDerivedFields(quasar_projectile)
radiator_grenade_projectile.Name = "radiator_grenade_projectile" // Todo : Radiator damages ?
radiator_grenade_projectile.GrenadeProjectile = true //not really, but technically yes
radiator_grenade_projectile.ProjectileDamageType = DamageType.Direct
radiator_grenade_projectile.InitialVelocity = 30
radiator_grenade_projectile.Lifespan = 3f
@ -3540,6 +3695,7 @@ object GlobalDefinitions {
radiator_sticky_projectile.Name = "radiator_sticky_projectile"
// TODO for later, maybe : set_resource_parent radiator_sticky_projectile game_objects radiator_grenade_projectile
radiator_sticky_projectile.GrenadeProjectile = true //not really, but technically yes
radiator_sticky_projectile.ProjectileDamageType = DamageType.Direct
radiator_sticky_projectile.InitialVelocity = 30
radiator_sticky_projectile.Lifespan = 4f
@ -3602,7 +3758,7 @@ object GlobalDefinitions {
rocklet_jammer_projectile.InitialVelocity = 50
rocklet_jammer_projectile.Lifespan = 8f
ProjectileDefinition.CalculateDerivedFields(rocklet_jammer_projectile)
rocklet_jammer_projectile.Modifiers = Nil
//TODO rocklet_jammer_projectile.Modifiers = DamageModifiers.RadialDegrade?
scattercannon_projectile.Name = "scattercannon_projectile"
scattercannon_projectile.Damage0 = 11
@ -3722,7 +3878,6 @@ object GlobalDefinitions {
spiker_projectile.InitialVelocity = 40
spiker_projectile.Lifespan = 5f
ProjectileDefinition.CalculateDerivedFields(spiker_projectile)
spiker_projectile.Modifiers = DamageModifiers.RadialDegrade
spitfire_aa_ammo_projectile.Name = "spitfire_aa_ammo_projectile"
spitfire_aa_ammo_projectile.Damage0 = 5
@ -3756,6 +3911,18 @@ object GlobalDefinitions {
starfire_projectile.Acceleration = 12
starfire_projectile.AccelerationUntil = 5f
starfire_projectile.ProjectileDamageType = DamageType.Aggravated
starfire_projectile.Aggravated = AggravatedDamage(
AggravatedInfo(DamageType.Direct, 0.25f, 250),
Aura.Comet,
2000,
0f,
true,
List(
TargetValidation(EffectTarget.Category.Player, EffectTarget.Validation.Player),
TargetValidation(EffectTarget.Category.Vehicle, EffectTarget.Validation.Vehicle),
TargetValidation(EffectTarget.Category.Turret, EffectTarget.Validation.Turret)
)
)
starfire_projectile.InitialVelocity = 45
starfire_projectile.Lifespan = 7.8f
starfire_projectile.ExistsOnRemoteClients = true
@ -3763,6 +3930,10 @@ object GlobalDefinitions {
starfire_projectile.AutoLock = true
starfire_projectile.Packet = projectileConverter
ProjectileDefinition.CalculateDerivedFields(starfire_projectile)
starfire_projectile.Modifiers = List(
DamageModifiers.StarfireAggravatedBurn,
DamageModifiers.RadialDegrade
)
striker_missile_projectile.Name = "striker_missile_projectile"
striker_missile_projectile.Damage0 = 35
@ -3813,6 +3984,7 @@ object GlobalDefinitions {
trek_projectile.InitialVelocity = 40
trek_projectile.Lifespan = 7f
ProjectileDefinition.CalculateDerivedFields(trek_projectile)
trek_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff
vanu_sentry_turret_projectile.Name = "vanu_sentry_turret_projectile"
vanu_sentry_turret_projectile.Damage0 = 25
@ -4491,7 +4663,7 @@ object GlobalDefinitions {
flamethrower.FireModes(1).AmmoSlotIndex = 0
flamethrower.FireModes(1).Magazine = 100
flamethrower.FireModes(1).RoundsPerShot = 50
flamethrower.Tile = InventoryTile.Tile63
flamethrower.Tile = InventoryTile.Tile93
winchester.Name = "winchester"
winchester.Size = EquipmentSize.Rifle

View file

@ -1,12 +1,20 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects
import net.psforever.objects.avatar.{Avatar, LoadoutManager}
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
import net.psforever.objects.avatar.{
Avatar,
LoadoutManager
}
import net.psforever.objects.definition.{
AvatarDefinition,
ExoSuitDefinition,
SpecialExoSuitDefinition
}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
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.aura.AuraContainer
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
import net.psforever.objects.zones.ZoneAware
@ -22,7 +30,8 @@ class Player(var avatar: Avatar)
with ResistanceProfile
with Container
with JammableUnit
with ZoneAware {
with ZoneAware
with AuraContainer {
Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead
Destroyed = true //see isAlive
private var backpack: Boolean = false

View file

@ -44,7 +44,6 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
def JammableObject = gen
def DamageableObject = gen
def RepairableObject = gen
private var handleDamageToShields: Boolean = false
def receive: Receive =
jammableBehavior
@ -90,18 +89,20 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable)
target,
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"
)
handleDamageToShields = damageToShields > 0
HandleDamage(target, cause, damageToHealth)
HandleDamage(target, cause, (damageToHealth, damageToShields))
} else {
gen.Health = originalHealth
gen.Shields = originalShields
}
}
override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = {
super.DamageAwareness(target, cause, amount)
ShieldGeneratorControl.DamageAwareness(gen, cause, handleDamageToShields)
handleDamageToShields = false
override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = {
val (damageToHealth, damageToShields) = amount match {
case (a: Int, b: Int) => (a, b)
case _ => (0, 0)
}
super.DamageAwareness(target, cause, damageToHealth)
ShieldGeneratorControl.DamageAwareness(gen, cause, damageToShields > 0)
}
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {

View file

@ -75,6 +75,11 @@ class TurretControl(turret: TurretDeployable)
def DamageableObject = turret
def RepairableObject = turret
override def postStop(): Unit = {
super.postStop()
damageableWeaponTurretPostStop()
}
def receive: Receive =
checkBehavior
.orElse(jammableBehavior)

View file

@ -7,6 +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.aura.AuraContainer
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.AmenityOwner
@ -78,7 +79,8 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
with StandardResistanceProfile
with JammableUnit
with CommonNtuContainer
with Container {
with Container
with AuraContainer {
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var shields: Int = 0
private var decal: Int = 0

View file

@ -8,14 +8,17 @@ 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.aura.{Aura, AuraEffectBehavior}
import net.psforever.objects.serverobject.containable.{Containable, ContainableBehavior}
import net.psforever.objects.vital.{PlayerSuicide, Vitality}
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.PlayerSuicide
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, Damageable}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.repair.Repairable
import net.psforever.objects.serverobject.terminals.Terminal
import net.psforever.objects.vital._
import net.psforever.objects.vital.resolution.ResolutionCalculations.Output
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
@ -30,13 +33,24 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
extends Actor
with JammableBehavior
with Damageable
with ContainableBehavior {
def JammableObject = player
with ContainableBehavior
with AggravatedBehavior
with AuraEffectBehavior {
def JammableObject = player
def DamageableObject = player
def ContainerObject = player
def AggravatedObject = player
ApplicableEffect(Aura.Plasma)
ApplicableEffect(Aura.Napalm)
ApplicableEffect(Aura.Comet)
ApplicableEffect(Aura.Fire)
def AuraTargetObject = player
private[this] val log = org.log4s.getLogger(player.Name)
private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
@ -53,11 +67,15 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
override def postStop(): Unit = {
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 {
case Player.Die() =>
@ -480,29 +498,31 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case _ => ;
}
protected def TakesDamage: Receive = {
case Vitality.Damage(applyDamageTo) =>
if (player.isAlive && !player.spectator) {
val originalHealth = player.Health
val originalArmor = player.Armor
val originalStamina = player.avatar.stamina
val originalCapacitor = player.Capacitor.toInt
val cause = applyDamageTo(player)
val health = player.Health
val armor = player.Armor
val stamina = player.avatar.stamina
val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor
val damageToStamina = originalStamina - stamina
val damageToCapacitor = originalCapacitor - capacitor
HandleDamage(player, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
if (damageToHealth > 0 || damageToArmor > 0 || damageToStamina > 0 || damageToCapacitor > 0) {
damageLog.info(
s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalStamina/$originalCapacitor, AFTER=$health/$armor/$stamina/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToStamina/$damageToCapacitor"
)
}
override protected def PerformDamage(
target: Target,
applyDamageTo: Output
): Unit = {
if (player.isAlive && !player.spectator) {
val originalHealth = player.Health
val originalArmor = player.Armor
val originalStamina = player.avatar.stamina
val originalCapacitor = player.Capacitor.toInt
val cause = applyDamageTo(player)
val health = player.Health
val armor = player.Armor
val stamina = player.avatar.stamina
val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor
val damageToStamina = originalStamina - stamina
val damageToCapacitor = originalCapacitor - capacitor
HandleDamage(player, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
if (damageToHealth > 0 || damageToArmor > 0 || damageToStamina > 0 || damageToCapacitor > 0) {
damageLog.info(
s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalStamina/$originalCapacitor, AFTER=$health/$armor/$stamina/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToStamina/$damageToCapacitor"
)
}
}
}
/**
@ -510,70 +530,110 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
* @param target na
*/
def HandleDamage(
target: Player,
cause: ResolvedProjectile,
damageToHealth: Int,
damageToArmor: Int,
damageToStamina: Int,
damageToCapacitor: Int
): Unit = {
val targetGUID = target.GUID
val zone = target.Zone
val zoneId = zone.id
val events = zone.AvatarEvents
val health = target.Health
target: Player,
cause: ResolvedProjectile,
damageToHealth: Int,
damageToArmor: Int,
damageToStamina: Int,
damageToCapacitor: Int
): Unit = {
//always do armor update
if (damageToArmor > 0) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
val zone = target.Zone
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
AvatarAction.PlanetsideAttributeToAll(target.GUID, 4, target.Armor)
)
}
if (health > 0) {
if (damageToCapacitor > 0) {
events ! AvatarServiceMessage(
target.Name,
AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong)
)
}
if (damageToHealth > 0 || damageToStamina > 0) {
target.History(cause)
if (damageToHealth > 0) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
}
if (damageToStamina > 0) {
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
}
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert damage source
DamageAwareness(target, cause)
}
if (Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause)
}
//choose
if (target.Health > 0) {
DamageAwareness(target, cause, damageToHealth, damageToArmor, damageToStamina, damageToCapacitor)
} else {
DestructionAwareness(target, Some(cause))
}
}
/**
* na
* @param target na
* @param cause na
*/
def DamageAwareness(target: Player, cause: ResolvedProjectile): Unit = {
val zone = target.Zone
zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
cause.projectile.owner match {
case pSource: PlayerSource => //player damage
val name = pSource.Name
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
case Some(tplayer) => AvatarAction.HitHint(tplayer.GUID, target.GUID)
case None =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
def DamageAwareness(
target: Player,
cause: ResolvedProjectile,
damageToHealth: Int,
damageToArmor: Int,
damageToStamina: Int,
damageToCapacitor: Int
): Unit = {
val targetGUID = target.GUID
val zone = target.Zone
val zoneId = zone.id
val events = zone.AvatarEvents
val health = target.Health
var announceConfrontation = damageToArmor > 0
//special effects
if (Damageable.CanJammer(target, cause)) {
TryJammerEffectActivate(target, cause)
}
val aggravated: Boolean = TryAggravationEffectActivate(cause) match {
case Some(aggravation) =>
StartAuraEffect(aggravation.effect_type, aggravation.timing.duration)
announceConfrontation = true //useful if initial damage (to anything) is zero
//initial damage for aggravation, but never treat as "aggravated"
false
case _ =>
cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)
}
//log historical event
target.History(cause)
//stat changes
if (damageToCapacitor > 0) {
events ! AvatarServiceMessage(
target.Name,
AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong)
)
announceConfrontation = true
}
if (damageToStamina > 0) {
avatarActor ! AvatarActor.ConsumeStamina(damageToStamina)
announceConfrontation = true
}
if (damageToHealth > 0) {
events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
announceConfrontation = true
}
val countableDamage = damageToHealth + damageToArmor
if(announceConfrontation) {
if (!aggravated) {
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert to damage source
zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
cause.projectile.owner match {
case pSource: PlayerSource => //player damage
val name = pSource.Name
zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
case Some(tplayer) =>
AvatarAction.HitHint(tplayer.GUID, target.GUID)
case None =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, pSource.Position))
}
case source =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, source.Position))
}
case source =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
)
}
)
else {
//general alert
zone.AvatarEvents ! AvatarServiceMessage(
target.Name,
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, Vector3.Zero))
)
}
}
if (aggravated) {
events ! AvatarServiceMessage(
zoneId,
AvatarAction.SendResponse(Service.defaultPlayerGUID, AggravatedDamageMessage(targetGUID, countableDamage))
)
}
}
/**
@ -600,6 +660,10 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
val nameChannel = target.Name
val zoneChannel = zone.id
target.Die
//aura effects cancel
EndAllEffects()
//aggravation cancel
EndAllAggravation()
//unjam
CancelJammeredSound(target)
CancelJammeredStatus(target)
@ -864,4 +928,28 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item.GUID)
)
}
def UpdateAuraEffect(target: AuraEffectBehavior.Target) : Unit = {
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))
}
}
object PlayerControl {
/**
* Transform an applicable Aura effect into its `PlanetsideAttributeMessage` value.
* @see `Aura`
* @see `PlanetsideAttributeMessage`
* @param effect the aura effect
* @return the attribute value for that effect
*/
private def auraEffectToAttributeValue(effect: Aura): Int = effect match {
case Aura.Plasma => 1
case Aura.Comet => 2
case Aura.Napalm => 4
case Aura.Fire => 8
case _ => 0
}
}

View file

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

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
@ -10,11 +12,11 @@ import net.psforever.types.Vector3
/**
* A summation of weapon (`Tool`) discharge.
* @see `ProjectileDefinition`<br>
* `ToolDefinition`<br>
* `FireModeDefinition`<br>
* `SourceEntry`<br>
* `PlayerSource`
* @see `ProjectileDefinition`
* @see `ToolDefinition`
* @see `FireModeDefinition`
* @see `SourceEntry`
* @see `PlayerSource`
* @param profile an explanation of the damage that can be performed by this discharge
* @param tool_def the weapon that caused this discharge
* @param fire_mode the current fire mode of the tool used
@ -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: ProjectileQuality = ProjectileQuality.Normal,
id: Long = Projectile.idGenerator.getAndIncrement(),
fire_time: Long = System.nanoTime
) extends PlanetSideGameObject {
Position = shot_origin
@ -52,6 +59,32 @@ 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: ProjectileQuality): Projectile = {
val projectile = Projectile(
profile,
tool_def,
fire_mode,
owner,
attribute_to,
shot_origin,
shot_angle,
value,
id,
fire_time
)
if(isMiss) projectile.Miss()
else if(isResolved) projectile.Resolve()
projectile
}
/**
* Mark the projectile as being "encountered" or "managed" at least once.
*/
@ -80,6 +113,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

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

View file

@ -2,16 +2,32 @@
package net.psforever.objects.ballistics
/**
* An `Enumeration` of outcomes regarding what actually happened to the projectile.
* An `Enumeration` of outcomes regarding what actually happened to the projectile,
* complementing normal damage type distinction in directing damage calculations.<br>
* <br>
* Although the latter states reflect what sort of damage the projectile might perform - `Hit`, `Splash`, etc. -
* the state is more a communication about how that damage is interpreted by the server.
* For example, some projectiles:
* perform `Direct` damage, are reported by `HitMessage` packets, and resolve as `Hit`;
* or, perform `Direct` damage, are reported by `LashDamage` packets, and resolve as `Lash`.
* Furthermore, some projectiles:
* perform `Splash` damage, are reported by `SplashHitMessage` packets, and resolve as `Splash`;
* or, perform `Aggravated` damage, are reported by `SplashHitMessage` packets
* and resolve either as `AggravatedDirect` or as `AggravatedSplash`.
*/
object ProjectileResolution extends Enumeration {
type Type = Value
val Unresolved, //original basic non-resolution
MissedShot, //projectile did not encounter any collision object and was despawned
Resolved, //a general "projectile encountered something" status with a more specific resolution
Hit, //direct hit, one target
Splash, //area of effect damage, potentially multiple targets
Lash //lashing damage, potentially multiple targets
val
Unresolved, //original basic non-resolution
MissedShot, //projectile did not encounter any collision object and was despawned
Resolved, //a general "projectile encountered something" status with a more specific resolution
Hit, //direct hit, one target
Splash, //area of effect damage, potentially multiple targets
Lash, //lashing damage, potentially multiple targets
AggravatedDirect, //direct hit aggravated damage
AggravatedDirectBurn, //continuous direct hit aggravated damage
AggravatedSplash, //splashed aggravated damage
AggravatedSplashBurn //continuous splashed aggravated damage
= Value
}

View file

@ -9,14 +9,13 @@ import net.psforever.types.Vector3
* about the interaction of weapons discharge and a target
* to the point that the original event might be reconstructed.
* Reenacting the calculations of this entry should always produce the same values.
* @param resolution how the projectile hit was executed
* @param projectile the original projectile
* @param target what the projectile hit
* @param damage_model the kind of damage model to which the `target` is/was subject
* @param hit_pos where the projectile hit
*/
final case class ResolvedProjectile(
resolution: ProjectileResolution.Value,
resolution : ProjectileResolution.Value,
projectile: Projectile,
target: SourceEntry,
damage_model: DamageResistanceModel,

View file

@ -33,7 +33,7 @@ object Deployable {
def Includes(category: DeployableCategory.Value): List[DeployedItem.Value] = {
(for {
(ce, cat) <- deployablesToCategories
(ce: DeployedItem.Value, cat: DeployableCategory.Value) <- deployablesToCategories
if cat == category
} yield ce) toList
}

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.definition
import net.psforever.objects.ballistics.Projectiles
import net.psforever.objects.ballistics.{AggravatedDamage, Projectiles}
import net.psforever.objects.equipment.JammingUnit
import net.psforever.objects.vital.damage.DamageModifiers
import net.psforever.objects.vital.{DamageType, StandardDamageProfile}
@ -16,30 +16,68 @@ class ProjectileDefinition(objectId: Int)
with JammingUnit
with StandardDamageProfile
with DamageModifiers {
/** ascertain that this object is a valid projectile type */
private val projectileType: Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException
/** how much faster (or slower) the projectile moves (m/s^2^) */
private var acceleration: Int = 0
/** when the acceleration stops being applied (s) */
private var accelerationUntil: Float = 0f
/** the type of damage that the projectile causes */
private var damageType: DamageType.Value = DamageType.None
/** an auxillary type of damage that the projectile causes */
private var damageTypeSecondary: DamageType.Value = DamageType.None
/** against Infantry targets, this projectile does not do armor damage */
private var damageToHealthOnly: Boolean = false
/** number of seconds before an airborne projectile's damage begins to degrade (s) */
private var degradeDelay: Float = 1f
/** the rate of degrade of projectile damage after the degrade delay */
private var degradeMultiplier: Float = 1f
/** the out-of-the-muzzle speed of a projectile (m/s) */
private var initialVelocity: Int = 1
/** for how long the projectile exists (s) */
private var lifespan: Float = 1f
/** for radial damage, how much damage has been lost the further away from the impact point (m) */
private var damageAtEdge: Float = 1f
/** for radial damage, the radial distance of the explosion effect (m) */
private var damageRadius: Float = 1f
/** for lashing damage, how far away a target will be affected by the projectile (m) */
private var lashRadius : Float = 0f
/** use a specific modifier as a part of damage calculations */
private var useDamage1Subtract: Boolean = false
private var existsOnRemoteClients: Boolean = false //`true` spawns a server-managed object
private var remoteClientData: (Int, Int) =
(0, 0) //artificial values; for ObjectCreateMessage packet (oicw_little_buddy is undefined)
/** the projectile is represented by a server-side entity
* that is updated by the projectile owner
* and transmitted to all projectile observers;
* `true` spawns a server-managed object */
private var existsOnRemoteClients: Boolean = false
/** the values used by the `ObjectCreateMessage` packet for construction of the server-managed projectile
* `0, 0` are artificial values;
* the oicw_little_buddy is undefined for these values */
private var remoteClientData: (Int, Int) = (0, 0)
/** some other entity confers projectile damage;
* a set value should not `None` and not `0` but is preferred to be the damager's uid */
private var damageProxy: Option[Int] = None
/** this projectile follows its target, after a fashion */
private var autoLock: Boolean = false
/** na;
* currently used with jammer properties only */
private var additionalEffect: Boolean = false
/** the projectile tries to confer the jammered status effect to its target(s) */
private var jammerProjectile: Boolean = false
/** projectile takes the form of a type of "grenade";
* grenades arc with gravity rather than travel in a relatively straight path */
private var grenade_projectile : Boolean = false
/** projectile tries to confers aggravated damage burn to its target */
private var aggravated_damage : Option[AggravatedDamage] = None
//derived calculations
/** the calculated distance at which the projectile have traveled far enough to despawn (m);
* typically handled as the projectile no longer performing damage;
* occasionally, this value is purely mathematical as opposed to realistic, e.g., the melee weapons */
private var distanceMax: Float = 0f
/** how far the projectile will travel while accelerating (m) */
private var distanceFromAcceleration: Float = 0f
/** how far the projectile will travel while no degrading (m) */
private var distanceNoDegrade: Float = 0f
/** after acceleration, if any, what is the final speed of the projectile (m/s) */
private var finalVelocity: Float = 0f
Name = "projectile"
Modifiers = DamageModifiers.DistanceDegrade
@ -81,6 +119,17 @@ class ProjectileDefinition(objectId: Int)
ProjectileDamageTypeSecondary
}
def ProjectileDamageTypes : Set[DamageType.Value] = {
Set(damageType, damageTypeSecondary).filterNot(_ == DamageType.None)
}
def DamageToHealthOnly : Boolean = damageToHealthOnly
def DamageToHealthOnly_=(healthOnly: Boolean) : Boolean = {
damageToHealthOnly = healthOnly
DamageToHealthOnly
}
def DegradeDelay: Float = degradeDelay
def DegradeDelay_=(degradeDelay: Float): Float = {
@ -174,7 +223,23 @@ class ProjectileDefinition(objectId: Int)
JammerProjectile
}
def DistanceMax: Float = distanceMax //accessor only
def GrenadeProjectile : Boolean = grenade_projectile
def GrenadeProjectile_=(isGrenade : Boolean) : Boolean = {
grenade_projectile = isGrenade
GrenadeProjectile
}
def Aggravated : Option[AggravatedDamage] = aggravated_damage
def Aggravated_=(damage : AggravatedDamage) : Option[AggravatedDamage] = Aggravated_=(Some(damage))
def Aggravated_=(damage : Option[AggravatedDamage]) : Option[AggravatedDamage] = {
aggravated_damage = damage
Aggravated
}
def DistanceMax : Float = distanceMax //accessor only
def DistanceFromAcceleration: Float = distanceFromAcceleration //accessor only

View file

@ -89,10 +89,10 @@ object EffectTarget {
false
}
def AMS(target: PlanetSideGameObject): Boolean =
def Vehicle(target: PlanetSideGameObject): Boolean =
target match {
case v: Vehicle =>
v.Health > 0 && v.Definition == GlobalDefinitions.ams
v.Health > 0
case _ =>
false
}
@ -104,5 +104,21 @@ object EffectTarget {
case _ =>
false
}
def AMS(target: PlanetSideGameObject): Boolean =
target match {
case v: Vehicle =>
v.Health > 0 && v.Definition == GlobalDefinitions.ams
case _ =>
false
}
def Aircraft(target: PlanetSideGameObject): Boolean =
target match {
case v: Vehicle =>
GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0
case _ =>
false
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

@ -8,6 +8,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.ResolutionCalculations
/**
* The base "control" `Actor` mixin for damage-handling code.
@ -24,16 +25,35 @@ trait Damageable {
*/
def DamageableObject: Damageable.Target
/** the official mixin hook; `orElse` onto the "control" `Actor` `receive` */
final val takesDamage: Receive = TakesDamage
/** the official mixin hook;
* `orElse` onto the "control" `Actor` `receive`; or,
* cite the `originalTakesDamage` protocol during inheritance overrides */
val takesDamage: Receive = {
case Vitality.Damage(damage_func) =>
val obj = DamageableObject
if (obj.CanDamage) {
PerformDamage(obj, damage_func)
}
}
/** a duplicate of the core implementation for the default mixin hook, for use in overriding */
final val originalTakesDamage: Receive = {
case Vitality.Damage(damage_func) =>
val obj = DamageableObject
if (obj.CanDamage) {
PerformDamage(obj, damage_func)
}
}
/**
* Implementation of the mixin hook will be provided by a child class.
* Override this method only when directly implementing.
* @see `takesDamage`
* @see `DamageableAmenity.PerformDamage`
* Assess the vital statistics of the target, apply the damage, and determine if any of those statistics changed.
* By default, only take an interest in the change of "health".
* If implementing custom damage with no new message handling, override this method.
* @see `ResolutionCalculations.Output`
* @param target the entity to be damaged
* @param applyDamageTo the function that applies the damage to the target in a target-tailored fashion
*/
protected def TakesDamage: Receive
protected def PerformDamage(target: Damageable.Target, applyDamageTo: ResolutionCalculations.Output): Unit
}
object Damageable {
@ -59,7 +79,7 @@ object Damageable {
*/
def CanDamage(obj: Vitality with FactionAffinity, damage: Int, data: ResolvedProjectile): Boolean = {
val definition = obj.Definition
damage > 0 &&
(damage > 0 || data.projectile.profile.Aggravated.nonEmpty) &&
definition.Damageable &&
(definition.DamageableByFriendlyFire ||
(data.projectile.owner.Faction != obj.Faction ||

View file

@ -1,10 +1,8 @@
//Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.damage
import akka.actor.Actor.Receive
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.objects.zones.Zone
import net.psforever.types.PlanetSideGUID
@ -42,23 +40,6 @@ trait DamageableEntity extends Damageable {
DamageLog(s"${name.substring(slashPoint + 1, name.length - 1)}: $msg")
}
/**
* Catch the expected damage message and apply checks to the target.
* If adding custom message handling in an future child implementation,
* override this method and call `super.TakesDamage.orElse { ... }`.
* @see `Damageable.TakesDamage`
* @see `ResolutionCalcultions.Output`
* @see `Vitality.CanDamage`
* @see `Vitality.Damage`
*/
protected def TakesDamage: Receive = {
case Vitality.Damage(damage_func) =>
val obj = DamageableObject
if (obj.CanDamage) {
PerformDamage(obj, damage_func)
}
}
/**
* Assess the vital statistics of the target, apply the damage, and determine if any of those statistics changed.
* By default, only take an interest in the change of "health".
@ -108,7 +89,7 @@ trait DamageableEntity extends Damageable {
* @param cause historical information about the damage
* @param damage the amount of damage
*/
protected def HandleDamage(target: Damageable.Target, cause: ResolvedProjectile, damage: Int): Unit = {
protected def HandleDamage(target: Damageable.Target, cause: ResolvedProjectile, damage: Any): Unit = {
if (!target.Destroyed && target.Health <= target.Definition.DamageDestroysAt) {
DestructionAwareness(target, cause)
} else {
@ -122,8 +103,12 @@ trait DamageableEntity extends Damageable {
* @param cause historical information about the damage
* @param amount the amount of damage
*/
protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = {
DamageableEntity.DamageAwareness(target, cause, amount)
protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = {
amount match {
case value: Int =>
DamageableEntity.DamageAwareness(target, cause, value)
case _ => ;
}
}
/**
@ -162,16 +147,22 @@ object DamageableEntity {
if (Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause)
}
if (amount > 0) {
if (DamageToHealth(target, cause, amount)) {
target.Zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
}
}
def DamageToHealth(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Boolean = {
if (amount > 0 && !target.Destroyed) {
val zone = target.Zone
if (!target.Destroyed) {
val tguid = target.GUID
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health)
)
}
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
zone.AvatarEvents ! AvatarServiceMessage(
zone.id,
AvatarAction.PlanetsideAttributeToAll(target.GUID, 0, target.Health)
)
true
}
else {
false
}
}

View file

@ -26,8 +26,13 @@ object DamageableMountable {
* @see `Zone.LivePlayers`
* @param target the entity being damaged
* @param cause historical information about the damage
* @param countableDamage the amount of damage being done, translating to the intensity of the damage indicator
*/
def DamageAwareness(target: Damageable.Target with Mountable, cause: ResolvedProjectile): Unit = {
def DamageAwareness(
target: Damageable.Target with Mountable,
cause: ResolvedProjectile,
countableDamage: Int
): Unit = {
val zone = target.Zone
val events = zone.AvatarEvents
val occupants = target.Seats.values.collect {
@ -38,9 +43,10 @@ object DamageableMountable {
case pSource: PlayerSource => //player damage
val name = pSource.Name
(zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
case Some(player) => AvatarAction.HitHint(player.GUID, player.GUID)
case Some(player) =>
AvatarAction.HitHint(player.GUID, player.GUID)
case None =>
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, pSource.Position))
}) match {
case AvatarAction.HitHint(_, guid) =>
occupants.map { tplayer => (tplayer.Name, AvatarAction.HitHint(guid, tplayer.GUID)) }
@ -48,7 +54,7 @@ object DamageableMountable {
occupants.map { tplayer => (tplayer.Name, msg) }
}
case source => //object damage
val msg = AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
val msg = AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, source.Position))
occupants.map { tplayer => (tplayer.Name, msg) }
}).foreach {
case (channel, msg) =>

View file

@ -1,43 +1,55 @@
//Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.damage
import akka.actor.Actor.Receive
import akka.actor.Actor
import net.psforever.objects.{Vehicle, Vehicles}
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.damage.Damageable.Target
import net.psforever.objects.vital.DamageType
import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.DamageWithPositionMessage
import net.psforever.types.Vector3
import scala.concurrent.duration._
/**
* The "control" `Actor` mixin for damage-handling code for `Vehicle` objects.
*/
trait DamageableVehicle extends DamageableEntity {
trait DamageableVehicle
extends DamageableEntity
with AggravatedBehavior {
_ : Actor =>
/** vehicles (may) have shields; they need to be handled */
private var handleDamageToShields: Boolean = false
def damageableVehiclePostStop(): Unit = {
EndAllAggravation()
}
/** whether or not the vehicle has been damaged directly, report that damage has occurred */
private var reportDamageToVehicle: Boolean = false
def DamageableObject: Vehicle
def AggravatedObject : Vehicle = DamageableObject
override protected def TakesDamage: Receive =
super.TakesDamage.orElse {
case DamageableVehicle.Damage(cause, damage) =>
//cargo vehicles inherit feedback from carrier
reportDamageToVehicle = damage > 0
DamageAwareness(DamageableObject, cause, amount = 0)
override val takesDamage: Receive =
originalTakesDamage
.orElse(aggravatedBehavior)
.orElse {
case DamageableVehicle.Damage(cause, damage) =>
//cargo vehicles inherit feedback from carrier
reportDamageToVehicle = damage > 0
DamageAwareness(DamageableObject, cause, amount = 0)
case DamageableVehicle.Destruction(cause) =>
//cargo vehicles are destroyed when carrier is destroyed
val obj = DamageableObject
obj.Health = 0
obj.History(cause)
DestructionAwareness(obj, cause)
}
case DamageableVehicle.Destruction(cause) =>
//cargo vehicles are destroyed when carrier is destroyed
val obj = DamageableObject
obj.Health = 0
obj.History(cause)
DestructionAwareness(obj, cause)
}
/**
* Vehicles may have charged shields that absorb damage before the vehicle's own health is affected.
@ -62,53 +74,13 @@ trait DamageableVehicle extends DamageableEntity {
target,
s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields"
)
handleDamageToShields = damageToShields > 0
HandleDamage(target, cause, damageToHealth + damageToShields)
HandleDamage(target, cause, (damageToHealth, damageToShields))
} else {
obj.Health = originalHealth
obj.Shields = originalShields
}
}
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = {
val obj = DamageableObject
val handleShields = handleDamageToShields
handleDamageToShields = false
val handleReport = reportDamageToVehicle || amount > 0
reportDamageToVehicle = false
if (Damageable.CanDamageOrJammer(target, amount, cause)) {
super.DamageAwareness(target, cause, amount)
}
if (handleReport) {
DamageableMountable.DamageAwareness(obj, cause)
}
DamageableVehicle.DamageAwareness(obj, cause, amount, handleShields)
}
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {
super.DestructionAwareness(target, cause)
val obj = DamageableObject
DamageableMountable.DestructionAwareness(obj, cause)
DamageableVehicle.DestructionAwareness(obj, cause)
DamageableWeaponTurret.DestructionAwareness(obj, cause)
}
}
object DamageableVehicle {
/**
* Message for instructing the target's cargo vehicles about a damage source affecting their carrier.
* @param cause historical information about damage
*/
private case class Damage(cause: ResolvedProjectile, amount: Int)
/**
* Message for instructing the target's cargo vehicles that their carrier is destroyed,
* and they should be destroyed too.
* @param cause historical information about damage
*/
private case class Destruction(cause: ResolvedProjectile)
/**
* Most all vehicles and the weapons mounted to them can jam
* if the projectile that strikes (near) them has jammering properties.
@ -121,25 +93,76 @@ object DamageableVehicle {
* @see `VehicleServiceMessage`
* @param target the entity being destroyed
* @param cause historical information about the damage
* @param damage how much damage was performed
* @param damageToShields dispatch a shield strength update
* @param amount how much damage was performed
*/
def DamageAwareness(target: Vehicle, cause: ResolvedProjectile, damage: Int, damageToShields: Boolean): Unit = {
//alert cargo occupants to damage source
target.CargoHolds.values.foreach(hold => {
hold.Occupant match {
case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Damage(cause, damage + (if (damageToShields) 1 else 0))
case None => ;
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = {
val obj = DamageableObject
val zone = target.Zone
val events = zone.VehicleEvents
val targetGUID = target.GUID
val zoneId = zone.id
val vehicleChannel = s"${obj.Actor}"
val (damageToHealth, damageToShields, totalDamage) = amount match {
case (a: Int, b: Int) => (a, b, a+b)
case _ => (0, 0, 0)
}
var announceConfrontation: Boolean = reportDamageToVehicle || totalDamage > 0
val aggravated = TryAggravationEffectActivate(cause) match {
case Some(_) =>
announceConfrontation = true
false
case _ =>
cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)
}
reportDamageToVehicle = false
//log historical event
target.History(cause)
//damage
if (Damageable.CanDamageOrJammer(target, totalDamage, cause)) {
//jammering
if (Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause)
}
})
//shields
if (damageToShields) {
val zone = target.Zone
zone.VehicleEvents ! VehicleServiceMessage(
s"${target.Actor}",
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields)
)
//stat changes
if (damageToShields > 0) {
events ! VehicleServiceMessage(
vehicleChannel,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, obj.Shields)
)
announceConfrontation = true
}
if (damageToHealth > 0) {
events ! VehicleServiceMessage(
zoneId,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, obj.Health)
)
announceConfrontation = true
}
}
if (announceConfrontation) {
if (aggravated) {
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(totalDamage, Vector3.Zero))
obj.Seats.values
.collect { case seat if seat.Occupant.nonEmpty => seat.Occupant.get.Name }
.foreach { channel =>
events ! VehicleServiceMessage(channel, msg)
}
}
else {
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert to damage source
DamageableMountable.DamageAwareness(obj, cause, totalDamage)
}
//alert cargo occupants to damage source
obj.CargoHolds.values.foreach(hold => {
hold.Occupant match {
case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Damage(cause, totalDamage)
case None => ;
}
})
}
}
@ -164,10 +187,15 @@ object DamageableVehicle {
* @param target the entity being destroyed
* @param cause historical information about the damage
*/
def DestructionAwareness(target: Vehicle, cause: ResolvedProjectile): Unit = {
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {
super.DestructionAwareness(target, cause)
val obj = DamageableObject
DamageableMountable.DestructionAwareness(obj, cause)
val zone = target.Zone
//aggravation cancel
EndAllAggravation()
//cargo vehicles die with us
target.CargoHolds.values.foreach(hold => {
obj.CargoHolds.values.foreach(hold => {
hold.Occupant match {
case Some(cargo) =>
cargo.Actor ! DamageableVehicle.Destruction(cause)
@ -175,10 +203,10 @@ object DamageableVehicle {
}
})
//special considerations for certain vehicles
Vehicles.BeforeUnloadVehicle(target, zone)
Vehicles.BeforeUnloadVehicle(obj, zone)
//shields
if (target.Shields > 0) {
target.Shields = 0
if (obj.Shields > 0) {
obj.Shields = 0
zone.VehicleEvents ! VehicleServiceMessage(
zone.id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0)
@ -186,5 +214,22 @@ object DamageableVehicle {
}
target.Actor ! Vehicle.Deconstruct(Some(1 minute))
target.ClearHistory()
DamageableWeaponTurret.DestructionAwareness(obj, cause)
}
}
object DamageableVehicle {
/**
* Message for instructing the target's cargo vehicles about a damage source affecting their carrier.
* @param cause historical information about damage
*/
private case class Damage(cause: ResolvedProjectile, amount: Int)
/**
* Message for instructing the target's cargo vehicles that their carrier is destroyed,
* and they should be destroyed too.
* @param cause historical information about damage
*/
private case class Destruction(cause: ResolvedProjectile)
}

View file

@ -1,30 +1,97 @@
//Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.damage
import akka.actor.Actor
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.turret.{TurretUpgrade, WeaponTurret}
import net.psforever.objects.vehicles.MountedWeapons
import net.psforever.objects.vital.DamageType
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.DamageWithPositionMessage
import net.psforever.types.Vector3
import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.support.TurretUpgrader
import net.psforever.services.vehicle.VehicleServiceMessage
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
/**
* The "control" `Actor` mixin for damage-handling code for `WeaponTurret` objects.
*/
trait DamageableWeaponTurret extends DamageableEntity {
def DamageableObject: Damageable.Target with WeaponTurret
trait DamageableWeaponTurret
extends DamageableEntity
with AggravatedBehavior {
_: Actor =>
override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = {
super.DamageAwareness(target, cause, amount)
if (amount > 0) {
DamageableMountable.DamageAwareness(DamageableObject, cause)
def damageableWeaponTurretPostStop(): Unit = {
EndAllAggravation()
}
def DamageableObject: Damageable.Target with WeaponTurret
def AggravatedObject: Damageable.Target with WeaponTurret = DamageableObject
override val takesDamage: Receive = originalTakesDamage.orElse(aggravatedBehavior)
override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = {
val obj = DamageableObject
val zone = target.Zone
val events = zone.VehicleEvents
val targetGUID = target.GUID
val zoneId = zone.id
val damageToHealth = amount match {
case a: Int => a
case _ => 0
}
var announceConfrontation: Boolean = damageToHealth > 0
val aggravated = TryAggravationEffectActivate(cause) match {
case Some(_) =>
announceConfrontation = true
false
case _ =>
cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated)
}
//log historical event
target.History(cause)
//damage
if (Damageable.CanDamageOrJammer(target, damageToHealth, cause)) {
//jammering
if (Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause)
}
//stat changes
//TODO some turrets have shields
if (damageToHealth > 0) {
DamageableMountable.DamageAwareness(DamageableObject, cause, damageToHealth)
events ! VehicleServiceMessage(
zoneId,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, obj.Health)
)
announceConfrontation = true
}
}
if (announceConfrontation) {
if (aggravated) {
val msg = VehicleAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(damageToHealth, Vector3.Zero))
obj.Seats.values
.collect { case seat if seat.Occupant.nonEmpty => seat.Occupant.get.Name }
.foreach { channel =>
events ! VehicleServiceMessage(channel, msg)
}
}
else {
//activity on map
zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
//alert to damage source
DamageableMountable.DamageAwareness(obj, cause, damageToHealth)
}
}
}
override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = {
super.DestructionAwareness(target, cause)
val obj = DamageableObject
EndAllAggravation()
DamageableWeaponTurret.DestructionAwareness(obj, cause)
DamageableMountable.DestructionAwareness(obj, cause)
}

View file

@ -88,9 +88,13 @@ class GeneratorControl(gen: Generator)
!imminentExplosion && super.WillAffectTarget(target, damage, cause)
}
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = {
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = {
super.DamageAwareness(target, cause, amount)
GeneratorControl.DamageAwareness(gen, cause, amount)
val damageTo = amount match {
case a: Int => a
case _ => 0
}
GeneratorControl.DamageAwareness(gen, cause, damageTo)
}
override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = {

View file

@ -72,9 +72,13 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
}
}
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Int): Unit = {
override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = {
super.DamageAwareness(target, cause, amount)
DamageableMountable.DamageAwareness(DamageableObject, cause)
val damageTo = amount match {
case a: Int => a
case _ => 0
}
DamageableMountable.DamageAwareness(DamageableObject, cause, damageTo)
}
override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = {

View file

@ -44,6 +44,11 @@ class FacilityTurretControl(turret: FacilityTurret)
// Used for timing ammo recharge for vanu turrets in caves
var weaponAmmoRechargeTimer = Default.Cancellable
override def postStop(): Unit = {
super.postStop()
damageableWeaponTurretPostStop()
}
def receive: Receive =
checkBehavior
.orElse(jammableBehavior)

View file

@ -11,7 +11,7 @@ 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.containable.{Containable, ContainableBehavior}
import net.psforever.objects.serverobject.damage.DamageableVehicle
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle}
import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject
import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
import net.psforever.objects.serverobject.hackable.GenericHackables
@ -51,11 +51,12 @@ class VehicleControl(vehicle: Vehicle)
with RepairableVehicle
with JammableMountedWeapons
with ContainableBehavior
with AntTransferBehavior {
with AntTransferBehavior
with AggravatedBehavior {
//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
@ -74,7 +75,7 @@ class VehicleControl(vehicle: Vehicle)
def ChargeTransferObject = vehicle
if (vehicle.Definition == GlobalDefinitions.ant) {
if(vehicle.Definition == GlobalDefinitions.ant) {
findChargeTargetFunc = Vehicles.FindANTChargingSource
findDischargeTargetFunc = Vehicles.FindANTDischargingTarget
}
@ -89,6 +90,7 @@ class VehicleControl(vehicle: Vehicle)
override def postStop(): Unit = {
super.postStop()
damageableVehiclePostStop()
decaying = false
decayTimer.cancel()
vehicle.Utilities.values.foreach { util =>

View file

@ -1,8 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vital
import net.psforever.objects.ballistics.{ProjectileResolution, ResolvedProjectile}
import net.psforever.objects.vital.damage.DamageCalculations
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.vital.projectile.ProjectileCalculations
import net.psforever.objects.vital.resistance.ResistanceSelection
import net.psforever.objects.vital.resolution.ResolutionCalculations
@ -72,7 +72,7 @@ trait DamageResistanceModel {
* @param resolution an explicit damage resolution overriding the one in the `ResolvedProjectile` object
* @return a function literal that encapsulates delayed modification instructions for certain objects
*/
def Calculate(data: ResolvedProjectile, resolution: ProjectileResolution.Value): ResolutionCalculations.Output = {
def Calculate(data: ResolvedProjectile, resolution: DamageType.Value): ResolutionCalculations.Output = {
val res: ProjectileCalculations.Form = ResistUsing(resolution)
Model(DamageUsing, res, data)
}

View file

@ -9,5 +9,5 @@ package net.psforever.objects.vital
object DamageType extends Enumeration(1) {
type Type = Value
final val Direct, Splash, Radiation, Aggravated, Plasma, Comet, None = Value
final val Direct, Splash, Lash, Radiation, Aggravated, Plasma, Comet, None = Value
}

View file

@ -6,70 +6,70 @@ import net.psforever.objects.vital.projectile.ProjectileCalculations
import net.psforever.objects.vital.resistance.{ResistanceCalculations, ResistanceSelection}
object NoResistance
extends ResistanceCalculations[SourceEntry](
ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.NoResistExtractor
)
extends ResistanceCalculations[SourceEntry](
ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.NoResistExtractor
)
object InfantryHitResistance
extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.ExoSuitDirectExtractor
)
extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.ExoSuitDirectExtractor
)
object InfantrySplashResistance
extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.ExoSuitSplashExtractor
)
extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.ExoSuitSplashExtractor
)
object InfantryLashResistance
extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.MaximumResistance
)
extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.MaximumResistance
)
object InfantryAggravatedResistance
extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.ExoSuitAggravatedExtractor
)
extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.ExoSuitAggravatedExtractor
)
object VehicleHitResistance
extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.VehicleDirectExtractor
)
extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.VehicleDirectExtractor
)
object VehicleSplashResistance
extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.VehicleSplashExtractor
)
extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.VehicleSplashExtractor
)
object VehicleLashResistance
extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.NoResistExtractor
)
extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.NoResistExtractor
)
object VehicleAggravatedResistance
extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.VehicleAggravatedExtractor
)
extends ResistanceCalculations[VehicleSource](
ResistanceCalculations.ValidVehicleTarget,
ResistanceCalculations.VehicleAggravatedExtractor
)
object AmenityHitResistance
extends ResistanceCalculations[ObjectSource](
ResistanceCalculations.ValidAmenityTarget,
ResistanceCalculations.OtherDirectExtractor
)
extends ResistanceCalculations[ObjectSource](
ResistanceCalculations.ValidAmenityTarget,
ResistanceCalculations.OtherDirectExtractor
)
object AMenitySplashResistance
extends ResistanceCalculations[ObjectSource](
ResistanceCalculations.ValidAmenityTarget,
ResistanceCalculations.OtherSplashExtractor
)
object AmenitySplashResistance
extends ResistanceCalculations[ObjectSource](
ResistanceCalculations.ValidAmenityTarget,
ResistanceCalculations.OtherSplashExtractor
)
object NoResistanceSelection extends ResistanceSelection {
def Direct: ProjectileCalculations.Form = None

View file

@ -11,13 +11,13 @@ object NoResolutions
object InfantryResolutions
extends DamageResistCalculations(
ResolutionCalculations.InfantryDamageAfterResist,
ResolutionCalculations.InfantryDamage,
ResolutionCalculations.InfantryApplication
)
object MaxResolutions
extends DamageResistCalculations(
ResolutionCalculations.MaxDamageAfterResist,
ResolutionCalculations.MaxDamage,
ResolutionCalculations.InfantryApplication
)

View file

@ -1,8 +1,9 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.vital.damage
import net.psforever.objects.ballistics.{ProjectileResolution, ResolvedProjectile}
import net.psforever.types.Vector3
import net.psforever.objects.ballistics._
import net.psforever.objects.vital.DamageType
import net.psforever.types.{ExoSuitType, Vector3}
/**
* Adjustments performed on the subsequent manipulations of the "base damage" value of an attack vector
@ -45,6 +46,21 @@ object DamageModifiers {
private def function(damage: Int, data: ResolvedProjectile): Int = damage
}
case object MaxDistanceCutoff extends Mod {
def Calculate: DamageModifiers.Format = function
private def function(damage: Int, data: ResolvedProjectile): Int = {
val projectile = data.projectile
val profile = projectile.profile
val distance = Vector3.Distance(data.hit_pos, projectile.shot_origin)
if (distance <= profile.DistanceMax) {
damage
} else {
0
}
}
}
/**
* The input value degrades (lessens)
* the further the distance between the point of origin (`shot_origin`)
@ -81,14 +97,12 @@ object DamageModifiers {
def Calculate: DamageModifiers.Format = function
private def function(damage: Int, data: ResolvedProjectile): Int = {
val projectile = data.projectile
val profile = projectile.profile
val profile = data.projectile.profile
val distance = Vector3.Distance(data.hit_pos, data.target.Position)
val radius = profile.DamageRadius
if (distance <= radius) {
val base: Float = profile.DamageAtEdge
val degrade: Float = (1 - base) * ((radius - distance) / radius) + base
(damage * degrade).toInt
val base: Float = profile.DamageAtEdge
(damage * ((1 - base) * ((radius - distance) / radius) + base)).toInt
} else {
0
}
@ -117,4 +131,291 @@ object DamageModifiers {
}
}
}
/*
Below this point are the calculations for sources of aggravated damage.
For the most part, these calculations are individualistic and arbitrary.
They exist in their current form to satisfy observed shots to kill (STK) of specific weapon systems
according to 2012 standards of the Youtube video series by TheLegendaryNarwhal.
*/
/**
* The initial application of aggravated damage against an infantry target
* where the specific damage component is `Direct`.
*/
case object InfantryAggravatedDirect extends Mod {
def Calculate: DamageModifiers.Format =
BaseAggravatedFormula(ProjectileResolution.AggravatedDirect, DamageType.Direct)
}
/**
* The initial application of aggravated damage against an infantry target
* where the specific damage component is `Splash`.
*/
case object InfantryAggravatedSplash extends Mod {
def Calculate: DamageModifiers.Format =
BaseAggravatedFormula(ProjectileResolution.AggravatedSplash, DamageType.Splash)
}
/**
* The ongoing application of aggravated damage ticks against an infantry target
* where the specific damage component is `Direct`.
* This is called "burning" regardless of what the active aura effect actually is.
*/
case object InfantryAggravatedDirectBurn extends Mod {
def Calculate: DamageModifiers.Format =
BaseAggravatedBurnFormula(ProjectileResolution.AggravatedDirectBurn, DamageType.Direct)
}
/**
* The ongoing application of aggravated damage ticks against an infantry target
* where the specific damage component is `Splash`.
* This is called "burning" regardless of what the active aura effect actually is.
*/
case object InfantryAggravatedSplashBurn extends Mod {
def Calculate: DamageModifiers.Format =
BaseAggravatedBurnFormula(ProjectileResolution.AggravatedSplashBurn, DamageType.Splash)
}
/**
* For damage application that involves aggravation of a particular damage type,
* calculate that initial damage application for infantry targets
* and produce the modified damage value.
* Infantry wearing mechanized assault exo-suits (MAX) incorporate an additional modifier.
* @see `AggravatedDamage`
* @see `ExoSuitType`
* @see `InfantryAggravatedDirect`
* @see `InfantryAggravatedSplash`
* @see `PlayerSource`
* @see `ProjectileTarget.AggravatesTarget`
* @see `ResolvedProjectile`
* @param resolution the projectile resolution to match against
* @param damageType the damage type to find in as a component of aggravated information
* @param damage the base damage value
* @param data historical information related to the damage interaction
* @return the modified damage
*/
private def BaseAggravatedFormula(
resolution: ProjectileResolution.Value,
damageType : DamageType.Value
)
(
damage: Int,
data: ResolvedProjectile
): Int = {
if (data.resolution == resolution &&
data.projectile.quality == ProjectileQuality.AggravatesTarget) {
(data.projectile.profile.Aggravated, data.target) match {
case (Some(aggravation), p: PlayerSource) =>
val aggravatedDamage = aggravation.info.find(_.damage_type == damageType) match {
case Some(infos) =>
damage * infos.degradation_percentage + damage
case _ =>
damage toFloat
}
if(p.ExoSuit == ExoSuitType.MAX) {
(aggravatedDamage * aggravation.max_factor) toInt
} else {
aggravatedDamage toInt
}
case _ =>
damage
}
} else {
damage
}
}
/**
* For damage application that involves aggravation of a particular damage type,
* calculate that damage application burn for each tick for infantry targets
* and produce the modified damage value.
* Infantry wearing mechanized assault exo-suits (MAX) incorporate an additional modifier.
* Vanilla infantry incorporate their resistance value into a slightly different calculation than usual.
* @see `AggravatedDamage`
* @see `ExoSuitType`
* @see `InfantryAggravatedDirectBurn`
* @see `InfantryAggravatedSplashBurn`
* @see `PlayerSource`
* @see `ResolvedProjectile`
* @param resolution the projectile resolution to match against
* @param damageType the damage type to find in as a component of aggravated information
* @param damage the base damage value
* @param data historical information related to the damage interaction
* @return the modified damage
*/
private def BaseAggravatedBurnFormula(
resolution: ProjectileResolution.Value,
damageType : DamageType.Value
)
(
damage: Int,
data: ResolvedProjectile
): Int = {
if (data.resolution == resolution) {
(data.projectile.profile.Aggravated, data.target) match {
case (Some(aggravation), p: PlayerSource) =>
val degradation = aggravation.info.find(_.damage_type == damageType) match {
case Some(info) =>
info.degradation_percentage
case _ =>
1f
}
if (p.exosuit == ExoSuitType.MAX) {
(damage * degradation * aggravation.max_factor) toInt
} else {
val resist = data.damage_model.ResistUsing(data)(data)
//add resist to offset resist subtraction later
if (damage > resist) {
((damage - resist) * degradation).toInt + resist
} else {
(damage * degradation).toInt + resist
}
}
case _ =>
0
}
} else {
damage
}
}
/**
* For damage application that involves aggravation of a fireball (Dragon secondary fire mode),
* perform 1 damage.
* @see `ResolvedProjectile`
*/
case object FireballAggravatedBurn extends Mod {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (damage > 0 &&
data.resolution == ProjectileResolution.AggravatedDirectBurn ||
data.resolution == ProjectileResolution.AggravatedSplashBurn) {
//add resist to offset resist subtraction later
1 + data.damage_model.ResistUsing(data)(data)
} else {
damage
}
}
}
/**
* The initial application of aggravated damage against an aircraft target.
* Primarily for use in the starfire weapon system.
* @see `AggravatedDamage`
* @see `ProjectileQuality.AggravatesTarget`
* @see `ResolvedProjectile`
*/
case object StarfireAggravated extends Mod {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (data.resolution == ProjectileResolution.AggravatedDirect &&
data.projectile.quality == ProjectileQuality.AggravatesTarget) {
data.projectile.profile.Aggravated match {
case Some(aggravation) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) =>
(damage * infos.degradation_percentage + damage) toInt
case _ =>
damage
}
case _ =>
damage
}
} else {
damage
}
}
}
/**
* The ongoing application of aggravated damage ticks against an aircraft target.
* Primarily for use in the starfire weapon system.
* This is called "burning" regardless of what the active aura effect actually is.
* @see `AggravatedDamage`
* @see `ProjectileQuality`
* @see `ResolvedProjectile`
*/
case object StarfireAggravatedBurn extends Mod {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (data.resolution == ProjectileResolution.AggravatedDirectBurn) {
data.projectile.profile.Aggravated match {
case Some(aggravation) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) =>
(math.floor(damage * infos.degradation_percentage) * data.projectile.quality.mod) toInt
case _ =>
damage
}
case _ =>
0
}
} else {
damage
}
}
}
/**
* The initial application of aggravated damage against a target.
* Primarily for use in the comet weapon system.
* @see `AggravatedDamage`
* @see `ProjectileQuality.AggravatesTarget`
* @see `ResolvedProjectile`
*/
case object CometAggravated extends Mod {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (data.resolution == ProjectileResolution.AggravatedDirect &&
data.projectile.quality == ProjectileQuality.AggravatesTarget) {
data.projectile.profile.Aggravated match {
case Some(aggravation) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) =>
damage - (damage * infos.degradation_percentage) toInt
case _ =>
damage
}
case _ =>
damage
}
} else {
damage
}
}
}
/**
* The ongoing application of aggravated damage ticks against a target.
* Primarily for use in the comet weapon system.
* This is called "burning" regardless of what the active aura effect actually is.
* @see `AggravatedDamage`
* @see `ProjectileQuality`
* @see `ResolvedProjectile`
*/
case object CometAggravatedBurn extends Mod {
def Calculate: DamageModifiers.Format = formula
private def formula(damage: Int, data: ResolvedProjectile): Int = {
if (data.resolution == ProjectileResolution.AggravatedDirectBurn) {
data.projectile.profile.Aggravated match {
case Some(aggravation) =>
aggravation.info.find(_.damage_type == DamageType.Direct) match {
case Some(infos) =>
damage - (damage * infos.degradation_percentage) toInt
case _ =>
damage
}
case _ =>
0
}
} else {
damage
}
}
}
}

View file

@ -2,7 +2,7 @@
package net.psforever.objects.vital.resistance
import net.psforever.objects.ballistics._
import net.psforever.objects.vital.NoResistance
import net.psforever.objects.vital.{DamageType, NoResistance}
import net.psforever.objects.vital.projectile.ProjectileCalculations
/**
@ -17,19 +17,19 @@ trait ResistanceSelection {
def Lash: ProjectileCalculations.Form
def Aggravated: ProjectileCalculations.Form
def apply(data: ResolvedProjectile): ProjectileCalculations.Form =
data.resolution match {
case ProjectileResolution.Hit => Direct
case ProjectileResolution.Splash => Splash
case ProjectileResolution.Lash => Lash
case _ => None
}
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: ProjectileResolution.Value): ProjectileCalculations.Form =
res match {
case ProjectileResolution.Hit => Direct
case ProjectileResolution.Splash => Splash
case ProjectileResolution.Lash => Lash
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
}
}

View file

@ -38,15 +38,31 @@ object ResolutionCalculations {
def NoDamage(data: ResolvedProjectile)(a: Int, b: Int): Int = 0
def InfantryDamageAfterResist(data: ResolvedProjectile): (Int, Int) => (Int, Int) = {
def InfantryDamage(data: ResolvedProjectile): (Int, Int) => (Int, Int) = {
data.target match {
case target: PlayerSource =>
InfantryDamageAfterResist(target.health, target.armor)
if(data.projectile.profile.DamageToHealthOnly) {
DamageToHealthOnly(target.health)
} else {
InfantryDamageAfterResist(target.health, target.armor)
}
case _ =>
InfantryDamageAfterResist(0, 0)
}
}
def DamageToHealthOnly(currentHP: Int)(damages: Int, resistance: Int): (Int, Int) = {
if (damages > 0 && currentHP > 0) {
if(damages > resistance) {
(damages - resistance, 0)
} else {
(damages, 0)
}
} else {
(0, 0)
}
}
def InfantryDamageAfterResist(currentHP: Int, currentArmor: Int)(damages: Int, resistance: Int): (Int, Int) = {
if (damages > 0 && currentHP > 0) {
if (currentArmor <= 0) {
@ -67,10 +83,14 @@ object ResolutionCalculations {
}
}
def MaxDamageAfterResist(data: ResolvedProjectile): (Int, Int) => (Int, Int) = {
def MaxDamage(data: ResolvedProjectile): (Int, Int) => (Int, Int) = {
data.target match {
case target: PlayerSource =>
MaxDamageAfterResist(target.health, target.armor)
if(data.projectile.profile.DamageToHealthOnly) {
DamageToHealthOnly(target.health)
} else {
MaxDamageAfterResist(target.health, target.armor)
}
case _ =>
MaxDamageAfterResist(0, 0)
}

View file

@ -451,7 +451,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
* `false`, if the new pool can not be created because the system has already been started
*/
def AddPool(name: String, pool: Seq[Int]): Boolean = {
if (accessor == Default.Actor) {
if (accessor == Default.Actor || accessor == null) {
guid.AddPool(name, pool.toList)
true
} else {

View file

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

View file

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

View file

@ -19,7 +19,7 @@ import shapeless.{::, HNil}
* @param unk3b if no global unique identifier (above), the name of the entity absorbing the damage
* @param unk3c if no global unique identifier (above), the object type of the entity absorbing the damage
* @param unk3d na
* @param unk4 na
* @param unk4 an indicator for the target-specific vital statistic being affected
* @param unk5 the amount of damage
* @param unk6 na
*/
@ -66,6 +66,13 @@ final case class DamageFeedbackMessage(
}
object DamageFeedbackMessage extends Marshallable[DamageFeedbackMessage] {
def apply(unk1: Int,
unk2: PlanetSideGUID,
unk3: PlanetSideGUID,
unk4: Int,
unk5: Long): DamageFeedbackMessage =
DamageFeedbackMessage(unk1, true, Some(unk2), None, None, true, Some(unk3), None, None, None, unk4, unk5, 0)
implicit val codec: Codec[DamageFeedbackMessage] = (
("unk1" | uint4) ::
(bool >>:~ { u2 =>

View file

@ -5,10 +5,13 @@ import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacke
import net.psforever.types.Vector3
import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
/**
* Dispatched by the server to indicate a source of damage affecting the player.
* Unlike `HitHint` the damage source is defined by an actual coordinate location rather than a physical target.<br>
* Unlike `HitHint` the damage source is defined by an actual coordinate location rather than a physical target.
* Setting the position to the world origin, however,
* can cause the damage tick mark to point towards the previous damaging entity in some situations.<br>
* <br>
* The player will be shown a fading, outwards drifting, red tick mark.
* The location will indicate a general direction towards the source.
@ -27,5 +30,14 @@ object DamageWithPositionMessage extends Marshallable[DamageWithPositionMessage]
implicit val codec: Codec[DamageWithPositionMessage] = (
("unk" | uint8L) ::
("pos" | Vector3.codec_pos)
).as[DamageWithPositionMessage]
).xmap[DamageWithPositionMessage] (
{
case unk :: pos :: HNil =>
DamageWithPositionMessage(math.max(0, math.min(unk, 255)), pos)
},
{
case DamageWithPositionMessage(unk, pos) =>
unk :: pos :: HNil
}
)
}

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

View 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()
}

View file

@ -28,7 +28,7 @@ class DamageCalculationsTests extends Specification {
val target = Vehicle(GlobalDefinitions.fury)
target.Position = Vector3(10, 0, 0)
val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target),
target.DamageModel,
@ -64,14 +64,7 @@ class DamageCalculationsTests extends Specification {
}
"degrade over distance damage modifier (no degrade)" in {
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(10, 0, 0)
)
DamageModifiers.DistanceDegrade.Calculate(100, resprojectile2) == 100 mustEqual true
DamageModifiers.DistanceDegrade.Calculate(100, resprojectile) == 100 mustEqual true
}
"degrade over distance damage modifier (some degrade)" in {
@ -109,7 +102,14 @@ class DamageCalculationsTests extends Specification {
}
"degrade at radial distance damage modifier (some degrade)" in {
val damage = DamageModifiers.RadialDegrade.Calculate(100, resprojectile)
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash,
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(12, 0, 0)
)
val damage = DamageModifiers.RadialDegrade.Calculate(100, resprojectile2)
damage < 100 && damage > 0 mustEqual true
}
@ -119,7 +119,7 @@ class DamageCalculationsTests extends Specification {
projectile,
SourceEntry(target),
target.DamageModel,
Vector3(1000, 0, 0)
Vector3(100, 0, 0)
)
DamageModifiers.RadialDegrade.Calculate(100, resprojectile2) == 0 mustEqual true
}
@ -180,7 +180,7 @@ class ResistanceCalculationsTests extends Specification {
"ignore all targets" in {
val target = Vehicle(GlobalDefinitions.fury)
val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target),
target.DamageModel,
@ -192,7 +192,7 @@ class ResistanceCalculationsTests extends Specification {
"discern standard infantry targets" in {
val target = player
val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target),
target.DamageModel,
@ -208,7 +208,7 @@ class ResistanceCalculationsTests extends Specification {
val target = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
target.ExoSuit = ExoSuitType.MAX
val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target),
target.DamageModel,
@ -223,7 +223,7 @@ class ResistanceCalculationsTests extends Specification {
"discern ground vehicle targets" in {
val target = Vehicle(GlobalDefinitions.fury)
val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target),
target.DamageModel,
@ -238,7 +238,7 @@ class ResistanceCalculationsTests extends Specification {
"discern flying vehicle targets" in {
val target = Vehicle(GlobalDefinitions.mosquito)
val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target),
target.DamageModel,
@ -284,7 +284,7 @@ class ResolutionCalculationsTests extends Specification {
"calculate no damage" in {
val target = player
val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target),
target.DamageModel,
@ -296,23 +296,23 @@ class ResolutionCalculationsTests extends Specification {
"calculate no infantry damage for vehicles" in {
val target1 = Vehicle(GlobalDefinitions.fury) //!
val resprojectile1 = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target1),
target1.DamageModel,
Vector3.Zero
)
InfantryDamageAfterResist(resprojectile1)(50, 10) mustEqual (0, 0)
InfantryDamage(resprojectile1)(50, 10) mustEqual (0, 0)
val target2 = player
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target2),
target2.DamageModel,
Vector3.Zero
)
InfantryDamageAfterResist(resprojectile2)(50, 10) mustEqual (40, 10)
InfantryDamage(resprojectile2)(50, 10) mustEqual (40, 10)
}
"calculate health and armor damage for infantry target" in {
@ -340,23 +340,23 @@ class ResolutionCalculationsTests extends Specification {
"calculate no max damage for vehicles" in {
val target1 = Vehicle(GlobalDefinitions.fury) //!
val resprojectile1 = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target1),
target1.DamageModel,
Vector3.Zero
)
MaxDamageAfterResist(resprojectile1)(50, 10) mustEqual (0, 0)
MaxDamage(resprojectile1)(50, 10) mustEqual (0, 0)
val target2 = player2
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target2),
target2.DamageModel,
Vector3.Zero
)
MaxDamageAfterResist(resprojectile2)(50, 10) mustEqual (0, 40)
MaxDamage(resprojectile2)(50, 10) mustEqual (0, 40)
}
"calculate health and armor damage for max target" in {
@ -376,7 +376,7 @@ class ResolutionCalculationsTests extends Specification {
"do not care if target is infantry for vehicle calculations" in {
val target1 = player
val resprojectile1 = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target1),
target1.DamageModel,
@ -386,7 +386,7 @@ class ResolutionCalculationsTests extends Specification {
val target2 = Vehicle(GlobalDefinitions.fury) //!
val resprojectile2 = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(target2),
target2.DamageModel,
@ -450,10 +450,9 @@ class DamageModelTests extends Specification {
Vector3.Zero
)
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 {
@ -471,8 +470,7 @@ class DamageModelTests extends Specification {
Vector3.Zero
)
val func: Any => ResolvedProjectile =
resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
resprojectile.damage_model.Calculate(resprojectile, DamageType.Splash)
func(tplayer)
tplayer.Health mustEqual 65
tplayer.Armor mustEqual 35
@ -572,7 +570,7 @@ class DamageModelTests extends Specification {
Vector3.Zero
)
val func: Any => ResolvedProjectile =
resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
resprojectile.damage_model.Calculate(resprojectile, DamageType.Splash)
func(vehicle)
vehicle.Health mustEqual 518

View file

@ -40,7 +40,7 @@ class DamageableTest extends Specification {
"permit damage" in {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target),
target.DamageModel,
@ -53,7 +53,7 @@ class DamageableTest extends Specification {
"ignore attempts at non-zero damage" in {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectileA,
weaponA.Definition,
@ -78,7 +78,7 @@ class DamageableTest extends Specification {
Faction = player1.Faction
}
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target),
target.DamageModel,
@ -101,7 +101,7 @@ class DamageableTest extends Specification {
Faction = PlanetSideEmpire.NC
}
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target),
target.DamageModel,
@ -128,7 +128,7 @@ class DamageableTest extends Specification {
Faction = player1.Faction
}
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target),
target.DamageModel,
@ -152,7 +152,7 @@ class DamageableTest extends Specification {
"permit jamming" in {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target),
target.DamageModel,
@ -166,7 +166,7 @@ class DamageableTest extends Specification {
"ignore attempts at jamming if the projectile is does not cause the effect" in {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target),
target.DamageModel,
@ -181,7 +181,7 @@ class DamageableTest extends Specification {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
target.Faction = player1.Faction
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target),
target.DamageModel,
@ -196,7 +196,7 @@ class DamageableTest extends Specification {
"ignore attempts at jamming targets that are not jammable" in {
val target = new TrapDeployable(GlobalDefinitions.tank_traps)
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target),
target.DamageModel,
@ -216,7 +216,7 @@ class DamageableTest extends Specification {
val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor)
target.Faction = player1.Faction
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
SourceEntry(target),
target.DamageModel,
@ -271,7 +271,7 @@ class DamageableEntityDamageTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -342,7 +342,7 @@ class DamageableEntityDestroyedTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -416,7 +416,7 @@ class DamageableEntityNotDestroyTwice extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -487,7 +487,7 @@ class DamageableAmenityTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -580,7 +580,7 @@ class DamageableMountableDamageTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -626,7 +626,7 @@ class DamageableMountableDamageTest extends ActorTest {
msg1_3(1) match {
case AvatarServiceMessage(
"TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 2, 2)))
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(_, Vector3(2, 2, 2)))
) =>
true
case _ => false
@ -675,7 +675,7 @@ class DamageableMountableDestroyTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -737,8 +737,10 @@ class DamageableWeaponTurretDamageTest extends ActorTest {
}
val activityProbe = TestProbe()
val avatarProbe = TestProbe()
val vehicleProbe = TestProbe()
zone.Activity = activityProbe.ref
zone.AvatarEvents = avatarProbe.ref
zone.VehicleEvents = vehicleProbe.ref
val turret = new TurretDeployable(GlobalDefinitions.portable_manned_turret_tr) //2
turret.Actor = system.actorOf(Props(classOf[TurretControl], turret), "turret-control")
turret.Zone = zone
@ -787,16 +789,17 @@ class DamageableWeaponTurretDamageTest extends ActorTest {
assert(turret.Health == turret.Definition.DefaultHealth)
turret.Actor ! Vitality.Damage(applyDamageTo)
val msg1_3 = avatarProbe.receiveN(2, 500 milliseconds)
val msg2 = activityProbe.receiveOne(500 milliseconds)
val msg12 = vehicleProbe.receiveOne(500 milliseconds)
val msg3 = activityProbe.receiveOne(500 milliseconds)
val msg4 = avatarProbe.receiveOne(500 milliseconds)
assert(
msg1_3.head match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
case _ => false
msg12 match {
case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(2), 0, _)) => true
case _ => false
}
)
assert(
msg2 match {
msg3 match {
case activity: Zone.HotSpot.Activity =>
activity.attacker == PlayerSource(player1) &&
activity.defender == turretSource &&
@ -805,10 +808,10 @@ class DamageableWeaponTurretDamageTest extends ActorTest {
}
)
assert(
msg1_3(1) match {
msg4 match {
case AvatarServiceMessage(
"TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 2, 2)))
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(_, Vector3(2, 2, 2)))
) =>
true
case _ => false
@ -862,7 +865,7 @@ class DamageableWeaponTurretJammerTest extends ActorTest {
val projectile = weapon.Projectile
val turretSource = SourceEntry(turret)
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -964,7 +967,7 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
val weaponA = Tool(GlobalDefinitions.jammer_grenade)
val projectileA = weaponA.Projectile
val resolvedA = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectileA,
weaponA.Definition,
@ -983,7 +986,7 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
val weaponB = Tool(GlobalDefinitions.phoenix) //decimator
val projectileB = weaponB.Projectile
val resolvedB = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectileB,
weaponB.Definition,
@ -1046,8 +1049,8 @@ class DamageableWeaponTurretDestructionTest extends ActorTest {
)
assert(
msg56.head match {
case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if t eq turret => true
case _ => false
case VehicleServiceMessage.TurretUpgrade(SupportActor.ClearSpecific(List(t), _)) if turret eq t => true
case _ => false
}
)
assert(
@ -1130,40 +1133,36 @@ class DamageableVehicleDamageTest extends ActorTest {
assert(atv.Shields == 1)
atv.Actor ! Vitality.Damage(applyDamageTo)
val msg1_3 = avatarProbe.receiveN(2, 500 milliseconds)
val msg2 = activityProbe.receiveOne(200 milliseconds)
val msg4 = vehicleProbe.receiveOne(200 milliseconds)
val msg12 = vehicleProbe.receiveN(2, 200 milliseconds)
val msg3 = activityProbe.receiveOne(200 milliseconds)
val msg4 = avatarProbe.receiveOne(200 milliseconds)
assert(
msg1_3.head match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(1), 0, _)) => true
case _ => false
msg12.head match {
case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => true
case _ => false
}
)
assert(
msg2 match {
msg12(1) match {
case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => true
case _ => false
}
)
assert(
msg3 match {
case activity: Zone.HotSpot.Activity =>
activity.attacker == PlayerSource(player1) &&
activity.defender == vehicleSource &&
activity.location == Vector3(1, 0, 0)
case _ => false
}
)
assert(
msg1_3(1) match {
case AvatarServiceMessage(
"TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0)))
) =>
true
activity.defender == vehicleSource &&
activity.location == Vector3(1, 0, 0)
case _ => false
}
)
assert(
msg4 match {
case VehicleServiceMessage(
channel,
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)
) if channel.equals(atv.Actor.toString) =>
case AvatarServiceMessage(
"TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(9, Vector3(2, 0, 0)))
) =>
true
case _ => false
}
@ -1264,17 +1263,23 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
assert(atv.Shields == 1)
lodestar.Actor ! Vitality.Damage(applyDamageTo)
val msg1_35 = avatarProbe.receiveN(3, 500 milliseconds)
val msg2 = activityProbe.receiveOne(200 milliseconds)
val msg4 = vehicleProbe.receiveOne(200 milliseconds)
val msg12 = vehicleProbe.receiveN(2, 200 milliseconds)
val msg3 = activityProbe.receiveOne(200 milliseconds)
val msg45 = avatarProbe.receiveN(2,200 milliseconds)
assert(
msg1_35.head match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(1), 0, _)) => true
case _ => false
msg12.head match {
case VehicleServiceMessage(_, VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)) => true
case _ => false
}
)
assert(
msg2 match {
msg12(1) match {
case VehicleServiceMessage("test", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 0, _)) => true
case _ => false
}
)
assert(
msg3 match {
case activity: Zone.HotSpot.Activity =>
activity.attacker == PlayerSource(player1) &&
activity.defender == vehicleSource &&
@ -1283,30 +1288,20 @@ class DamageableVehicleDamageMountedTest extends ActorTest {
}
)
assert(
msg1_35(1) match {
msg45.head match {
case AvatarServiceMessage(
"TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0)))
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(400, Vector3(2, 0, 0)))
) =>
true
case _ => false
}
)
assert(
msg4 match {
case VehicleServiceMessage(
channel,
VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), PlanetSideGUID(1), 68, _)
) if channel.equals(lodestar.Actor.toString) =>
true
case _ => false
}
)
assert(
msg1_35(2) match {
msg45(1) match {
case AvatarServiceMessage(
"TestCharacter3",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0)))
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(0, Vector3(2, 0, 0)))
) =>
true
case _ => false
@ -1385,7 +1380,7 @@ class DamageableVehicleJammeringMountedTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.jammer_grenade)
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -1601,7 +1596,7 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
val weaponA = Tool(GlobalDefinitions.jammer_grenade)
val projectileA = weaponA.Projectile
val resolvedA = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectileA,
weaponA.Definition,
@ -1620,7 +1615,7 @@ class DamageableVehicleDestroyMountedTest extends ActorTest {
val weaponB = Tool(GlobalDefinitions.phoenix) //decimator
val projectileB = weaponB.Projectile
val resolvedB = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectileB,
weaponB.Definition,

View file

@ -334,7 +334,7 @@ class ExplosiveDeployableJammerTest extends ActorTest {
val pSource = PlayerSource(player1)
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
jMineSource,
j_mine.DamageModel,
@ -374,7 +374,7 @@ class ExplosiveDeployableJammerTest extends ActorTest {
assert(
msg_local(2) match {
case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) =>
(target eq j_mine) && (_zone eq zone)
(j_mine eq target) && (_zone eq zone)
case _ => false
}
)
@ -432,7 +432,7 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
val pSource = PlayerSource(player1)
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
hMineSource,
h_mine.DamageModel,
@ -478,7 +478,7 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest {
assert(
msg_local(3) match {
case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) =>
(target eq h_mine) && (_zone eq zone)
(h_mine eq target) && (_zone eq zone)
case _ => false
}
)
@ -542,7 +542,7 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
val pSource = PlayerSource(player1)
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
hMineSource,
h_mine.DamageModel,
@ -584,7 +584,7 @@ class ExplosiveDeployableDestructionTest extends ActorTest {
assert(
msg_local(2) match {
case LocalServiceMessage.Deployables(SupportActor.ClearSpecific(List(target), _zone)) =>
(target eq h_mine) && (_zone eq zone)
(h_mine eq target) && (_zone eq zone)
case _ => false
}
)

View file

@ -80,7 +80,7 @@ class GeneratorControlDamageTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -162,7 +162,7 @@ class GeneratorControlCriticalTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -253,7 +253,7 @@ class GeneratorControlDestroyedTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -384,7 +384,7 @@ class GeneratorControlKillsTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -507,7 +507,7 @@ class GeneratorControlNotDestroyTwice extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -597,7 +597,7 @@ class GeneratorControlNotDamageIfExplodingTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,
@ -691,7 +691,7 @@ class GeneratorControlNotRepairIfExplodingTest extends ActorTest {
val weapon = Tool(GlobalDefinitions.phoenix) //decimator
val projectile = weapon.Projectile
val resolved = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
Projectile(
projectile,
weapon.Definition,

View file

@ -413,14 +413,13 @@ class PlayerControlDamageTest extends ActorTest {
)
assert(
msg_avatar(1) match {
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) => true
case _ => false
}
)
assert(
msg_avatar(2) match {
case AvatarServiceMessage("TestCharacter2", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 2, _)) =>
true
case AvatarServiceMessage("test", AvatarAction.PlanetsideAttributeToAll(PlanetSideGUID(2), 0, _)) => true
case _ => false
}
)
@ -437,7 +436,7 @@ class PlayerControlDamageTest extends ActorTest {
msg_avatar(3) match {
case AvatarServiceMessage(
"TestCharacter2",
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, Vector3(2, 0, 0)))
AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(17, Vector3(2, 0, 0)))
) =>
true
case _ => false

View file

@ -358,7 +358,6 @@ class ProjectileTest extends Specification {
fury_dm,
Vector3(1.2f, 3.4f, 5.6f)
)
obj.resolution mustEqual ProjectileResolution.Hit
obj.projectile mustEqual projectile
obj.target mustEqual p2_source
obj.damage_model mustEqual fury.DamageModel

View file

@ -997,7 +997,7 @@ class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest {
// val p_source = PlayerSource( Player(Avatar(0, "TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) )
// val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero)
// val fury_dm = Vehicle(GlobalDefinitions.fury).DamageModel
// val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f))
// val obj = ResolvedProjectile(projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f))
//
// "not charge vehicle shields if recently damaged" in {
// assert(vehicle.Shields == 0)

View file

@ -21,7 +21,7 @@ class VitalityTest extends Specification {
val pSource = PlayerSource(player)
val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero)
val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(player),
player.DamageModel,
@ -69,7 +69,7 @@ class VitalityTest extends Specification {
val pSource = PlayerSource(player)
val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero)
val resprojectile = ResolvedProjectile(
ProjectileResolution.Splash,
ProjectileResolution.Hit,
projectile,
SourceEntry(player),
player.DamageModel,

View file

@ -110,7 +110,8 @@ class ZoneTest extends Specification {
zone.AddPool("pool2", (51 to 75).toList)
val obj = new TestObject()
guid1.register(obj, "pool2").isSuccess mustEqual true
val registration = guid1.register(obj, "pool2")
registration.isSuccess mustEqual true
guid1.WhichPool(obj).contains("pool2") mustEqual true
zone.GUID(new NumberPoolHub(new LimitedNumberSource(150))) mustEqual false
@ -214,13 +215,15 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {}
}
val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
assert(zone.Players.isEmpty)
assert(zone.LivePlayers.isEmpty)
zone.Population ! Zone.Population.Join(avatar)
zone.Population ! Zone.Population.Spawn(avatar, Player(avatar), null)
zone.Population ! Zone.Population.Spawn(avatar, player, null)
expectNoMessage(Duration.create(200, "ms"))
assert(zone.Players.size == 1)
assert(zone.Players.head == avatar)
@ -232,10 +235,12 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {}
}
val avatar = Avatar(1, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
receiveOne(Duration.create(200, "ms")) //consume
zone.Population ! Zone.Population.Join(avatar)
zone.Population ! Zone.Population.Spawn(avatar, Player(avatar), null)
zone.Population ! Zone.Population.Spawn(avatar, player, null)
expectNoMessage(Duration.create(100, "ms"))
assert(zone.Players.size == 1)
@ -251,6 +256,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar)
@ -271,6 +277,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar)
@ -318,7 +325,9 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player1 = Player(avatar)
player1.GUID = PlanetSideGUID(1)
val player2 = Player(avatar)
player2.GUID = PlanetSideGUID(2)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar)
@ -344,6 +353,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar(0, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
@ -364,10 +374,12 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {}
}
val avatar = Avatar(2, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar)
zone.Population ! Zone.Population.Spawn(avatar, Player(avatar), null)
zone.Population ! Zone.Population.Spawn(avatar, player, null)
expectNoMessage(Duration.create(100, "ms"))
assert(zone.Players.size == 1)
@ -388,6 +400,7 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {}
}
val player = Player(Avatar(3, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.GUID = PlanetSideGUID(1)
player.Release
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
@ -404,6 +417,7 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {}
}
val player = Player(Avatar(4, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.GUID = PlanetSideGUID(1)
player.Release
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
@ -422,10 +436,13 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {}
}
val player1 = Player(Avatar(5, "Chord1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player1.GUID = PlanetSideGUID(1)
player1.Release
val player2 = Player(Avatar(6, "Chord2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player2.GUID = PlanetSideGUID(2)
player2.Release
val player3 = Player(Avatar(7, "Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player3.GUID = PlanetSideGUID(3)
player3.Release
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
@ -450,6 +467,7 @@ class ZonePopulationTest extends ActorTest {
override def SetupNumberPools() = {}
}
val player = Player(Avatar(8, "Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.GUID = PlanetSideGUID(1)
//player.Release !!important
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)