diff --git a/.codecov.yml b/.codecov.yml index 2ce4e5825..351d39df4 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -4,7 +4,7 @@ comment: off ignore: - "src/main/scala/net/psforever/objects/ObjectType.scala" - "src/main/scala/net/psforever/objects/avatar/Avatars.scala" - - "src/main/scala/net/psforever/objects/ballistics/ProjectileResolution.scala" + - "src/main/scala/net/psforever/objects/ballistics/DamageResolution.scala" - "src/main/scala/net/psforever/objects/ballistics/Projectiles.scala" - "src/main/scala/net/psforever/objects/equipment/Ammo.scala" - "src/main/scala/net/psforever/objects/equipment/CItem.scala" @@ -22,15 +22,23 @@ ignore: - "src/main/scala/net/psforever/objects/vehicles/SeatArmoRestriction.scala" - "src/main/scala/net/psforever/objects/vehicles/Turrets.scala" - "src/main/scala/net/psforever/objects/vehicles/VehicleLockState.scala" + - "src/main/scala/net/psforever/objects/vital/base" + - "src/main/scala/net/psforever/objects/vital/collision" - "src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala" - - "src/main/scala/net/psforever/objects/vital/projectile/ProjectileCalculations.scala" + - "src/main/scala/net/psforever/objects/vital/damage/SpecificDamageProfile.scala" + - "src/main/scala/net/psforever/objects/vital/etc" + - "src/main/scala/net/psforever/objects/vital/interaction" + - "src/main/scala/net/psforever/objects/vital/projectile/ProjectileReason.scala" + - "src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifiers.scala" + - "src/main/scala/net/psforever/objects/vital/prop" - "src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala" + - "src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala" + - "src/main/scala/net/psforever/objects/vital/resolution/DamageResistanceCalculations.scala" + - "src/main/scala/net/psforever/objects/vital/resolution/DamageResistanceModel.scala" - "src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala" - - "src/main/scala/net/psforever/objects/vital/DamageType.scala" - - "src/main/scala/net/psforever/objects/vital/StandardDamages.scala" - - "src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala" - "src/main/scala/net/psforever/objects/vital/StandardResistances.scala" - "src/main/scala/net/psforever/objects/vital/StandardResolutions.scala" + - "src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala" - "src/main/scala/net/psforever/packet/crypto" - "src/main/scala/net/psforever/packet/game/objectcreate/DrawnSlot.scala" - "src/main/scala/net/psforever/packet/game/objectcreate/DriveState.scala" diff --git a/src/main/scala/net/psforever/actors/session/SessionActor.scala b/src/main/scala/net/psforever/actors/session/SessionActor.scala index adb25ec35..96f046720 100644 --- a/src/main/scala/net/psforever/actors/session/SessionActor.scala +++ b/src/main/scala/net/psforever/actors/session/SessionActor.scala @@ -7,10 +7,12 @@ import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} import akka.pattern.ask import akka.util.Timeout import java.util.concurrent.TimeUnit + import net.psforever.actors.net.MiddlewareActor import net.psforever.services.ServiceManager.Lookup import net.psforever.objects.locker.LockerContainer import org.log4s.MDC + import scala.collection.mutable import scala.concurrent.{Await, Future} import scala.concurrent.duration._ @@ -24,7 +26,7 @@ import net.psforever.objects.ce._ import net.psforever.objects.definition._ import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter} import net.psforever.objects.entity.{SimpleWorldEntity, WorldEntity} -import net.psforever.objects.equipment.{ChargeFireModeDefinition, EffectTarget, Equipment, FireModeSwitch, JammableUnit} +import net.psforever.objects.equipment._ import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.inventory.{Container, InventoryItem} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} @@ -40,7 +42,6 @@ import net.psforever.objects.serverobject.locks.IFFLock import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.pad.VehicleSpawnPad -import net.psforever.objects.serverobject.painbox.Painbox import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate} import net.psforever.objects.serverobject.terminals._ @@ -51,6 +52,9 @@ import net.psforever.objects.teamwork.Squad import net.psforever.objects.vehicles._ import net.psforever.objects.vehicles.Utility.InternalTelepad import net.psforever.objects.vital._ +import net.psforever.objects.vital.base._ +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.zones.{Zone, ZoneHotSpotProjector, Zoning} import net.psforever.packet._ import net.psforever.packet.game.{HotSpotInfo => PacketHotSpotInfo, _} @@ -64,12 +68,7 @@ import net.psforever.services.local.support.RouterTelepadActivation import net.psforever.services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} import net.psforever.services.properties.PropertyOverrideManager import net.psforever.services.support.SupportActor -import net.psforever.services.teamwork.{ - SquadResponse, - SquadServiceMessage, - SquadServiceResponse, - SquadAction => SquadServiceAction -} +import net.psforever.services.teamwork.{SquadResponse, SquadServiceMessage, SquadServiceResponse, SquadAction => SquadServiceAction} import net.psforever.services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import net.psforever.services.{InterstellarClusterService, RemoverActor, Service, ServiceManager} import net.psforever.types._ @@ -1783,31 +1782,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con sendResponse(GenericObjectActionMessage(guid, 9)) case AvatarResponse.EnvironmentalDamage(target, source, amount) => - if (player.isAlive && amount > 0) { - val playerGUID = player.GUID - val armor = player.Armor - val capacitor = player.Capacitor - val originalHealth = player.Health - //history - continent.GUID(source) match { - case Some(obj: Painbox) => - player.History(DamageFromPainbox(PlayerSource(player), obj, amount)) - case _ => ; - } - CancelZoningProcessWithDescriptiveReason("cancel_dmg") - player.Health = originalHealth - amount - sendResponse(PlanetsideAttributeMessage(target, 0, player.Health)) - continent.AvatarEvents ! AvatarServiceMessage( - continent.id, - AvatarAction.PlanetsideAttribute(target, 0, player.Health) - ) - damageLog.info( - s"${player.Name}-infantry: BEFORE=$originalHealth/$armor/$capacitor, AFTER=${player.Health}/$armor/$capacitor, CHANGE=$amount/0/0" - ) - if (player.Health == 0 && player.isAlive) { - player.Actor ! Player.Die() - } - } + CancelZoningProcessWithDescriptiveReason("cancel_dmg") + //TODO damage marker? case AvatarResponse.Destroy(victim, killer, weapon, pos) => // guid = victim // killer = killer ;) @@ -5215,7 +5191,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con shotOrigin: Vector3, hitPos: Vector3 ) => - ResolveProjectileEntry(projectile, ProjectileResolution.Hit, target, hitPos) match { + ResolveProjectileInteraction(projectile, DamageResolution.Hit, target, hitPos) match { case Some(resprojectile) => HandleDealingDamage(target, resprojectile) case None => ; @@ -5244,15 +5220,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con projectile.Velocity = projectile_vel val (resolution1, resolution2) = profile.Aggravated match { case Some(_) if profile.ProjectileDamageTypes.contains(DamageType.Aggravated) => - (ProjectileResolution.AggravatedDirect, ProjectileResolution.AggravatedSplash) + (DamageResolution.AggravatedDirect, DamageResolution.AggravatedSplash) case _ => - (ProjectileResolution.Splash, ProjectileResolution.Splash) + (DamageResolution.Splash, DamageResolution.Splash) } //direct_victim_uid ValidObject(direct_victim_uid) match { case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => CheckForHitPositionDiscrepancy(projectile_guid, target.Position, target) - ResolveProjectileEntry(projectile, resolution1, target, target.Position) match { + ResolveProjectileInteraction(projectile, resolution1, target, target.Position) match { case Some(projectile) => HandleDealingDamage(target, projectile) case None => ; @@ -5264,7 +5240,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con ValidObject(elem.uid) match { case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target) - ResolveProjectileEntry(projectile, resolution2, target, explosion_pos) match { + ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos) match { case Some(projectile) => HandleDealingDamage(target, projectile) case None => ; @@ -5289,7 +5265,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con ValidObject(victim_guid) match { case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) => CheckForHitPositionDiscrepancy(projectile_guid, hit_pos, target) - ResolveProjectileEntry(projectile_guid, ProjectileResolution.Lash, target, hit_pos) match { + ResolveProjectileInteraction(projectile_guid, DamageResolution.Lash, target, hit_pos) match { case Some(projectile) => HandleDealingDamage(target, projectile) case None => ; @@ -7648,15 +7624,15 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con * @param resolution the resolution status to promote the projectile * @return the projectile */ - def ResolveProjectileEntry( - projectile_guid: PlanetSideGUID, - resolution: ProjectileResolution.Value, - target: PlanetSideGameObject with FactionAffinity with Vitality, - pos: Vector3 - ): Option[ResolvedProjectile] = { + def ResolveProjectileInteraction( + projectile_guid: PlanetSideGUID, + resolution: DamageResolution.Value, + target: PlanetSideGameObject with FactionAffinity with Vitality, + pos: Vector3 + ): Option[DamageInteraction] = { FindProjectileEntry(projectile_guid) match { case Some(projectile) => - ResolveProjectileEntry(projectile, resolution, target, pos) + ResolveProjectileInteraction(projectile, resolution, target, pos) case None => log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found") None @@ -7670,18 +7646,18 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con * @param resolution the resolution status to promote the projectile * @return a copy of the projectile */ - def ResolveProjectileEntry( - projectile: Projectile, - index: Int, - resolution: ProjectileResolution.Value, - target: PlanetSideGameObject with FactionAffinity with Vitality, - pos: Vector3 - ): Option[ResolvedProjectile] = { + def ResolveProjectileInteraction( + projectile: Projectile, + index: Int, + resolution: DamageResolution.Value, + target: PlanetSideGameObject with FactionAffinity with Vitality, + pos: Vector3 + ): Option[DamageInteraction] = { if (!projectiles(index).contains(projectile)) { log.error(s"expected projectile could not be found at $index; can not resolve") None } else { - ResolveProjectileEntry(projectile, resolution, target, pos) + ResolveProjectileInteraction(projectile, resolution, target, pos) } } @@ -7691,12 +7667,12 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con * @param resolution the resolution status to promote the projectile * @return a copy of the projectile */ - def ResolveProjectileEntry( - projectile: Projectile, - resolution: ProjectileResolution.Value, - target: PlanetSideGameObject with FactionAffinity with Vitality, - pos: Vector3 - ): Option[ResolvedProjectile] = { + def ResolveProjectileInteraction( + projectile: Projectile, + resolution: DamageResolution.Value, + target: PlanetSideGameObject with FactionAffinity with Vitality, + pos: Vector3 + ): Option[DamageInteraction] = { if (projectile.isMiss) { log.error("expected projectile was already counted as a missed shot; can not resolve any further") None @@ -7715,7 +7691,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con } else { projectile } - Some(ResolvedProjectile(resolution, outProjectile, SourceEntry(target), target.DamageModel, pos)) + Some(DamageInteraction(SourceEntry(target), ProjectileReason(resolution, outProjectile, target.DamageModel), pos)) } } @@ -7760,7 +7736,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con * Calculate the amount of damage to be dealt to an active `target` * using the information reconstructed from a `Resolvedprojectile` * and affect the `target` in a synchronized manner. - * The active `target` and the target of the `ResolvedProjectile` do not have be the same. + * The active `target` and the target of the `DamageResult` do not have be the same. * While the "tell" for being able to sustain damage is an entity of type `Vitality`, * only specific `Vitality` entity types are being screened for sustaining damage. * @see `DamageResistanceModel` @@ -7768,8 +7744,8 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con * @param target a valid game object that is known to the server * @param data a projectile that will affect the target */ - def HandleDealingDamage(target: PlanetSideGameObject with Vitality, data: ResolvedProjectile): Unit = { - val func = data.damage_model.Calculate(data) + def HandleDealingDamage(target: PlanetSideGameObject with Vitality, data: DamageInteraction): Unit = { + val func = data.calculate() target match { case obj: Player if obj.CanDamage && obj.Actor != Default.Actor => // auto kick players damaging spectators @@ -7847,7 +7823,7 @@ class SessionActor(middlewareActor: typed.ActorRef[MiddlewareActor.Command], con def UpdateDeployableUIElements(list: List[(Int, Int, Int, Int)]): Unit = { val guid = PlanetSideGUID(0) list.foreach({ - case ((currElem, curr, maxElem, max)) => + case (currElem, curr, maxElem, max) => //fields must update in ordered pairs: max, curr sendResponse(PlanetsideAttributeMessage(guid, maxElem, max)) sendResponse(PlanetsideAttributeMessage(guid, currElem, curr)) diff --git a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala index f29719658..57808839a 100644 --- a/src/main/scala/net/psforever/actors/zone/BuildingActor.scala +++ b/src/main/scala/net/psforever/actors/zone/BuildingActor.scala @@ -440,7 +440,7 @@ class BuildingActor( case Request(amount, replyTo) => building match { case b: WarpGate => - //warp gates are an infiite source of nanites + //warp gates are an infinite source of nanites replyTo ! Grant(b, if (b.Active) amount else 0) Behaviors.same case _ if building.BuildingType == StructureType.Tower || building.Zone.map.cavern => diff --git a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala index 252d3e0c4..8b749a608 100644 --- a/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala +++ b/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala @@ -2,18 +2,19 @@ package net.psforever.objects import akka.actor.{Actor, ActorContext, Props} -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ce._ import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition} 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.serverobject.damage.{Damageable, DamageableEntity} 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.vital.SimpleResolutions +import net.psforever.objects.vital.interaction.DamageResult +import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.zones.Zone -import net.psforever.types.{PlanetSideGUID, Vector3} +import net.psforever.types.Vector3 import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -28,7 +29,7 @@ class ExplosiveDeployable(cdef: ExplosiveDeployableDefinition) extends ComplexDe class ExplosiveDeployableDefinition(private val objectId: Int) extends ComplexDeployableDefinition(objectId) { Name = "explosive_deployable" DeployCategory = DeployableCategory.Mines - Model = StandardResolutions.SimpleDeployables + Model = SimpleResolutions.calculate Packet = new SmallDeployableConverter private var detonateOnJamming: Boolean = true @@ -73,7 +74,7 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D val originalHealth = mine.Health val cause = applyDamageTo(mine) val damage = originalHealth - mine.Health - if (Damageable.CanDamageOrJammer(mine, damage, cause)) { + if (Damageable.CanDamageOrJammer(mine, damage, cause.interaction)) { ExplosiveDeployableControl.DamageResolution(mine, cause, damage) } else { mine.Health = originalHealth @@ -83,21 +84,26 @@ class ExplosiveDeployableControl(mine: ExplosiveDeployable) extends Actor with D } object ExplosiveDeployableControl { - def DamageResolution(target: ExplosiveDeployable, cause: ResolvedProjectile, damage: Int): Unit = { + def DamageResolution(target: ExplosiveDeployable, cause: DamageResult, damage: Int): Unit = { target.History(cause) if (target.Health == 0) { DestructionAwareness(target, cause) - } else if (!target.Jammed && Damageable.CanJammer(target, cause)) { - if ( - target.Jammed = { - val radius = cause.projectile.profile.DamageRadius - Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius + } else if (!target.Jammed && Damageable.CanJammer(target, cause.interaction)) { + if ( { + target.Jammed = cause.interaction.cause match { + case o: ProjectileReason => + val radius = o.projectile.profile.DamageRadius + Vector3.DistanceSquared(cause.interaction.hitPos, cause.interaction.target.Position) < radius * radius + case _ => + true } + } ) { - if (target.Definition.DetonateOnJamming) { + if (cause.interaction.cause.source.SympatheticExplosion || target.Definition.DetonateOnJamming) { val zone = target.Zone - zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + zone.Activity ! Zone.HotSpot.Activity(cause) zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.Detonate(target.GUID, target)) + Zone.causeExplosion(zone, target, Some(cause)) } DestructionAwareness(target, cause) } @@ -109,12 +115,9 @@ object ExplosiveDeployableControl { * @param target na * @param cause na */ - def DestructionAwareness(target: ExplosiveDeployable, cause: ResolvedProjectile): Unit = { + def DestructionAwareness(target: ExplosiveDeployable, cause: DamageResult): Unit = { val zone = target.Zone - val attribution = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match { - case Some(player) => player.GUID - case _ => PlanetSideGUID(0) - } + val attribution = DamageableEntity.attributionTo(cause, target.Zone) target.Destroyed = true Deployables.AnnounceDestroyDeployable(target, Some(if (target.Jammed) 0 seconds else 500 milliseconds)) zone.AvatarEvents ! AvatarServiceMessage( diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 48d1dcc51..4f031f167 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -22,8 +22,11 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition import net.psforever.objects.serverobject.structures.{AutoRepairStats, BuildingDefinition, WarpGateDefinition} import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade} import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, SeatArmorRestriction, UtilityType} -import net.psforever.objects.vital.damage.{DamageCalculations, DamageModifiers} -import net.psforever.objects.vital.{DamageType, StandardDamageProfile, StandardResolutions} +import net.psforever.objects.vital.base.DamageType +import net.psforever.objects.vital.damage._ +import net.psforever.objects.vital.projectile._ +import net.psforever.objects.vital.prop.DamageWithPosition +import net.psforever.objects.vital.{ComplexDeployableResolutions, MaxResolutions, SimpleResolutions} import net.psforever.types.{ExoSuitType, ImplantType, PlanetSideEmpire, Vector3} import scala.collection.mutable @@ -1691,12 +1694,12 @@ object GlobalDefinitions { max.InventoryOffset = 6 max.Holster(0, EquipmentSize.Max) max.Holster(4, EquipmentSize.Melee) - max.Subtract.Damage1 = -2 + max.Subtract.Damage1 = 2 max.ResistanceDirectHit = 6 max.ResistanceSplash = 35 max.ResistanceAggravated = 10 max.DamageUsing = DamageCalculations.AgainstMaxSuit - max.Model = StandardResolutions.Max + max.Model = MaxResolutions.calculate } CommonMaxConfig(VSMAX) @@ -2095,7 +2098,7 @@ object GlobalDefinitions { bullet_150mm_projectile.InitialVelocity = 100 bullet_150mm_projectile.Lifespan = 4f ProjectileDefinition.CalculateDerivedFields(bullet_150mm_projectile) - bullet_150mm_projectile.Modifiers = DamageModifiers.RadialDegrade + bullet_150mm_projectile.Modifiers = RadialDegrade bullet_15mm_apc_projectile.Name = "15mmbullet_apc_projectile" // TODO for later, maybe : set_resource_parent 15mmbullet_apc_projectile game_objects 15mmbullet_projectile @@ -2183,7 +2186,7 @@ object GlobalDefinitions { bullet_75mm_apc_projectile.InitialVelocity = 100 bullet_75mm_apc_projectile.Lifespan = 4f ProjectileDefinition.CalculateDerivedFields(bullet_75mm_apc_projectile) - bullet_75mm_apc_projectile.Modifiers = DamageModifiers.RadialDegrade + bullet_75mm_apc_projectile.Modifiers = RadialDegrade bullet_75mm_projectile.Name = "75mmbullet_projectile" bullet_75mm_projectile.Damage0 = 75 @@ -2194,7 +2197,7 @@ object GlobalDefinitions { bullet_75mm_projectile.InitialVelocity = 100 bullet_75mm_projectile.Lifespan = 4f ProjectileDefinition.CalculateDerivedFields(bullet_75mm_projectile) - bullet_75mm_projectile.Modifiers = DamageModifiers.RadialDegrade + bullet_75mm_projectile.Modifiers = RadialDegrade bullet_9mm_AP_projectile.Name = "9mmbullet_AP_projectile" // TODO for later, maybe : set_resource_parent 9mmbullet_AP_projectile game_objects 9mmbullet_projectile @@ -2258,7 +2261,7 @@ object GlobalDefinitions { aphelion_immolation_cannon_projectile.InitialVelocity = 250 aphelion_immolation_cannon_projectile.Lifespan = 1.4f ProjectileDefinition.CalculateDerivedFields(aphelion_immolation_cannon_projectile) - aphelion_immolation_cannon_projectile.Modifiers = DamageModifiers.RadialDegrade + aphelion_immolation_cannon_projectile.Modifiers = RadialDegrade aphelion_laser_projectile.Name = "aphelion_laser_projectile" aphelion_laser_projectile.Damage0 = 3 @@ -2288,7 +2291,7 @@ object GlobalDefinitions { aphelion_plasma_rocket_projectile.InitialVelocity = 75 aphelion_plasma_rocket_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(aphelion_plasma_rocket_projectile) - aphelion_plasma_rocket_projectile.Modifiers = DamageModifiers.RadialDegrade + aphelion_plasma_rocket_projectile.Modifiers = RadialDegrade aphelion_ppa_projectile.Name = "aphelion_ppa_projectile" // TODO for later, maybe : set_resource_parent aphelion_ppa_projectile game_objects ppa_projectile @@ -2305,7 +2308,7 @@ object GlobalDefinitions { aphelion_ppa_projectile.InitialVelocity = 350 aphelion_ppa_projectile.Lifespan = .7f ProjectileDefinition.CalculateDerivedFields(aphelion_ppa_projectile) - aphelion_ppa_projectile.Modifiers = DamageModifiers.RadialDegrade + aphelion_ppa_projectile.Modifiers = RadialDegrade aphelion_starfire_projectile.Name = "aphelion_starfire_projectile" // TODO for later, maybe : set_resource_parent aphelion_starfire_projectile game_objects starfire_projectile @@ -2343,7 +2346,7 @@ object GlobalDefinitions { bolt_projectile.InitialVelocity = 500 bolt_projectile.Lifespan = 1.0f ProjectileDefinition.CalculateDerivedFields(bolt_projectile) - //TODO bolt_projectile.Modifiers = DamageModifiers.DistanceDegrade? + //TODO bolt_projectile.Modifiers = DistanceDegrade? burster_projectile.Name = "burster_projectile" burster_projectile.Damage0 = 18 @@ -2357,9 +2360,9 @@ object GlobalDefinitions { burster_projectile.Lifespan = 4f ProjectileDefinition.CalculateDerivedFields(burster_projectile) burster_projectile.Modifiers = List( - //DamageModifiers.FlakHit, - DamageModifiers.FlakBurst, - DamageModifiers.MaxDistanceCutoff + //FlakHit, + FlakBurst, + MaxDistanceCutoff ) chainblade_projectile.Name = "chainblade_projectile" @@ -2370,7 +2373,7 @@ object GlobalDefinitions { chainblade_projectile.InitialVelocity = 100 chainblade_projectile.Lifespan = .02f ProjectileDefinition.CalculateDerivedFields(chainblade_projectile) - chainblade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff + chainblade_projectile.Modifiers = MaxDistanceCutoff colossus_100mm_projectile.Name = "colossus_100mm_projectile" colossus_100mm_projectile.Damage0 = 58 @@ -2384,7 +2387,7 @@ object GlobalDefinitions { colossus_100mm_projectile.InitialVelocity = 100 colossus_100mm_projectile.Lifespan = 4f ProjectileDefinition.CalculateDerivedFields(colossus_100mm_projectile) - colossus_100mm_projectile.Modifiers = DamageModifiers.RadialDegrade + colossus_100mm_projectile.Modifiers = RadialDegrade colossus_burster_projectile.Name = "colossus_burster_projectile" // TODO for later, maybe : set_resource_parent colossus_burster_projectile game_objects burster_projectile @@ -2401,9 +2404,9 @@ object GlobalDefinitions { colossus_burster_projectile.Lifespan = 2.5f ProjectileDefinition.CalculateDerivedFields(colossus_burster_projectile) colossus_burster_projectile.Modifiers = List( - //DamageModifiers.FlakHit, - DamageModifiers.FlakBurst, - DamageModifiers.MaxDistanceCutoff + //FlakHit, + FlakBurst, + MaxDistanceCutoff ) colossus_chaingun_projectile.Name = "colossus_chaingun_projectile" @@ -2432,7 +2435,7 @@ object GlobalDefinitions { colossus_cluster_bomb_projectile.InitialVelocity = 75 colossus_cluster_bomb_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(colossus_cluster_bomb_projectile) - colossus_cluster_bomb_projectile.Modifiers = DamageModifiers.RadialDegrade + colossus_cluster_bomb_projectile.Modifiers = RadialDegrade colossus_tank_cannon_projectile.Name = "colossus_tank_cannon_projectile" // TODO for later, maybe : set_resource_parent colossus_tank_cannon_projectile game_objects 75mmbullet_projectile @@ -2447,7 +2450,7 @@ object GlobalDefinitions { colossus_tank_cannon_projectile.InitialVelocity = 165 colossus_tank_cannon_projectile.Lifespan = 2f ProjectileDefinition.CalculateDerivedFields(colossus_tank_cannon_projectile) - colossus_tank_cannon_projectile.Modifiers = DamageModifiers.RadialDegrade + colossus_tank_cannon_projectile.Modifiers = RadialDegrade comet_projectile.Name = "comet_projectile" comet_projectile.Damage0 = 15 @@ -2475,8 +2478,8 @@ object GlobalDefinitions { comet_projectile.Lifespan = 3.1f ProjectileDefinition.CalculateDerivedFields(comet_projectile) comet_projectile.Modifiers = List( - DamageModifiers.CometAggravated, - DamageModifiers.CometAggravatedBurn + CometAggravated, + CometAggravatedBurn ) dualcycler_projectile.Name = "dualcycler_projectile" @@ -2500,7 +2503,7 @@ object GlobalDefinitions { dynomite_projectile.InitialVelocity = 30 dynomite_projectile.Lifespan = 3f ProjectileDefinition.CalculateDerivedFields(dynomite_projectile) - dynomite_projectile.Modifiers = DamageModifiers.RadialDegrade + dynomite_projectile.Modifiers = RadialDegrade energy_cell_projectile.Name = "energy_cell_projectile" energy_cell_projectile.Damage0 = 18 @@ -2578,7 +2581,7 @@ object GlobalDefinitions { falcon_projectile.InitialVelocity = 120 falcon_projectile.Lifespan = 2.1f ProjectileDefinition.CalculateDerivedFields(falcon_projectile) - falcon_projectile.Modifiers = DamageModifiers.RadialDegrade + falcon_projectile.Modifiers = RadialDegrade firebird_missile_projectile.Name = "firebird_missile_projectile" firebird_missile_projectile.Damage0 = 125 @@ -2594,7 +2597,7 @@ object GlobalDefinitions { firebird_missile_projectile.InitialVelocity = 75 firebird_missile_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(firebird_missile_projectile) - firebird_missile_projectile.Modifiers = DamageModifiers.RadialDegrade + firebird_missile_projectile.Modifiers = RadialDegrade flail_projectile.Name = "flail_projectile" flail_projectile.Damage0 = 75 @@ -2610,7 +2613,7 @@ object GlobalDefinitions { flail_projectile.InitialVelocity = 75 flail_projectile.Lifespan = 40f ProjectileDefinition.CalculateDerivedFields(flail_projectile) - //TODO flail_projectile.Modifiers = DamageModifiers.RadialDegrade? + //TODO flail_projectile.Modifiers = RadialDegrade? flamethrower_fireball.Name = "flamethrower_fireball" flamethrower_fireball.Damage0 = 30 @@ -2635,10 +2638,10 @@ object GlobalDefinitions { flamethrower_fireball.Lifespan = 1.2f ProjectileDefinition.CalculateDerivedFields(flamethrower_fireball) flamethrower_fireball.Modifiers = List( - DamageModifiers.InfantryAggravatedDirect, - DamageModifiers.InfantryAggravatedSplash, - DamageModifiers.RadialDegrade, - DamageModifiers.FireballAggravatedBurn + InfantryAggravatedDirect, + InfantryAggravatedSplash, + RadialDegrade, + FireballAggravatedBurn ) flamethrower_projectile.Name = "flamethrower_projectile" @@ -2666,9 +2669,9 @@ object GlobalDefinitions { flamethrower_projectile.Lifespan = 2.0f ProjectileDefinition.CalculateDerivedFields(flamethrower_projectile) flamethrower_projectile.Modifiers = List( - DamageModifiers.InfantryAggravatedDirect, - DamageModifiers.FireballAggravatedBurn, - DamageModifiers.MaxDistanceCutoff + InfantryAggravatedDirect, + FireballAggravatedBurn, + MaxDistanceCutoff ) flux_cannon_apc_projectile.Name = "flux_cannon_apc_projectile" @@ -2697,7 +2700,7 @@ object GlobalDefinitions { flux_cannon_thresher_projectile.InitialVelocity = 75 flux_cannon_thresher_projectile.Lifespan = 3f ProjectileDefinition.CalculateDerivedFields(flux_cannon_thresher_projectile) - flux_cannon_thresher_projectile.Modifiers = DamageModifiers.RadialDegrade + flux_cannon_thresher_projectile.Modifiers = RadialDegrade fluxpod_projectile.Name = "fluxpod_projectile" fluxpod_projectile.Damage0 = 110 @@ -2711,7 +2714,7 @@ object GlobalDefinitions { fluxpod_projectile.InitialVelocity = 80 fluxpod_projectile.Lifespan = 4f ProjectileDefinition.CalculateDerivedFields(fluxpod_projectile) - fluxpod_projectile.Modifiers = DamageModifiers.RadialDegrade + fluxpod_projectile.Modifiers = RadialDegrade forceblade_projectile.Name = "forceblade_projectile" // TODO for later, maybe : set_resource_parent forceblade_projectile game_objects melee_ammo_projectile @@ -2721,7 +2724,7 @@ object GlobalDefinitions { forceblade_projectile.InitialVelocity = 100 forceblade_projectile.Lifespan = .02f ProjectileDefinition.CalculateDerivedFields(forceblade_projectile) - forceblade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff + forceblade_projectile.Modifiers = MaxDistanceCutoff frag_cartridge_projectile.Name = "frag_cartridge_projectile" // TODO for later, maybe : set_resource_parent frag_cartridge_projectile game_objects frag_grenade_projectile @@ -2734,7 +2737,7 @@ object GlobalDefinitions { frag_cartridge_projectile.InitialVelocity = 30 frag_cartridge_projectile.Lifespan = 15f ProjectileDefinition.CalculateDerivedFields(frag_cartridge_projectile) - frag_cartridge_projectile.Modifiers = DamageModifiers.RadialDegrade + frag_cartridge_projectile.Modifiers = RadialDegrade frag_cartridge_projectile_b.Name = "frag_cartridge_projectile_b" // TODO for later, maybe : set_resource_parent frag_cartridge_projectile_b game_objects frag_grenade_projectile_enh @@ -2747,7 +2750,7 @@ object GlobalDefinitions { frag_cartridge_projectile_b.InitialVelocity = 30 frag_cartridge_projectile_b.Lifespan = 2f ProjectileDefinition.CalculateDerivedFields(frag_cartridge_projectile_b) - frag_cartridge_projectile_b.Modifiers = DamageModifiers.RadialDegrade + frag_cartridge_projectile_b.Modifiers = RadialDegrade frag_grenade_projectile.Name = "frag_grenade_projectile" frag_grenade_projectile.Damage0 = 75 @@ -2759,7 +2762,7 @@ object GlobalDefinitions { frag_grenade_projectile.InitialVelocity = 30 frag_grenade_projectile.Lifespan = 15f ProjectileDefinition.CalculateDerivedFields(frag_grenade_projectile) - frag_grenade_projectile.Modifiers = DamageModifiers.RadialDegrade + frag_grenade_projectile.Modifiers = RadialDegrade frag_grenade_projectile_enh.Name = "frag_grenade_projectile_enh" // TODO for later, maybe : set_resource_parent frag_grenade_projectile_enh game_objects frag_grenade_projectile @@ -2772,7 +2775,7 @@ object GlobalDefinitions { frag_grenade_projectile_enh.InitialVelocity = 30 frag_grenade_projectile_enh.Lifespan = 2f ProjectileDefinition.CalculateDerivedFields(frag_grenade_projectile_enh) - frag_grenade_projectile_enh.Modifiers = DamageModifiers.RadialDegrade + frag_grenade_projectile_enh.Modifiers = RadialDegrade galaxy_gunship_gun_projectile.Name = "galaxy_gunship_gun_projectile" // TODO for later, maybe : set_resource_parent galaxy_gunship_gun_projectile game_objects 35mmbullet_projectile @@ -2798,7 +2801,7 @@ object GlobalDefinitions { gauss_cannon_projectile.InitialVelocity = 150 gauss_cannon_projectile.Lifespan = 2.67f ProjectileDefinition.CalculateDerivedFields(gauss_cannon_projectile) - gauss_cannon_projectile.Modifiers = DamageModifiers.RadialDegrade + gauss_cannon_projectile.Modifiers = RadialDegrade grenade_projectile.Name = "grenade_projectile" grenade_projectile.Damage0 = 50 @@ -2808,7 +2811,7 @@ object GlobalDefinitions { grenade_projectile.InitialVelocity = 15 grenade_projectile.Lifespan = 15f ProjectileDefinition.CalculateDerivedFields(grenade_projectile) - grenade_projectile.Modifiers = DamageModifiers.RadialDegrade + grenade_projectile.Modifiers = RadialDegrade heavy_grenade_projectile.Name = "heavy_grenade_projectile" heavy_grenade_projectile.Damage0 = 50 @@ -2823,7 +2826,7 @@ object GlobalDefinitions { heavy_grenade_projectile.InitialVelocity = 75 heavy_grenade_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(heavy_grenade_projectile) - heavy_grenade_projectile.Modifiers = DamageModifiers.RadialDegrade + heavy_grenade_projectile.Modifiers = RadialDegrade heavy_rail_beam_projectile.Name = "heavy_rail_beam_projectile" heavy_rail_beam_projectile.Damage0 = 75 @@ -2837,7 +2840,7 @@ object GlobalDefinitions { heavy_rail_beam_projectile.InitialVelocity = 600 heavy_rail_beam_projectile.Lifespan = .5f ProjectileDefinition.CalculateDerivedFields(heavy_rail_beam_projectile) - heavy_rail_beam_projectile.Modifiers = DamageModifiers.RadialDegrade + heavy_rail_beam_projectile.Modifiers = RadialDegrade heavy_sniper_projectile.Name = "heavy_sniper_projectile" heavy_sniper_projectile.Damage0 = 55 @@ -2849,7 +2852,7 @@ object GlobalDefinitions { heavy_sniper_projectile.InitialVelocity = 500 heavy_sniper_projectile.Lifespan = 1.0f ProjectileDefinition.CalculateDerivedFields(heavy_sniper_projectile) - heavy_sniper_projectile.Modifiers = DamageModifiers.RadialDegrade + heavy_sniper_projectile.Modifiers = RadialDegrade hellfire_projectile.Name = "hellfire_projectile" hellfire_projectile.Damage0 = 50 @@ -2865,7 +2868,7 @@ object GlobalDefinitions { hellfire_projectile.InitialVelocity = 125 hellfire_projectile.Lifespan = 1.5f ProjectileDefinition.CalculateDerivedFields(hellfire_projectile) - hellfire_projectile.Modifiers = DamageModifiers.RadialDegrade + hellfire_projectile.Modifiers = RadialDegrade hunter_seeker_missile_dumbfire.Name = "hunter_seeker_missile_dumbfire" hunter_seeker_missile_dumbfire.Damage0 = 50 @@ -2879,7 +2882,7 @@ object GlobalDefinitions { hunter_seeker_missile_dumbfire.InitialVelocity = 40 hunter_seeker_missile_dumbfire.Lifespan = 6.3f ProjectileDefinition.CalculateDerivedFields(hunter_seeker_missile_dumbfire) - hunter_seeker_missile_dumbfire.Modifiers = DamageModifiers.RadialDegrade + hunter_seeker_missile_dumbfire.Modifiers = RadialDegrade hunter_seeker_missile_projectile.Name = "hunter_seeker_missile_projectile" hunter_seeker_missile_projectile.Damage0 = 50 @@ -2896,7 +2899,7 @@ object GlobalDefinitions { hunter_seeker_missile_projectile.RemoteClientData = (39577, 201) hunter_seeker_missile_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(hunter_seeker_missile_projectile) - hunter_seeker_missile_projectile.Modifiers = DamageModifiers.RadialDegrade + hunter_seeker_missile_projectile.Modifiers = RadialDegrade jammer_cartridge_projectile.Name = "jammer_cartridge_projectile" // TODO for later, maybe : set_resource_parent jammer_cartridge_projectile game_objects jammer_grenade_projectile @@ -2935,7 +2938,7 @@ object GlobalDefinitions { EffectTarget.Validation.VehicleNotAMS ) -> 10000 ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile) - jammer_cartridge_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff + jammer_cartridge_projectile.Modifiers = 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 @@ -2974,7 +2977,7 @@ object GlobalDefinitions { EffectTarget.Validation.VehicleNotAMS ) -> 10000 ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile_b) - jammer_cartridge_projectile_b.Modifiers = DamageModifiers.MaxDistanceCutoff + jammer_cartridge_projectile_b.Modifiers = MaxDistanceCutoff jammer_grenade_projectile.Name = "jammer_grenade_projectile" jammer_grenade_projectile.Damage0 = 0 @@ -3012,7 +3015,7 @@ object GlobalDefinitions { EffectTarget.Validation.VehicleNotAMS ) -> 10000 ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile) - jammer_grenade_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff + jammer_grenade_projectile.Modifiers = 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 @@ -3051,7 +3054,7 @@ object GlobalDefinitions { EffectTarget.Validation.VehicleNotAMS ) -> 10000 ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile_enh) - jammer_grenade_projectile_enh.Modifiers = DamageModifiers.MaxDistanceCutoff + jammer_grenade_projectile_enh.Modifiers = MaxDistanceCutoff katana_projectile.Name = "katana_projectile" katana_projectile.Damage0 = 25 @@ -3095,8 +3098,8 @@ object GlobalDefinitions { lasher_projectile.Lifespan = 0.75f ProjectileDefinition.CalculateDerivedFields(lasher_projectile) lasher_projectile.Modifiers = List( - DamageModifiers.DistanceDegrade, - DamageModifiers.Lash + DistanceDegrade, + Lash ) lasher_projectile_ap.Name = "lasher_projectile_ap" @@ -3113,8 +3116,8 @@ object GlobalDefinitions { lasher_projectile_ap.Lifespan = 0.75f ProjectileDefinition.CalculateDerivedFields(lasher_projectile_ap) lasher_projectile_ap.Modifiers = List( - DamageModifiers.DistanceDegrade, - DamageModifiers.Lash + DistanceDegrade, + Lash ) liberator_bomb_cluster_bomblet_projectile.Name = "liberator_bomb_cluster_bomblet_projectile" @@ -3126,7 +3129,7 @@ object GlobalDefinitions { liberator_bomb_cluster_bomblet_projectile.InitialVelocity = 0 liberator_bomb_cluster_bomblet_projectile.Lifespan = 30f ProjectileDefinition.CalculateDerivedFields(liberator_bomb_cluster_bomblet_projectile) - liberator_bomb_cluster_bomblet_projectile.Modifiers = DamageModifiers.RadialDegrade + liberator_bomb_cluster_bomblet_projectile.Modifiers = RadialDegrade liberator_bomb_cluster_projectile.Name = "liberator_bomb_cluster_projectile" liberator_bomb_cluster_projectile.Damage0 = 75 @@ -3150,7 +3153,7 @@ object GlobalDefinitions { liberator_bomb_projectile.InitialVelocity = 0 liberator_bomb_projectile.Lifespan = 30f ProjectileDefinition.CalculateDerivedFields(liberator_bomb_projectile) - liberator_bomb_projectile.Modifiers = DamageModifiers.RadialDegrade + liberator_bomb_projectile.Modifiers = RadialDegrade maelstrom_grenade_projectile.Name = "maelstrom_grenade_projectile" maelstrom_grenade_projectile.Damage0 = 32 @@ -3163,7 +3166,7 @@ object GlobalDefinitions { maelstrom_grenade_projectile.Lifespan = 2f maelstrom_grenade_projectile.DamageProxy = 464 ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile) - maelstrom_grenade_projectile.Modifiers = DamageModifiers.RadialDegrade + maelstrom_grenade_projectile.Modifiers = RadialDegrade maelstrom_grenade_projectile_contact.Name = "maelstrom_grenade_projectile_contact" // TODO for later, maybe : set_resource_parent maelstrom_grenade_projectile_contact game_objects maelstrom_grenade_projectile @@ -3177,7 +3180,7 @@ object GlobalDefinitions { maelstrom_grenade_projectile_contact.Lifespan = 15f maelstrom_grenade_projectile_contact.DamageProxy = 464 ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile_contact) - maelstrom_grenade_projectile_contact.Modifiers = DamageModifiers.RadialDegrade + maelstrom_grenade_projectile_contact.Modifiers = RadialDegrade maelstrom_stream_projectile.Name = "maelstrom_stream_projectile" maelstrom_stream_projectile.Damage0 = 15 @@ -3188,7 +3191,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 + maelstrom_stream_projectile.Modifiers = MaxDistanceCutoff magcutter_projectile.Name = "magcutter_projectile" // TODO for later, maybe : set_resource_parent magcutter_projectile game_objects melee_ammo_projectile @@ -3198,7 +3201,7 @@ object GlobalDefinitions { magcutter_projectile.InitialVelocity = 100 magcutter_projectile.Lifespan = .02f ProjectileDefinition.CalculateDerivedFields(magcutter_projectile) - magcutter_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff + magcutter_projectile.Modifiers = MaxDistanceCutoff melee_ammo_projectile.Name = "melee_ammo_projectile" melee_ammo_projectile.Damage0 = 25 @@ -3207,7 +3210,7 @@ object GlobalDefinitions { melee_ammo_projectile.InitialVelocity = 100 melee_ammo_projectile.Lifespan = .02f ProjectileDefinition.CalculateDerivedFields(melee_ammo_projectile) - melee_ammo_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff + melee_ammo_projectile.Modifiers = MaxDistanceCutoff meteor_common.Name = "meteor_common" meteor_common.DamageAtEdge = .1f @@ -3215,7 +3218,7 @@ object GlobalDefinitions { meteor_common.InitialVelocity = 0 meteor_common.Lifespan = 40 ProjectileDefinition.CalculateDerivedFields(meteor_common) - meteor_common.Modifiers = DamageModifiers.RadialDegrade + meteor_common.Modifiers = RadialDegrade meteor_projectile_b_large.Name = "meteor_projectile_b_large" // TODO for later, maybe : set_resource_parent meteor_projectile_b_large game_objects meteor_common @@ -3227,7 +3230,7 @@ object GlobalDefinitions { meteor_projectile_b_large.InitialVelocity = 0 meteor_projectile_b_large.Lifespan = 40 ProjectileDefinition.CalculateDerivedFields(meteor_projectile_b_large) - meteor_projectile_b_large.Modifiers = DamageModifiers.RadialDegrade + meteor_projectile_b_large.Modifiers = RadialDegrade meteor_projectile_b_medium.Name = "meteor_projectile_b_medium" // TODO for later, maybe : set_resource_parent meteor_projectile_b_medium game_objects meteor_common @@ -3239,7 +3242,7 @@ object GlobalDefinitions { meteor_projectile_b_medium.InitialVelocity = 0 meteor_projectile_b_medium.Lifespan = 40 ProjectileDefinition.CalculateDerivedFields(meteor_projectile_b_medium) - meteor_projectile_b_medium.Modifiers = DamageModifiers.RadialDegrade + meteor_projectile_b_medium.Modifiers = RadialDegrade meteor_projectile_b_small.Name = "meteor_projectile_b_small" // TODO for later, maybe : set_resource_parent meteor_projectile_b_small game_objects meteor_common @@ -3251,7 +3254,7 @@ object GlobalDefinitions { meteor_projectile_b_small.InitialVelocity = 0 meteor_projectile_b_small.Lifespan = 40 ProjectileDefinition.CalculateDerivedFields(meteor_projectile_b_small) - meteor_projectile_b_small.Modifiers = DamageModifiers.RadialDegrade + meteor_projectile_b_small.Modifiers = RadialDegrade meteor_projectile_large.Name = "meteor_projectile_large" // TODO for later, maybe : set_resource_parent meteor_projectile_large game_objects meteor_common @@ -3263,7 +3266,7 @@ object GlobalDefinitions { meteor_projectile_large.InitialVelocity = 0 meteor_projectile_large.Lifespan = 40 ProjectileDefinition.CalculateDerivedFields(meteor_projectile_large) - meteor_projectile_large.Modifiers = DamageModifiers.RadialDegrade + meteor_projectile_large.Modifiers = RadialDegrade meteor_projectile_medium.Name = "meteor_projectile_medium" // TODO for later, maybe : set_resource_parent meteor_projectile_medium game_objects meteor_common @@ -3275,7 +3278,7 @@ object GlobalDefinitions { meteor_projectile_medium.InitialVelocity = 0 meteor_projectile_medium.Lifespan = 40 ProjectileDefinition.CalculateDerivedFields(meteor_projectile_medium) - meteor_projectile_medium.Modifiers = DamageModifiers.RadialDegrade + meteor_projectile_medium.Modifiers = RadialDegrade meteor_projectile_small.Name = "meteor_projectile_small" // TODO for later, maybe : set_resource_parent meteor_projectile_small game_objects meteor_common @@ -3287,7 +3290,7 @@ object GlobalDefinitions { meteor_projectile_small.InitialVelocity = 0 meteor_projectile_small.Lifespan = 40 ProjectileDefinition.CalculateDerivedFields(meteor_projectile_small) - meteor_projectile_small.Modifiers = DamageModifiers.RadialDegrade + meteor_projectile_small.Modifiers = RadialDegrade mine_projectile.Name = "mine_projectile" mine_projectile.Lifespan = 0.01f @@ -3304,7 +3307,7 @@ object GlobalDefinitions { mine_sweeper_projectile.InitialVelocity = 30 mine_sweeper_projectile.Lifespan = 15f ProjectileDefinition.CalculateDerivedFields(mine_sweeper_projectile) - mine_sweeper_projectile.Modifiers = DamageModifiers.RadialDegrade + mine_sweeper_projectile.Modifiers = RadialDegrade mine_sweeper_projectile_enh.Name = "mine_sweeper_projectile_enh" mine_sweeper_projectile_enh.Damage0 = 0 @@ -3316,7 +3319,7 @@ object GlobalDefinitions { mine_sweeper_projectile_enh.InitialVelocity = 30 mine_sweeper_projectile_enh.Lifespan = 3f ProjectileDefinition.CalculateDerivedFields(mine_sweeper_projectile_enh) - mine_sweeper_projectile_enh.Modifiers = DamageModifiers.RadialDegrade + mine_sweeper_projectile_enh.Modifiers = RadialDegrade oicw_projectile.Name = "oicw_projectile" oicw_projectile.Damage0 = 50 @@ -3332,7 +3335,7 @@ object GlobalDefinitions { oicw_projectile.RemoteClientData = (13107, 195) oicw_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(oicw_projectile) - oicw_projectile.Modifiers = DamageModifiers.RadialDegrade + oicw_projectile.Modifiers = RadialDegrade oicw_little_buddy.Name = "oicw_little_buddy" oicw_little_buddy.Damage0 = 75 @@ -3346,7 +3349,7 @@ object GlobalDefinitions { oicw_little_buddy.Packet = projectileConverter //add_property oicw_little_buddy multi_stage_spawn_server_side true ... ProjectileDefinition.CalculateDerivedFields(oicw_little_buddy) - oicw_little_buddy.Modifiers = DamageModifiers.RadialDegrade + oicw_little_buddy.Modifiers = RadialDegrade pellet_gun_projectile.Name = "pellet_gun_projectile" // TODO for later, maybe : set_resource_parent pellet_gun_projectile game_objects shotgun_shell_projectile @@ -3395,7 +3398,7 @@ object GlobalDefinitions { peregrine_particle_cannon_projectile.InitialVelocity = 500 peregrine_particle_cannon_projectile.Lifespan = .6f ProjectileDefinition.CalculateDerivedFields(peregrine_particle_cannon_projectile) - peregrine_particle_cannon_projectile.Modifiers = DamageModifiers.RadialDegrade + peregrine_particle_cannon_projectile.Modifiers = RadialDegrade peregrine_rocket_pod_projectile.Name = "peregrine_rocket_pod_projectile" peregrine_rocket_pod_projectile.Damage0 = 30 @@ -3411,7 +3414,7 @@ object GlobalDefinitions { peregrine_rocket_pod_projectile.InitialVelocity = 200 peregrine_rocket_pod_projectile.Lifespan = 1.85f ProjectileDefinition.CalculateDerivedFields(peregrine_rocket_pod_projectile) - peregrine_rocket_pod_projectile.Modifiers = DamageModifiers.RadialDegrade + peregrine_rocket_pod_projectile.Modifiers = RadialDegrade peregrine_sparrow_projectile.Name = "peregrine_sparrow_projectile" // TODO for later, maybe : set_resource_parent peregrine_sparrow_projectile game_objects sparrow_projectile @@ -3432,7 +3435,7 @@ object GlobalDefinitions { peregrine_sparrow_projectile.AutoLock = true peregrine_sparrow_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(peregrine_sparrow_projectile) - peregrine_sparrow_projectile.Modifiers = DamageModifiers.RadialDegrade + peregrine_sparrow_projectile.Modifiers = RadialDegrade phalanx_av_projectile.Name = "phalanx_av_projectile" phalanx_av_projectile.Damage0 = 60 @@ -3443,7 +3446,7 @@ object GlobalDefinitions { phalanx_av_projectile.InitialVelocity = 100 phalanx_av_projectile.Lifespan = 4f ProjectileDefinition.CalculateDerivedFields(phalanx_av_projectile) - phalanx_av_projectile.Modifiers = DamageModifiers.RadialDegrade + phalanx_av_projectile.Modifiers = RadialDegrade phalanx_flak_projectile.Name = "phalanx_flak_projectile" phalanx_flak_projectile.Damage0 = 15 @@ -3457,9 +3460,9 @@ object GlobalDefinitions { phalanx_flak_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(phalanx_flak_projectile) phalanx_flak_projectile.Modifiers = List( - //DamageModifiers.FlakHit, - DamageModifiers.FlakBurst, - DamageModifiers.MaxDistanceCutoff + //FlakHit, + FlakBurst, + MaxDistanceCutoff ) phalanx_projectile.Name = "phalanx_projectile" @@ -3495,7 +3498,7 @@ object GlobalDefinitions { phoenix_missile_guided_projectile.Packet = projectileConverter // ProjectileDefinition.CalculateDerivedFields(phoenix_missile_guided_projectile) - phoenix_missile_guided_projectile.Modifiers = DamageModifiers.RadialDegrade + phoenix_missile_guided_projectile.Modifiers = RadialDegrade phoenix_missile_projectile.Name = "phoenix_missile_projectile" phoenix_missile_projectile.Damage0 = 80 @@ -3511,7 +3514,7 @@ object GlobalDefinitions { phoenix_missile_projectile.InitialVelocity = 0 phoenix_missile_projectile.Lifespan = 3f ProjectileDefinition.CalculateDerivedFields(phoenix_missile_projectile) - phoenix_missile_projectile.Modifiers = DamageModifiers.RadialDegrade + phoenix_missile_projectile.Modifiers = RadialDegrade plasma_cartridge_projectile.Name = "plasma_cartridge_projectile" // TODO for later, maybe : set_resource_parent plasma_cartridge_projectile game_objects plasma_grenade_projectile @@ -3534,11 +3537,11 @@ object GlobalDefinitions { 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 + InfantryAggravatedDirect, + InfantryAggravatedDirectBurn, + InfantryAggravatedSplash, + InfantryAggravatedSplashBurn, + RadialDegrade ) plasma_cartridge_projectile_b.Name = "plasma_cartridge_projectile_b" @@ -3562,11 +3565,11 @@ object GlobalDefinitions { 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 + InfantryAggravatedDirect, + InfantryAggravatedDirectBurn, + InfantryAggravatedSplash, + InfantryAggravatedSplashBurn, + RadialDegrade ) plasma_grenade_projectile.Name = "plasma_grenade_projectile" @@ -3589,11 +3592,11 @@ object GlobalDefinitions { 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 + InfantryAggravatedDirect, + InfantryAggravatedDirectBurn, + InfantryAggravatedSplash, + InfantryAggravatedSplashBurn, + RadialDegrade ) plasma_grenade_projectile_B.Name = "plasma_grenade_projectile_B" @@ -3617,11 +3620,11 @@ object GlobalDefinitions { 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 + InfantryAggravatedDirect, + InfantryAggravatedDirectBurn, + InfantryAggravatedSplash, + InfantryAggravatedSplashBurn, + RadialDegrade ) pounder_projectile.Name = "pounder_projectile" @@ -3637,7 +3640,7 @@ object GlobalDefinitions { pounder_projectile.InitialVelocity = 120 pounder_projectile.Lifespan = 2.5f ProjectileDefinition.CalculateDerivedFields(pounder_projectile) - pounder_projectile.Modifiers = DamageModifiers.RadialDegrade + pounder_projectile.Modifiers = RadialDegrade pounder_projectile_enh.Name = "pounder_projectile_enh" // TODO for later, maybe : set_resource_parent pounder_projectile_enh game_objects pounder_projectile @@ -3653,7 +3656,7 @@ object GlobalDefinitions { pounder_projectile_enh.InitialVelocity = 120 pounder_projectile_enh.Lifespan = 3.2f ProjectileDefinition.CalculateDerivedFields(pounder_projectile_enh) - pounder_projectile_enh.Modifiers = DamageModifiers.RadialDegrade + pounder_projectile_enh.Modifiers = RadialDegrade ppa_projectile.Name = "ppa_projectile" ppa_projectile.Damage0 = 20 @@ -3728,7 +3731,7 @@ object GlobalDefinitions { reaver_rocket_projectile.InitialVelocity = 100 reaver_rocket_projectile.Lifespan = 2.1f ProjectileDefinition.CalculateDerivedFields(reaver_rocket_projectile) - reaver_rocket_projectile.Modifiers = DamageModifiers.RadialDegrade + reaver_rocket_projectile.Modifiers = RadialDegrade rocket_projectile.Name = "rocket_projectile" rocket_projectile.Damage0 = 50 @@ -3744,7 +3747,7 @@ object GlobalDefinitions { rocket_projectile.InitialVelocity = 50 rocket_projectile.Lifespan = 8f ProjectileDefinition.CalculateDerivedFields(rocket_projectile) - rocket_projectile.Modifiers = DamageModifiers.RadialDegrade + rocket_projectile.Modifiers = RadialDegrade rocklet_flak_projectile.Name = "rocklet_flak_projectile" rocklet_flak_projectile.Damage0 = 20 @@ -3760,9 +3763,9 @@ object GlobalDefinitions { rocklet_flak_projectile.Lifespan = 3.2f ProjectileDefinition.CalculateDerivedFields(rocklet_flak_projectile) rocklet_flak_projectile.Modifiers = List( - //DamageModifiers.FlakHit, - DamageModifiers.FlakBurst, - DamageModifiers.MaxDistanceCutoff + //FlakHit, + FlakBurst, + MaxDistanceCutoff ) rocklet_jammer_projectile.Name = "rocklet_jammer_projectile" @@ -3775,7 +3778,7 @@ object GlobalDefinitions { rocklet_jammer_projectile.InitialVelocity = 50 rocklet_jammer_projectile.Lifespan = 8f ProjectileDefinition.CalculateDerivedFields(rocklet_jammer_projectile) - //TODO rocklet_jammer_projectile.Modifiers = DamageModifiers.RadialDegrade? + //TODO rocklet_jammer_projectile.Modifiers = RadialDegrade? scattercannon_projectile.Name = "scattercannon_projectile" scattercannon_projectile.Damage0 = 11 @@ -3843,9 +3846,9 @@ object GlobalDefinitions { skyguard_flak_cannon_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(skyguard_flak_cannon_projectile) skyguard_flak_cannon_projectile.Modifiers = List( - //DamageModifiers.FlakHit, - DamageModifiers.FlakBurst, - DamageModifiers.MaxDistanceCutoff + //FlakHit, + FlakBurst, + MaxDistanceCutoff ) sparrow_projectile.Name = "sparrow_projectile" @@ -3864,7 +3867,7 @@ object GlobalDefinitions { sparrow_projectile.AutoLock = true sparrow_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(sparrow_projectile) - sparrow_projectile.Modifiers = DamageModifiers.RadialDegrade + sparrow_projectile.Modifiers = RadialDegrade sparrow_secondary_projectile.Name = "sparrow_secondary_projectile" // TODO for later, maybe : set_resource_parent sparrow_secondary_projectile game_objects sparrow_projectile @@ -3883,7 +3886,7 @@ object GlobalDefinitions { sparrow_secondary_projectile.AutoLock = true sparrow_secondary_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(sparrow_secondary_projectile) - sparrow_secondary_projectile.Modifiers = DamageModifiers.RadialDegrade + sparrow_secondary_projectile.Modifiers = RadialDegrade spiker_projectile.Name = "spiker_projectile" spiker_projectile.Charging = ChargeDamage(4, StandardDamageProfile(damage0 = Some(20), damage1 = Some(20))) @@ -3897,8 +3900,8 @@ object GlobalDefinitions { spiker_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(spiker_projectile) spiker_projectile.Modifiers = List( - DamageModifiers.SpikerChargeDamage, - DamageModifiers.RadialDegrade + SpikerChargeDamage, + RadialDegrade ) spitfire_aa_ammo_projectile.Name = "spitfire_aa_ammo_projectile" @@ -3915,9 +3918,9 @@ object GlobalDefinitions { spitfire_aa_ammo_projectile.Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(spitfire_aa_ammo_projectile) spitfire_aa_ammo_projectile.Modifiers = List( - //DamageModifiers.FlakHit, - DamageModifiers.FlakBurst, - DamageModifiers.MaxDistanceCutoff + //FlakHit, + FlakBurst, + MaxDistanceCutoff ) spitfire_ammo_projectile.Name = "spitfire_ammo_projectile" @@ -3957,8 +3960,8 @@ object GlobalDefinitions { starfire_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(starfire_projectile) starfire_projectile.Modifiers = List( - DamageModifiers.StarfireAggravatedBurn, - DamageModifiers.RadialDegrade + StarfireAggravatedBurn, + RadialDegrade ) striker_missile_projectile.Name = "striker_missile_projectile" @@ -3975,7 +3978,7 @@ object GlobalDefinitions { striker_missile_projectile.InitialVelocity = 30 striker_missile_projectile.Lifespan = 4.2f ProjectileDefinition.CalculateDerivedFields(striker_missile_projectile) - striker_missile_projectile.Modifiers = DamageModifiers.RadialDegrade + striker_missile_projectile.Modifiers = RadialDegrade striker_missile_targeting_projectile.Name = "striker_missile_targeting_projectile" // TODO for later, maybe : set_resource_parent striker_missile_targeting_projectile game_objects striker_missile_projectile @@ -3996,7 +3999,7 @@ object GlobalDefinitions { striker_missile_targeting_projectile.AutoLock = true striker_missile_targeting_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(striker_missile_targeting_projectile) - striker_missile_targeting_projectile.Modifiers = DamageModifiers.RadialDegrade + striker_missile_targeting_projectile.Modifiers = RadialDegrade trek_projectile.Name = "trek_projectile" trek_projectile.Damage0 = 0 @@ -4010,7 +4013,7 @@ object GlobalDefinitions { trek_projectile.InitialVelocity = 40 trek_projectile.Lifespan = 7f ProjectileDefinition.CalculateDerivedFields(trek_projectile) - trek_projectile.Modifiers = DamageModifiers.MaxDistanceCutoff + trek_projectile.Modifiers = MaxDistanceCutoff vanu_sentry_turret_projectile.Name = "vanu_sentry_turret_projectile" vanu_sentry_turret_projectile.Damage0 = 25 @@ -4022,7 +4025,7 @@ object GlobalDefinitions { vanu_sentry_turret_projectile.InitialVelocity = 240 vanu_sentry_turret_projectile.Lifespan = 1.3f ProjectileDefinition.CalculateDerivedFields(vanu_sentry_turret_projectile) - vanu_sentry_turret_projectile.Modifiers = DamageModifiers.RadialDegrade + vanu_sentry_turret_projectile.Modifiers = RadialDegrade vulture_bomb_projectile.Name = "vulture_bomb_projectile" vulture_bomb_projectile.Damage0 = 175 @@ -4036,7 +4039,7 @@ object GlobalDefinitions { vulture_bomb_projectile.InitialVelocity = 0 vulture_bomb_projectile.Lifespan = 30f ProjectileDefinition.CalculateDerivedFields(vulture_bomb_projectile) - vulture_bomb_projectile.Modifiers = DamageModifiers.RadialDegrade + vulture_bomb_projectile.Modifiers = RadialDegrade vulture_nose_bullet_projectile.Name = "vulture_nose_bullet_projectile" vulture_nose_bullet_projectile.Damage0 = 12 @@ -4091,7 +4094,7 @@ object GlobalDefinitions { wasp_rocket_projectile.AutoLock = true wasp_rocket_projectile.Packet = projectileConverter ProjectileDefinition.CalculateDerivedFields(wasp_rocket_projectile) - wasp_rocket_projectile.Modifiers = DamageModifiers.RadialDegrade + wasp_rocket_projectile.Modifiers = RadialDegrade winchester_projectile.Name = "winchester_projectile" // TODO for later, maybe : set_resource_parent winchester_projectile game_objects bolt_projectile @@ -5595,6 +5598,15 @@ object GlobalDefinitions { fury.AutoPilotSpeeds = (24, 10) fury.DestroyedModel = Some(DestroyedVehicle.QuadAssault) fury.JackingDuration = Array(0, 10, 3, 2) + fury.explodes = true + fury.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 150 + Damage1 = 225 + DamageRadius = 5 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } quadassault.Name = "quadassault" // Basilisk quadassault.MaxHealth = 650 @@ -5614,6 +5626,15 @@ object GlobalDefinitions { quadassault.AutoPilotSpeeds = (24, 10) quadassault.DestroyedModel = Some(DestroyedVehicle.QuadAssault) quadassault.JackingDuration = Array(0, 10, 3, 2) + quadassault.explodes = true + quadassault.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 150 + Damage1 = 225 + DamageRadius = 5 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } quadstealth.Name = "quadstealth" // Wraith quadstealth.MaxHealth = 650 @@ -5633,6 +5654,15 @@ object GlobalDefinitions { quadstealth.AutoPilotSpeeds = (24, 10) quadstealth.DestroyedModel = Some(DestroyedVehicle.QuadStealth) quadstealth.JackingDuration = Array(0, 10, 3, 2) + quadstealth.explodes = true + quadstealth.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 150 + Damage1 = 225 + DamageRadius = 5 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } two_man_assault_buggy.Name = "two_man_assault_buggy" // Harasser two_man_assault_buggy.MaxHealth = 1250 @@ -5654,6 +5684,15 @@ object GlobalDefinitions { two_man_assault_buggy.AutoPilotSpeeds = (22, 8) two_man_assault_buggy.DestroyedModel = Some(DestroyedVehicle.TwoManAssaultBuggy) two_man_assault_buggy.JackingDuration = Array(0, 15, 5, 3) + two_man_assault_buggy.explodes = true + two_man_assault_buggy.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 8 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } skyguard.Name = "skyguard" skyguard.MaxHealth = 1000 @@ -5676,6 +5715,15 @@ object GlobalDefinitions { skyguard.AutoPilotSpeeds = (22, 8) skyguard.DestroyedModel = Some(DestroyedVehicle.Skyguard) skyguard.JackingDuration = Array(0, 15, 5, 3) + skyguard.explodes = true + skyguard.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 8 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } threemanheavybuggy.Name = "threemanheavybuggy" // Marauder threemanheavybuggy.MaxHealth = 1700 @@ -5703,6 +5751,15 @@ object GlobalDefinitions { threemanheavybuggy.DestroyedModel = Some(DestroyedVehicle.ThreeManHeavyBuggy) threemanheavybuggy.Subtract.Damage1 = 5 threemanheavybuggy.JackingDuration = Array(0, 20, 7, 5) + threemanheavybuggy.explodes = true + threemanheavybuggy.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 10 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } twomanheavybuggy.Name = "twomanheavybuggy" // Enforcer twomanheavybuggy.MaxHealth = 1800 @@ -5725,6 +5782,15 @@ object GlobalDefinitions { twomanheavybuggy.DestroyedModel = Some(DestroyedVehicle.TwoManHeavyBuggy) twomanheavybuggy.Subtract.Damage1 = 5 twomanheavybuggy.JackingDuration = Array(0, 20, 7, 5) + twomanheavybuggy.explodes = true + twomanheavybuggy.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 8 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } twomanhoverbuggy.Name = "twomanhoverbuggy" // Thresher twomanhoverbuggy.MaxHealth = 1600 @@ -5747,6 +5813,15 @@ object GlobalDefinitions { twomanhoverbuggy.DestroyedModel = Some(DestroyedVehicle.TwoManHoverBuggy) twomanhoverbuggy.Subtract.Damage1 = 5 twomanhoverbuggy.JackingDuration = Array(0, 20, 7, 5) + twomanhoverbuggy.explodes = true + twomanhoverbuggy.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 10 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } mediumtransport.Name = "mediumtransport" // Deliverer mediumtransport.MaxHealth = 2500 @@ -5776,6 +5851,15 @@ object GlobalDefinitions { mediumtransport.DestroyedModel = Some(DestroyedVehicle.MediumTransport) mediumtransport.Subtract.Damage1 = 7 mediumtransport.JackingDuration = Array(0, 25, 8, 5) + mediumtransport.explodes = true + mediumtransport.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } battlewagon.Name = "battlewagon" // Raider battlewagon.MaxHealth = 2500 @@ -5808,6 +5892,15 @@ object GlobalDefinitions { battlewagon.AutoPilotSpeeds = (18, 6) battlewagon.DestroyedModel = Some(DestroyedVehicle.MediumTransport) battlewagon.JackingDuration = Array(0, 25, 8, 5) + battlewagon.explodes = true + battlewagon.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } thunderer.Name = "thunderer" thunderer.MaxHealth = 2500 @@ -5837,6 +5930,15 @@ object GlobalDefinitions { thunderer.DestroyedModel = Some(DestroyedVehicle.MediumTransport) thunderer.Subtract.Damage1 = 7 thunderer.JackingDuration = Array(0, 25, 8, 5) + thunderer.explodes = true + thunderer.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } aurora.Name = "aurora" aurora.MaxHealth = 2500 @@ -5866,6 +5968,15 @@ object GlobalDefinitions { aurora.DestroyedModel = Some(DestroyedVehicle.MediumTransport) aurora.Subtract.Damage1 = 7 aurora.JackingDuration = Array(0, 25, 8, 5) + aurora.explodes = true + aurora.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } apc_tr.Name = "apc_tr" // Juggernaut apc_tr.MaxHealth = 6000 @@ -5917,6 +6028,16 @@ object GlobalDefinitions { apc_tr.AutoPilotSpeeds = (16, 6) apc_tr.DestroyedModel = Some(DestroyedVehicle.Apc) apc_tr.JackingDuration = Array(0, 45, 15, 10) + apc_tr.Subtract.Damage1 = 10 + apc_tr.explodes = true + apc_tr.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 300 + Damage1 = 450 + DamageRadius = 15 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } apc_nc.Name = "apc_nc" // Vindicator apc_nc.MaxHealth = 6000 @@ -5968,6 +6089,16 @@ object GlobalDefinitions { apc_nc.AutoPilotSpeeds = (16, 6) apc_nc.DestroyedModel = Some(DestroyedVehicle.Apc) apc_nc.JackingDuration = Array(0, 45, 15, 10) + apc_nc.Subtract.Damage1 = 10 + apc_nc.explodes = true + apc_nc.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 300 + Damage1 = 450 + DamageRadius = 15 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } apc_vs.Name = "apc_vs" // Leviathan apc_vs.MaxHealth = 6000 @@ -6019,6 +6150,16 @@ object GlobalDefinitions { apc_vs.AutoPilotSpeeds = (16, 6) apc_vs.DestroyedModel = Some(DestroyedVehicle.Apc) apc_vs.JackingDuration = Array(0, 45, 15, 10) + apc_vs.Subtract.Damage1 = 10 + apc_vs.explodes = true + apc_vs.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 300 + Damage1 = 450 + DamageRadius = 15 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } lightning.Name = "lightning" lightning.MaxHealth = 2000 @@ -6039,6 +6180,15 @@ object GlobalDefinitions { lightning.DestroyedModel = Some(DestroyedVehicle.Lightning) lightning.Subtract.Damage1 = 7 lightning.JackingDuration = Array(0, 20, 7, 5) + lightning.explodes = true + lightning.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 250 + Damage1 = 375 + DamageRadius = 10 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } prowler.Name = "prowler" prowler.MaxHealth = 4800 @@ -6064,6 +6214,15 @@ object GlobalDefinitions { prowler.DestroyedModel = Some(DestroyedVehicle.Prowler) prowler.Subtract.Damage1 = 9 prowler.JackingDuration = Array(0, 30, 10, 5) + prowler.explodes = true + prowler.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 250 + Damage1 = 375 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } vanguard.Name = "vanguard" vanguard.MaxHealth = 5400 @@ -6085,6 +6244,15 @@ object GlobalDefinitions { vanguard.DestroyedModel = Some(DestroyedVehicle.Vanguard) vanguard.Subtract.Damage1 = 9 vanguard.JackingDuration = Array(0, 30, 10, 5) + vanguard.explodes = true + vanguard.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 250 + Damage1 = 375 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } magrider.Name = "magrider" magrider.MaxHealth = 4200 @@ -6108,6 +6276,15 @@ object GlobalDefinitions { magrider.DestroyedModel = Some(DestroyedVehicle.Magrider) magrider.Subtract.Damage1 = 9 magrider.JackingDuration = Array(0, 30, 10, 5) + magrider.explodes = true + magrider.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 250 + Damage1 = 375 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } val utilityConverter = new UtilityVehicleConverter ant.Name = "ant" @@ -6129,6 +6306,15 @@ object GlobalDefinitions { ant.DestroyedModel = Some(DestroyedVehicle.Ant) ant.Subtract.Damage1 = 5 ant.JackingDuration = Array(0, 60, 20, 15) + ant.explodes = true + ant.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 300 + Damage1 = 450 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } ams.Name = "ams" ams.MaxHealth = 5000 // Temporary - original value is 3000 @@ -6153,6 +6339,15 @@ object GlobalDefinitions { ams.DestroyedModel = Some(DestroyedVehicle.Ams) ams.Subtract.Damage1 = 10 ams.JackingDuration = Array(0, 60, 40, 30) // Temporary - original values are 0, 60, 20, 15 + ams.explodes = true + ams.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.Splash + Damage0 = 300 + Damage1 = 450 + DamageRadius = 15 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } val variantConverter = new VariantVehicleConverter router.Name = "router" @@ -6177,6 +6372,15 @@ object GlobalDefinitions { router.DestroyedModel = Some(DestroyedVehicle.Router) router.Subtract.Damage1 = 5 router.JackingDuration = Array(0, 20, 7, 5) + router.explodes = true + router.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 10 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } switchblade.Name = "switchblade" switchblade.MaxHealth = 1750 @@ -6201,6 +6405,15 @@ object GlobalDefinitions { switchblade.Subtract.Damage0 = 5 switchblade.Subtract.Damage1 = 5 switchblade.JackingDuration = Array(0, 20, 7, 5) + switchblade.explodes = true + switchblade.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 10 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } flail.Name = "flail" flail.MaxHealth = 2400 @@ -6223,6 +6436,15 @@ object GlobalDefinitions { flail.DestroyedModel = Some(DestroyedVehicle.Flail) flail.Subtract.Damage1 = 7 flail.JackingDuration = Array(0, 20, 7, 5) + flail.explodes = true + flail.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 10 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } mosquito.Name = "mosquito" mosquito.MaxHealth = 665 @@ -6245,6 +6467,15 @@ object GlobalDefinitions { mosquito.DestroyedModel = Some(DestroyedVehicle.Mosquito) mosquito.JackingDuration = Array(0, 20, 7, 5) mosquito.DamageUsing = DamageCalculations.AgainstAircraft + mosquito.explodes = true + mosquito.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 10 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } lightgunship.Name = "lightgunship" // Reaver lightgunship.MaxHealth = 1000 @@ -6268,6 +6499,15 @@ object GlobalDefinitions { lightgunship.Subtract.Damage1 = 3 lightgunship.JackingDuration = Array(0, 30, 10, 5) lightgunship.DamageUsing = DamageCalculations.AgainstAircraft + lightgunship.explodes = true + lightgunship.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 250 + Damage1 = 375 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } wasp.Name = "wasp" wasp.MaxHealth = 515 @@ -6290,6 +6530,15 @@ object GlobalDefinitions { wasp.DestroyedModel = Some(DestroyedVehicle.Mosquito) //set_resource_parent wasp game_objects mosquito wasp.JackingDuration = Array(0, 20, 7, 5) wasp.DamageUsing = DamageCalculations.AgainstAircraft + wasp.explodes = true + wasp.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 10 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } liberator.Name = "liberator" liberator.MaxHealth = 2500 @@ -6320,6 +6569,15 @@ object GlobalDefinitions { liberator.Subtract.Damage1 = 5 liberator.JackingDuration = Array(0, 30, 10, 5) liberator.DamageUsing = DamageCalculations.AgainstAircraft + liberator.explodes = true + liberator.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 250 + Damage1 = 375 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } vulture.Name = "vulture" vulture.MaxHealth = 2500 @@ -6351,6 +6609,15 @@ object GlobalDefinitions { vulture.Subtract.Damage1 = 5 vulture.JackingDuration = Array(0, 30, 10, 5) vulture.DamageUsing = DamageCalculations.AgainstAircraft + vulture.explodes = true + vulture.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 250 + Damage1 = 375 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } dropship.Name = "dropship" // Galaxy dropship.MaxHealth = 5000 @@ -6414,6 +6681,15 @@ object GlobalDefinitions { dropship.Subtract.Damage1 = 7 dropship.JackingDuration = Array(0, 60, 20, 10) dropship.DamageUsing = DamageCalculations.AgainstAircraft + dropship.explodes = true + dropship.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 300 + Damage1 = 450 + DamageRadius = 30 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } galaxy_gunship.Name = "galaxy_gunship" galaxy_gunship.MaxHealth = 6000 @@ -6455,7 +6731,16 @@ object GlobalDefinitions { galaxy_gunship.Subtract.Damage1 = 7 galaxy_gunship.JackingDuration = Array(0, 60, 20, 10) galaxy_gunship.DamageUsing = DamageCalculations.AgainstAircraft - galaxy_gunship.Modifiers = DamageModifiers.GalaxyGunshipReduction(0.63f) + galaxy_gunship.Modifiers = GalaxyGunshipReduction(0.63f) + galaxy_gunship.explodes = true + galaxy_gunship.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 300 + Damage1 = 450 + DamageRadius = 30 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } lodestar.Name = "lodestar" lodestar.MaxHealth = 5000 @@ -6486,6 +6771,15 @@ object GlobalDefinitions { lodestar.Subtract.Damage1 = 7 lodestar.JackingDuration = Array(0, 60, 20, 10) lodestar.DamageUsing = DamageCalculations.AgainstAircraft + lodestar.explodes = true + lodestar.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 300 + Damage1 = 450 + DamageRadius = 30 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } phantasm.Name = "phantasm" phantasm.MaxHealth = 2500 @@ -6517,6 +6811,15 @@ object GlobalDefinitions { phantasm.DestroyedModel = None //the adb calls out a phantasm_destroyed but no such asset exists phantasm.JackingDuration = Array(0, 60, 20, 10) phantasm.DamageUsing = DamageCalculations.AgainstAircraft + phantasm.explodes = true + phantasm.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 100 + Damage1 = 150 + DamageRadius = 12 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } droppod.Name = "droppod" droppod.MaxHealth = 20000 @@ -6543,6 +6846,19 @@ object GlobalDefinitions { boomer.Repairable = false boomer.DeployCategory = DeployableCategory.Boomers boomer.DeployTime = Duration.create(1000, "ms") + boomer.explodes = true + boomer.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.Splash + SympatheticExplosion = true + Damage0 = 250 + Damage1 = 750 + Damage2 = 400 + Damage3 = 400 + Damage4 = 1850 + DamageRadius = 5.1f + DamageAtEdge = 0.1f + Modifiers = RadialDegrade + } he_mine.Name = "he_mine" he_mine.Descriptor = "Mines" @@ -6551,6 +6867,19 @@ object GlobalDefinitions { he_mine.DamageableByFriendlyFire = false he_mine.Repairable = false he_mine.DeployTime = Duration.create(1000, "ms") + he_mine.explodes = true + he_mine.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.Splash + SympatheticExplosion = true + Damage0 = 100 + Damage1 = 750 + Damage2 = 400 + Damage3 = 565 + Damage4 = 1600 + DamageRadius = 6.6f + DamageAtEdge = 0.25f + Modifiers = RadialDegrade + } jammer_mine.Name = "jammer_mine" jammer_mine.Descriptor = "JammerMines" @@ -6572,7 +6901,17 @@ object GlobalDefinitions { spitfire_turret.ReserveAmmunition = false spitfire_turret.DeployCategory = DeployableCategory.SmallTurrets spitfire_turret.DeployTime = Duration.create(5000, "ms") - spitfire_turret.Model = StandardResolutions.ComplexDeployables + spitfire_turret.Model = ComplexDeployableResolutions.calculate + spitfire_turret.explodes = true + spitfire_turret.explodes = true + spitfire_turret.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 8 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } spitfire_cloaked.Name = "spitfire_cloaked" spitfire_cloaked.Descriptor = "CloakingSpitfires" @@ -6585,7 +6924,16 @@ object GlobalDefinitions { spitfire_cloaked.ReserveAmmunition = false spitfire_cloaked.DeployCategory = DeployableCategory.SmallTurrets spitfire_cloaked.DeployTime = Duration.create(5000, "ms") - spitfire_cloaked.Model = StandardResolutions.ComplexDeployables + spitfire_cloaked.Model = ComplexDeployableResolutions.calculate + spitfire_cloaked.explodes = true + spitfire_cloaked.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 50 + Damage1 = 75 + DamageRadius = 8 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } spitfire_aa.Name = "spitfire_aa" spitfire_aa.Descriptor = "FlakSpitfires" @@ -6598,7 +6946,16 @@ object GlobalDefinitions { spitfire_aa.ReserveAmmunition = false spitfire_aa.DeployCategory = DeployableCategory.SmallTurrets spitfire_aa.DeployTime = Duration.create(5000, "ms") - spitfire_aa.Model = StandardResolutions.ComplexDeployables + spitfire_aa.Model = ComplexDeployableResolutions.calculate + spitfire_aa.explodes = true + spitfire_aa.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 200 + Damage1 = 300 + DamageRadius = 8 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } motionalarmsensor.Name = "motionalarmsensor" motionalarmsensor.Descriptor = "MotionSensors" @@ -6624,7 +6981,15 @@ object GlobalDefinitions { tank_traps.RepairIfDestroyed = false tank_traps.DeployCategory = DeployableCategory.TankTraps tank_traps.DeployTime = Duration.create(6000, "ms") - tank_traps.Model = StandardResolutions.SimpleDeployables + //tank_traps do not explode + tank_traps.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 10 + Damage1 = 10 + DamageRadius = 8 + DamageAtEdge = 0.2f + Modifiers = RadialDegrade + } val fieldTurretConverter = new FieldTurretConverter portable_manned_turret.Name = "portable_manned_turret" @@ -6642,7 +7007,16 @@ object GlobalDefinitions { portable_manned_turret.Packet = fieldTurretConverter portable_manned_turret.DeployCategory = DeployableCategory.FieldTurrets portable_manned_turret.DeployTime = Duration.create(6000, "ms") - portable_manned_turret.Model = StandardResolutions.ComplexDeployables + portable_manned_turret.Model = ComplexDeployableResolutions.calculate + portable_manned_turret.explodes = true + portable_manned_turret.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 150 + Damage1 = 300 + DamageRadius = 8 + DamageAtEdge = 0.1f + Modifiers = RadialDegrade + } portable_manned_turret_nc.Name = "portable_manned_turret_nc" portable_manned_turret_nc.Descriptor = "FieldTurrets" @@ -6659,7 +7033,16 @@ object GlobalDefinitions { portable_manned_turret_nc.Packet = fieldTurretConverter portable_manned_turret_nc.DeployCategory = DeployableCategory.FieldTurrets portable_manned_turret_nc.DeployTime = Duration.create(6000, "ms") - portable_manned_turret_nc.Model = StandardResolutions.ComplexDeployables + portable_manned_turret_nc.Model = ComplexDeployableResolutions.calculate + portable_manned_turret_nc.explodes = true + portable_manned_turret_nc.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 150 + Damage1 = 300 + DamageRadius = 8 + DamageAtEdge = 0.1f + Modifiers = RadialDegrade + } portable_manned_turret_tr.Name = "portable_manned_turret_tr" portable_manned_turret_tr.Descriptor = "FieldTurrets" @@ -6676,7 +7059,16 @@ object GlobalDefinitions { portable_manned_turret_tr.Packet = fieldTurretConverter portable_manned_turret_tr.DeployCategory = DeployableCategory.FieldTurrets portable_manned_turret_tr.DeployTime = Duration.create(6000, "ms") - portable_manned_turret_tr.Model = StandardResolutions.ComplexDeployables + portable_manned_turret_tr.Model = ComplexDeployableResolutions.calculate + portable_manned_turret_tr.explodes = true + portable_manned_turret_tr.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 150 + Damage1 = 300 + DamageRadius = 8 + DamageAtEdge = 0.1f + Modifiers = RadialDegrade + } portable_manned_turret_vs.Name = "portable_manned_turret_vs" portable_manned_turret_vs.Descriptor = "FieldTurrets" @@ -6693,7 +7085,16 @@ object GlobalDefinitions { portable_manned_turret_vs.Packet = fieldTurretConverter portable_manned_turret_vs.DeployCategory = DeployableCategory.FieldTurrets portable_manned_turret_vs.DeployTime = Duration.create(6000, "ms") - portable_manned_turret_vs.Model = StandardResolutions.ComplexDeployables + portable_manned_turret_vs.Model = ComplexDeployableResolutions.calculate + portable_manned_turret_vs.explodes = true + portable_manned_turret_vs.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 150 + Damage1 = 300 + DamageRadius = 8 + DamageAtEdge = 0.1f + Modifiers = RadialDegrade + } deployable_shield_generator.Name = "deployable_shield_generator" deployable_shield_generator.Descriptor = "ShieldGenerators" @@ -6702,7 +7103,7 @@ object GlobalDefinitions { deployable_shield_generator.Repairable = true deployable_shield_generator.RepairIfDestroyed = false deployable_shield_generator.DeployTime = Duration.create(6000, "ms") - deployable_shield_generator.Model = StandardResolutions.ComplexDeployables + deployable_shield_generator.Model = ComplexDeployableResolutions.calculate router_telepad_deployable.Name = "router_telepad_deployable" router_telepad_deployable.MaxHealth = 100 @@ -6711,7 +7112,7 @@ object GlobalDefinitions { router_telepad_deployable.DeployTime = Duration.create(1, "ms") router_telepad_deployable.DeployCategory = DeployableCategory.Telepads router_telepad_deployable.Packet = new TelepadDeployableConverter - router_telepad_deployable.Model = StandardResolutions.SimpleDeployables + router_telepad_deployable.Model = SimpleResolutions.calculate internal_router_telepad_deployable.Name = "router_telepad_deployable" internal_router_telepad_deployable.MaxHealth = 1 @@ -7147,6 +7548,15 @@ object GlobalDefinitions { manned_turret.MountPoints += 1 -> 0 manned_turret.FactionLocked = true manned_turret.ReserveAmmunition = false + manned_turret.explodes = true + manned_turret.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 150 + Damage1 = 300 + DamageRadius = 5 + DamageAtEdge = 0.1f + Modifiers = RadialDegrade + } vanu_sentry_turret.Name = "vanu_sentry_turret" vanu_sentry_turret.MaxHealth = 1500 @@ -7163,28 +7573,68 @@ object GlobalDefinitions { vanu_sentry_turret.ReserveAmmunition = false painbox.Name = "painbox" + painbox.alwaysOn = false + painbox.sphereOffset = Vector3(0, 0, -0.4f) painbox.Damageable = false painbox.Repairable = false + painbox.innateDamage = new DamageWithPosition { + Damage0 = 5 + DamageRadius = 0 + DamageToHealthOnly = true + } painbox_continuous.Name = "painbox_continuous" + painbox_continuous.sphereOffset = Vector3(0, 0, -0.4f) painbox_continuous.Damageable = false painbox_continuous.Repairable = false + painbox_continuous.innateDamage = new DamageWithPosition { + Damage0 = 5 + DamageRadius = 0 + DamageToHealthOnly = true + } painbox_door_radius.Name = "painbox_door_radius" + painbox_door_radius.alwaysOn = false + painbox_door_radius.sphereOffset = Vector3(0, 0, -0.4f) + painbox_door_radius.hasNearestDoorDependency = true painbox_door_radius.Damageable = false painbox_door_radius.Repairable = false + painbox_door_radius.innateDamage = new DamageWithPosition { + Damage0 = 5 + DamageRadius = 10f * 0.6928f + DamageToHealthOnly = true + } painbox_door_radius_continuous.Name = "painbox_door_radius_continuous" + painbox_door_radius_continuous.sphereOffset = Vector3(0, 0, -0.4f) + painbox_door_radius_continuous.hasNearestDoorDependency = true painbox_door_radius_continuous.Damageable = false painbox_door_radius_continuous.Repairable = false + painbox_door_radius_continuous.innateDamage = new DamageWithPosition { + Damage0 = 5 + DamageRadius = 10f * 0.6928f + DamageToHealthOnly = true + } painbox_radius.Name = "painbox_radius" + painbox_radius.alwaysOn = false + painbox_radius.sphereOffset = Vector3(0, 0, -0.4f) painbox_radius.Damageable = false painbox_radius.Repairable = false + painbox_radius.innateDamage = new DamageWithPosition { + Damage0 = 5 + DamageRadius = 10f * 0.6928f + DamageToHealthOnly = true + } painbox_radius_continuous.Name = "painbox_radius_continuous" painbox_radius_continuous.Damageable = false painbox_radius_continuous.Repairable = false + painbox_radius_continuous.innateDamage = new DamageWithPosition { + Damage0 = 5 + DamageRadius = 8.55f + DamageToHealthOnly = true + } gen_control.Name = "gen_control" gen_control.Damageable = false @@ -7199,5 +7649,16 @@ object GlobalDefinitions { generator.RepairDistance = 13.5f generator.RepairIfDestroyed = true generator.Subtract.Damage1 = 9 + generator.explodes = true + generator.innateDamage = new DamageWithPosition { + CausesDamageType = DamageType.One + Damage0 = 99999 + //DamageRadius should be 14, but 14 is insufficient for hitting the whole chamber; hence, ... + DamageRadius = 15.75f + DamageRadiusMin = 14 + DamageAtEdge = 0.00002f + Modifiers = RadialDegrade + //damage is 99999 at 14m, dropping rapidly to ~1 at 15.75m + } } } diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index e0da9c914..79c4c2fd5 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -1,22 +1,17 @@ // 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.vital.Vitality +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.resolution.DamageResistanceModel import net.psforever.objects.zones.ZoneAware import net.psforever.types.{PlanetSideGUID, _} @@ -532,7 +527,15 @@ object Player { final val FreeHandSlot: Int = 250 final val HandsDownSlot: Int = 255 - final case class Die() + final case class Die(reason: Option[DamageInteraction]) + + object Die { + def apply(): Die = Die(None) + + def apply(reason: DamageInteraction): Die = { + Die(Some(reason)) + } + } def apply(core: Avatar): Player = { new Player(core) diff --git a/src/main/scala/net/psforever/objects/SensorDeployable.scala b/src/main/scala/net/psforever/objects/SensorDeployable.scala index c7314dc6b..24e268d9b 100644 --- a/src/main/scala/net/psforever/objects/SensorDeployable.scala +++ b/src/main/scala/net/psforever/objects/SensorDeployable.scala @@ -2,7 +2,6 @@ package net.psforever.objects import akka.actor.{Actor, ActorContext, Props} -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ce._ import net.psforever.objects.definition.converter.SmallDeployableConverter import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition} @@ -11,7 +10,8 @@ import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity} import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.repair.RepairableEntity -import net.psforever.objects.vital.StandardResolutions +import net.psforever.objects.vital.SimpleResolutions +import net.psforever.objects.vital.interaction.DamageResult import net.psforever.types.{PlanetSideGUID, Vector3} import net.psforever.services.Service import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -24,7 +24,7 @@ class SensorDeployable(cdef: SensorDeployableDefinition) extends ComplexDeployab class SensorDeployableDefinition(private val objectId: Int) extends ComplexDeployableDefinition(objectId) { Name = "sensor_deployable" DeployCategory = DeployableCategory.Sensors - Model = StandardResolutions.SimpleDeployables + Model = SimpleResolutions.calculate Packet = new SmallDeployableConverter override def Initialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = { @@ -62,7 +62,7 @@ class SensorDeployableControl(sensor: SensorDeployable) override protected def DamageLog(msg: String): Unit = {} - override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { super.DestructionAwareness(target, cause) SensorDeployableControl.DestructionAwareness(sensor, PlanetSideGUID(0)) } diff --git a/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala b/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala index 75a375ec8..d8b8f8bb4 100644 --- a/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala +++ b/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala @@ -2,7 +2,6 @@ package net.psforever.objects import akka.actor.{Actor, ActorContext, Props} -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployableCategory} import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition} import net.psforever.objects.definition.converter.ShieldGeneratorConverter @@ -12,6 +11,7 @@ import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.repair.RepairableEntity +import net.psforever.objects.vital.interaction.DamageResult import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.types.PlanetSideGUID import net.psforever.services.Service @@ -96,7 +96,7 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable) } } - override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = { + override protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any): Unit = { val (damageToHealth, damageToShields) = amount match { case (a: Int, b: Int) => (a, b) case _ => (0, 0) @@ -105,7 +105,7 @@ class ShieldGeneratorControl(gen: ShieldGeneratorDeployable) ShieldGeneratorControl.DamageAwareness(gen, cause, damageToShields > 0) } - override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = { super.DestructionAwareness(target, cause) ShieldGeneratorControl.DestructionAwareness(gen, PlanetSideGUID(0)) } @@ -150,7 +150,7 @@ object ShieldGeneratorControl { * @param cause na * @param damageToShields na */ - def DamageAwareness(target: ShieldGeneratorDeployable, cause: ResolvedProjectile, damageToShields: Boolean): Unit = { + def DamageAwareness(target: ShieldGeneratorDeployable, cause: DamageResult, damageToShields: Boolean): Unit = { //shields if (damageToShields) { val zone = target.Zone diff --git a/src/main/scala/net/psforever/objects/TrapDeployable.scala b/src/main/scala/net/psforever/objects/TrapDeployable.scala index 337e8483e..4dc3a591f 100644 --- a/src/main/scala/net/psforever/objects/TrapDeployable.scala +++ b/src/main/scala/net/psforever/objects/TrapDeployable.scala @@ -2,19 +2,20 @@ package net.psforever.objects import akka.actor.{Actor, ActorContext, Props} -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployedItem} import net.psforever.objects.definition.converter.TRAPConverter import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity} import net.psforever.objects.serverobject.repair.RepairableEntity -import net.psforever.objects.vital.StandardResolutions +import net.psforever.objects.vital.SimpleResolutions +import net.psforever.objects.vital.interaction.DamageResult +import net.psforever.objects.zones.Zone class TrapDeployable(cdef: TrapDeployableDefinition) extends ComplexDeployable(cdef) class TrapDeployableDefinition(objectId: Int) extends ComplexDeployableDefinition(objectId) { - Model = StandardResolutions.SimpleDeployables + Model = SimpleResolutions.calculate Packet = new TRAPConverter override def Initialize(obj: PlanetSideServerObject with Deployable, context: ActorContext) = { @@ -43,8 +44,9 @@ class TrapDeployableControl(trap: TrapDeployable) extends Actor with DamageableE case _ => } - override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { super.DestructionAwareness(target, cause) Deployables.AnnounceDestroyDeployable(trap, None) + Zone.causeExplosion(target.Zone, target, Some(cause)) } } diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala index fd9f85697..91dd38966 100644 --- a/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -2,7 +2,6 @@ package net.psforever.objects import akka.actor.{Actor, ActorContext, Props} -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployedItem} import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition} import net.psforever.objects.definition.converter.SmallTurretConverter @@ -16,7 +15,8 @@ import net.psforever.objects.serverobject.mount.MountableBehavior import net.psforever.objects.serverobject.repair.RepairableWeaponTurret import net.psforever.objects.serverobject.turret.{TurretDefinition, WeaponTurret} import net.psforever.objects.vital.damage.DamageCalculations -import net.psforever.objects.vital.{StandardResolutions, StandardVehicleResistance} +import net.psforever.objects.vital.interaction.DamageResult +import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance} class TurretDeployable(tdef: TurretDeployableDefinition) extends ComplexDeployable(tdef) @@ -37,7 +37,7 @@ class TurretDeployableDefinition(private val objectId: Int) Packet = new SmallTurretConverter DamageUsing = DamageCalculations.AgainstVehicle ResistUsing = StandardVehicleResistance - Model = StandardResolutions.FacilityTurrets + Model = SimpleResolutions.calculate //override to clarify inheritance conflict override def MaxHealth: Int = super[ComplexDeployableDefinition].MaxHealth @@ -91,7 +91,7 @@ class TurretControl(turret: TurretDeployable) case _ => ; } - override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = { super.DestructionAwareness(target, cause) Deployables.AnnounceDestroyDeployable(turret, None) } diff --git a/src/main/scala/net/psforever/objects/Vehicle.scala b/src/main/scala/net/psforever/objects/Vehicle.scala index b1a472021..6676aa43d 100644 --- a/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/src/main/scala/net/psforever/objects/Vehicle.scala @@ -12,7 +12,9 @@ import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.structures.AmenityOwner import net.psforever.objects.vehicles._ -import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality} +import net.psforever.objects.vital.resistance.StandardResistanceProfile +import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.resolution.DamageResistanceModel import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3} import scala.annotation.tailrec @@ -531,7 +533,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition) def PrepareGatingManifest(): VehicleManifest = { val manifest = VehicleManifest(this) - seats.collect { case (index, seat) if index > 0 => seat.Occupant = None } + seats.collect { case (index: Int, seat: Seat) if index > 0 => seat.Occupant = None } vehicleGatingManifest = Some(manifest) previousVehicleGatingManifest = None manifest diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala index 78df50675..91e06966f 100644 --- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala +++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala @@ -4,16 +4,15 @@ package net.psforever.objects.avatar import akka.actor.{Actor, ActorRef, Props} import net.psforever.actors.session.AvatarActor import net.psforever.objects.{Player, _} -import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} +import net.psforever.objects.ballistics.{ObjectSource, PlayerSource} 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.serverobject.damage.Damageable.Target -import net.psforever.objects.vital.PlayerSuicide import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} -import net.psforever.objects.serverobject.damage.{AggravatedBehavior, Damageable} +import net.psforever.objects.serverobject.damage.{AggravatedBehavior, Damageable, DamageableEntity} import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.repair.Repairable import net.psforever.objects.serverobject.terminals.Terminal @@ -28,6 +27,10 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage} import akka.actor.typed import net.psforever.objects.locker.LockerContainerControl +import net.psforever.objects.serverobject.painbox.Painbox +import net.psforever.objects.vital.base.DamageResolution +import net.psforever.objects.vital.etc.SuicideReason +import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import scala.concurrent.duration._ @@ -80,9 +83,28 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm .orElse(auraBehavior) .orElse(containerBehavior) .orElse { - case Player.Die() => + case Player.Die(Some(reason)) => if (player.isAlive) { - DestructionAwareness(player, None) + //primary death + PerformDamage(player, reason.calculate()) + if(player.Health > 0 || player.isAlive) { + //that wasn't good enough + DestructionAwareness(player, None) + } + } + + case Player.Die(None) => + if (player.isAlive) { + //suicide + PerformDamage( + player, + DamageInteraction( + DamageResolution.Resolved, + PlayerSource(player), + SuicideReason(), + player.Position + ).calculate() + ) } case CommonMessages.Use(user, Some(item: Tool)) @@ -524,7 +546,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm */ def HandleDamage( target: Player, - cause: ResolvedProjectile, + cause: DamageResult, damageToHealth: Int, damageToArmor: Int, damageToStamina: Int, @@ -548,7 +570,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm def DamageAwareness( target: Player, - cause: ResolvedProjectile, + cause: DamageResult, damageToHealth: Int, damageToArmor: Int, damageToStamina: Int, @@ -561,7 +583,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm val health = target.Health var announceConfrontation = damageToArmor > 0 //special effects - if (Damageable.CanJammer(target, cause)) { + if (Damageable.CanJammer(target, cause.interaction)) { TryJammerEffectActivate(target, cause) } val aggravated: Boolean = TryAggravationEffectActivate(cause) match { @@ -571,7 +593,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm //initial damage for aggravation, but never treat as "aggravated" false case _ => - cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) + cause.interaction.cause.source.Aggravated.nonEmpty } //log historical event target.History(cause) @@ -595,23 +617,44 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm if(announceConfrontation) { if (!aggravated) { //activity on map - zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + zone.Activity ! Zone.HotSpot.Activity(cause) //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)) - } - ) + cause.adversarial match { + case Some(adversarial) => + adversarial.attacker 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) => + zone.AvatarEvents ! AvatarServiceMessage( + target.Name, + AvatarAction.HitHint(tplayer.GUID, target.GUID) + ) + case None => + zone.AvatarEvents ! AvatarServiceMessage( + target.Name, + AvatarAction.SendResponse( + Service.defaultPlayerGUID, + DamageWithPositionMessage(countableDamage, pSource.Position) + ) + ) + } + case source: ObjectSource if source.obj.isInstanceOf[Painbox] => + zone.AvatarEvents ! AvatarServiceMessage( + target.Name, + AvatarAction.EnvironmentalDamage(target.GUID, source.obj.GUID, countableDamage) + ) + case source => + zone.AvatarEvents ! AvatarServiceMessage( + target.Name, + AvatarAction.SendResponse( + Service.defaultPlayerGUID, + DamageWithPositionMessage(countableDamage, source.Position) + ) + ) + } + case None => + } } else { //general alert @@ -644,7 +687,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm * @param target na * @param cause na */ - def DestructionAwareness(target: Player, cause: Option[ResolvedProjectile]): Unit = { + def DestructionAwareness(target: Player, cause: Option[DamageResult]): Unit = { val player_guid = target.GUID val pos = target.Position val respawnTimer = 300000 //milliseconds @@ -694,17 +737,8 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 7, 0)) // capacitor } val attribute = cause match { - case Some(resolved) => - resolved.projectile.owner match { - case pSource: PlayerSource => - val name = pSource.Name - zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match { - case Some(tplayer) => tplayer.GUID - case None => player_guid - } - case _ => player_guid - } - case _ => player_guid + case Some(reason) => DamageableEntity.attributionTo(reason, target.Zone, player_guid) + case None => player_guid } events ! AvatarServiceMessage( nameChannel, @@ -722,25 +756,21 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm ) //TODO other methods of death? val pentry = PlayerSource(target) - (target.History.find({ p => p.isInstanceOf[PlayerSuicide] }) match { - case Some(PlayerSuicide(_)) => - None - case _ => - cause.orElse { target.LastShot } match { - case out @ Some(shot) => - if (System.nanoTime - shot.hit_time < (10 seconds).toNanos) { - out - } else { - None //suicide - } + (cause match { + case Some(result) => + result.adversarial + case None => + target.LastDamage match { + case Some(attack) if System.currentTimeMillis() - attack.interaction.hitTime < (10 seconds).toMillis => + attack.adversarial case None => - None //suicide + None } }) match { - case Some(shot) => + case Some(adversarial) => events ! AvatarServiceMessage( zoneChannel, - AvatarAction.DestroyDisplay(shot.projectile.owner, pentry, shot.projectile.attribute_to) + AvatarAction.DestroyDisplay(adversarial.attacker, pentry, adversarial.implement) ) case None => events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0)) diff --git a/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala b/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala index 91c9c3c25..26ab98f30 100644 --- a/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala +++ b/src/main/scala/net/psforever/objects/ballistics/AggravatedDamage.scala @@ -2,7 +2,7 @@ package net.psforever.objects.ballistics import net.psforever.objects.equipment.TargetValidation import net.psforever.objects.serverobject.aura.Aura -import net.psforever.objects.vital.DamageType +import net.psforever.objects.vital.base.{DamageResolution, DamageType} /** * In what manner of pacing the aggravated damage ticks are applied. @@ -157,19 +157,19 @@ object AggravatedDamage { targets ) - def burning(resolution: ProjectileResolution.Value): ProjectileResolution.Value = { + def burning(resolution: DamageResolution.Value): DamageResolution.Value = { resolution match { - case ProjectileResolution.AggravatedDirect => ProjectileResolution.AggravatedDirectBurn - case ProjectileResolution.AggravatedSplash => ProjectileResolution.AggravatedSplashBurn + case DamageResolution.AggravatedDirect => DamageResolution.AggravatedDirectBurn + case DamageResolution.AggravatedSplash => DamageResolution.AggravatedSplashBurn case _ => resolution } } - def basicDamageType(resolution: ProjectileResolution.Value): DamageType.Value = { + def basicDamageType(resolution: DamageResolution.Value): DamageType.Value = { resolution match { - case ProjectileResolution.AggravatedDirect | ProjectileResolution.AggravatedDirectBurn => + case DamageResolution.AggravatedDirect | DamageResolution.AggravatedDirectBurn => DamageType.Direct - case ProjectileResolution.AggravatedSplash | ProjectileResolution.AggravatedSplashBurn => + case DamageResolution.AggravatedSplash | DamageResolution.AggravatedSplashBurn => DamageType.Splash case _ => DamageType.None diff --git a/src/main/scala/net/psforever/objects/ballistics/ChargeDamage.scala b/src/main/scala/net/psforever/objects/ballistics/ChargeDamage.scala index c3633dcad..a4226d4e4 100644 --- a/src/main/scala/net/psforever/objects/ballistics/ChargeDamage.scala +++ b/src/main/scala/net/psforever/objects/ballistics/ChargeDamage.scala @@ -1,7 +1,7 @@ // Copyright (c) 2020 PSForever package net.psforever.objects.ballistics -import net.psforever.objects.vital.StandardDamageProfile +import net.psforever.objects.vital.damage.StandardDamageProfile final case class ChargeDamage( effect_count: Int, diff --git a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index 8f000b70e..36c2913db 100644 --- a/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -8,6 +8,7 @@ import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition} import net.psforever.objects.entity.SimpleWorldEntity import net.psforever.objects.equipment.FireModeDefinition import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.vital.base.DamageResolution import net.psforever.types.Vector3 /** @@ -31,7 +32,7 @@ import net.psforever.types.Vector3 * @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` + * defaults to `System.currentTimeMillis()` */ final case class Projectile( profile: ProjectileDefinition, @@ -57,7 +58,7 @@ final case class Projectile( /** Information about the current world coordinates and orientation of the projectile */ val current: SimpleWorldEntity = new SimpleWorldEntity() - private var resolved: ProjectileResolution.Value = ProjectileResolution.Unresolved + private var resolved: DamageResolution.Value = DamageResolution.Unresolved /** * Create a copy of this projectile with all the same information @@ -89,16 +90,16 @@ final case class Projectile( * Mark the projectile as being "encountered" or "managed" at least once. */ def Resolve(): Unit = { - resolved = ProjectileResolution.Resolved + resolved = DamageResolution.Resolved } def Miss(): Unit = { - resolved = ProjectileResolution.MissedShot + resolved = DamageResolution.Missed } - def isResolved: Boolean = resolved == ProjectileResolution.Resolved || resolved == ProjectileResolution.MissedShot + def isResolved: Boolean = resolved == DamageResolution.Resolved || resolved == DamageResolution.Missed - def isMiss: Boolean = resolved == ProjectileResolution.MissedShot + def isMiss: Boolean = resolved == DamageResolution.Missed def Definition = profile } diff --git a/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala b/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala deleted file mode 100644 index c06e3e169..000000000 --- a/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.ballistics - -import net.psforever.objects.vital.DamageResistanceModel -import net.psforever.types.Vector3 - -/** - * An encapsulation of a projectile event that records sufficient historical information - * 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 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, - projectile: Projectile, - target: SourceEntry, - damage_model: DamageResistanceModel, - hit_pos: Vector3 -) { - val hit_time: Long = System.nanoTime -} diff --git a/src/main/scala/net/psforever/objects/ce/Deployable.scala b/src/main/scala/net/psforever/objects/ce/Deployable.scala index 56736d35b..844965e01 100644 --- a/src/main/scala/net/psforever/objects/ce/Deployable.scala +++ b/src/main/scala/net/psforever/objects/ce/Deployable.scala @@ -4,7 +4,8 @@ package net.psforever.objects.ce import net.psforever.objects._ import net.psforever.objects.definition.DeployableDefinition import net.psforever.objects.serverobject.affinity.FactionAffinity -import net.psforever.objects.vital.{DamageResistanceModel, Vitality} +import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.resolution.DamageResistanceModel import net.psforever.objects.zones.ZoneAware import net.psforever.packet.game.DeployableIcon import net.psforever.types.PlanetSideEmpire diff --git a/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala b/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala index fdb26e216..1fd9056d8 100644 --- a/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala @@ -8,6 +8,7 @@ import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.vital._ import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.resistance.ResistanceProfileMutators +import net.psforever.objects.vital.resolution.DamageResistanceModel import net.psforever.types.{ExoSuitType, PlanetSideEmpire} /** @@ -31,7 +32,7 @@ class ExoSuitDefinition(private val suitType: ExoSuitType.Value) Name = "exo-suit" DamageUsing = DamageCalculations.AgainstExoSuit ResistUsing = StandardInfantryResistance - Model = StandardResolutions.Infantry + Model = InfantryResolutions.calculate def SuitType: ExoSuitType.Value = suitType diff --git a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index f4294e6df..75f916cac 100644 --- a/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -1,10 +1,10 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.definition -import net.psforever.objects.ballistics.{AggravatedDamage, ChargeDamage, Projectiles} -import net.psforever.objects.equipment.JammingUnit -import net.psforever.objects.vital.damage.DamageModifiers -import net.psforever.objects.vital.{DamageType, StandardDamageProfile} +import net.psforever.objects.ballistics.Projectiles +import net.psforever.objects.vital.base.DamageType +import net.psforever.objects.vital.projectile.DistanceDegrade +import net.psforever.objects.vital.prop.DamageWithPosition /** * The definition that outlines the damage-dealing characteristics of any projectile. @@ -12,22 +12,14 @@ import net.psforever.objects.vital.{DamageType, StandardDamageProfile} * @param objectId the object's identifier number */ class ProjectileDefinition(objectId: Int) - extends ObjectDefinition(objectId) - with JammingUnit - with StandardDamageProfile - with DamageModifiers { + extends ObjectDefinition(objectId) + with DamageWithPosition { /** 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 */ @@ -36,16 +28,8 @@ class ProjectileDefinition(objectId: Int) 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 distance of the explosion effect (m) */ - private var damageRadius: Float = 0f - /** for radial damage, the distance before degradation of the explosion effect (m) */ - private var damageRadiusMin: 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 /** the projectile is represented by a server-side entity * that is updated by the projectile owner * and transmitted to all projectile observers; @@ -55,23 +39,13 @@ class ProjectileDefinition(objectId: Int) * `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 - /** */ - private var charging: Option[ChargeDamage] = 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; @@ -85,17 +59,10 @@ class ProjectileDefinition(objectId: Int) /** after acceleration, if any, what is the final speed of the projectile (m/s) */ private var finalVelocity: Float = 0f Name = "projectile" - Modifiers = DamageModifiers.DistanceDegrade + Modifiers = DistanceDegrade def ProjectileType: Projectiles.Value = projectileType - def UseDamage1Subtract: Boolean = useDamage1Subtract - - def UseDamage1Subtract_=(useDamage1Subtract: Boolean): Boolean = { - this.useDamage1Subtract = useDamage1Subtract - UseDamage1Subtract - } - def Acceleration: Int = acceleration def Acceleration_=(accel: Int): Int = { @@ -110,30 +77,21 @@ class ProjectileDefinition(objectId: Int) AccelerationUntil } - def ProjectileDamageType: DamageType.Value = damageType + def ProjectileDamageType: DamageType.Value = CausesDamageType def ProjectileDamageType_=(damageType1: DamageType.Value): DamageType.Value = { - damageType = damageType1 + CausesDamageType = damageType1 ProjectileDamageType } - def ProjectileDamageTypeSecondary: DamageType.Value = damageTypeSecondary + def ProjectileDamageTypeSecondary: DamageType.Value = CausesDamageTypeSecondary def ProjectileDamageTypeSecondary_=(damageTypeSecondary1: DamageType.Value): DamageType.Value = { - damageTypeSecondary = damageTypeSecondary1 + CausesDamageTypeSecondary = damageTypeSecondary1 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 ProjectileDamageTypes : Set[DamageType.Value] = AllDamageTypes def DegradeDelay: Float = degradeDelay @@ -163,27 +121,6 @@ class ProjectileDefinition(objectId: Int) Lifespan } - def DamageAtEdge: Float = damageAtEdge - - def DamageAtEdge_=(damageAtEdge: Float): Float = { - this.damageAtEdge = damageAtEdge - DamageAtEdge - } - - def DamageRadius: Float = damageRadius - - def DamageRadius_=(damageRadius: Float): Float = { - this.damageRadius = damageRadius - DamageRadius - } - - def DamageRadiusMin: Float = damageRadiusMin - - def DamageRadiusMin_=(damageRadius: Float): Float = { - this.damageRadiusMin = damageRadius - DamageRadiusMin - } - def LashRadius: Float = lashRadius def LashRadius_=(radius: Float): Float = { @@ -205,15 +142,6 @@ class ProjectileDefinition(objectId: Int) RemoteClientData } - def DamageProxy : Option[Int] = damageProxy - - def DamageProxy_=(proxyObjectId : Int) : Option[Int] = DamageProxy_=(Some(proxyObjectId)) - - def DamageProxy_=(proxyObjectId : Option[Int]) : Option[Int] = { - damageProxy = proxyObjectId - DamageProxy - } - def AutoLock: Boolean = autoLock def AutoLock_=(lockState: Boolean): Boolean = { @@ -221,13 +149,6 @@ class ProjectileDefinition(objectId: Int) AutoLock } - def AdditionalEffect: Boolean = additionalEffect - - def AdditionalEffect_=(effect: Boolean): Boolean = { - additionalEffect = effect - AdditionalEffect - } - def JammerProjectile: Boolean = jammerProjectile def JammerProjectile_=(effect: Boolean): Boolean = { @@ -242,24 +163,6 @@ class ProjectileDefinition(objectId: Int) 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 Charging : Option[ChargeDamage] = charging - - def Charging_=(damage : ChargeDamage) : Option[ChargeDamage] = Charging_=(Some(damage)) - - def Charging_=(damage : Option[ChargeDamage]) : Option[ChargeDamage] = { - charging = damage - Charging - } - def DistanceMax : Float = distanceMax //accessor only def DistanceFromAcceleration: Float = distanceFromAcceleration //accessor only diff --git a/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala b/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala index f07eba39d..89810d07e 100644 --- a/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala @@ -8,7 +8,8 @@ import net.psforever.objects.definition.converter.SmallDeployableConverter import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.resistance.ResistanceProfileMutators -import net.psforever.objects.vital.{DamageResistanceModel, NoResistanceSelection, VitalityDefinition} +import net.psforever.objects.vital.resolution.DamageResistanceModel +import net.psforever.objects.vital.{NoResistanceSelection, VitalityDefinition} import scala.concurrent.duration._ diff --git a/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala index 7be4520fa..d208c2c01 100644 --- a/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala +++ b/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala @@ -8,6 +8,7 @@ import net.psforever.objects.vehicles.{DestroyedVehicle, UtilityType} import net.psforever.objects.vital._ import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.resistance.ResistanceProfileMutators +import net.psforever.objects.vital.resolution.DamageResistanceModel import net.psforever.types.Vector3 import scala.collection.mutable @@ -39,8 +40,7 @@ class VehicleDefinition(objectId: Int) private var deploymentTime_Undeploy: Int = 0 //ms private var trunkSize: InventoryTile = InventoryTile.None private var trunkOffset: Int = 0 - - /** The position offset of the trunk, orientation as East = 0 */ + /* The position offset of the trunk, orientation as East = 0 */ private var trunkLocation: Vector3 = Vector3.Zero private var canCloak: Boolean = false private var canFly: Boolean = false @@ -53,7 +53,7 @@ class VehicleDefinition(objectId: Int) Packet = VehicleDefinition.converter DamageUsing = DamageCalculations.AgainstVehicle ResistUsing = StandardVehicleResistance - Model = StandardResolutions.Vehicle + Model = VehicleResolutions.calculate RepairDistance = 10 RepairRestoresAt = 1 diff --git a/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala b/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala index 3fc46bbaa..36b14ba4c 100644 --- a/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala +++ b/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala @@ -2,8 +2,8 @@ package net.psforever.objects.equipment import net.psforever.objects.Tool -import net.psforever.objects.vital.SpecificDamageProfile -import net.psforever.objects.vital.damage.DamageModifiers +import net.psforever.objects.vital.base.DamageModifiers +import net.psforever.objects.vital.damage.SpecificDamageProfile import scala.collection.mutable diff --git a/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala b/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala index 191712708..a52e1fcd0 100644 --- a/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala +++ b/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala @@ -3,9 +3,10 @@ package net.psforever.objects.equipment import akka.actor.{Actor, Cancellable} import net.psforever.objects.{Default, PlanetSideGameObject, Tool} -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.vehicles.MountedWeapons +import net.psforever.objects.vital.interaction.DamageResult +import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.zones.ZoneAware import net.psforever.types.Vector3 import net.psforever.services.Service @@ -43,7 +44,7 @@ object JammableUnit { * A message for jammering due to a projectile. * @param cause information pertaining to the projectile */ - final case class Jammered(cause: ResolvedProjectile) + final case class Jammered(cause: DamageResult) /** * Stop the auditory aspect of being jammered. @@ -68,7 +69,7 @@ trait JammingUnit { */ private val jammedEffectDuration: mutable.ListBuffer[(TargetValidation, Int)] = new mutable.ListBuffer() - def HasJammedEffectDuration: Boolean = jammedEffectDuration.isEmpty + def HasJammedEffectDuration: Boolean = jammedEffectDuration.nonEmpty def JammedEffectDuration: mutable.ListBuffer[(TargetValidation, Int)] = jammedEffectDuration } @@ -85,7 +86,11 @@ object JammingUnit { * @return the duration to be jammered, if any, in milliseconds */ def FindJammerDuration(jammer: JammingUnit, target: PlanetSideGameObject): Option[Int] = { - jammer.JammedEffectDuration + FindJammerDuration(jammer.JammedEffectDuration.toList, target) + } + + def FindJammerDuration(durations: Iterable[(TargetValidation, Int)], target: PlanetSideGameObject): Option[Int] = { + durations .collect { case (TargetValidation(_, test), duration) if test(target) => duration } .toList .sortWith(_ > _) @@ -134,15 +139,23 @@ trait JammableBehavior { * @param target the objects to be determined if affected by the source's jammering * @param cause the source of the "jammered" status */ - def TryJammerEffectActivate(target: Any, cause: ResolvedProjectile): Unit = + def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = target match { case obj: PlanetSideServerObject => - val radius = cause.projectile.profile.DamageRadius - JammingUnit.FindJammerDuration(cause.projectile.profile, obj) match { - case Some(dur) if Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius => - StartJammeredSound(obj, dur) - StartJammeredStatus(obj, dur) - case _ => ; + val interaction = cause.interaction + JammingUnit.FindJammerDuration(interaction.cause.source.JammedEffectDuration.toList, obj) match { + case Some(dur) => + if(interaction.cause match { + case reason: ProjectileReason => + val radius = reason.projectile.profile.DamageRadius + Vector3.DistanceSquared(interaction.hitPos, interaction.target.Position) < radius * radius + case _ => + true + }) { + StartJammeredSound(obj, dur) + StartJammeredStatus(obj, dur) + } + case None => } case _ => ; } diff --git a/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala b/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala index ffd6906b0..4373dd9da 100644 --- a/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala +++ b/src/main/scala/net/psforever/objects/serverobject/PlanetSideServerObject.scala @@ -10,7 +10,10 @@ import net.psforever.objects.zones.ZoneAware * An object layered on top of the standard game object class that maintains an internal `ActorRef`. * A measure of synchronization can be managed using this `Actor` as a "controlling agent." */ -abstract class PlanetSideServerObject extends PlanetSideGameObject with FactionAffinity with ZoneAware { +abstract class PlanetSideServerObject + extends PlanetSideGameObject + with FactionAffinity + with ZoneAware { private var actor: ActorRef = ActorRef.noSender private var getActorFunc: () => ActorRef = PlanetSideServerObject.getDefaultActor diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala index ad211cdb3..ba8b1524a 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/AggravatedBehavior.scala @@ -4,7 +4,10 @@ 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 net.psforever.objects.vital.base._ +import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} +import net.psforever.objects.vital.projectile.ProjectileReason import scala.collection.mutable import scala.concurrent.ExecutionContext.Implicits.global @@ -21,16 +24,15 @@ trait AggravatedBehavior { 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)) && + def TryAggravationEffectActivate(data: DamageResult): Option[AggravatedDamage] = { + (data.interaction.cause, data.interaction.cause.source.Aggravated) match { + case (o: ProjectileReason, Some(damage)) + if data.interaction.cause.source.AllDamageTypes.contains(DamageType.Aggravated) && + damage.info.exists(_.damage_type == AggravatedDamage.basicDamageType(data.interaction.resolution)) && damage.effect_type != Aura.Nothing && - (projectile.quality == ProjectileQuality.AggravatesTarget || - damage.targets.exists(validation => validation.test(AggravatedObject))) => - TryAggravationEffectActivate(damage, data) + (o.projectile.quality == ProjectileQuality.AggravatesTarget || + damage.targets.exists(validation => validation.test(AggravatedObject))) => + TryAggravationEffectActivate(damage, data.interaction) case _ => None } @@ -38,10 +40,10 @@ trait AggravatedBehavior { private def TryAggravationEffectActivate( aggravation: AggravatedDamage, - data: ResolvedProjectile + data: DamageInteraction ): Option[AggravatedDamage] = { val effect = aggravation.effect_type - if(CheckForUniqueUnqueuedProjectile(data.projectile)) { + if(CheckForUniqueUnqueuedCause(data.cause)) { val sameEffect = entryIdToEntry.values.filter(entry => entry.effect == effect) if(sameEffect.isEmpty || sameEffect.nonEmpty && aggravation.cumulative_damage_degrade) { SetupAggravationEntry(aggravation, data) @@ -56,18 +58,21 @@ trait AggravatedBehavior { } } - private def CheckForUniqueUnqueuedProjectile(projectile : Projectile): Boolean = { - !entryIdToEntry.values.exists { entry => entry.data.projectile.id == projectile.id } + private def CheckForUniqueUnqueuedCause(cause : DamageReason): Boolean = { + !entryIdToEntry.values.exists { entry => entry.data.cause.same(cause) } } - private def SetupAggravationEntry(aggravation: AggravatedDamage, data: ResolvedProjectile): Boolean = { + private def SetupAggravationEntry(aggravation: AggravatedDamage, data: DamageInteraction): Boolean = { val effect = aggravation.effect_type aggravation.info.find(_.damage_type == AggravatedDamage.basicDamageType(data.resolution)) match { case Some(info) => + //setup effect val timing = aggravation.timing val duration = timing.duration - //setup effect - val id = data.projectile.id + val id = data.cause match { + case o: ProjectileReason => o.projectile.id + case _ => data.hitTime + } //setup timer data val (tick: Long, iterations: Int) = timing.ticks match { case Some(n) if n < 1 => @@ -103,16 +108,16 @@ trait AggravatedBehavior { id: Long, effect: Aura, retime: Long, - data: ResolvedProjectile, + data: DamageInteraction, target: SourceEntry, powerOffset: List[Float] ): AggravatedBehavior.Entry = { - val aggravatedDamageInfo = ResolvedProjectile( - AggravatedDamage.burning(data.resolution), - data.projectile, + val cause = data.cause + val aggravatedDamageInfo = DamageInteraction( + AggravatedDamage.burning(cause.resolution), target, - data.damage_model, - data.hit_pos + cause, + data.hitPos ) val entry = AggravatedBehavior.Entry(id, effect, retime, aggravatedDamageInfo, powerOffset) entryIdToEntry += id -> entry @@ -159,7 +164,7 @@ trait AggravatedBehavior { entryIdToEntry.remove(id) match { case Some(entry) => ongoingAggravated = entryIdToEntry.nonEmpty - entry.data.projectile.profile.Aggravated.get.effect_type + entry.data.cause.source.Aggravated.get.effect_type case _ => Aura.Nothing } @@ -187,23 +192,31 @@ trait AggravatedBehavior { 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))) + entry.data.cause match { + case o: ProjectileReason => + val aggravatedProjectileData = DamageInteraction( + entry.data.resolution, + entry.data.target, + ProjectileReason( + o.resolution, + o.projectile.quality(ProjectileQuality.Modified(entry.qualityPerTick(tick))), + o.damageModel + ), + entry.data.hitPos + ) + takesDamage.apply(Vitality.Damage(aggravatedProjectileData.calculate())) + + case _ => + //TODO how to apply tick damage degradation + takesDamage.apply(Vitality.Damage(entry.data.calculate())) + } } } object AggravatedBehavior { type Target = Damageable.Target - private case class Entry(id: Long, effect: Aura, retime: Long, data: ResolvedProjectile, qualityPerTick: List[Float]) + private case class Entry(id: Long, effect: Aura, retime: Long, data: DamageInteraction, qualityPerTick: List[Float]) private case class Aggravate(id: Long, iterations: Int) } diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala index e36b3c95c..28c24a5e9 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala @@ -2,12 +2,12 @@ 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.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.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.resolution.ResolutionCalculations /** @@ -77,16 +77,11 @@ object Damageable { * @return `true`, if the target can be affected; * `false`, otherwise */ - def CanDamage(obj: Vitality with FactionAffinity, damage: Int, data: ResolvedProjectile): Boolean = { + def CanDamage(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = { val definition = obj.Definition - (damage > 0 || data.projectile.profile.Aggravated.nonEmpty) && + (damage > 0 || data.cause.source.Aggravated.nonEmpty) && definition.Damageable && - (definition.DamageableByFriendlyFire || - (data.projectile.owner.Faction != obj.Faction || - (obj match { - case hobj: Hackable => hobj.HackedBy.nonEmpty - case _ => false - }))) + (definition.DamageableByFriendlyFire || adversarialOrHackableChecks(obj, data)) } /** @@ -98,15 +93,21 @@ object Damageable { * @return `true`, if the target can be affected; * `false`, otherwise */ - def CanJammer(obj: Vitality with FactionAffinity, data: ResolvedProjectile): Boolean = { - val projectile = data.projectile - projectile.profile.JammerProjectile && + def CanJammer(obj: Vitality with FactionAffinity, data: DamageInteraction): Boolean = { + data.cause.source.HasJammedEffectDuration && obj.isInstanceOf[JammableUnit] && - (projectile.owner.Faction != obj.Faction || - (obj match { - case hobj: Hackable => hobj.HackedBy.nonEmpty - case _ => false - })) + adversarialOrHackableChecks(obj, data) + } + + private def adversarialOrHackableChecks(obj: Vitality with FactionAffinity, data: DamageInteraction): Boolean = { + (data.adversarial match { + case Some(adversarial) => adversarial.attacker.Faction != adversarial.defender.Faction + case None => true + }) || + (obj match { + case hobj: Hackable => hobj.HackedBy.nonEmpty + case _ => false + }) } /** @@ -117,7 +118,7 @@ object Damageable { * @return `true`, if the target can be affected; * `false`, otherwise */ - def CanDamageOrJammer(obj: Vitality with FactionAffinity, damage: Int, data: ResolvedProjectile): Boolean = { + def CanDamageOrJammer(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = { CanDamage(obj, damage, data) || CanJammer(obj, data) } @@ -126,7 +127,7 @@ object Damageable { * @param target the entity being damaged * @param cause historical information about the damage */ - def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { + def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { target.Destroyed = true } } diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableAmenity.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableAmenity.scala index 3a9eac903..bcb185f78 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableAmenity.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableAmenity.scala @@ -1,8 +1,8 @@ //Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.damage -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.serverobject.structures.Amenity +import net.psforever.objects.vital.interaction.DamageResult import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} /** @@ -13,7 +13,7 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} trait DamageableAmenity extends DamageableEntity { def DamageableObject: Amenity - override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { super.DestructionAwareness(target, cause) DamageableAmenity.DestructionAwareness(target, cause) target.ClearHistory() @@ -32,7 +32,7 @@ object DamageableAmenity { * @param target the entity being destroyed * @param cause historical information about the damage */ - def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { + def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { val zone = target.Zone val zoneId = zone.id val events = zone.AvatarEvents diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala index a2db8e3c9..240ae5cae 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala @@ -1,8 +1,8 @@ //Copyright (c) 2020 PSForever package net.psforever.objects.serverobject.damage -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.equipment.JammableUnit +import net.psforever.objects.vital.interaction.DamageResult import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.objects.zones.Zone import net.psforever.types.PlanetSideGUID @@ -78,8 +78,8 @@ trait DamageableEntity extends Damageable { * @return `true`, if damage resolution is to be evaluated; * `false`, otherwise */ - protected def WillAffectTarget(target: Damageable.Target, damage: Int, cause: ResolvedProjectile): Boolean = { - Damageable.CanDamageOrJammer(target, damage, cause) + protected def WillAffectTarget(target: Damageable.Target, damage: Int, cause: DamageResult): Boolean = { + Damageable.CanDamageOrJammer(target, damage, cause.interaction) } /** @@ -89,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: Any): Unit = { + protected def HandleDamage(target: Damageable.Target, cause: DamageResult, damage: Any): Unit = { if (!target.Destroyed && target.Health <= target.Definition.DamageDestroysAt) { DestructionAwareness(target, cause) } else { @@ -103,7 +103,7 @@ 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: Any): Unit = { + protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any): Unit = { amount match { case value: Int => DamageableEntity.DamageAwareness(target, cause, value) @@ -117,13 +117,22 @@ trait DamageableEntity extends Damageable { * @param target the entity being destroyed * @param cause historical information about the damage */ - protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { + protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { Damageable.DestructionAwareness(target, cause) DamageableEntity.DestructionAwareness(target, cause) } } object DamageableEntity { + def attributionTo(cause: DamageResult, zone: Zone, default: PlanetSideGUID = PlanetSideGUID(0)): PlanetSideGUID = { + (cause.adversarial match { + case Some(adversarial) => zone.LivePlayers.find { p => adversarial.attacker.Name.equals(p.Name) } + case None => None + }) match { + case Some(player) => player.GUID + case None => default + } + } /** * A damaged target dispatches messages to: @@ -143,16 +152,16 @@ object DamageableEntity { * @param target the entity being damaged * @param cause historical information about the damage */ - def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Unit = { - if (Damageable.CanJammer(target, cause)) { + def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Int): Unit = { + if (Damageable.CanJammer(target, cause.interaction)) { target.Actor ! JammableUnit.Jammered(cause) } if (DamageToHealth(target, cause, amount)) { - target.Zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + target.Zone.Activity ! Zone.HotSpot.Activity(cause) } } - def DamageToHealth(target: Damageable.Target, cause: ResolvedProjectile, amount: Int): Boolean = { + def DamageToHealth(target: Damageable.Target, cause: DamageResult, amount: Int): Boolean = { if (amount > 0 && !target.Destroyed) { val zone = target.Zone zone.AvatarEvents ! AvatarServiceMessage( @@ -180,7 +189,7 @@ object DamageableEntity { * @param target the entity being destroyed * @param cause historical information about the damage */ - def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { + def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { //un-jam target.Actor ! JammableUnit.ClearJammeredSound() target.Actor ! JammableUnit.ClearJammeredStatus() @@ -188,10 +197,7 @@ object DamageableEntity { val zone = target.Zone val zoneId = zone.id val tguid = target.GUID - val attribution = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match { - case Some(player) => player.GUID - case _ => PlanetSideGUID(0) - } + val attribution = attributionTo(cause, target.Zone) zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health)) zone.AvatarEvents ! AvatarServiceMessage( zoneId, diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala index abfb7922e..3e5150ce5 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala @@ -2,8 +2,9 @@ package net.psforever.objects.serverobject.damage import net.psforever.objects.Player -import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} +import net.psforever.objects.ballistics.{PlayerSource, SourceEntry} import net.psforever.objects.serverobject.mount.Mountable +import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} import net.psforever.packet.game.DamageWithPositionMessage import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} @@ -30,7 +31,7 @@ object DamageableMountable { */ def DamageAwareness( target: Damageable.Target with Mountable, - cause: ResolvedProjectile, + cause: DamageResult, countableDamage: Int ): Unit = { val zone = target.Zone @@ -39,8 +40,11 @@ object DamageableMountable { case seat if seat.isOccupied && seat.Occupant.get.isAlive => seat.Occupant.get } - (cause.projectile.owner match { - case pSource: PlayerSource => //player damage + ((cause.adversarial match { + case Some(adversarial) => Some(adversarial.attacker) + case None => None + }) match { + case Some(pSource: PlayerSource) => //player damage val name = pSource.Name (zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match { case Some(player) => @@ -53,9 +57,11 @@ object DamageableMountable { case msg => occupants.map { tplayer => (tplayer.Name, msg) } } - case source => //object damage + case Some(source) => //object damage val msg = AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(countableDamage, source.Position)) occupants.map { tplayer => (tplayer.Name, msg) } + case None => + List.empty }).foreach { case (channel, msg) => events ! AvatarServiceMessage(channel, msg) @@ -70,15 +76,18 @@ object DamageableMountable { * @param target the entity being destroyed * @param cause historical information about the damage */ - def DestructionAwareness(target: Damageable.Target with Mountable, cause: ResolvedProjectile): Unit = { + def DestructionAwareness(target: Damageable.Target with Mountable, cause: DamageResult): Unit = { + val interaction = cause.interaction target.Seats.values .filter(seat => { seat.isOccupied && seat.Occupant.get.isAlive }) .foreach(seat => { val tplayer = seat.Occupant.get - tplayer.History(cause) - tplayer.Actor ! Player.Die() + //tplayer.History(cause) + tplayer.Actor ! Player.Die( + DamageInteraction(interaction.resolution, SourceEntry(tplayer), interaction.cause, interaction.hitPos) + ) }) } } diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala index ceea8af0c..edbb51955 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala @@ -3,10 +3,9 @@ package net.psforever.objects.serverobject.damage 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.interaction.DamageResult import net.psforever.objects.vital.resolution.ResolutionCalculations import net.psforever.services.Service import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} @@ -17,7 +16,7 @@ import net.psforever.types.Vector3 import scala.concurrent.duration._ /** - * The "control" `Actor` mixin for damage-handling code for `Vehicle` objects. + * The mixin for damage-handling code for `Vehicle` entities. */ trait DamageableVehicle extends DamageableEntity @@ -95,7 +94,7 @@ trait DamageableVehicle * @param cause historical information about the damage * @param amount how much damage was performed */ - override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = { + override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any): Unit = { val obj = DamageableObject val zone = target.Zone val events = zone.VehicleEvents @@ -112,16 +111,16 @@ trait DamageableVehicle announceConfrontation = true false case _ => - cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) + cause.interaction.cause.source.Aggravated.nonEmpty } reportDamageToVehicle = false //log historical event target.History(cause) //damage - if (Damageable.CanDamageOrJammer(target, totalDamage, cause)) { + if (Damageable.CanDamageOrJammer(target, totalDamage, cause.interaction)) { //jammering - if (Damageable.CanJammer(target, cause)) { + if (Damageable.CanJammer(target, cause.interaction)) { target.Actor ! JammableUnit.Jammered(cause) } //stat changes @@ -151,7 +150,7 @@ trait DamageableVehicle } else { //activity on map - zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + zone.Activity ! Zone.HotSpot.Activity(cause) //alert to damage source DamageableMountable.DamageAwareness(obj, cause, totalDamage) } @@ -187,13 +186,14 @@ trait DamageableVehicle * @param target the entity being destroyed * @param cause historical information about the damage */ - override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = { super.DestructionAwareness(target, cause) val obj = DamageableObject - DamageableMountable.DestructionAwareness(obj, cause) val zone = target.Zone //aggravation cancel EndAllAggravation() + //passengers die with us + DamageableMountable.DestructionAwareness(obj, cause) //cargo vehicles die with us obj.CargoHolds.values.foreach(hold => { hold.Occupant match { @@ -202,6 +202,8 @@ trait DamageableVehicle case None => ; } }) + //things positioned around us can get hurt from us + Zone.causeExplosion(obj.Zone, obj, Some(cause)) //special considerations for certain vehicles Vehicles.BeforeUnloadVehicle(obj, zone) //shields @@ -212,6 +214,7 @@ trait DamageableVehicle VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0) ) } + //clean up target.Actor ! Vehicle.Deconstruct(Some(1 minute)) target.ClearHistory() DamageableWeaponTurret.DestructionAwareness(obj, cause) @@ -224,12 +227,12 @@ 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) + private case class Damage(cause: DamageResult, 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) + private case class Destruction(cause: DamageResult) } diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala index f4ff4b2c5..4b8400077 100644 --- a/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala +++ b/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala @@ -2,11 +2,10 @@ 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.vital.interaction.DamageResult import net.psforever.objects.zones.Zone import net.psforever.packet.game.DamageWithPositionMessage import net.psforever.types.Vector3 @@ -32,7 +31,7 @@ trait DamageableWeaponTurret override val takesDamage: Receive = originalTakesDamage.orElse(aggravatedBehavior) - override protected def DamageAwareness(target: Damageable.Target, cause: ResolvedProjectile, amount: Any): Unit = { + override protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any): Unit = { val obj = DamageableObject val zone = target.Zone val events = zone.VehicleEvents @@ -48,15 +47,15 @@ trait DamageableWeaponTurret announceConfrontation = true false case _ => - cause.projectile.profile.ProjectileDamageTypes.contains(DamageType.Aggravated) + cause.interaction.cause.source.Aggravated.nonEmpty } //log historical event target.History(cause) //damage - if (Damageable.CanDamageOrJammer(target, damageToHealth, cause)) { + if (Damageable.CanDamageOrJammer(target, damageToHealth, cause.interaction)) { //jammering - if (Damageable.CanJammer(target, cause)) { + if (Damageable.CanJammer(target, cause.interaction)) { target.Actor ! JammableUnit.Jammered(cause) } //stat changes @@ -81,19 +80,20 @@ trait DamageableWeaponTurret } else { //activity on map - zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos) + zone.Activity ! Zone.HotSpot.Activity(cause) //alert to damage source DamageableMountable.DamageAwareness(obj, cause, damageToHealth) } } } - override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { super.DestructionAwareness(target, cause) val obj = DamageableObject EndAllAggravation() DamageableWeaponTurret.DestructionAwareness(obj, cause) DamageableMountable.DestructionAwareness(obj, cause) + Zone.causeExplosion(target.Zone, target, Some(cause)) } } @@ -120,7 +120,7 @@ object DamageableWeaponTurret { * but the handling code closely associates with the former * @param cause historical information about the damage */ - def DestructionAwareness(target: Damageable.Target with MountedWeapons, cause: ResolvedProjectile): Unit = { + def DestructionAwareness(target: Damageable.Target with MountedWeapons, cause: DamageResult): Unit = { //wreckage has no (visible) mounted weapons val zone = target.Zone val zoneId = zone.id diff --git a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala index 503f1dec6..06cdc3534 100644 --- a/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala @@ -4,15 +4,14 @@ package net.psforever.objects.serverobject.generator import akka.actor.{Actor, Cancellable} import net.psforever.actors.zone.BuildingActor import net.psforever.objects.{Default, Player, Tool} -import net.psforever.objects.ballistics._ import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.DamageableEntity import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, Repairable, RepairableEntity} -import net.psforever.objects.serverobject.structures.Building -import net.psforever.objects.vital.DamageFromExplosion +import net.psforever.objects.vital.interaction.DamageResult +import net.psforever.objects.zones.Zone import net.psforever.packet.game.TriggerEffectMessage -import net.psforever.types.{PlanetSideGeneratorState, Vector3} +import net.psforever.types.PlanetSideGeneratorState import net.psforever.services.Service import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} @@ -103,17 +102,8 @@ class GeneratorControl(gen: Generator) queuedExplosion.cancel() queuedExplosion = Default.Cancellable imminentExplosion = false - //kill everyone within 14m - gen.Owner match { - case b: Building => - val genDef = gen.Definition - b.PlayersInSOI.collect { - case player if player.isAlive && Vector3.DistanceSquared(player.Position, gen.Position) < 196 => - player.History(DamageFromExplosion(PlayerSource(player), genDef)) - player.Actor ! Player.Die() - } - case _ => ; - } + //hate on everything nearby + Zone.causeExplosion(gen.Zone, gen, gen.LastDamage) gen.ClearHistory() case GeneratorControl.Restored() => @@ -154,12 +144,12 @@ class GeneratorControl(gen: Generator) !imminentExplosion && super.CanPerformRepairs(obj, player, item) } - override protected def WillAffectTarget(target: Target, damage: Int, cause: ResolvedProjectile): Boolean = { + override protected def WillAffectTarget(target: Target, damage: Int, cause: DamageResult): Boolean = { //if an explosion is queued, disallow further damage !imminentExplosion && super.WillAffectTarget(target, damage, cause) } - override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = { + override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any): Unit = { tryAutoRepair() super.DamageAwareness(target, cause, amount) val damageTo = amount match { @@ -169,7 +159,7 @@ class GeneratorControl(gen: Generator) GeneratorControl.DamageAwareness(gen, cause, damageTo) } - override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = { tryAutoRepair() //if the target is already destroyed, do not let it be destroyed again if (!target.Destroyed) { @@ -211,7 +201,7 @@ class GeneratorControl(gen: Generator) if(!gen.Destroyed) { GeneratorControl.UpdateOwner(gen, Some(GeneratorControl.Event.Offline)) } - //can any explosion (see withoutNtu->GenweratorControl.Destabilized) + //quit any explosion (see withoutNtu->GeneratorControl.Destabilized) if(!queuedExplosion.isCancelled) { queuedExplosion.cancel() self ! GeneratorControl.Destabilized() @@ -275,7 +265,7 @@ object GeneratorControl { * @param cause historical information about the damage * @param amount the amount of damage */ - def DamageAwareness(target: Generator, cause: ResolvedProjectile, amount: Int): Unit = { + def DamageAwareness(target: Generator, cause: DamageResult, amount: Int): Unit = { if (!target.Destroyed) { val health: Float = target.Health.toFloat val max: Float = target.MaxHealth.toFloat diff --git a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala index 4400a3e58..8d893e5b9 100644 --- a/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.implantmech -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.{GlobalDefinitions, Player, SimpleItem} import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} @@ -11,6 +10,7 @@ import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity, import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableEntity} import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} +import net.psforever.objects.vital.interaction.DamageResult import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} /** @@ -83,7 +83,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) } } - override protected def DamageAwareness(target: Target, cause: ResolvedProjectile, amount: Any): Unit = { + override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any): Unit = { tryAutoRepair() super.DamageAwareness(target, cause, amount) val damageTo = amount match { @@ -93,7 +93,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech) DamageableMountable.DamageAwareness(DamageableObject, cause, damageTo) } - override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { super.DestructionAwareness(target, cause) DamageableMountable.DestructionAwareness(DamageableObject, cause) target.ClearHistory() diff --git a/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala b/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala index 8e09e30cb..f9545546f 100644 --- a/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxControl.scala @@ -1,12 +1,14 @@ package net.psforever.objects.serverobject.painbox import akka.actor.Cancellable -import net.psforever.actors.zone.BuildingActor +import net.psforever.objects.ballistics.SourceEntry import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} -import net.psforever.objects.{Default, GlobalDefinitions} +import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.etc.PainboxReason +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.{Default, GlobalDefinitions, Player} import net.psforever.types.{PlanetSideEmpire, Vector3} -import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ @@ -15,87 +17,49 @@ class PainboxControl(painbox: Painbox) extends PoweredAmenityControl { private[this] val log = org.log4s.getLogger(s"Painbox") var painboxTick: Cancellable = Default.Cancellable var nearestDoor: Option[Door] = None - var bBoxMinCorner = Vector3.Zero - var bBoxMaxCorner = Vector3.Zero - var bBoxMidPoint = Vector3.Zero - - var disabled = false // Temporary to disable cavern non-radius fields + var domain: PainboxControl.Shape = PainboxControl.Unshaped() + var disabled = false def initialStartup(): Unit = { - if (painbox.Definition.HasNearestDoorDependency) { - (painbox.Owner match { - case obj : Building => - obj.Amenities - .collect { case door : Door => door } - .sortBy(door => Vector3.DistanceSquared(painbox.Position, door.Position)) - .headOption - case _ => - None - }) match { - case door@Some(_) => - nearestDoor = door - case _ => - log.error( - s"Painbox ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position} can not find a door that it is dependent on" - ) - } - } - else { - if (painbox.Definition.Radius == 0f) { - if (painbox.Owner.Continent.matches("c[0-9]")) { - // todo: handle non-radius painboxes in caverns properly - log.warn(s"Skipping initialization of ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position}") - disabled = true - } - else { - painbox.Owner match { - case obj : Building => - val planarRange = 16.5f - val aboveRange = 5 - val belowRange = 5 - // Find amenities within the specified range - val nearbyAmenities = obj.Amenities - .filter(amenity => - amenity.Position != Vector3.Zero - && (amenity.Definition == GlobalDefinitions.mb_locker - || amenity.Definition == GlobalDefinitions.respawn_tube - || amenity.Definition == GlobalDefinitions.spawn_terminal - || amenity.Definition == GlobalDefinitions.order_terminal - || amenity.Definition == GlobalDefinitions.door) - && amenity.Position.x > painbox.Position.x - planarRange && amenity.Position.x < painbox.Position.x + planarRange - && amenity.Position.y > painbox.Position.y - planarRange && amenity.Position.y < painbox.Position.y + planarRange - && amenity.Position.z > painbox.Position.z - belowRange && amenity.Position.z < painbox.Position.z + aboveRange - ) - - // Calculate bounding box of amenities - bBoxMinCorner = Vector3( - nearbyAmenities.minBy(amenity => amenity.Position.x).Position.x, - nearbyAmenities.minBy(amenity => amenity.Position.y).Position.y, - nearbyAmenities.minBy(x => x.Position.z).Position.z - ) - bBoxMaxCorner = Vector3( - nearbyAmenities.maxBy(amenity => amenity.Position.x).Position.x, - nearbyAmenities.maxBy(amenity => amenity.Position.y).Position.y, - painbox.Position.z - ) - bBoxMidPoint = Vector3( - (bBoxMinCorner.x + bBoxMaxCorner.x) / 2, - (bBoxMinCorner.y + bBoxMaxCorner.y) / 2, - (bBoxMinCorner.z + bBoxMaxCorner.z) / 2 - ) - case _ => None - } + if (painbox.Owner.Continent.matches("c[0-9]")) { + //are we in a safe zone? + // todo: handle non-radius painboxes in caverns properly + log.warn(s"Skipping initialization of ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position}") + disabled = true + } else { + if (painbox.Definition.HasNearestDoorDependency) { + //whether an open door summons the pain + (painbox.Owner match { + case obj : Building => + obj.Amenities + .collect { case door : Door => door } + .sortBy(door => Vector3.DistanceSquared(painbox.Position, door.Position)) + .headOption + case _ => + None + }) match { + case door@Some(_) => + nearestDoor = door + case _ => + log.error( + s"Painbox ${painbox.GUID} on ${painbox.Owner.Continent} - ${painbox.Position} can not find a door that it is dependent on" + ) + disabled = true } } - } - if (!disabled) { - self ! BuildingActor.PowerOff() + //the region the painbox endangers + domain = painbox.Definition.innateDamage match { + case Some(properties) if properties.DamageRadius > 0f => + PainboxControl.Spherical(painbox.Position + painbox.Definition.sphereOffset, properties.DamageRadius) + case _ => + PainboxControl.Box(painbox) + } } } var commonBehavior: Receive = { case "startup" => - if (bBoxMidPoint == Vector3.Zero) { + if (!disabled && domain.midpoint == Vector3.Zero) { initialStartup() } @@ -107,7 +71,7 @@ class PainboxControl(painbox: Painbox) extends PoweredAmenityControl { def poweredStateLogic: Receive = commonBehavior .orElse { - case Painbox.Start() if isPowered => + case Painbox.Start() if isPowered && !disabled => painboxTick.cancel() painboxTick = context.system.scheduler.scheduleWithFixedDelay(0 seconds, 1 second, self, Painbox.Tick()) @@ -115,53 +79,26 @@ class PainboxControl(painbox: Painbox) extends PoweredAmenityControl { //todo: Account for overlapping pain fields //todo: Pain module //todo: REK boosting - val guid = painbox.GUID - val owner = painbox.Owner.asInstanceOf[Building] - val faction = owner.Faction + val faction = painbox.Faction if ( isPowered && faction != PlanetSideEmpire.NEUTRAL && (nearestDoor match { case Some(door) => door.Open.nonEmpty; case _ => true }) ) { - val events = painbox.Zone.AvatarEvents - val damage = painbox.Definition.Damage - val radius = painbox.Definition.Radius * painbox.Definition.Radius - val position = painbox.Position - - if (painbox.Definition.Radius != 0f) { - // Spherical pain field - owner.PlayersInSOI - .collect { - case p - if p.Faction != faction - && p.Health > 0 - && Vector3.DistanceSquared(p.Position, position) < radius => - events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage)) + val pain = PainboxReason(painbox) + domain + .filterTargets( + (painbox.Owner match { + case b: Building => b.PlayersInSOI + case _ => painbox.Zone.LivePlayers + }).filter { p => + p.Faction != faction && p.Health > 0 } - } else { - // Bounding box pain field - owner.PlayersInSOI - .collect { - case p - if p.Faction != faction - && p.Health > 0 => - /* - This may be cpu intensive with a large number of players in SOI. Further performance tweaking may be required - The bounding box is calculated aligned to the world XY axis, instead of rotating the painbox corners to match the base rotation - we instead rotate the player's current coordinates to match the base rotation, which allows for much simplified checking of if the player is - within the bounding box - */ - val playerRot = - Vector3.PlanarRotateAroundPoint(p.Position, bBoxMidPoint, painbox.Owner.Orientation.z.toRadians) - if ( - bBoxMinCorner.x <= playerRot.x && playerRot.x <= bBoxMaxCorner.x && bBoxMinCorner.y <= playerRot.y && playerRot.y <= bBoxMaxCorner.y - && playerRot.z >= bBoxMinCorner.z && playerRot.z <= bBoxMaxCorner.z - ) { - events ! AvatarServiceMessage(p.Name, AvatarAction.EnvironmentalDamage(p.GUID, guid, damage)) - } - } - } + ) + .foreach { p => + p.Actor ! Vitality.Damage(DamageInteraction(SourceEntry(p), pain, p.Position).calculate()) + } } case _ => ; @@ -179,9 +116,96 @@ class PainboxControl(painbox: Painbox) extends PoweredAmenityControl { def powerTurnOnCallback(): Unit = { painbox.Owner match { - case b: Building if b.PlayersInSOI.nonEmpty => + case b: Building if b.PlayersInSOI.nonEmpty && !disabled => self ! Painbox.Start() case _ => ; } } } + +object PainboxControl { + sealed trait Shape { + def midpoint: Vector3 + def filterTargets(available : List[Player]): List[Player] + } + + final case class Unshaped() extends Shape { + def midpoint: Vector3 = Vector3.Zero + + def filterTargets(available: List[Player]) : List[Player] = Nil + } + + final case class Passthrough(midpoint: Vector3) extends Shape { + + def filterTargets(available: List[Player]) : List[Player] = available + } + + final case class Spherical(midpoint: Vector3, radius: Float) extends Shape { + def filterTargets(available: List[Player]): List[Player] = { + available.filter { p => + Vector3.DistanceSquared(p.Position, midpoint) < radius + } + } + } + + final case class Box(painbox: Painbox) extends Shape { + private val (bBoxMinCorner, bBoxMaxCorner, bBoxMidPoint): (Vector3, Vector3, Vector3) = { + painbox.Owner match { + case obj : Building => + val planarRange = 16.5f + val aboveRange = 5 + val belowRange = 5 + // Find amenities within the specified range + val nearbyAmenities = obj.Amenities + .filter(amenity => + amenity.Position != Vector3.Zero + && (amenity.Definition == GlobalDefinitions.mb_locker + || amenity.Definition == GlobalDefinitions.respawn_tube + || amenity.Definition == GlobalDefinitions.spawn_terminal + || amenity.Definition == GlobalDefinitions.order_terminal + || amenity.Definition == GlobalDefinitions.door) + && amenity.Position.x > painbox.Position.x - planarRange && amenity.Position.x < painbox.Position.x + planarRange + && amenity.Position.y > painbox.Position.y - planarRange && amenity.Position.y < painbox.Position.y + planarRange + && amenity.Position.z > painbox.Position.z - belowRange && amenity.Position.z < painbox.Position.z + aboveRange + ) + // Calculate bounding box of amenities + //0.5 is added/removed to ensure entirety of valid amenities were encompassed by field + val min = Vector3( + nearbyAmenities.minBy(_.Position.x).Position.x - 0.5f, + nearbyAmenities.minBy(_.Position.y).Position.y - 0.5f, + nearbyAmenities.minBy(_.Position.z).Position.z + ) + val max = Vector3( + nearbyAmenities.maxBy(_.Position.x).Position.x + 0.5f, + nearbyAmenities.maxBy(_.Position.y).Position.y + 0.5f, + painbox.Position.z + ) + (min, max, Vector3.midpoint(min, max)) + case _ => + (Vector3.Zero, Vector3.Zero, painbox.Position) + } + } + private val ownerRotZRadians = painbox.Owner.Orientation.z.toRadians + + def midpoint: Vector3 = bBoxMidPoint + + def filterTargets(available : List[Player]) : List[Player] = { + available.filter { p => + /* + This may be cpu intensive with a large number of players in SOI. Further performance tweaking may be required + The bounding box is calculated aligned to the world XY axis, instead of rotating the painbox corners to match the base rotation + we instead rotate the player's current coordinates to match the base rotation, + allowing for much simplified checking of if the player is within the bounding box + */ + val playerRot = Vector3.PlanarRotateAroundPoint( + p.Position, + bBoxMidPoint, + ownerRotZRadians + ) + bBoxMinCorner.x <= playerRot.x && playerRot.x <= bBoxMaxCorner.x && + bBoxMinCorner.y <= playerRot.y && playerRot.y <= bBoxMaxCorner.y && + bBoxMinCorner.z <= playerRot.z && playerRot.z <= bBoxMaxCorner.z + } + } + } +} diff --git a/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxDefinition.scala index b92e199a9..d76b82d51 100644 --- a/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxDefinition.scala @@ -4,45 +4,11 @@ import net.psforever.objects.serverobject.structures.AmenityDefinition import net.psforever.types.Vector3 class PainboxDefinition(objectId: Int) extends AmenityDefinition(objectId) { - private var alwaysOn: Boolean = true - private var radius: Float = 0f - private var damage: Int = 5 - private var sphereOffset = Vector3(0f, 0f, -0.4f) - private var hasNearestDoorDependency = false + var alwaysOn: Boolean = true + var sphereOffset = Vector3.Zero + var hasNearestDoorDependency = false - objectId match { - case 622 => - Name = "painbox" - alwaysOn = false - damage = 0 - case 623 => - Name = "painbox_continuous" - case 624 => - Name = "painbox_door_radius" - alwaysOn = false - radius = 10f * 0.6928f - hasNearestDoorDependency = true - damage = 0 - case 625 => - Name = "painbox_door_radius_continuous" - radius = 10f * 0.6928f - hasNearestDoorDependency = true - case 626 => - Name = "painbox_radius" - alwaysOn = false - radius = 10f * 0.6928f - damage = 0 - case 627 => - Name = "painbox_radius_continuous" - radius = 8.55f - sphereOffset = Vector3.Zero - case _ => - throw new IllegalArgumentException(s"$objectId is not a valid painbox object id") - } - - def Radius: Float = radius def AlwaysOn: Boolean = alwaysOn - def Damage: Int = damage def SphereOffset: Vector3 = sphereOffset def HasNearestDoorDependency: Boolean = hasNearestDoorDependency } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/Amenity.scala b/src/main/scala/net/psforever/objects/serverobject/structures/Amenity.scala index 79739c230..cd03d2860 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/Amenity.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/Amenity.scala @@ -2,8 +2,10 @@ package net.psforever.objects.serverobject.structures import net.psforever.objects.serverobject.PlanetSideServerObject -import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality} -import net.psforever.objects.zones.{Zone, ZoneAware} +import net.psforever.objects.vital.resistance.StandardResistanceProfile +import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.resolution.DamageAndResistance +import net.psforever.objects.zones.Zone import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.objects.zones.{Zone => World} @@ -18,7 +20,10 @@ import net.psforever.objects.zones.{Zone => World} * @see `AmenityOwner` * @see `FactionAffinity` */ -abstract class Amenity extends PlanetSideServerObject with Vitality with ZoneAware with StandardResistanceProfile { +abstract class Amenity + extends PlanetSideServerObject + with Vitality + with StandardResistanceProfile { private[this] val log = org.log4s.getLogger("Amenity") /** what other entity has authority over this amenity; usually either a building or a vehicle */ @@ -76,7 +81,7 @@ abstract class Amenity extends PlanetSideServerObject with Vitality with ZoneAwa LocationOffset } - def DamageModel = Definition.asInstanceOf[DamageResistanceModel] + def DamageModel: DamageAndResistance = Definition.asInstanceOf[DamageAndResistance] def Definition: AmenityDefinition } diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/AmenityDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/structures/AmenityDefinition.scala index 49aa21ee2..bcf4b3ee7 100644 --- a/src/main/scala/net/psforever/objects/serverobject/structures/AmenityDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/structures/AmenityDefinition.scala @@ -3,8 +3,9 @@ package net.psforever.objects.serverobject.structures import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.vital.damage.DamageCalculations -import net.psforever.objects.vital.{DamageResistanceModel, StandardAmenityResistance, StandardResolutions, VitalityDefinition} +import net.psforever.objects.vital._ import net.psforever.objects.vital.resistance.ResistanceProfileMutators +import net.psforever.objects.vital.resolution.DamageResistanceModel final case class AutoRepairStats(amount: Int, start: Long, repeat: Long, drain: Float) @@ -16,7 +17,7 @@ abstract class AmenityDefinition(objectId: Int) Name = "amenity" DamageUsing = DamageCalculations.AgainstVehicle ResistUsing = StandardAmenityResistance - Model = StandardResolutions.Amenities + Model = SimpleResolutions.calculate var autoRepair: Option[AutoRepairStats] = None diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala index fcfdba3ce..a020e8426 100644 --- a/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala @@ -2,7 +2,6 @@ package net.psforever.objects.serverobject.terminals import akka.actor.ActorRef -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.{GlobalDefinitions, SimpleItem} import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior @@ -11,6 +10,7 @@ import net.psforever.objects.serverobject.damage.{Damageable, DamageableAmenity} import net.psforever.objects.serverobject.hackable.{GenericHackables, HackableBehavior} import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableAmenity} import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} +import net.psforever.objects.vital.interaction.DamageResult import net.psforever.services.Service import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -67,12 +67,12 @@ class TerminalControl(term: Terminal) case _ => ; } - override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Any) : Unit = { + override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any) : Unit = { tryAutoRepair() super.DamageAwareness(target, cause, amount) } - override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile) : Unit = { + override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult) : Unit = { tryAutoRepair() if (term.HackedBy.nonEmpty) { val zone = term.Zone diff --git a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala index c7607bc14..7b747e0dc 100644 --- a/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala @@ -2,12 +2,12 @@ package net.psforever.objects.serverobject.tube import net.psforever.actors.zone.BuildingActor -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.damage.Damageable.Target import net.psforever.objects.serverobject.damage.DamageableAmenity import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, Repairable, RepairableAmenity} import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} +import net.psforever.objects.vital.interaction.DamageResult /** * An `Actor` that handles messages being dispatched to a specific `SpawnTube`. @@ -41,12 +41,12 @@ class SpawnTubeControl(tube: SpawnTube) case _ => ; } - override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Any) : Unit = { + override protected def DamageAwareness(target : Target, cause : DamageResult, amount : Any) : Unit = { tryAutoRepair() super.DamageAwareness(target, cause, amount) } - override protected def DestructionAwareness(target: Target, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = { tryAutoRepair() super.DestructionAwareness(target, cause) tube.Owner match { diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala index 3ae7f1601..415c0f1fe 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.turret -import net.psforever.objects.ballistics.ResolvedProjectile import net.psforever.objects.{Default, GlobalDefinitions, Player, Tool} import net.psforever.objects.equipment.{Ammo, JammableMountedWeapons} import net.psforever.objects.serverobject.CommonMessages @@ -11,6 +10,7 @@ import net.psforever.objects.serverobject.damage.{Damageable, DamageableWeaponTu import net.psforever.objects.serverobject.hackable.GenericHackables import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableWeaponTurret} import net.psforever.objects.serverobject.structures.PoweredAmenityControl +import net.psforever.objects.vital.interaction.DamageResult import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} @@ -123,12 +123,12 @@ class FacilityTurretControl(turret: FacilityTurret) case _ => ; } - override protected def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Any) : Unit = { + override protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any) : Unit = { tryAutoRepair() super.DamageAwareness(target, cause, amount) } - override protected def DestructionAwareness(target: Damageable.Target, cause: ResolvedProjectile): Unit = { + override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { tryAutoRepair() super.DestructionAwareness(target, cause) val zone = target.Zone diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala index 7b3a0eb48..136c846cd 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala @@ -3,7 +3,7 @@ package net.psforever.objects.serverobject.turret import net.psforever.objects.serverobject.structures.AmenityDefinition import net.psforever.objects.vital.damage.DamageCalculations -import net.psforever.objects.vital.{StandardResolutions, StandardVehicleResistance} +import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance} /** * The definition for any `FacilityTurret`. @@ -12,5 +12,5 @@ import net.psforever.objects.vital.{StandardResolutions, StandardVehicleResistan class FacilityTurretDefinition(private val objectId: Int) extends AmenityDefinition(objectId) with TurretDefinition { DamageUsing = DamageCalculations.AgainstVehicle ResistUsing = StandardVehicleResistance - Model = StandardResolutions.FacilityTurrets + Model = SimpleResolutions.calculate } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala index 95a4365fb..9c6e0de98 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala @@ -3,8 +3,8 @@ package net.psforever.objects.serverobject.turret import net.psforever.objects.definition.{ObjectDefinition, ToolDefinition} import net.psforever.objects.vehicles.Turrets -import net.psforever.objects.vital.DamageResistanceModel import net.psforever.objects.vital.resistance.ResistanceProfileMutators +import net.psforever.objects.vital.resolution.DamageResistanceModel import scala.collection.mutable diff --git a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index 238349f2b..29c322dd8 100644 --- a/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -3,7 +3,7 @@ package net.psforever.objects.vehicles import akka.actor.{Actor, Cancellable} import net.psforever.objects._ -import net.psforever.objects.ballistics.{ResolvedProjectile, VehicleSource} +import net.psforever.objects.ballistics.VehicleSource import net.psforever.objects.ce.TelepadLike import net.psforever.objects.equipment.{Equipment, EquipmentSlot, JammableMountedWeapons} import net.psforever.objects.guid.GUIDTask @@ -19,6 +19,7 @@ import net.psforever.objects.serverobject.hackable.GenericHackables import net.psforever.objects.serverobject.transfer.TransferBehavior import net.psforever.objects.serverobject.repair.RepairableVehicle import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.vital.interaction.DamageResult import net.psforever.objects.vital.VehicleShieldCharge import net.psforever.objects.zones.Zone import net.psforever.packet.game._ @@ -156,7 +157,7 @@ class VehicleControl(vehicle: Vehicle) } case Vehicle.ChargeShields(amount) => - val now: Long = System.nanoTime + val now: Long = System.currentTimeMillis() //make certain vehicle doesn't charge shields too quickly if ( vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields && @@ -361,7 +362,7 @@ class VehicleControl(vehicle: Vehicle) case _ => } - override def TryJammerEffectActivate(target: Any, cause: ResolvedProjectile): Unit = { + override def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = { if (vehicle.MountedIn.isEmpty) { super.TryJammerEffectActivate(target, cause) } @@ -601,8 +602,8 @@ object VehicleControl { */ def LastShieldChargeOrDamage(now: Long)(act: VitalsActivity): Boolean = { act match { - case DamageFromProjectile(data) => now - data.hit_time < (5 seconds).toNanos //damage delays next charge by 5s - case vsc: VehicleShieldCharge => now - vsc.time < (1 seconds).toNanos //previous charge delays next by 1s + case DamageFromProjectile(data) => now - data.interaction.hitTime < (5 seconds).toMillis //damage delays next charge by 5s + case vsc: VehicleShieldCharge => now - vsc.time < (1 seconds).toMillis //previous charge delays next by 1s case _ => false } } diff --git a/src/main/scala/net/psforever/objects/vital/DamageType.scala b/src/main/scala/net/psforever/objects/vital/DamageType.scala deleted file mode 100644 index 5f5edb655..000000000 --- a/src/main/scala/net/psforever/objects/vital/DamageType.scala +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.vital - -/** - * An `Enumeration` of the damage type. - * These types are not necessarily representative of the kind of projectile being used; - * for example, the bolt driver's `bolt` is considered "splash." - */ -object DamageType extends Enumeration(1) { - type Type = Value - - final val Direct, Splash, Lash, Radiation, Aggravated, Plasma, Comet, None = Value -} diff --git a/src/main/scala/net/psforever/objects/vital/StandardResistances.scala b/src/main/scala/net/psforever/objects/vital/StandardResistances.scala index c8a5a74f8..6a7f5cbc6 100644 --- a/src/main/scala/net/psforever/objects/vital/StandardResistances.scala +++ b/src/main/scala/net/psforever/objects/vital/StandardResistances.scala @@ -1,100 +1,98 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.vital -import net.psforever.objects.ballistics.{ObjectSource, PlayerSource, SourceEntry, VehicleSource} -import net.psforever.objects.vital.projectile.ProjectileCalculations import net.psforever.objects.vital.resistance.{ResistanceCalculations, ResistanceSelection} object NoResistance - extends ResistanceCalculations[SourceEntry]( - ResistanceCalculations.ValidInfantryTarget, + extends ResistanceCalculations( + ResistanceCalculations.AlwaysValidTarget, ResistanceCalculations.NoResistExtractor ) object InfantryHitResistance - extends ResistanceCalculations[PlayerSource]( + extends ResistanceCalculations( ResistanceCalculations.ValidInfantryTarget, ResistanceCalculations.ExoSuitDirectExtractor ) object InfantrySplashResistance - extends ResistanceCalculations[PlayerSource]( + extends ResistanceCalculations( ResistanceCalculations.ValidInfantryTarget, ResistanceCalculations.ExoSuitSplashExtractor ) object InfantryLashResistance - extends ResistanceCalculations[PlayerSource]( + extends ResistanceCalculations( ResistanceCalculations.ValidInfantryTarget, ResistanceCalculations.MaximumResistance ) object InfantryAggravatedResistance - extends ResistanceCalculations[PlayerSource]( + extends ResistanceCalculations( ResistanceCalculations.ValidInfantryTarget, ResistanceCalculations.ExoSuitAggravatedExtractor ) object VehicleHitResistance - extends ResistanceCalculations[VehicleSource]( + extends ResistanceCalculations( ResistanceCalculations.ValidVehicleTarget, ResistanceCalculations.VehicleDirectExtractor ) object VehicleSplashResistance - extends ResistanceCalculations[VehicleSource]( + extends ResistanceCalculations( ResistanceCalculations.ValidVehicleTarget, ResistanceCalculations.VehicleSplashExtractor ) object VehicleLashResistance - extends ResistanceCalculations[VehicleSource]( + extends ResistanceCalculations( ResistanceCalculations.ValidVehicleTarget, ResistanceCalculations.NoResistExtractor ) object VehicleAggravatedResistance - extends ResistanceCalculations[VehicleSource]( + extends ResistanceCalculations( ResistanceCalculations.ValidVehicleTarget, ResistanceCalculations.VehicleAggravatedExtractor ) object AmenityHitResistance - extends ResistanceCalculations[ObjectSource]( + extends ResistanceCalculations( ResistanceCalculations.ValidAmenityTarget, ResistanceCalculations.OtherDirectExtractor ) object AmenitySplashResistance - extends ResistanceCalculations[ObjectSource]( + extends ResistanceCalculations( ResistanceCalculations.ValidAmenityTarget, ResistanceCalculations.OtherSplashExtractor ) object NoResistanceSelection extends ResistanceSelection { - def Direct: ProjectileCalculations.Form = None - def Splash: ProjectileCalculations.Form = None - def Lash: ProjectileCalculations.Form = None - def Aggravated: ProjectileCalculations.Form = None + def Direct: ResistanceSelection.Format = NoResistance.Calculate + def Splash: ResistanceSelection.Format = NoResistance.Calculate + def Lash: ResistanceSelection.Format = NoResistance.Calculate + def Aggravated: ResistanceSelection.Format = NoResistance.Calculate } object StandardInfantryResistance extends ResistanceSelection { - def Direct: ProjectileCalculations.Form = InfantryHitResistance.Calculate - def Splash: ProjectileCalculations.Form = InfantrySplashResistance.Calculate - def Lash: ProjectileCalculations.Form = InfantryLashResistance.Calculate - def Aggravated: ProjectileCalculations.Form = InfantryAggravatedResistance.Calculate + def Direct: ResistanceSelection.Format = InfantryHitResistance.Calculate + def Splash: ResistanceSelection.Format = InfantrySplashResistance.Calculate + def Lash: ResistanceSelection.Format = InfantryLashResistance.Calculate + def Aggravated: ResistanceSelection.Format = InfantryAggravatedResistance.Calculate } object StandardVehicleResistance extends ResistanceSelection { - def Direct: ProjectileCalculations.Form = VehicleHitResistance.Calculate - def Splash: ProjectileCalculations.Form = VehicleSplashResistance.Calculate - def Lash: ProjectileCalculations.Form = VehicleLashResistance.Calculate - def Aggravated: ProjectileCalculations.Form = VehicleAggravatedResistance.Calculate + def Direct: ResistanceSelection.Format = VehicleHitResistance.Calculate + def Splash: ResistanceSelection.Format = VehicleSplashResistance.Calculate + def Lash: ResistanceSelection.Format = VehicleLashResistance.Calculate + def Aggravated: ResistanceSelection.Format = VehicleAggravatedResistance.Calculate } object StandardAmenityResistance extends ResistanceSelection { - def Direct: ProjectileCalculations.Form = AmenityHitResistance.Calculate - def Splash: ProjectileCalculations.Form = AmenityHitResistance.Calculate - def Lash: ProjectileCalculations.Form = None - def Aggravated: ProjectileCalculations.Form = None + def Direct: ResistanceSelection.Format = AmenityHitResistance.Calculate + def Splash: ResistanceSelection.Format = AmenityHitResistance.Calculate + def Lash: ResistanceSelection.Format = ResistanceSelection.None + def Aggravated: ResistanceSelection.Format = ResistanceSelection.None } diff --git a/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala b/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala index 673c7aeaf..cf8d002fc 100644 --- a/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala +++ b/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala @@ -4,48 +4,43 @@ package net.psforever.objects.vital import net.psforever.objects.vital.resolution._ object NoResolutions - extends DamageResistCalculations( + extends DamageResistanceCalculations( ResolutionCalculations.NoDamage, ResolutionCalculations.NoApplication ) +object AnyResolutions + extends DamageResistanceCalculations( + ResolutionCalculations.WildcardCalculations, + ResolutionCalculations.WildcardApplication + ) + object InfantryResolutions - extends DamageResistCalculations( + extends DamageResistanceCalculations( ResolutionCalculations.InfantryDamage, ResolutionCalculations.InfantryApplication ) object MaxResolutions - extends DamageResistCalculations( + extends DamageResistanceCalculations( ResolutionCalculations.MaxDamage, ResolutionCalculations.InfantryApplication ) object VehicleResolutions - extends DamageResistCalculations( + extends DamageResistanceCalculations( ResolutionCalculations.VehicleDamageAfterResist, ResolutionCalculations.VehicleApplication ) object SimpleResolutions - extends DamageResistCalculations( + extends DamageResistanceCalculations( ResolutionCalculations.VehicleDamageAfterResist, ResolutionCalculations.SimpleApplication ) object ComplexDeployableResolutions - extends DamageResistCalculations( + extends DamageResistanceCalculations( ResolutionCalculations.VehicleDamageAfterResist, ResolutionCalculations.ComplexDeployableApplication ) - -object StandardResolutions extends ResolutionSelection { - def Infantry: ResolutionCalculations.Form = InfantryResolutions.Calculate - def Max: ResolutionCalculations.Form = MaxResolutions.Calculate - def Vehicle: ResolutionCalculations.Form = VehicleResolutions.Calculate - def Aircraft: ResolutionCalculations.Form = VehicleResolutions.Calculate - def SimpleDeployables: ResolutionCalculations.Form = SimpleResolutions.Calculate - def ComplexDeployables: ResolutionCalculations.Form = ComplexDeployableResolutions.Calculate - def FacilityTurrets: ResolutionCalculations.Form = SimpleResolutions.Calculate - def Amenities: ResolutionCalculations.Form = SimpleResolutions.Calculate -} diff --git a/src/main/scala/net/psforever/objects/vital/Vitality.scala b/src/main/scala/net/psforever/objects/vital/Vitality.scala index 1721ab95b..c31aa88db 100644 --- a/src/main/scala/net/psforever/objects/vital/Vitality.scala +++ b/src/main/scala/net/psforever/objects/vital/Vitality.scala @@ -1,8 +1,8 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.vital -import net.psforever.objects.ballistics.ResolvedProjectile -import net.psforever.objects.vital.resolution.ResolutionCalculations +import net.psforever.objects.vital.resolution.{DamageAndResistance, ResolutionCalculations} +import net.psforever.objects.vital.interaction.DamageResult /** * A vital object can be hurt or damaged or healed or repaired (HDHR). @@ -40,7 +40,7 @@ trait Vitality extends VitalsHistory { Definition.Repairable && Health < MaxHealth && (Health > 0 || Definition.RepairIfDestroyed) } - def DamageModel: DamageResistanceModel + def DamageModel: DamageAndResistance def Definition: VitalityDefinition } @@ -61,5 +61,5 @@ object Vitality { * Report that a vitals object must be updated due to damage. * @param obj the vital object */ - final case class DamageResolution(obj: Vitality, cause: ResolvedProjectile) + final case class DamageResolution(obj: Vitality, cause: DamageResult) } diff --git a/src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala b/src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala index fa6b45132..1f13f2fab 100644 --- a/src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala +++ b/src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala @@ -1,6 +1,8 @@ //Copyright (c) 2020 PSForever package net.psforever.objects.vital -import net.psforever.objects.vital.damage.DamageModifiers + +import net.psforever.objects.vital.base.DamageModifiers +import net.psforever.objects.vital.prop.DamageWithPosition /** * na
@@ -148,4 +150,22 @@ trait VitalityDefinition extends DamageModifiers { repairMod = mod RepairMod } + + /** + * Characteristics of the objects dealing area of effect damage + * under guidance of the server motivated by client actions. + * Although "vitality" has nothing to do with explosions directly, + * exploding objects tend to be entities with `Vitality` (lowest common denominator inheritance). + */ + var explodes: Boolean = false + + /** + * damage that is inherent to the object, used for explosions and collisions, mainly + */ + var innateDamage: Option[DamageWithPosition] = None + + def innateDamage_=(combustion: DamageWithPosition): Option[DamageWithPosition] = { + innateDamage = Some(combustion) + innateDamage + } } diff --git a/src/main/scala/net/psforever/objects/vital/VitalsHistory.scala b/src/main/scala/net/psforever/objects/vital/VitalsHistory.scala index c5e010c63..d1ec63caa 100644 --- a/src/main/scala/net/psforever/objects/vital/VitalsHistory.scala +++ b/src/main/scala/net/psforever/objects/vital/VitalsHistory.scala @@ -1,15 +1,17 @@ // Copyright (c) 2020 PSForever package net.psforever.objects.vital -import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry, VehicleSource} +import net.psforever.objects.ballistics._ import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition, ObjectDefinition} -import net.psforever.objects.serverobject.painbox.Painbox import net.psforever.objects.serverobject.terminals.TerminalDefinition +import net.psforever.objects.vital.etc.{ExplodingEntityReason, PainboxReason} +import net.psforever.objects.vital.interaction.DamageResult +import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.types.{ExoSuitType, ImplantType} abstract class VitalsActivity(target: SourceEntry) { def Target: SourceEntry = target - val t: Long = System.nanoTime //??? + val t: Long = System.currentTimeMillis() //??? def time: Long = t } @@ -50,14 +52,16 @@ final case class RepairFromTerm(target: VehicleSource, amount: Int, term_def: Te final case class VehicleShieldCharge(target: VehicleSource, amount: Int) extends HealingActivity(target) //TODO facility -final case class DamageFromProjectile(data: ResolvedProjectile) extends DamagingActivity(data.target) +final case class DamageFromProjectile(data: DamageResult) extends DamagingActivity(data.targetBefore) -final case class DamageFromPainbox(target: PlayerSource, painbox: Painbox, damage: Int) extends DamagingActivity(target) +final case class DamageFromPainbox(data: DamageResult) extends DamagingActivity(data.targetBefore) final case class PlayerSuicide(target: PlayerSource) extends DamagingActivity(target) final case class DamageFromExplosion(target: PlayerSource, cause: ObjectDefinition) extends DamagingActivity(target) +final case class DamageFromExplodingEntity(data: DamageResult) extends DamagingActivity(data.targetBefore) + /** * A vital object can be hurt or damaged or healed or repaired (HDHR). * A history of the previous changes in vital statistics of the underlying object is recorded @@ -68,6 +72,8 @@ trait VitalsHistory { /** a reverse-order list of chronological events that have occurred to these vital statistics */ private var vitalsHistory: List[VitalsActivity] = List.empty[VitalsActivity] + private var lastDamage: Option[DamageResult] = None + def History: List[VitalsActivity] = vitalsHistory /** @@ -94,20 +100,32 @@ trait VitalsHistory { } /** - * Very common example of a `VitalsActivity` event involving weapon discharge. - * @param projectile the fully-informed entry of discharge of a weapon + * Very common example of a `VitalsActivity` event involving damage. + * @param result the fully-informed entry * @return the list of previous changes to this object's vital statistics */ - def History(projectile: ResolvedProjectile): List[VitalsActivity] = { - vitalsHistory = DamageFromProjectile(projectile) +: vitalsHistory + def History(result: DamageResult): List[VitalsActivity] = { + result.interaction.cause match { + case _: ProjectileReason => + vitalsHistory = DamageFromProjectile(result) +: vitalsHistory + lastDamage = Some(result) + case _: ExplodingEntityReason => + vitalsHistory = DamageFromExplodingEntity(result) +: vitalsHistory + lastDamage = Some(result) + case _: PainboxReason => + vitalsHistory = DamageFromPainbox(result) +: vitalsHistory + case _ => ; + } vitalsHistory } + def LastDamage: Option[DamageResult] = lastDamage + /** * Find, specifically, the last instance of a weapon discharge vital statistics change. * @return information about the discharge */ - def LastShot: Option[ResolvedProjectile] = { + def LastShot: Option[DamageResult] = { vitalsHistory.find({ p => p.isInstanceOf[DamageFromProjectile] }) match { case Some(entry: DamageFromProjectile) => Some(entry.data) diff --git a/src/main/scala/net/psforever/objects/vital/base/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/base/DamageModifiers.scala new file mode 100644 index 000000000..6977cd10f --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/base/DamageModifiers.scala @@ -0,0 +1,41 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.base + +import net.psforever.objects.vital.interaction.DamageInteraction + +/** + * Adjustments performed on the subsequent manipulations of the "base damage" value of an attack vector + * (like a projectile).
+ *
+ * Unlike static damage modifications which are structured like other `DamageProfiles` + * and offer purely additive or subtractive effects on the base damage, + * these modifiers should focus on unstructured, scaled manipulation of the value. + * The most common modifiers change the damage value based on distance between two points, called "degrading". + * The list of modifiers must be allocated in a single attempt, overriding previously-set modifiers. + * @see `DamageCalculations.WithModifiers` + * @see `DamageModifiers.Mod` + * @see `DamageProfile` + */ +trait DamageModifiers { + private var mods: List[DamageModifiers.Mod] = Nil + + def Modifiers: List[DamageModifiers.Mod] = mods + + def Modifiers_=(modifier: DamageModifiers.Mod): List[DamageModifiers.Mod] = Modifiers_=(List(modifier)) + + def Modifiers_=(modifiers: List[DamageModifiers.Mod]): List[DamageModifiers.Mod] = { + mods = modifiers + Modifiers + } +} + +object DamageModifiers { + trait Mod { + /** Perform the underlying calculations, returning a modified value from the input value. */ + final def calculate(damage : Int, data : DamageInteraction) : Int = { + calculate(damage, data, data.cause) + } + + def calculate(damage : Int, data : DamageInteraction, cause : DamageReason) : Int + } +} diff --git a/src/main/scala/net/psforever/objects/vital/base/DamageReason.scala b/src/main/scala/net/psforever/objects/vital/base/DamageReason.scala new file mode 100644 index 000000000..a854a028f --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/base/DamageReason.scala @@ -0,0 +1,83 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.base + +import net.psforever.objects.ballistics.SourceEntry +import net.psforever.objects.vital.damage.DamageProfile +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.prop.DamageProperties +import net.psforever.objects.vital.resolution.{DamageAndResistance, ResolutionCalculations} + +/** + * A wrapper for ambiguity of the "damage source" in damage calculations. + * The base reason does not convey any specific requirements in regards to the interaction being described. + */ +trait DamageReason { + /** + * An indication about how the damage was or will be processed. + */ + def resolution: DamageResolution.Value + + /** + * A direct connection to the damage information, numbers and properties. + */ + def source: DamageProperties + + /** + * Determine whether two damage sources are equivalent. + * @param test the damage source to compare against + * @return `true`, if equivalent; + * `false`, otherwise + */ + def same(test: DamageReason): Boolean + + /** + * The functionality that is necessary for interaction of a vital game object with the rest of the hostile game world. + */ + def damageModel: DamageAndResistance + + /** + * Modifiers to the raw/modified damage value that are additive in nature. + * These modifiers use a selector function to extract the damage value from the profile, + * a process required to acquire the raw damage value, outlined elsewhere. + * @return a list of modifications to apply (in order) + */ + def staticModifiers: List[DamageProfile] = Nil + + /** + * Modifiers to the raw/modified damage value that are multiplicative or provide disjoint modification. + * @return a list of modifications to apply (in order) + */ + def unstructuredModifiers: List[DamageModifiers.Mod] = Nil + + /** + * The person to be blamed for this. + */ + def adversary: Option[SourceEntry] + + /** + * Specifics about the method of damage, expected as an object class's unique identifier. + * @return defaults to 0 + */ + def attribution: Int = 0 + + /** + * Perform the modified damage value and the basic resistance value allocations + * to be used against a given valid target. + * @param data the damaging interaction to be evaluated + * @return an application function that takes a target and returns a result + */ + def calculate(data: DamageInteraction): ResolutionCalculations.Output = { + damageModel.calculate(data) + } + + /** + * Perform the modified damage value and the basic resistance value allocations + * to be used against a given valid target. + * @param data the damaging interaction to be evaluated + * @param dtype custom damage property for resistance allocation + * @return an application function that takes a target and returns a result + */ + def calculate(data: DamageInteraction, dtype: DamageType.Value): ResolutionCalculations.Output = { + damageModel.calculate(data, dtype) + } +} diff --git a/src/main/scala/net/psforever/objects/ballistics/ProjectileResolution.scala b/src/main/scala/net/psforever/objects/vital/base/DamageResolution.scala similarity index 63% rename from src/main/scala/net/psforever/objects/ballistics/ProjectileResolution.scala rename to src/main/scala/net/psforever/objects/vital/base/DamageResolution.scala index 09b2e3b31..921607821 100644 --- a/src/main/scala/net/psforever/objects/ballistics/ProjectileResolution.scala +++ b/src/main/scala/net/psforever/objects/vital/base/DamageResolution.scala @@ -1,12 +1,11 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.ballistics +package net.psforever.objects.vital.base /** - * An `Enumeration` of outcomes regarding what actually happened to the projectile, + * An `Enumeration` of outcomes regarding what actually happened to the damage, * complementing normal damage type distinction in directing damage calculations.
*
- * 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. + * Although some of the earlier states reflect what sort of damage might perform - `Hit`, `Splash`, etc. - + * this 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`. @@ -15,19 +14,20 @@ package net.psforever.objects.ballistics * or, perform `Aggravated` damage, are reported by `SplashHitMessage` packets * and resolve either as `AggravatedDirect` or as `AggravatedSplash`. */ -object ProjectileResolution extends Enumeration { +object DamageResolution 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 + Missed, //did not interact with anything and was neutralized + Resolved, //a general "interacted with something" status, begging for 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 + AggravatedSplashBurn, //continuous splashed aggravated damage + Explosion //area of effect damage caused by an internal mechanism; unrelated to Splash = Value } diff --git a/src/main/scala/net/psforever/objects/vital/base/DamageType.scala b/src/main/scala/net/psforever/objects/vital/base/DamageType.scala new file mode 100644 index 000000000..74a1a8a9f --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/base/DamageType.scala @@ -0,0 +1,14 @@ +package net.psforever.objects.vital.base + +/** + * An `Enumeration` of the damage types + * not only distinguishing damage being inflicted + * but, more importantly, what kind of resistance is brought to bare against that damage. + * For additional types exclusive to aggravation, refer to `Aura`. + */ +object DamageType extends Enumeration(1) { + type Type = Value + + //"one" (numerical 1 in the ADB) corresponds to objects that explode + final val Direct, Splash, Lash, Radiation, Aggravated, One, None = Value +} diff --git a/src/main/scala/net/psforever/objects/vital/collision/CollisionReason.scala b/src/main/scala/net/psforever/objects/vital/collision/CollisionReason.scala new file mode 100644 index 000000000..e24fd14ec --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/collision/CollisionReason.scala @@ -0,0 +1,38 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.collision + +import net.psforever.objects.ballistics.SourceEntry +import net.psforever.objects.vital.base.{DamageReason, DamageResolution} +import net.psforever.objects.vital.prop.DamageProperties +import net.psforever.objects.vital.resolution.DamageAndResistance + +/** + * A wrapper for a "damage source" in damage calculations + * that parameterizes information necessary to explain a collision. + * Being "adversarial" requires that the damage be performed as an aggressive action between individuals. + * @param source na + */ +final case class AdversarialCollisionReason(source: DamageProperties) extends DamageReason { + def resolution: DamageResolution.Value = DamageResolution.Unresolved + + def same(test: DamageReason): Boolean = false + + def damageModel: DamageAndResistance = null + + override def adversary : Option[SourceEntry] = None +} + +/** + * A wrapper for a "damage source" in damage calculations + * that parameterizes information necessary to explain a collision. + * @param source na + */ +final case class CollisionReason(source: DamageProperties) extends DamageReason { + def resolution: DamageResolution.Value = DamageResolution.Unresolved + + def same(test: DamageReason): Boolean = false + + def damageModel: DamageAndResistance = null + + override def adversary : Option[SourceEntry] = None +} diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala index 8d22383ae..3e090838a 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.vital.damage -import net.psforever.objects.ballistics.ResolvedProjectile +import net.psforever.objects.vital.interaction.DamageInteraction /** * A series of methods for extraction of the base damage against a given target type @@ -11,41 +11,48 @@ object DamageCalculations { type Selector = DamageProfile => Int //raw damage selectors - def AgainstNothing(profile: DamageProfile): Int = 0 + def AgainstNothing(profile : DamageProfile) : Int = 0 - def AgainstExoSuit(profile: DamageProfile): Int = profile.Damage0 + def AgainstExoSuit(profile : DamageProfile) : Int = profile.Damage0 - def AgainstVehicle(profile: DamageProfile): Int = profile.Damage1 + def AgainstVehicle(profile : DamageProfile) : Int = profile.Damage1 - def AgainstAircraft(profile: DamageProfile): Int = profile.Damage2 + def AgainstAircraft(profile : DamageProfile) : Int = profile.Damage2 - def AgainstMaxSuit(profile: DamageProfile): Int = profile.Damage3 + def AgainstMaxSuit(profile : DamageProfile) : Int = profile.Damage3 - def AgainstBFR(profile: DamageProfile): Int = profile.Damage4 + def AgainstBFR(profile : DamageProfile) : Int = profile.Damage4 /** - * Get damage information from a series of profiles related to the weapon discharge. + * Get the damage value. * @param selector the function that recovers the damage value - * @param data na - * @return the accumulated damage value + * @param data na + * @return the raw damage value */ - def DamageWithModifiers(selector: DamageProfile => Int, data: ResolvedProjectile): Int = { - val projectile = data.projectile - val profile = projectile.profile - val fireMode = projectile.fire_mode - //static (additive and subtractive) modifiers - val staticModifiers = if (profile.UseDamage1Subtract) { - List(fireMode.Add, data.target.Modifiers.Subtract) - } else { - List(fireMode.Add) - } + def Raw(selector: DamageProfile => Int, data: DamageInteraction) : Int = { + selector(data.cause.source) + } + + /** + * Get the damage value after it has been modified by context-related operations. + * Used as the default modifier function for `DamageResistanceCalculations`. + * @param selector the function that recovers the damage value + * @param data the interaction being processed + * @return the accumulated damage value + */ + def WithModifiers(selector: DamageProfile => Int, data: DamageInteraction) : Int = { + val cause = data.cause + val source = cause.source + val target = data.target //base damage + static modifiers - var damage = selector(profile) + staticModifiers.foldLeft(0)(_ + selector(_)) - //unstructured modifiers (the order is intentional, however) - (fireMode.Modifiers ++ - profile.Modifiers ++ - data.target.Definition.Modifiers) - .foreach { mod => damage = mod.Calculate(damage, data) } + val staticModifiers = cause.staticModifiers ++ + (if (source.UseDamage1Subtract) List(target.Modifiers.Subtract) else Nil) + //unstructured modifiers (their ordering is intentional) + val unstructuredModifiers = cause.unstructuredModifiers ++ + source.Modifiers ++ target.Definition.Modifiers + //apply + var damage = selector(source) + staticModifiers.foldLeft(0)(_ + selector(_)) + unstructuredModifiers.foreach { mod => damage = mod.calculate(damage, data) } damage } } diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifierFunctions.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifierFunctions.scala new file mode 100644 index 000000000..0983a016f --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageModifierFunctions.scala @@ -0,0 +1,72 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.damage + +import net.psforever.objects.GlobalDefinitions +import net.psforever.objects.ballistics._ +import net.psforever.objects.vital.base.{DamageModifiers, DamageReason} +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.prop.DamageWithPosition +import net.psforever.types.Vector3 + +/** The input value is the same as the output value. */ +case object SameHit extends DamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: DamageReason): Int = damage +} + +/** + * The input value degrades (lessens) + * the further the distance between the point of origin (target position) + * and the point of encounter (`hitPos`) of its vector. + * If the value is encountered beyond its maximum radial distance, the value is zero'd. + */ +case object RadialDegrade extends DamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: DamageReason): Int = + DamageModifierFunctions.radialDegradeFunction(damage, data, cause) +} + +/** + * The input value degrades (lessens) + * to the percentage of its original value + * if the target is a vehicle with no shields. + * Specifically used for the `galaxy_gunship`. + */ +final case class GalaxyGunshipReduction(multiplier: Float) extends DamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: DamageReason): Int = { + data.target match { + case v: VehicleSource + if v.Definition == GlobalDefinitions.galaxy_gunship && v.Shields == 0 => + (damage * multiplier).toInt + case _ => + damage + } + } +} + +object DamageModifierFunctions { + /** + * The input value degrades (lessens) + * the further the distance between the point of origin (target position) + * and the point of encounter (`hitPos`) of its vector (projectile). + * If the value is encountered beyond its maximum radial distance, the value is zero'd. + */ + def radialDegradeFunction(damage: Int, data: DamageInteraction, cause: DamageReason): Int = { + cause.source match { + case withPosition: DamageWithPosition => + val distance = Vector3.Distance(data.hitPos, data.target.Position) + val radius = withPosition.DamageRadius + val radiusMin = withPosition.DamageRadiusMin + if (distance <= radiusMin) { + damage + } else if (distance <= radius) { + //damage - (damage * profile.DamageAtEdge * (distance - radiusMin) / (radius - radiusMin)).toInt + val base = withPosition.DamageAtEdge + val radi = radius - radiusMin + (damage * ((1 - base) * ((radi - (distance - radiusMin)) / radi) + base)).toInt + } else { + 0 + } + case _ => + damage + } + } +} diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala deleted file mode 100644 index d9a63022c..000000000 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageModifiers.scala +++ /dev/null @@ -1,545 +0,0 @@ -// Copyright (c) 2020 PSForever -package net.psforever.objects.vital.damage - -import net.psforever.objects.GlobalDefinitions -import net.psforever.objects.ballistics._ -import net.psforever.objects.equipment.ChargeFireModeDefinition -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 - * (like a projectile).
- *
- * Unlike static damage modifications which are structured like other `DamageProfiles` - * and offer purely additive or subtractive effects on the base damage, - * these modifiers should focus on unstructured, scaled manipulation of the value. - * The most common modifiers change the damage value based on distance between two points, called "degrading". - * The list of modifiers must be allocated in a single attempt, overriding previously-set modifiers. - * @see `DamageCalculations.DamageWithModifiers` - * @see `DamageProfile` - * @see `ResolvedProjectile` - */ -trait DamageModifiers { - private var mods: List[DamageModifiers.Mod] = Nil - - def Modifiers: List[DamageModifiers.Mod] = mods - - def Modifiers_=(modifier: DamageModifiers.Mod): List[DamageModifiers.Mod] = Modifiers_=(List(modifier)) - - def Modifiers_=(modifiers: List[DamageModifiers.Mod]): List[DamageModifiers.Mod] = { - mods = modifiers - Modifiers - } -} - -object DamageModifiers { - type Format = (Int, ResolvedProjectile) => Int - - trait Mod { - /** Perform the underlying calculations, returning a modified value from the input value. */ - def Calculate: DamageModifiers.Format - } - - /** The input value is the same as the output value. */ - case object SameHit extends Mod { - def Calculate: DamageModifiers.Format = function - - private def function(damage: Int, data: ResolvedProjectile): Int = damage - } - - /** If the calculated distance is greater than the maximum distance of the projectile, damage is zero'd. */ - 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 - } - } - } - - /** If the calculated distance is greater than a custom distance, damage is zero'd. */ - case class CustomDistanceCutoff(cutoff: Float) extends Mod { - def Calculate: DamageModifiers.Format = function - - private def function(damage: Int, data: ResolvedProjectile): Int = { - val projectile = data.projectile - val distance = Vector3.Distance(data.hit_pos, projectile.shot_origin) - if (distance <= cutoff) { - damage - } else { - 0 - } - } - } - - /** - * The input value degrades (lessens) - * the further the distance between the point of origin (`shot_origin`) - * and the point of encounter (`hit_pos`) of its vector (projectile). - * If the value is not set to degrade over any distance within its maximum distance, the value goes unmodified. - * If the value is encountered beyond its maximum distance, the value is zero'd. - */ - case object DistanceDegrade extends Mod { - def Calculate: DamageModifiers.Format = distanceDegradeFunction - } - - /** - * The input value degrades (lessens) - * the further the distance between the point of origin (target position) - * and the point of encounter (`hit_pos`) of its vector (projectile). - * If the value is encountered beyond its maximum radial distance, the value is zero'd. - */ - case object RadialDegrade extends Mod { - def Calculate: DamageModifiers.Format = radialDegradeFunction - } - - /** - * Lashing is the property of a projectile affecting nearby targets without coming into direct contact with them. - * The effect only activates after 5m from the point of origin (`shot_origin`) before the maximum distance. - * If lashing does not apply, the value goes unmodified. - * If lashing is valid but the value is encountered beyond its maximum radial distance, the value is zero'd. - */ - case object Lash extends Mod { - def Calculate: DamageModifiers.Format = function - - private def function(damage: Int, data: ResolvedProjectile): Int = { - if (data.resolution == ProjectileResolution.Lash) { - val distance = Vector3.Distance(data.hit_pos, data.projectile.shot_origin) - if (distance > 5 && distance <= data.projectile.profile.DistanceMax) { - (damage * 0.2f) toInt - } else { - 0 - } - } else { - damage - } - } - } - - /* - 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 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 - } - } - } - - /** - * If the projectile has charging properties, - * and the weapon that produced the projectile has charging mechanics, - * calculate the current value of the damage as a sum - * of some minimum damage and scaled normal damage. - * The projectile quality has information about the "factor" of damage scaling. - * @see `ChargeDamage` - * @see `ChargeFireModeDefinition` - * @see `ProjectileQuality` - * @see `ResolvedProjectile` - */ - case object SpikerChargeDamage extends Mod { - def Calculate: DamageModifiers.Format = formula - - private def formula(damage: Int, data: ResolvedProjectile): Int = { - val projectile = data.projectile - (projectile.fire_mode, projectile.profile.Charging) match { - case (_: ChargeFireModeDefinition, Some(info: ChargeDamage)) => - val chargeQuality = math.max(0f, math.min(projectile.quality.mod, 1f)) - data.damage_model.DamageUsing(info.min) + (damage * chargeQuality).toInt - case _ => - damage - } - } - } - - /** - * If the damage is resolved through a `HitDamage` packet, - * calculate the damage as a function of its degrading value over distance traveled by its carrier projectile. - * @see `distanceDegradeFunction` - * @see `ProjectileQuality` - * @see `ResolvedProjectile` - */ - case object FlakHit extends Mod { - def Calculate: DamageModifiers.Format = formula - - private def formula(damage: Int, data: ResolvedProjectile): Int = { - if(data.resolution == ProjectileResolution.Hit) { - distanceDegradeFunction(damage, data) - } else { - damage - } - } - } - - /** - * If the damage is resolved through a `SplashHitDamage` packet, - * calculate the damage as a function of its degrading value over distance - * between the hit position of the projectile and the position of the target. - * @see `radialDegradeFunction` - * @see `ProjectileQuality` - * @see `ResolvedProjectile` - */ - case object FlakBurst extends Mod { - def Calculate: DamageModifiers.Format = formula - - private def formula(damage: Int, data: ResolvedProjectile): Int = { - if(data.resolution == ProjectileResolution.Splash) { - radialDegradeFunction(damage, data) - } else { - damage - } - } - } - - /** - * The input value degrades (lessens) - * to the percentage of its original value - * if the target is a vehicle with no shields. - * Mainly used for the `galaxy_gunship` vehicle. - * @see `ResolvedProjectile` - */ - case class GalaxyGunshipReduction(multiplier: Float) extends Mod { - def Calculate: DamageModifiers.Format = formula - - private def formula(damage: Int, data: ResolvedProjectile): Int = { - data.target match { - case v: VehicleSource - if v.Definition == GlobalDefinitions.galaxy_gunship && v.Shields == 0 => - (damage * multiplier).toInt - case _ => - damage - } - } - } - - /* Functions */ - - /** - * The input value degrades (lessens) - * the further the distance between the point of origin (`shot_origin`) - * and the point of encounter (`hit_pos`) of its vector (projectile). - * If the value is not set to degrade over any distance within its maximum distance, the value goes unmodified. - * If the value is encountered beyond its maximum distance, the value is zero'd. - */ - private def distanceDegradeFunction(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) { - if (profile.DistanceNoDegrade == profile.DistanceMax || distance <= profile.DistanceNoDegrade) { - damage - } else { - damage - ((damage - profile.DegradeMultiplier * damage) * ((distance - profile.DistanceNoDegrade) / (profile.DistanceMax - profile.DistanceNoDegrade))).toInt - } - } else { - 0 - } - } - - /** - * The input value degrades (lessens) - * the further the distance between the point of origin (target position) - * and the point of encounter (`hit_pos`) of its vector (projectile). - * If the value is encountered beyond its maximum radial distance, the value is zero'd. - */ - private def radialDegradeFunction(damage: Int, data: ResolvedProjectile): Int = { - val profile = data.projectile.profile - val distance = Vector3.Distance(data.hit_pos, data.target.Position) - val radius = profile.DamageRadius - val radiusMin = profile.DamageRadiusMin - if (distance <= radiusMin) { - damage - } else if (distance <= radius) { - //damage - (damage * profile.DamageAtEdge * (distance - radiusMin) / (radius - radiusMin)).toInt - val base = profile.DamageAtEdge - val radi = radius - radiusMin - (damage * ((1 - base) * ((radi - (distance - radiusMin)) / radi) + base)).toInt - } else { - 0 - } - } - - /** - * 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 - } - } -} diff --git a/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala b/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala index 1b3a353d2..ff0439392 100644 --- a/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala @@ -1,4 +1,4 @@ -// Copyright (c) 2017 PSForever +// Copyright (c) 2020 PSForever package net.psforever.objects.vital.damage /** @@ -19,10 +19,10 @@ trait DamageProfile { /** `damage1` is for armor, amenities, deployables, etc. */ def Damage1_=(damage: Int): Int - /** `damage2` if for aircraft */ + /** `damage2` is for aircraft */ def Damage2: Int - /** `damage2` if for aircraft */ + /** `damage2` is for aircraft */ def Damage2_=(damage: Int): Int /** `damage3` is for mechanized infantry */ diff --git a/src/main/scala/net/psforever/objects/vital/SpecificDamageProfile.scala b/src/main/scala/net/psforever/objects/vital/damage/SpecificDamageProfile.scala similarity index 92% rename from src/main/scala/net/psforever/objects/vital/SpecificDamageProfile.scala rename to src/main/scala/net/psforever/objects/vital/damage/SpecificDamageProfile.scala index 33a4c23f4..a578f9e2a 100644 --- a/src/main/scala/net/psforever/objects/vital/SpecificDamageProfile.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/SpecificDamageProfile.scala @@ -1,8 +1,9 @@ // Copyright (c) 2020 PSForever -package net.psforever.objects.vital - -import net.psforever.objects.vital.damage.DamageProfile +package net.psforever.objects.vital.damage +/** + * A static modifier for damage. + */ class SpecificDamageProfile extends DamageProfile { private var damage0: Int = 0 private var damage1: Int = 0 diff --git a/src/main/scala/net/psforever/objects/vital/StandardDamageProfile.scala b/src/main/scala/net/psforever/objects/vital/damage/StandardDamageProfile.scala similarity index 80% rename from src/main/scala/net/psforever/objects/vital/StandardDamageProfile.scala rename to src/main/scala/net/psforever/objects/vital/damage/StandardDamageProfile.scala index 87b0681fc..86f2ea387 100644 --- a/src/main/scala/net/psforever/objects/vital/StandardDamageProfile.scala +++ b/src/main/scala/net/psforever/objects/vital/damage/StandardDamageProfile.scala @@ -1,10 +1,15 @@ // Copyright (c) 2017 PSForever -package net.psforever.objects.vital - -import net.psforever.objects.vital.damage.DamageProfile +package net.psforever.objects.vital.damage +/** + * A static modifier for damage. + * Typically considered the "raw damage" when a part of the "damage source". + * The value determination cascades down to the first defined one. + * Requesting `Damage3` returns the value for `Damage1` + * if `Damage3 == None` and `Damage2 == None` but `Damage1` is defined. + */ trait StandardDamageProfile extends DamageProfile { - private var damage0: Int = 0 + private var damage0: Int = 0 //always considered as a defined value private var damage1: Option[Int] = None private var damage2: Option[Int] = None private var damage3: Option[Int] = None diff --git a/src/main/scala/net/psforever/objects/vital/etc/ExplodingEntityReason.scala b/src/main/scala/net/psforever/objects/vital/etc/ExplodingEntityReason.scala new file mode 100644 index 000000000..7e652ff13 --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/etc/ExplodingEntityReason.scala @@ -0,0 +1,50 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.etc + +import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.ballistics.SourceEntry +import net.psforever.objects.definition.ObjectDefinition +import net.psforever.objects.vital.{Vitality, VitalityDefinition} +import net.psforever.objects.vital.base.{DamageReason, DamageResolution} +import net.psforever.objects.vital.interaction.DamageResult +import net.psforever.objects.vital.prop.DamageWithPosition +import net.psforever.objects.vital.resolution.DamageAndResistance + +/** + * A wrapper for a "damage source" in damage calculations + * that parameterizes information necessary to explain a server-driven explosion occurring. + * Some game objects cause area-of-effect damage upon being destroyed. + * @see `VitalityDefinition.explodes` + * @see `VitalityDefinition.innateDamage` + * @see `Zone.causesExplosion` + * @param entity the source of the explosive yield + * @param damageModel the model to be utilized in these calculations; + * typically, but not always, defined by the target + * @param instigation what previous event happened, if any, that caused this explosion + */ +final case class ExplodingEntityReason( + entity: PlanetSideGameObject with Vitality, + damageModel: DamageAndResistance, + instigation: Option[DamageResult] + ) extends DamageReason { + private val definition = entity.Definition.asInstanceOf[ObjectDefinition with VitalityDefinition] + assert(definition.explodes && definition.innateDamage.nonEmpty, "causal entity does not explode") + + def source: DamageWithPosition = definition.innateDamage.get + + def resolution: DamageResolution.Value = DamageResolution.Explosion + + def same(test: DamageReason): Boolean = test match { + case eer: ExplodingEntityReason => eer.entity eq entity + case _ => false + } + + /** lay the blame on that which caused this explosion to occur */ + def adversary: Option[SourceEntry] = instigation match { + case Some(prior) => prior.interaction.cause.adversary + case None => None + } + + /** the entity that exploded is the source of the damage */ + override def attribution: Int = definition.ObjectId +} diff --git a/src/main/scala/net/psforever/objects/vital/etc/OtherReasons.scala b/src/main/scala/net/psforever/objects/vital/etc/OtherReasons.scala new file mode 100644 index 000000000..3e5e437e5 --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/etc/OtherReasons.scala @@ -0,0 +1,22 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.etc + +import net.psforever.objects.ballistics.SourceEntry +import net.psforever.objects.vital.base.{DamageReason, DamageResolution} +import net.psforever.objects.vital.prop.DamageProperties +import net.psforever.objects.vital.resolution.DamageAndResistance + +final case class EnvironmentReason(body: Any, source: DamageProperties) extends DamageReason { + def resolution: DamageResolution.Value = DamageResolution.Unresolved + + def same(test: DamageReason): Boolean = { + test match { + case o : EnvironmentReason => body == o.body //TODO eq + case _ => false + } + } + + def adversary: Option[SourceEntry] = None + + def damageModel: DamageAndResistance = null +} diff --git a/src/main/scala/net/psforever/objects/vital/etc/PainboxReason.scala b/src/main/scala/net/psforever/objects/vital/etc/PainboxReason.scala new file mode 100644 index 000000000..3397ce747 --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/etc/PainboxReason.scala @@ -0,0 +1,37 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.etc + +import net.psforever.objects.ballistics.SourceEntry +import net.psforever.objects.serverobject.painbox.Painbox +import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions} +import net.psforever.objects.vital.base.{DamageReason, DamageResolution} +import net.psforever.objects.vital.damage.DamageCalculations +import net.psforever.objects.vital.prop.DamageWithPosition +import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel} + +final case class PainboxReason(entity: Painbox) extends DamageReason { + private val definition = entity.Definition + assert(definition.innateDamage.nonEmpty, "causal entity does not emit pain field") + + def source: DamageWithPosition = definition.innateDamage.get + + def resolution: DamageResolution.Value = DamageResolution.Resolved + + def same(test: DamageReason): Boolean = test match { + case eer: PainboxReason => eer.entity eq entity + case _ => false + } + + def adversary: Option[SourceEntry] = None + + def damageModel : DamageAndResistance = PainboxReason.drm +} + +object PainboxReason { + /** damage0, no resisting, quick and simple */ + val drm = new DamageResistanceModel { + DamageUsing = DamageCalculations.AgainstExoSuit + ResistUsing = NoResistanceSelection + Model = SimpleResolutions.calculate + } +} diff --git a/src/main/scala/net/psforever/objects/vital/etc/SuicideReason.scala b/src/main/scala/net/psforever/objects/vital/etc/SuicideReason.scala new file mode 100644 index 000000000..8dff90bf4 --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/etc/SuicideReason.scala @@ -0,0 +1,62 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.etc + +import net.psforever.objects.ballistics.SourceEntry +import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions} +import net.psforever.objects.vital.base.{DamageReason, DamageResolution} +import net.psforever.objects.vital.damage.DamageCalculations +import net.psforever.objects.vital.prop.DamageProperties +import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel} + +/** + * A wrapper for a "damage source" in damage calculations + * that parameterizes information necessary to explain wanting to kill oneself. + */ +final case class SuicideReason() + extends DamageReason { + /* + In my head canon, there is a neverending termite eating into the Auraxian human empires. + There is no recruitment. + People are barely alive long enough to feel basic needs like hunger or thirst. + All that is still thrives on the zealous fervor to keep the army motivated. + But what do people do if they don't want to fight anymore? + Do they just never come back from being a speck of thought floating in the air + and, in frustration at life, endure in a disembodied, solitary limbo of the nanites? + But that doesn't stop the thoughts, does it? + Never able to go back to Earth; + becoming a lifeform between organic and information, wandering the endless void known as space for eternity; + being unable to die even though they wish for it; + eventually, they stop logging in. + + Anyway, this has nothing to do with that. + Most playes probably just want to jump to the next base over. + */ + def source: DamageProperties = SuicideReason.damageProperties + + def resolution: DamageResolution.Value = DamageResolution.Resolved + + def same(test: DamageReason): Boolean = { + test.source eq source + } + + def adversary: Option[SourceEntry] = None + + def damageModel: DamageAndResistance = SuicideReason.drm +} + +object SuicideReason { + /** one swift blow that guarantees death */ + val damageProperties = new DamageProperties { + Damage0 = 99999 + DamageToHealthOnly = true + DamageToVehicleOnly = true + DamageToBattleframeOnly = true + } + + /** damage0, no resisting, quick and simple */ + val drm = new DamageResistanceModel { + DamageUsing = DamageCalculations.AgainstExoSuit + ResistUsing = NoResistanceSelection + Model = SimpleResolutions.calculate + } +} diff --git a/src/main/scala/net/psforever/objects/vital/interaction/DamageInteraction.scala b/src/main/scala/net/psforever/objects/vital/interaction/DamageInteraction.scala new file mode 100644 index 000000000..a6d908bd4 --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/interaction/DamageInteraction.scala @@ -0,0 +1,100 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.interaction + +import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.ballistics.SourceEntry +import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.vital.base.{DamageReason, DamageResolution, DamageType} +import net.psforever.objects.vital.resolution.{DamageAndResistance, ResolutionCalculations} +import net.psforever.types.Vector3 + +/** + * The recorded encounter of a damage source and a damageable target. + * @param target the original affected target; + * not necessarily the currently affected target + * @param hitPos the coordinate location where the damage was inflicted + * @param cause the method by which the damage was produced + * @param resolution how the damage is being processed + * @param hitTime when the interaction originally occurred; + * defaults to `System.currentTimeMills()` at object creation + */ +final case class DamageInteraction( + target: SourceEntry, + hitPos: Vector3, + cause: DamageReason, + resolution: DamageResolution.Value, + hitTime: Long = System.currentTimeMillis() + ) { + /** + * If the cause of the original interaction can be attributed to some agency. + * @return a connection between offender, victim, and method + */ + def adversarial: Option[Adversarial] = cause.adversary match { + case Some(adversity) => Some(Adversarial(adversity, target, cause.attribution)) + case None => None + } + + /** + * Process the primary parameters from the interaction + * and produce the application function literal that can have a target entity applied to it. + * @return the function that applies changes to a target entity + */ + def calculate(): ResolutionCalculations.Output = cause.calculate(data = this) + + /** + * Process the primary parameters from the interaction + * including a custom category of damage by which to temporarily reframe the interaction + * and produce the application function literal that can have a target entity applied to it. + * @return the function that applies changes to a target entity + */ + def calculate(dtype: DamageType.Value): ResolutionCalculations.Output = cause.calculate(data = this, dtype) + + /** + * Process the primary parameters from the interaction + * in the context a custom damage processing method by which to temporarily reframe the interaction + * and produce a application function literal where the specified target entity can be applied to it. + * @param model the custom processing method + * @param target the target entity + * @return the outcome of the interaction under the given re-framing + */ + def calculate(model: DamageAndResistance)(target: PlanetSideGameObject with FactionAffinity): DamageResult = { + model.calculate(data = this)(target) + } +} + +object DamageInteraction { + /** + * Overloaded constructor for an interaction. + * Shuffle the order of parameters; let time default. + * @param resolution how the damage is being processed + * @param target the original affected target + * @param cause the method by which the damage was produced + * @param hitPos the coordinate location where the damage was inflicted + * @return a `DamageInteraction` object + */ + def apply(resolution: DamageResolution.Value, target: SourceEntry, cause: DamageReason, hitPos: Vector3): DamageInteraction = { + DamageInteraction( + target, + hitPos, + cause, + resolution + ) + } + + /** + * Overloaded constructor for an interaction. + * Use the resolution from the reason for the damage, shuffle the parameters, and let time default. + * @param target the original affected target + * @param cause the method by which the damage was produced + * @param hitPos the coordinate location where the damage was inflicted + * @return a `DamageInteraction` object + */ + def apply(target: SourceEntry, cause: DamageReason, hitPos: Vector3): DamageInteraction = { + DamageInteraction( + target, + hitPos, + cause, + cause.resolution + ) + } +} diff --git a/src/main/scala/net/psforever/objects/vital/interaction/DamageResult.scala b/src/main/scala/net/psforever/objects/vital/interaction/DamageResult.scala new file mode 100644 index 000000000..d1029ec67 --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/interaction/DamageResult.scala @@ -0,0 +1,25 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.interaction + +import net.psforever.objects.ballistics.SourceEntry + +/** + * But one thing's sure. The player is hurt, attacked, and somebody's responsible. + * @param attacker the source of the damage + * @param defender the recipient of the damage + * @param implement how the damage was invoked; + * the object id of the method of punishment, used for reporting + */ +final case class Adversarial(attacker: SourceEntry, defender: SourceEntry, implement: Int) + +/** + * The outcome of the damage interaction, after all the numbers have been processed and properly applied. + */ +final case class DamageResult(targetBefore: SourceEntry, targetAfter: SourceEntry, interaction: DamageInteraction) { + def adversarial: Option[Adversarial] = { + interaction.adversarial match { + case Some(adversarial) => Some(Adversarial(adversarial.attacker, targetAfter, adversarial.implement)) + case None => None + } + } +} diff --git a/src/main/scala/net/psforever/objects/vital/projectile/ProjectileCalculations.scala b/src/main/scala/net/psforever/objects/vital/projectile/ProjectileCalculations.scala deleted file mode 100644 index 7555bf9ec..000000000 --- a/src/main/scala/net/psforever/objects/vital/projectile/ProjectileCalculations.scala +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.vital.projectile - -import net.psforever.objects.ballistics.ResolvedProjectile - -/** - * The base for all projectile-induced damage calculation function literals. - */ -trait ProjectileCalculations { - - /** - * The exposed entry for the calculation function literal defined by this base. - * @param data the historical `ResolvedProjectile` information - * @return the calculated value - */ - def Calculate(data: ResolvedProjectile): Int -} - -object ProjectileCalculations { - type Form = (ResolvedProjectile) => Int -} diff --git a/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifierFunctions.scala b/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifierFunctions.scala new file mode 100644 index 000000000..da2178fb1 --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifierFunctions.scala @@ -0,0 +1,422 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.projectile + +import net.psforever.objects.ballistics.{ChargeDamage, PlayerSource, ProjectileQuality} +import net.psforever.objects.equipment.ChargeFireModeDefinition +import net.psforever.objects.vital.base._ +import net.psforever.objects.vital.damage.DamageModifierFunctions +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.types.{ExoSuitType, Vector3} + +/** If the calculated distance is greater than the maximum distance of the projectile, damage is zero'd. */ +case object MaxDistanceCutoff extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + val projectile = cause.projectile + val profile = projectile.profile + val distance = Vector3.Distance(data.hitPos, projectile.shot_origin) + if (distance <= profile.DistanceMax) { + damage + } else { + 0 + } + } +} + +/** If the calculated distance is greater than a custom distance, damage is zero'd. */ +case class CustomDistanceCutoff(cutoff: Float) extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + val projectile = cause.projectile + val distance = Vector3.Distance(data.hitPos, projectile.shot_origin) + if (distance <= cutoff) { + damage + } else { + 0 + } + } +} + +/** + * The input value degrades (lessens) + * the further the distance between the point of origin (`shot_origin`) + * and the point of encounter (`hitPos`) of its vector (projectile). + * If the value is not set to degrade over any distance within its maximum distance, the value goes unmodified. + * If the value is encountered beyond its maximum distance, the value is zero'd. + */ +case object DistanceDegrade extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = + ProjectileDamageModifierFunctions.distanceDegradeFunction(damage, data, cause) +} + +/** + * Lashing is the property of a projectile affecting nearby targets without coming into direct contact with them. + * The effect only activates after 5m from the point of origin (`shot_origin`) before the maximum distance. + * If lashing does not apply, the value goes unmodified. + * If lashing is valid but the value is encountered beyond its maximum radial distance, the value is zero'd. + */ +case object Lash extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + if (cause.resolution == DamageResolution.Lash) { + val distance = Vector3.Distance(data.hitPos, cause.projectile.shot_origin) + if (distance > 5 && distance <= cause.projectile.profile.DistanceMax) { + (damage * 0.2f) toInt + } else { + 0 + } + } else { + damage + } + } +} + +/* +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 ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = + ProjectileDamageModifierFunctions.baseAggravatedFormula(DamageResolution.AggravatedDirect, DamageType.Direct)(damage, data, cause) +} + +/** + * The initial application of aggravated damage against an infantry target + * where the specific damage component is `Splash`. + */ +case object InfantryAggravatedSplash extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = + ProjectileDamageModifierFunctions.baseAggravatedFormula(DamageResolution.AggravatedSplash, DamageType.Splash)(damage, data, cause) +} + +/** + * 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 ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = + ProjectileDamageModifierFunctions.baseAggravatedBurnFormula(DamageResolution.AggravatedDirectBurn, DamageType.Direct)(damage, data, cause) +} + +/** + * 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 ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = + ProjectileDamageModifierFunctions.baseAggravatedBurnFormula(DamageResolution.AggravatedSplashBurn, DamageType.Splash)(damage, data, cause) +} + +/** + * For damage application that involves aggravation of a fireball (Dragon secondary fire mode), + * perform 1 damage. + */ +case object FireballAggravatedBurn extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + if (damage > 0 && + (cause.resolution == DamageResolution.AggravatedDirectBurn || + cause.resolution == DamageResolution.AggravatedSplashBurn)) { + //add resist to offset resist subtraction later + 1 + cause.damageModel.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` + */ +case object StarfireAggravated extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + if (cause.resolution == DamageResolution.AggravatedDirect && + cause.projectile.quality == ProjectileQuality.AggravatesTarget) { + data.cause.source.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` + */ +case object StarfireAggravatedBurn extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + if (cause.resolution == DamageResolution.AggravatedDirectBurn) { + data.cause.source.Aggravated match { + case Some(aggravation) => + aggravation.info.find(_.damage_type == DamageType.Direct) match { + case Some(infos) => + (math.floor(damage * infos.degradation_percentage) * cause.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` + */ +case object CometAggravated extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + if (cause.resolution == DamageResolution.AggravatedDirect && + cause.projectile.quality == ProjectileQuality.AggravatesTarget) { + data.cause.source.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` + */ +case object CometAggravatedBurn extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + if (cause.resolution == DamageResolution.AggravatedDirectBurn) { + data.cause.source.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 + } + } +} + +/** + * If the projectile has charging properties, + * and the weapon that produced the projectile has charging mechanics, + * calculate the current value of the damage as a sum + * of some minimum damage and scaled normal damage. + * The projectile quality has information about the "factor" of damage scaling. + * @see `ChargeDamage` + * @see `ChargeFireModeDefinition` + * @see `ProjectileQuality` + */ +case object SpikerChargeDamage extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + val projectile = cause.projectile + (projectile.fire_mode, projectile.profile.Charging) match { + case (_: ChargeFireModeDefinition, Some(info: ChargeDamage)) => + val chargeQuality = math.max(0f, math.min(projectile.quality.mod, 1f)) + cause.damageModel.DamageUsing(info.min) + (damage * chargeQuality).toInt + case _ => + damage + } + } +} + +/** + * If the damage is resolved through a `HitDamage` packet, + * calculate the damage as a function of its degrading value over distance traveled by its carrier projectile. + * @see `distanceDegradeFunction` + * @see `ProjectileQuality` + */ +case object FlakHit extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + if(cause.resolution == DamageResolution.Hit) { + ProjectileDamageModifierFunctions.distanceDegradeFunction(damage, data, cause) + } else { + damage + } + } +} + +/** + * If the damage is resolved through a `SplashHitDamage` packet, + * calculate the damage as a function of its degrading value over distance + * between the hit position of the projectile and the position of the target. + * @see `DamageModifierFunctions.radialDegradeFunction` + * @see `ProjectileQuality` + */ +case object FlakBurst extends ProjectileDamageModifiers.Mod { + def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + if(cause.resolution == DamageResolution.Splash) { + DamageModifierFunctions.radialDegradeFunction(damage, data, cause) + } else { + damage + } + } +} + +/* Functions */ +object ProjectileDamageModifierFunctions { + /** + * The input value degrades (lessens) + * the further the distance between the point of origin (`shot_origin`) + * and the point of encounter (`hitPos`) of its vector (projectile). + * If the value is not set to degrade over any distance within its maximum distance, the value goes unmodified. + * If the value is encountered beyond its maximum distance, the value is zero'd. + */ + def distanceDegradeFunction(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = { + val projectile = cause.projectile + val profile = projectile.profile + val distance = Vector3.Distance(data.hitPos, projectile.shot_origin) + if (distance <= profile.DistanceMax) { + if (profile.DistanceNoDegrade == profile.DistanceMax || distance <= profile.DistanceNoDegrade) { + damage + } else { + damage - ((damage - profile.DegradeMultiplier * damage) * ((distance - profile.DistanceNoDegrade) / (profile.DistanceMax - profile.DistanceNoDegrade))).toInt + } + } else { + 0 + } + } + + /** + * 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` + * @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 + */ + def baseAggravatedFormula( + resolution: DamageResolution.Value, + damageType : DamageType.Value + ) + ( + damage: Int, + data: DamageInteraction, + cause: ProjectileReason + ): Int = { + if (cause.resolution == resolution && + cause.projectile.quality == ProjectileQuality.AggravatesTarget) { + (data.cause.source.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` + * @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 + */ + def baseAggravatedBurnFormula( + resolution: DamageResolution.Value, + damageType : DamageType.Value + ) + ( + damage: Int, + data: DamageInteraction, + cause: ProjectileReason + ): Int = { + if (cause.resolution == resolution) { + (data.cause.source.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 = cause.damageModel.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 + } + } +} diff --git a/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifiers.scala b/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifiers.scala new file mode 100644 index 000000000..38df038f7 --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifiers.scala @@ -0,0 +1,18 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.projectile + +import net.psforever.objects.vital.base._ +import net.psforever.objects.vital.interaction.DamageInteraction + +object ProjectileDamageModifiers { + trait Mod extends DamageModifiers.Mod { + def calculate(damage : Int, data : DamageInteraction, cause : DamageReason) : Int = { + cause match { + case o : ProjectileReason => calculate(damage, data, o) + case _ => damage + } + } + + def calculate(damage : Int, data : DamageInteraction, cause : ProjectileReason) : Int + } +} diff --git a/src/main/scala/net/psforever/objects/vital/projectile/ProjectileReason.scala b/src/main/scala/net/psforever/objects/vital/projectile/ProjectileReason.scala new file mode 100644 index 000000000..d125b15d4 --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/projectile/ProjectileReason.scala @@ -0,0 +1,39 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.projectile + +import net.psforever.objects.ballistics.{SourceEntry, Projectile => ActualProjectile} +import net.psforever.objects.vital.base._ +import net.psforever.objects.vital.damage.DamageProfile +import net.psforever.objects.vital.prop.DamageProperties +import net.psforever.objects.vital.resolution.DamageAndResistance + +/** + * A wrapper for a "damage source" in damage calculations + * that parameterizes information necessary to explain a projectile being used. + * @param resolution how the damage is processed + * @param projectile the projectile that caused the damage + * @param damageModel the model to be utilized in these calculations; + * typically, but not always, defined by the target + */ +final case class ProjectileReason( + resolution : DamageResolution.Value, + projectile: ActualProjectile, + damageModel: DamageAndResistance + ) extends DamageReason { + def source: DamageProperties = projectile.profile + + def same(test: DamageReason): Boolean = { + test match { + case o: ProjectileReason => o.projectile.id == projectile.id //can only be another projectile with the same uid + case _ => false + } + } + + override def staticModifiers: List[DamageProfile] = List(projectile.fire_mode.Add) + + override def unstructuredModifiers: List[DamageModifiers.Mod] = projectile.fire_mode.Modifiers + + def adversary: Option[SourceEntry] = Some(projectile.owner) + + override def attribution: Int = projectile.attribute_to +} diff --git a/src/main/scala/net/psforever/objects/vital/prop/DamageProperties.scala b/src/main/scala/net/psforever/objects/vital/prop/DamageProperties.scala new file mode 100644 index 000000000..3d513ba8d --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/prop/DamageProperties.scala @@ -0,0 +1,130 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.prop + +import net.psforever.objects.ballistics.{AggravatedDamage, ChargeDamage} +import net.psforever.objects.equipment.JammingUnit +import net.psforever.objects.vital.base.{DamageModifiers, DamageType} +import net.psforever.objects.vital.damage.StandardDamageProfile + +/** + * Information that explains aspects of the the damage being performed that go beyond simple numbers. + * Activation of these "special effects" may or may not even require the damage to be countable + * which is the context in which it is formally normalized. + */ +trait DamageProperties + extends StandardDamageProfile + with JammingUnit + with DamageModifiers { + /** the type of damage cuased */ + private var damageType: DamageType.Value = DamageType.None + /** an auxiliary type of damage caused */ + private var damageTypeSecondary: DamageType.Value = DamageType.None + /** against Infantry targets, damage does not apply to armor damage */ + private var damageToHealthOnly: Boolean = false + /** against Vehicle targets, damage does not apply to vehicle shield */ + private var damageToVehicleOnly: Boolean = false + /** against battleframe targets, damage does not apply to battleframe robotics shield; + * this is equivalent to the property "bfr_permeate_shield" */ + private var damageToBFROnly: Boolean = false + /** use a specific modifier as a part of damage calculations */ + private var useDamage1Subtract: Boolean = false + /** some other entity confers 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 + /** na; + * currently used with jammer properties only */ + private var additionalEffect: Boolean = false + /** confers aggravated damage burn to its target */ + private var aggravatedDamage: Option[AggravatedDamage] = None + /** modifies based on some measure of time */ + private var charging: Option[ChargeDamage] = None + /** a destroyed mine will detonate rather than fizzle-out */ + private var sympathy: Boolean = false + + def UseDamage1Subtract: Boolean = useDamage1Subtract + + def UseDamage1Subtract_=(useDamage1Subtract: Boolean): Boolean = { + this.useDamage1Subtract = useDamage1Subtract + UseDamage1Subtract + } + + def CausesDamageType: DamageType.Value = damageType + + def CausesDamageType_=(damageType1: DamageType.Value): DamageType.Value = { + damageType = damageType1 + CausesDamageType + } + + def CausesDamageTypeSecondary: DamageType.Value = damageTypeSecondary + + def CausesDamageTypeSecondary_=(damageTypeSecondary1: DamageType.Value): DamageType.Value = { + damageTypeSecondary = damageTypeSecondary1 + CausesDamageTypeSecondary + } + + def AllDamageTypes : Set[DamageType.Value] = { + Set(damageType, damageTypeSecondary).filterNot(_ == DamageType.None) + } + + def DamageToHealthOnly : Boolean = damageToHealthOnly + + def DamageToHealthOnly_=(healthOnly: Boolean) : Boolean = { + damageToHealthOnly = healthOnly + DamageToHealthOnly + } + + def DamageToVehicleOnly : Boolean = damageToVehicleOnly + + def DamageToVehicleOnly_=(vehicleOnly: Boolean) : Boolean = { + damageToVehicleOnly = vehicleOnly + DamageToVehicleOnly + } + + def DamageToBattleframeOnly : Boolean = damageToBFROnly + + def DamageToBattleframeOnly_=(bfrOnly: Boolean) : Boolean = { + damageToBFROnly = bfrOnly + DamageToBattleframeOnly + } + + def DamageProxy : Option[Int] = damageProxy + + def DamageProxy_=(proxyObjectId : Int) : Option[Int] = DamageProxy_=(Some(proxyObjectId)) + + def DamageProxy_=(proxyObjectId : Option[Int]) : Option[Int] = { + damageProxy = proxyObjectId + DamageProxy + } + + def AdditionalEffect: Boolean = additionalEffect + + def AdditionalEffect_=(effect: Boolean): Boolean = { + additionalEffect = effect + AdditionalEffect + } + + def Aggravated : Option[AggravatedDamage] = aggravatedDamage + + def Aggravated_=(damage : AggravatedDamage) : Option[AggravatedDamage] = Aggravated_=(Some(damage)) + + def Aggravated_=(damage : Option[AggravatedDamage]) : Option[AggravatedDamage] = { + aggravatedDamage = damage + Aggravated + } + + def Charging : Option[ChargeDamage] = charging + + def Charging_=(damage : ChargeDamage) : Option[ChargeDamage] = Charging_=(Some(damage)) + + def Charging_=(damage : Option[ChargeDamage]) : Option[ChargeDamage] = { + charging = damage + Charging + } + + def SympatheticExplosion: Boolean = sympathy + + def SympatheticExplosion_=(chain: Boolean): Boolean = { + sympathy = chain + SympatheticExplosion + } +} diff --git a/src/main/scala/net/psforever/objects/vital/prop/DamageWithPosition.scala b/src/main/scala/net/psforever/objects/vital/prop/DamageWithPosition.scala new file mode 100644 index 000000000..8564b5f6c --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/prop/DamageWithPosition.scala @@ -0,0 +1,38 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.vital.prop + +/** + * Damage that has a sense of occurring in a place where the target is not + * but the target is still affected by the damage. + * The distance between the target and the point of activation can have a modifying effect. + */ +trait DamageWithPosition + extends DamageProperties { + /** for radial damage, how much damage has been lost the furthest away from the point of origin (m) */ + private var damageAtEdge: Float = 1f + /** for radial damage, the distance of the effect (m) */ + private var damageRadius: Float = 0f + /** for radial damage, the distance before initial degradation of the effect (m) */ + private var damageRadiusMin: Float = 1f + + def DamageAtEdge: Float = damageAtEdge + + def DamageAtEdge_=(atEdge: Float): Float = { + damageAtEdge = atEdge + DamageAtEdge + } + + def DamageRadius: Float = damageRadius + + def DamageRadius_=(radius: Float): Float = { + damageRadius = radius + DamageRadius + } + + def DamageRadiusMin: Float = damageRadiusMin + + def DamageRadiusMin_=(radius: Float): Float = { + damageRadiusMin = radius + DamageRadiusMin + } +} diff --git a/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala b/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala index ec0fed060..63a7d0cd1 100644 --- a/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala +++ b/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala @@ -5,42 +5,43 @@ import net.psforever.objects.GlobalDefinitions import net.psforever.objects.ballistics._ import net.psforever.objects.definition.ExoSuitDefinition import net.psforever.objects.serverobject.structures.Amenity -import net.psforever.objects.vital.projectile.ProjectileCalculations +import net.psforever.objects.vital.interaction.DamageInteraction import net.psforever.types.ExoSuitType import scala.util.{Failure, Success, Try} /** - * The base class for function literal description related to calculating resistance information.
- *
- * Implementing functionality of the children is the product of two user-defined processes - * and information for the calculation is extracted from the to-be-provided weapon discharge information. - * Specifically, the information is found as the `target` object which is a member of the said information. - * The specific functions passed into this object typically operate simultaneously normally - * and are related to the target and the kind of interaction the weapon discharge had with the target. + * The base for function literal description related to calculating resistance information. + * This glue connects target validation to value extraction + * to avoid the possibility of `NullPointerException` and `ClassCastException`. + * Some different types of vital objects store their resistance values in different places. * @param validate determine if a more generic `target` object is actually an expected type; * cast to and return that type of object * @param extractor recover the resistance values from an approved type of object + * @param default if the target does not match the validator, this is the constant resistance to return; + * the code really needs to be examined in this case; + * defaults to 0 * @tparam TargetType an internal type that converts between `validate`'s output and `extractor`'s input; * in essence, should match the type of object container to which these resistances belong; - * never has to be defined explicitly, but will be checked upon object definition + * never has to be defined explicitly but will be checked at compile time */ abstract class ResistanceCalculations[TargetType]( - validate: ResolvedProjectile => Try[TargetType], - extractor: TargetType => Int -) extends ProjectileCalculations { + validate: DamageInteraction => Try[TargetType], + extractor: TargetType => Int, + default: Int = 0 +) { /** - * Get resistance valuess. - * @param data the historical `ResolvedProjectile` information + * Get resistance values. + * @param data the historical `DamageInteraction` information * @return the damage value */ - def Calculate(data: ResolvedProjectile): Int = { + def Calculate(data: DamageInteraction): Int = { validate(data) match { case Success(target) => extractor(target) case _ => - 0 + default } } } @@ -49,9 +50,12 @@ object ResistanceCalculations { private def failure(typeName: String) = Failure(new Exception(s"can not match expected target $typeName")) //target identification - def InvalidTarget(data: ResolvedProjectile): Try[SourceEntry] = failure(s"invalid ${data.target.Definition.Name}") + def InvalidTarget(data: DamageInteraction): Try[SourceEntry] = failure(s"invalid ${data.target.Definition.Name}") - def ValidInfantryTarget(data: ResolvedProjectile): Try[PlayerSource] = { + //target is always considered valid + def AlwaysValidTarget(data: DamageInteraction): Try[SourceEntry] = Success(data.target) + + def ValidInfantryTarget(data: DamageInteraction): Try[PlayerSource] = { data.target match { case target: PlayerSource => if (target.ExoSuit != ExoSuitType.MAX) { //max is not counted as an official infantry exo-suit type @@ -64,7 +68,7 @@ object ResistanceCalculations { } } - def ValidMaxTarget(data: ResolvedProjectile): Try[PlayerSource] = { + def ValidMaxTarget(data: DamageInteraction): Try[PlayerSource] = { data.target match { case target: PlayerSource => if (target.ExoSuit == ExoSuitType.MAX) { @@ -77,7 +81,7 @@ object ResistanceCalculations { } } - def ValidVehicleTarget(data: ResolvedProjectile): Try[VehicleSource] = { + def ValidVehicleTarget(data: DamageInteraction): Try[VehicleSource] = { data.target match { case target: VehicleSource => if (!GlobalDefinitions.isFlightVehicle(target.Definition)) { @@ -90,7 +94,7 @@ object ResistanceCalculations { } } - def ValidAircraftTarget(data: ResolvedProjectile): Try[VehicleSource] = { + def ValidAircraftTarget(data: DamageInteraction): Try[VehicleSource] = { data.target match { case target: VehicleSource => if (GlobalDefinitions.isFlightVehicle(target.Definition)) { @@ -103,16 +107,16 @@ object ResistanceCalculations { } } - def ValidAmenityTarget(data: ResolvedProjectile): Try[ObjectSource] = { + def ValidAmenityTarget(data: DamageInteraction): Try[ObjectSource] = { data.target match { case target: ObjectSource => if (target.obj.isInstanceOf[Amenity]) { Success(target) } else { - failure("something else") + failure(s"${target.Definition.Name} amenity") } case _ => - failure("something else") + failure(s"amenity") } } diff --git a/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala b/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala index 244364177..7e5a4c4ed 100644 --- a/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala +++ b/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala @@ -1,8 +1,8 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.vital.resistance -import net.psforever.objects.vital.damage.DamageProfile -import net.psforever.objects.vital.{DamageType, StandardDamageProfile} +import net.psforever.objects.vital.damage.{DamageProfile, StandardDamageProfile} +import net.psforever.objects.vital.base.DamageType /** * The different values for four common methods of modifying incoming damage. diff --git a/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala b/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala index 0e7c466d9..e1f6cd2bf 100644 --- a/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala +++ b/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala @@ -1,35 +1,39 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.vital.resistance -import net.psforever.objects.ballistics._ -import net.psforever.objects.vital.{DamageType, NoResistance} -import net.psforever.objects.vital.projectile.ProjectileCalculations +import net.psforever.objects.vital.NoResistance +import net.psforever.objects.vital.base.DamageType +import net.psforever.objects.vital.interaction.DamageInteraction /** * Maintain information about four primary forms of resistance calculation * and a means to test which calculation is valid in a given situation. */ trait ResistanceSelection { - final def None: ProjectileCalculations.Form = NoResistance.Calculate + def Direct: ResistanceSelection.Format + def Splash: ResistanceSelection.Format + def Lash: ResistanceSelection.Format + def Aggravated: ResistanceSelection.Format - def Direct: ProjectileCalculations.Form - def Splash: ProjectileCalculations.Form - def Lash: ProjectileCalculations.Form - def Aggravated: ProjectileCalculations.Form - - def apply(data : ResolvedProjectile) : ProjectileCalculations.Form = data.projectile.profile.ProjectileDamageType match { - case DamageType.Direct => Direct - case DamageType.Splash => Splash - case DamageType.Lash => Lash + def apply(data: DamageInteraction) : ResistanceSelection.Format = data.cause.source.CausesDamageType match { + case DamageType.Direct => Direct + case DamageType.Splash => Splash + case DamageType.Lash => Lash case DamageType.Aggravated => Aggravated - case _ => None + case _ => ResistanceSelection.None } - def apply(res : DamageType.Value) : ProjectileCalculations.Form = res match { - case DamageType.Direct => Direct - case DamageType.Splash => Splash - case DamageType.Lash => Lash + def apply(res: DamageType.Value) : ResistanceSelection.Format = res match { + case DamageType.Direct => Direct + case DamageType.Splash => Splash + case DamageType.Lash => Lash case DamageType.Aggravated => Aggravated - case _ => None + case _ => ResistanceSelection.None } } + +object ResistanceSelection { + type Format = DamageInteraction => Int + + final val None: ResistanceSelection.Format = NoResistance.Calculate +} diff --git a/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala b/src/main/scala/net/psforever/objects/vital/resistance/StandardResistanceProfile.scala similarity index 89% rename from src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala rename to src/main/scala/net/psforever/objects/vital/resistance/StandardResistanceProfile.scala index 8a62ce2f1..4c943c2ef 100644 --- a/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala +++ b/src/main/scala/net/psforever/objects/vital/resistance/StandardResistanceProfile.scala @@ -1,9 +1,7 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.vital +package net.psforever.objects.vital.resistance import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.vital.damage.DamageProfile -import net.psforever.objects.vital.resistance.ResistanceProfile /** * The different values for four common methods of modifying incoming damage. diff --git a/src/main/scala/net/psforever/objects/vital/resolution/DamageResistCalculations.scala b/src/main/scala/net/psforever/objects/vital/resolution/DamageResistCalculations.scala deleted file mode 100644 index 4a4c93a6c..000000000 --- a/src/main/scala/net/psforever/objects/vital/resolution/DamageResistCalculations.scala +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.vital.resolution - -import net.psforever.objects.ballistics.ResolvedProjectile -import net.psforever.objects.vital.damage.DamageCalculations -import net.psforever.objects.vital.projectile.ProjectileCalculations - -/** - * A specific implementation of `ResolutionCalculations` that deals with - * the damage value and the resistance value in a specific manner. - * (The input type of the function literal output of `calcFunc`.) - * @param calcFunc a function literal that retrieves the function - * that factors the affects of damage and resistance values - * @param applyFunc a function literal that applies the final modified values to a target object - * @tparam A an internal type that converts between `calcFunc`'s output and `applyFunc`'s input; - * never has to be defined explicitly, but will be checked upon object definition - */ -abstract class DamageResistCalculations[A]( - calcFunc: ResolvedProjectile => (Int, Int) => A, - applyFunc: (A, ResolvedProjectile) => ResolutionCalculations.Output -) extends ResolutionCalculations { - def Calculate( - damages: DamageCalculations.Selector, - resistances: ProjectileCalculations.Form, - data: ResolvedProjectile - ): ResolutionCalculations.Output = { - val modDam = Sample(damages, resistances, data) - applyFunc(modDam, data) - } - - /** - * An intermediate step of the normal `Calculate` operation that retrieves the damage values in their transitory form. - * @param damages the function that calculations raw damage values - * @param resistances the function that calculates resistance values - * @param data a historical projectile interaction; - * the origin of the data used to extract damage and resistance values - * @return the transitory form of the modified damage(s); - * usually, a single `Int` value or a tuple of `Int` values - */ - def Sample( - damages: DamageCalculations.Selector, - resistances: ProjectileCalculations.Form, - data: ResolvedProjectile - ): A = { - val dam = DamageCalculations.DamageWithModifiers(damages, data) - val res: Int = resistances(data) - val mod = calcFunc(data) - mod(dam, res) - } -} diff --git a/src/main/scala/net/psforever/objects/vital/resolution/DamageResistanceCalculations.scala b/src/main/scala/net/psforever/objects/vital/resolution/DamageResistanceCalculations.scala new file mode 100644 index 000000000..f7e3809ad --- /dev/null +++ b/src/main/scala/net/psforever/objects/vital/resolution/DamageResistanceCalculations.scala @@ -0,0 +1,38 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital.resolution + +import net.psforever.objects.vital.damage.DamageCalculations +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.resistance.ResistanceSelection + +/** + * A specific implementation of `ResolutionCalculations` that deals with + * the damage value and the resistance value in a specific manner. + * (The input type of the function literal output of `calcFunc`.) + * @see `DamageCalculations.WithModifiers` + * @param calcFunc a function literal that retrieves the function + * that factors the affects of damage and resistance values + * @param applyFunc a function literal that applies the final modified values to a target object + * @param modifiersFunc a function literal that extracts and modifies a numeric damage value; + * even if no modifiers are to be used, the base damage value needs to be extracted; + * defaults to a function that utilizes all of the available information + * @tparam A an internal type that converts between `calcFunc`'s output and `applyFunc`'s input; + * never has to be defined explicitly but will be checked at compile time + */ +abstract class DamageResistanceCalculations[A]( + calcFunc: DamageInteraction => (Int, Int) => A, + applyFunc: (A, DamageInteraction) => ResolutionCalculations.Output, + modifiersFunc: (DamageCalculations.Selector, DamageInteraction) => Int = DamageCalculations.WithModifiers +) extends ResolutionCalculations { + def calculate( + damages: DamageCalculations.Selector, + resistances: ResistanceSelection.Format, + data: DamageInteraction + ): ResolutionCalculations.Output = { + val dam = modifiersFunc(damages, data) + val res = resistances(data) + val mod = calcFunc(data) + val modDam = mod(dam, res) + applyFunc(modDam, data) + } +} diff --git a/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala b/src/main/scala/net/psforever/objects/vital/resolution/DamageResistanceModel.scala similarity index 61% rename from src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala rename to src/main/scala/net/psforever/objects/vital/resolution/DamageResistanceModel.scala index 56140883c..50c33aea2 100644 --- a/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala +++ b/src/main/scala/net/psforever/objects/vital/resolution/DamageResistanceModel.scala @@ -1,14 +1,40 @@ // Copyright (c) 2017 PSForever -package net.psforever.objects.vital +package net.psforever.objects.vital.resolution +import net.psforever.objects.vital.base.DamageType 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.interaction.{DamageInteraction, DamageResult} import net.psforever.objects.vital.resistance.ResistanceSelection -import net.psforever.objects.vital.resolution.ResolutionCalculations +import net.psforever.objects.vital.{NoResistanceSelection, NoResolutions} /** - * The functionality that is necessary for interaction of a vital game object with the rest of the game world.
+ * The functionality that is necessary for interaction of a vital game object with the rest of the hostile game world. + */ +trait DamageAndResistance { + def DamageUsing: DamageCalculations.Selector + + def ResistUsing: ResistanceSelection + + def Model: ResolutionCalculations.Form + + def calculate(data: DamageInteraction): ResolutionCalculations.Output + + def calculate(data: DamageInteraction, resolution: DamageType.Value): ResolutionCalculations.Output +} + +object DamageAndResistance { + /** + * A pass-through function. + * @param data garbage in + * @return garbage out + */ + def doNothingFallback(data: DamageInteraction): ResolutionCalculations.Output = { + _: Any => DamageResult(data.target, data.target, data) + } +} + +/** + * The functionality that is necessary for interaction of a vital game object with the rest of the hostile game world.
*
* A vital object can be hurt or damaged or healed or repaired (HDHR). * The actual implementation of how that works is left to the specific object and its interfaces, however. @@ -24,7 +50,7 @@ import net.psforever.objects.vital.resolution.ResolutionCalculations * By default, nothing should do anything of substance. * @see `Vitality` */ -trait DamageResistanceModel { +trait DamageResistanceModel extends DamageAndResistance { /** the functionality that processes damage; required */ private var damageUsing: DamageCalculations.Selector = DamageCalculations.AgainstNothing @@ -33,7 +59,7 @@ trait DamageResistanceModel { private var resistUsing: ResistanceSelection = NoResistanceSelection /** the functionality that prepares for damage application actions; required */ - private var model: ResolutionCalculations.Form = NoResolutions.Calculate + private var model: ResolutionCalculations.Form = NoResolutions.calculate def DamageUsing: DamageCalculations.Selector = damageUsing @@ -58,22 +84,22 @@ trait DamageResistanceModel { /** * Magic stuff. - * @param data the historical `ResolvedProjectile` information + * @param data the historical damage information * @return a function literal that encapsulates delayed modification instructions for certain objects */ - def Calculate(data: ResolvedProjectile): ResolutionCalculations.Output = { - val res: ProjectileCalculations.Form = ResistUsing(data) + def calculate(data: DamageInteraction): ResolutionCalculations.Output = { + val res: ResistanceSelection.Format = ResistUsing(data) Model(DamageUsing, res, data) } /** * Magic stuff. - * @param data the historical `ResolvedProjectile` information - * @param resolution an explicit damage resolution overriding the one in the `ResolvedProjectile` object + * @param data the historical damage information + * @param resolution an explicit damage resolution overriding the one provided * @return a function literal that encapsulates delayed modification instructions for certain objects */ - def Calculate(data: ResolvedProjectile, resolution: DamageType.Value): ResolutionCalculations.Output = { - val res: ProjectileCalculations.Form = ResistUsing(resolution) + def calculate(data: DamageInteraction, resolution: DamageType.Value): ResolutionCalculations.Output = { + val res: ResistanceSelection.Format = ResistUsing(resolution) Model(DamageUsing, res, data) } } diff --git a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala b/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala index 9166f7d07..dc3daf174 100644 --- a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala +++ b/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala @@ -1,47 +1,45 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.vital.resolution -import net.psforever.objects.{Player, TurretDeployable, Vehicle} -import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} -import net.psforever.objects.ce.{ComplexDeployable, Deployable} +import net.psforever.objects.{PlanetSideGameObject, Player, TurretDeployable, Vehicle} +import net.psforever.objects.ballistics.{PlayerSource, SourceEntry} +import net.psforever.objects.ce.ComplexDeployable import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.damage.Damageable -import net.psforever.objects.serverobject.structures.Amenity -import net.psforever.objects.serverobject.turret.FacilityTurret import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.damage.DamageCalculations -import net.psforever.objects.vital.projectile.ProjectileCalculations -import net.psforever.types.ImplantType +import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} +import net.psforever.objects.vital.resistance.ResistanceSelection +import net.psforever.types.{ExoSuitType, ImplantType} /** * The base for the combining step of all projectile-induced damage calculation function literals. */ trait ResolutionCalculations { - /** * The exposed entry for the calculation function literal defined by this base. * @param damages the function literal that accumulates and calculates damages * @param resistances the function literal that collects resistance values - * @param data the historical `ResolvedProjectile` information + * @param data the historical damage information * @return a function literal that encapsulates delayed modification instructions for certain objects */ - def Calculate( + def calculate( damages: DamageCalculations.Selector, - resistances: ProjectileCalculations.Form, - data: ResolvedProjectile + resistances: ResistanceSelection.Format, + data: DamageInteraction ): ResolutionCalculations.Output } object ResolutionCalculations { - type Output = Any => ResolvedProjectile - type Form = (DamageCalculations.Selector, ProjectileCalculations.Form, ResolvedProjectile) => Output + type Output = PlanetSideGameObject with FactionAffinity => DamageResult + type Form = (DamageCalculations.Selector, ResistanceSelection.Format, DamageInteraction) => Output - def NoDamage(data: ResolvedProjectile)(a: Int, b: Int): Int = 0 + def NoDamage(data: DamageInteraction)(a: Int, b: Int): Int = 0 - def InfantryDamage(data: ResolvedProjectile): (Int, Int) => (Int, Int) = { + def InfantryDamage(data: DamageInteraction): (Int, Int) => (Int, Int) = { data.target match { case target: PlayerSource => - if(data.projectile.profile.DamageToHealthOnly) { + if(data.cause.source.DamageToHealthOnly) { DamageToHealthOnly(target.health) } else { InfantryDamageAfterResist(target.health, target.armor) @@ -83,10 +81,10 @@ object ResolutionCalculations { } } - def MaxDamage(data: ResolvedProjectile): (Int, Int) => (Int, Int) = { + def MaxDamage(data: DamageInteraction): (Int, Int) => (Int, Int) = { data.target match { case target: PlayerSource => - if(data.projectile.profile.DamageToHealthOnly) { + if(data.cause.source.DamageToHealthOnly) { DamageToHealthOnly(target.health) } else { MaxDamageAfterResist(target.health, target.armor) @@ -115,10 +113,10 @@ object ResolutionCalculations { * Unlike with `Infantry*` and with `Max*`'s, * `VehicleDamageAfterResist` does not necessarily need to validate its target object. * The required input is sufficient. - * @param data the historical `ResolvedProjectile` information + * @param data the historical damage information * @return a function literal for dealing with damage values and resistance values together */ - def VehicleDamageAfterResist(data: ResolvedProjectile): (Int, Int) => Int = { + def VehicleDamageAfterResist(data: DamageInteraction): (Int, Int) => Int = { VehicleDamageAfterResist } @@ -130,7 +128,10 @@ object ResolutionCalculations { } } - def NoApplication(damageValue: Int, data: ResolvedProjectile)(target: Any): ResolvedProjectile = data + def NoApplication(damageValue: Int, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = { + val sameTarget = SourceEntry(target) + DamageResult(sameTarget, sameTarget, data) + } def SubtractWithRemainder(current: Int, damage: Int): (Int, Int) = { val a = Math.max(0, current - damage) @@ -138,7 +139,7 @@ object ResolutionCalculations { (a, remainingDamage) } - private def CanDamage(obj: Vitality with FactionAffinity, damage: Int, data: ResolvedProjectile): Boolean = { + private def CanDamage(obj: Vitality with FactionAffinity, damage: Int, data: DamageInteraction): Boolean = { obj.Health > 0 && Damageable.CanDamage(obj, damage, data) } @@ -146,58 +147,61 @@ object ResolutionCalculations { * The expanded `(Any)=>Unit` function for infantry. * Apply the damage values to the capacitor (if shielded NC max), health field and personal armor field for an infantry target. * @param damageValues a tuple containing damage values for: health, personal armor - * @param data the historical `ResolvedProjectile` information + * @param data the historical damage information * @param target the `Player` object to be affected by these damage values (at some point) */ - def InfantryApplication(damageValues: (Int, Int), data: ResolvedProjectile)(target: Any): ResolvedProjectile = { + def InfantryApplication(damageValues: (Int, Int), data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = { + val targetBefore = SourceEntry(target) target match { case player: Player => var (a, b) = damageValues - var result = (0, 0) if (player.isAlive && !(a == 0 && b == 0)) { - if (player.Capacitor.toInt > 0 && player.isShielded) { - // Subtract armour damage from capacitor - result = SubtractWithRemainder(player.Capacitor.toInt, b) - player.Capacitor = result._1.toFloat - b = result._2 - - // Then follow up with health damage if any capacitor is left - result = SubtractWithRemainder(player.Capacitor.toInt, a) - player.Capacitor = result._1.toFloat - a = result._2 - } - - player.avatar.implants.flatten.find(x => x.definition.implantType == ImplantType.PersonalShield) match { - case Some(implant) if implant.active => { - // Subtract armour damage from stamina - result = SubtractWithRemainder(player.avatar.stamina, b) - player.avatar = player.avatar.copy(stamina = result._1) + val originalHealth = player.Health + if (data.cause.source.DamageToHealthOnly) { + player.Health = SubtractWithRemainder(player.Health, a)._1 + } else { + var result = (0, 0) + if (player.Capacitor.toInt > 0 && player.isShielded) { + // Subtract armour damage from capacitor + result = SubtractWithRemainder(player.Capacitor.toInt, b) + player.Capacitor = result._1.toFloat b = result._2 - // Then follow up with health damage if any stamina is left - result = SubtractWithRemainder(player.avatar.stamina, a) - player.avatar = player.avatar.copy(stamina = result._1) + // Then follow up with health damage if any capacitor is left + result = SubtractWithRemainder(player.Capacitor.toInt, a) + player.Capacitor = result._1.toFloat a = result._2 } - case _ => ; + player.avatar.implants.flatten.find(x => x.definition.implantType == ImplantType.PersonalShield) match { + case Some(implant) if implant.active => + // Subtract armour damage from stamina + result = SubtractWithRemainder(player.avatar.stamina, b) + player.avatar = player.avatar.copy(stamina = result._1) + b = result._2 + + // Then follow up with health damage if any stamina is left + result = SubtractWithRemainder(player.avatar.stamina, a) + player.avatar = player.avatar.copy(stamina = result._1) + a = result._2 + + case _ => ; + } + + // Subtract any remaining armour damage from armour + result = SubtractWithRemainder(player.Armor, b) + player.Armor = result._1 + b = result._2 + // Then bleed through to health if armour ran out + result = SubtractWithRemainder(player.Health, b) + player.Health = result._1 + b = result._2 + + // Finally, apply health damage to health + result = SubtractWithRemainder(player.Health, a) + player.Health = result._1 + //if b > 0 (armor) or result._2 > 0 (health), then we did the math wrong } - // Subtract any remaining armour damage from armour - result = SubtractWithRemainder(player.Armor, b) - player.Armor = result._1 - b = result._2 - - val originalHealth = player.Health - // Then bleed through to health if armour ran out - result = SubtractWithRemainder(player.Health, b) - player.Health = result._1 - b = result._2 - - // Finally, apply health damage to health - result = SubtractWithRemainder(player.Health, a) - player.Health = result._1 - a = result._2 - // If any health damage was applied also drain an amount of stamina equal to half the health damage if (player.Health < originalHealth) { val delta = originalHealth - player.Health @@ -207,17 +211,18 @@ object ResolutionCalculations { } case _ => } - data + DamageResult(targetBefore, SourceEntry(target), data) } /** * The expanded `(Any)=>Unit` function for vehicles. * Apply the damage value to the shield field and then the health field (that order) for a vehicle target. * @param damage the raw damage - * @param data the historical `ResolvedProjectile` information + * @param data the historical damage information * @param target the `Vehicle` object to be affected by these damage values (at some point) */ - def VehicleApplication(damage: Int, data: ResolvedProjectile)(target: Any): ResolvedProjectile = { + def VehicleApplication(damage: Int, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = { + val targetBefore = SourceEntry(target) target match { case vehicle: Vehicle if CanDamage(vehicle, damage, data) => val shields = vehicle.Shields @@ -231,36 +236,22 @@ object ResolutionCalculations { } case _ => ; } - data + DamageResult(targetBefore, SourceEntry(target), data) } - def SimpleApplication(damage: Int, data: ResolvedProjectile)(target: Any): ResolvedProjectile = { + def SimpleApplication(damage: Int, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = { + val targetBefore = SourceEntry(target) target match { - case obj: Deployable if CanDamage(obj, damage, data) => - obj.Health -= damage - case turret: FacilityTurret if CanDamage(turret, damage, data) => - turret.Health -= damage - case amenity: Amenity if CanDamage(amenity, damage, data) => - amenity.Health -= damage + case entity: Vitality if CanDamage(entity, damage, data) => + entity.Health -= damage case _ => ; } - data + DamageResult(targetBefore, SourceEntry(target), data) } - def ComplexDeployableApplication(damage: Int, data: ResolvedProjectile)(target: Any): ResolvedProjectile = { + def ComplexDeployableApplication(damage: Int, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = { + val targetBefore = SourceEntry(target) target match { - case ce: ComplexDeployable if CanDamage(ce, damage, data) => - if (ce.Shields > 0) { - if (damage > ce.Shields) { - ce.Health -= (damage - ce.Shields) - ce.Shields = 0 - } else { - ce.Shields -= damage - } - } else { - ce.Health -= damage - } - case ce: TurretDeployable if CanDamage(ce, damage, data) => if (ce.Shields > 0) { if (damage > ce.Shields) { @@ -273,8 +264,63 @@ object ResolutionCalculations { ce.Health -= damage } + case ce: ComplexDeployable if CanDamage(ce, damage, data) => + if (ce.Shields > 0) { + if (damage > ce.Shields) { + ce.Health -= (damage - ce.Shields) + ce.Shields = 0 + } else { + ce.Shields -= damage + } + } else { + ce.Health -= damage + } + case _ => ; } - data + DamageResult(targetBefore, SourceEntry(target), data) + } + + def WildcardCalculations(data: DamageInteraction): (Int, Int) => Any = { + data.target match { + case p: PlayerSource => + if(p.ExoSuit == ExoSuitType.MAX) MaxDamage(data) + else InfantryDamage(data) + case _ => + VehicleDamageAfterResist(data) + } + } + + def WildcardApplication(damage: Any, data: DamageInteraction)(target: PlanetSideGameObject with FactionAffinity): DamageResult = { + target match { + case _: Player => + val dam : (Int, Int) = damage match { + case (a: Int, b: Int) => (a, b) + case a: Int => (a, 0) + case _ => (0, 0) + } + InfantryApplication(dam, data)(target) + + case _: Vehicle => + val dam : Int = damage match { + case a: Int => a + case _ => 0 + } + VehicleApplication(dam, data)(target) + + case _: ComplexDeployable => + val dam : Int = damage match { + case a: Int => a + case _ => 0 + } + ComplexDeployableApplication(dam, data)(target) + + case _ => + val dam : Int = damage match { + case a: Int => a + case _ => 0 + } + SimpleApplication(dam, data)(target) + } } } diff --git a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionSelection.scala b/src/main/scala/net/psforever/objects/vital/resolution/ResolutionSelection.scala deleted file mode 100644 index e95eb9f6a..000000000 --- a/src/main/scala/net/psforever/objects/vital/resolution/ResolutionSelection.scala +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.vital.resolution - -/** - * Maintain information about four target types as the entry points for damage calculation. - */ -trait ResolutionSelection { - def Infantry: ResolutionCalculations.Form - def Max: ResolutionCalculations.Form - def Vehicle: ResolutionCalculations.Form - def Aircraft: ResolutionCalculations.Form - def SimpleDeployables: ResolutionCalculations.Form - def ComplexDeployables: ResolutionCalculations.Form - def FacilityTurrets: ResolutionCalculations.Form - def Amenities: ResolutionCalculations.Form -} diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala index c94876024..8622ca769 100644 --- a/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -5,7 +5,7 @@ import akka.actor.{ActorContext, ActorRef, Props} import akka.routing.RandomPool import net.psforever.objects.ballistics.{Projectile, SourceEntry} import net.psforever.objects._ -import net.psforever.objects.ce.Deployable +import net.psforever.objects.ce.{ComplexDeployable, Deployable, SimpleDeployable} import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.equipment.Equipment import net.psforever.objects.guid.{NumberPoolHub, TaskResolver} @@ -38,8 +38,12 @@ import akka.actor.typed import net.psforever.actors.session.AvatarActor import net.psforever.actors.zone.ZoneActor import net.psforever.objects.avatar.Avatar +import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.vehicles.UtilityType +import net.psforever.objects.vital.etc.ExplodingEntityReason +import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult} /** * A server object representing the one-landmass planets as well as the individual subterranean caverns.
@@ -923,7 +927,35 @@ object Zone { } object HotSpot { - final case class Activity(defender: SourceEntry, attacker: SourceEntry, location: Vector3) + trait Activity { + def defender: SourceEntry + + def attacker: SourceEntry + + def location: Vector3 + } + + final case class Conflict(defender: SourceEntry, attacker: SourceEntry, location: Vector3) extends Activity + + final case class NonEvent() extends Activity { + def defender: SourceEntry = SourceEntry.None + + def attacker: SourceEntry = SourceEntry.None + + def location: Vector3 = Vector3.Zero + } + + object Activity { + def apply(data: DamageResult): Activity = { + data.adversarial match { + case Some(adversity) => Conflict(adversity.defender, adversity.attacker, data.interaction.hitPos) + case None => NonEvent() + } + } + + def apply(defender: SourceEntry, attacker: SourceEntry, location: Vector3): Activity = + Conflict(defender, attacker, location) + } final case class Cleanup() @@ -1034,4 +1066,124 @@ object Zone { } } } + + /** + * Allocates `Damageable` targets within the radius of a server-prepared explosion + * and informs those entities that they have affected by the aforementioned explosion. + * @see `Amenity.Owner` + * @see `ComplexDeployable` + * @see `DamageInteraction` + * @see `DamageResult` + * @see `DamageWithPosition` + * @see `ExplodingEntityReason` + * @see `SimpleDeployable` + * @see `VitalityDefinition` + * @see `VitalityDefinition.innateDamage` + * @see `Zone.Buildings` + * @see `Zone.DeployableList` + * @see `Zone.LivePlayers` + * @see `Zone.LocalEvents` + * @see `Zone.Vehicles` + * @param zone the zone in which the explosion should occur + * @param obj the entity that embodies the explosion (information) + * @param instigation whatever prior action triggered the entity to explode, if anything + * @param detectionTest a custom test to determine if any given target is affected; + * defaults to an internal test for simple radial proximity + * @return a list of affected entities; + * only mostly complete due to the exclusion of objects whose damage resolution is different than usual + */ + def causeExplosion( + zone: Zone, + obj: PlanetSideGameObject with Vitality, + instigation: Option[DamageResult], + detectionTest: (PlanetSideGameObject, PlanetSideGameObject, Float) => Boolean = distanceCheck + ): List[PlanetSideServerObject] = { + obj.Definition.innateDamage match { + case Some(explosion) if obj.Definition.explodes => + //useful in this form + val sourcePosition = obj.Position + val sourcePositionXY = sourcePosition.xy + val radius = explosion.DamageRadius * explosion.DamageRadius + //collect all targets that can be damaged + //players + val playerTargets = zone.LivePlayers.filterNot { _.VehicleSeated.nonEmpty } + //vehicles + val vehicleTargets = zone.Vehicles.filterNot { v => v.Destroyed || v.MountedIn.nonEmpty } + //deployables + val (simpleDeployableTargets, complexDeployableTargets) = + zone.DeployableList + .filterNot { _.Destroyed } + .foldRight((List.empty[SimpleDeployable], List.empty[ComplexDeployable])) { case (f, (simp, comp)) => + f match { + case o: SimpleDeployable => (simp :+ o, comp) + case o: ComplexDeployable => (simp, comp :+ o) + case _ => (simp, comp) + } + } + //amenities + val soiTargets = obj match { + case o: Amenity => + //fortunately, even where soi overlap, amenities in different buildings are never that close to each other + o.Owner.Amenities + case _ => + zone.Buildings.values + .filter { b => + val soiRadius = b.Definition.SOIRadius * b.Definition.SOIRadius + Vector3.DistanceSquared(sourcePositionXY, b.Position.xy) < soiRadius || soiRadius <= radius + } + .flatMap { _.Amenities } + .filter { _.Definition.Damageable } + } + //restrict to targets in the damage radius + val allAffectedTargets = (playerTargets ++ vehicleTargets ++ complexDeployableTargets ++ soiTargets) + .filter { target => + (target ne obj) && detectionTest(obj, target, radius) + } + //inform remaining targets that they have suffered an explosion + allAffectedTargets + .foreach { target => + target.Actor ! Vitality.Damage( + DamageInteraction( + SourceEntry(target), + ExplodingEntityReason(obj, target.DamageModel, instigation), + target.Position + ).calculate() + ) + } + //important note - these are not returned as targets that were affected + simpleDeployableTargets + .filter { target => + (target ne obj) && detectionTest(obj, target, radius) + } + .foreach { target => + zone.LocalEvents ! Vitality.DamageOn( + target, + DamageInteraction( + SourceEntry(target), + ExplodingEntityReason(obj, target.DamageModel, instigation), + target.Position + ).calculate() + ) + } + allAffectedTargets + case None => + Nil + } + } + + /** + * Two game entities are considered "near" each other if they are within a certain distance of one another. + * A default function literal mainly used for `causesExplosion`. + * @see `causeExplosion` + * @see `Vector3.DistanceSquare` + * @param obj1 a game entity + * @param obj2 a game entity + * @param maxDistance the square of the maximum distance permissible between game entities + * before they are no longer considered "near" + * @return `true`, if the target entities are near to each other; + * `false`, otherwise + */ + private def distanceCheck(obj1: PlanetSideGameObject, obj2: PlanetSideGameObject, maxDistance: Float): Boolean = { + Vector3.DistanceSquared(obj1.Position, obj2.Position) <= maxDistance + } } diff --git a/src/main/scala/net/psforever/objects/zones/ZoneHotSpotProjector.scala b/src/main/scala/net/psforever/objects/zones/ZoneHotSpotProjector.scala index 4cd228f04..3fa058d36 100644 --- a/src/main/scala/net/psforever/objects/zones/ZoneHotSpotProjector.scala +++ b/src/main/scala/net/psforever/objects/zones/ZoneHotSpotProjector.scala @@ -151,7 +151,7 @@ class ZoneHotSpotProjector(zone: Zone, hotspots: ListBuffer[HotSpotInfo], blanki import scala.concurrent.ExecutionContext.Implicits.global blanking = context.system.scheduler.scheduleOnce(blankingDelay, self, ZoneHotSpotProjector.BlankingPhase()) - case Zone.HotSpot.Activity(defender, attacker, location) => + case Zone.HotSpot.Conflict(defender, attacker, location) => log.trace(s"received information about activity in ${zone.id}@$location") val defenderFaction = defender.Faction val attackerFaction = attacker.Faction @@ -194,7 +194,7 @@ class ZoneHotSpotProjector(zone: Zone, hotspots: ListBuffer[HotSpotInfo], blanki //blanking dated activity reports val changed = hotspots.flatMap(spot => { spot.Activity.collect { - case (b, a) if a.LastReport + a.Duration.toNanos <= curr => + case (b, a: ActivityReport) if a.LastReport + a.Duration.toNanos <= curr => a.Clear() //this faction has no more activity in this sector (b, spot) } diff --git a/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala b/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala index d933b0089..e80b9e080 100644 --- a/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala +++ b/src/main/scala/net/psforever/packet/game/HotSpotUpdateMessage.scala @@ -16,7 +16,7 @@ import shapeless.{::, HNil} * Instead, all maps use a 0 - 8192 coordinate overlay. * @param x the x-coord of the center of the hotspot * @param y the y-coord of the center of the hotspot - * @param scale how big the hotspot explosion icon appears + * @param scale how big the hotspot sunburst icon appears */ final case class HotSpotInfo(x: Float, y: Float, scale: Float) diff --git a/src/main/scala/net/psforever/services/InterstellarClusterService.scala b/src/main/scala/net/psforever/services/InterstellarClusterService.scala index c96e199fa..c8ed4d95a 100644 --- a/src/main/scala/net/psforever/services/InterstellarClusterService.scala +++ b/src/main/scala/net/psforever/services/InterstellarClusterService.scala @@ -82,14 +82,14 @@ class InterstellarClusterService(context: ActorContext[InterstellarClusterServic val zoneActors: mutable.Map[String, (ActorRef[ZoneActor.Command], Zone)] = mutable.Map( _zones.map { - case zone => + zone => val zoneActor = context.spawn(ZoneActor(zone), s"zone-${zone.id}") (zone.id, (zoneActor, zone)) }.toSeq: _* ) - val zones = zoneActors.map { - case (id, (_, zone)) => zone + val zones: Iterable[Zone] = zoneActors.map { + case (_, (_, zone: Zone)) => zone } override def onMessage(msg: Command): Behavior[Command] = { @@ -154,7 +154,7 @@ class InterstellarClusterService(context: ActorContext[InterstellarClusterServic case GetRandomSpawnPoint(zoneNumber, faction, spawnGroups, replyTo) => val response = zones.find(_.Number == zoneNumber) match { - case Some(zone) => + case Some(zone: Zone) => /* val location = math.abs(Random.nextInt() % 4) match { case 0 => Vector3(sanctuary.map.Scale.width, sanctuary.map.Scale.height, 0) //NE diff --git a/src/main/scala/net/psforever/services/ServiceManager.scala b/src/main/scala/net/psforever/services/ServiceManager.scala index 22a4110e0..4c5c9e97b 100644 --- a/src/main/scala/net/psforever/services/ServiceManager.scala +++ b/src/main/scala/net/psforever/services/ServiceManager.scala @@ -1,9 +1,8 @@ // Copyright (c) 2017 PSForever package net.psforever.services -import akka.actor.{Actor, ActorIdentity, ActorRef, ActorSystem, Identify, Props} +import akka.actor.{Actor, ActorIdentity, ActorRef, ActorSystem, Identify, InvalidActorNameException, Props, typed} import akka.actor.typed.scaladsl.adapter._ -import akka.actor.typed import akka.actor.typed.receptionist.Receptionist import scala.collection.mutable @@ -35,6 +34,7 @@ class ServiceManager extends Actor { var nextLookupId: Long = 0 val lookups: mutable.LongMap[RequestEntry] = mutable.LongMap() + val retainedRequests: mutable.HashMap[String, Set[ActorRef]] = mutable.HashMap() override def preStart() = { log.info("Starting...") @@ -43,7 +43,32 @@ class ServiceManager extends Actor { def receive = { case Register(props, name) => log.info(s"Registered $name service") - context.actorOf(props, name) + try { + val ref = context.actorOf(props, name) + val result = LookupResult(name, ref) + //handle logged premature requests + retainedRequests.remove(name) match { + case Some(oldRequests) => + oldRequests.foreach { + _ ! result + } + case None => ; + } + //handle active requests that will probably miss + val poorlytTimedRequests = lookups.filter { + _._2.request.equals(name) + } + poorlytTimedRequests.foreach { case (id, entry) => + entry.responder ! result + lookups.remove(id) + } + } + catch { + case e: InvalidActorNameException => //if an entry already exists, no harm, no foul, just don't do it again + log.warn(s"service manager says: ${e.getMessage}") + case _ => ; + } + case Lookup(name) => context.actorSelection(name) ! Identify(nextLookupId) lookups += nextLookupId -> RequestEntry(name, sender()) @@ -60,22 +85,21 @@ class ServiceManager extends Actor { case Some(RequestEntry(name, sender)) => sender ! LookupResult(name, ref) lookups.remove(idNumber) - case _ => - //TODO something + case _ => ; } case ActorIdentity(id, None) => val idNumber = id.asInstanceOf[Long] lookups.get(idNumber) match { - case Some(RequestEntry(name, _)) => - log.error(s"request #$idNumber for service `$name` came back empty; it may not exist") + case Some(RequestEntry(name, sender)) => + log.error(s"service manager says: request #$idNumber for service `$name` came back empty; it may not exist") lookups.remove(idNumber) - case _ => - //TODO something + retainedRequests(name) = retainedRequests.getOrElse(name, Set[ActorRef]()) ++ Set(sender) + case _ => ; } case default => - log.error(s"invalid message received - $default") + log.error(s"service manager says: invalid message received - $default") } protected case class RequestEntry(request: String, responder: ActorRef) diff --git a/src/main/scala/net/psforever/services/local/LocalService.scala b/src/main/scala/net/psforever/services/local/LocalService.scala index c508028c6..9857cd210 100644 --- a/src/main/scala/net/psforever/services/local/LocalService.scala +++ b/src/main/scala/net/psforever/services/local/LocalService.scala @@ -378,7 +378,7 @@ class LocalService(zone: Zone) extends Actor { } //synchronized damage calculations - case Vitality.DamageOn(target: Deployable, damage_func) => + case Vitality.DamageOn(target: PlanetSideGameObject with Deployable, damage_func) => val cause = damage_func(target) sender() ! Vitality.DamageResolution(target, cause) diff --git a/src/main/scala/net/psforever/types/Vector3.scala b/src/main/scala/net/psforever/types/Vector3.scala index ab89ee93a..43c4f43f6 100644 --- a/src/main/scala/net/psforever/types/Vector3.scala +++ b/src/main/scala/net/psforever/types/Vector3.scala @@ -329,10 +329,68 @@ object Vector3 { ) } - def PlanarRotateAroundPoint(point: Vector3, center: Vector3, radians: Float): Vector3 = { - val x = (Math.cos(radians) * (point.x - center.x) - Math.sin(radians) * (point.y - center.y) + center.x).toFloat - val y = (Math.sin(radians) * (point.x - center.x) + Math.cos(radians) * (point.y - center.y) + center.y).toFloat + /** + * Perform a standard z-axis rotation of a `Vector3` element representing a point in space + * around a `Vector3` element representing a point representing an axis of rotation + * where the angle of rotation is assumed in radians. + * This follows number circle rotation (counterclockwise) instead of compass rose rotation (clockwise). + * It can not be substituted with `(Rz(point - axis, radians) + center).xy + z(point.z)`. + * @see `Vector3.Rz(Vector3, Double)` + * @param point a mathematical vector representing a point in space + * @param axis a mathematical vector representing an axis of rotation + * @param radians a rotation angle, in radians + * @return the rotated point + */ + def PlanarRotateAroundPoint(point: Vector3, axis: Vector3, radians: Float): Vector3 = { + val cos = math.cos(radians).toFloat + val sin = math.sin(radians).toFloat + val dx = point.x - axis.x + val dy = point.y - axis.y + val x = cos * dx - sin * dy + axis.x + val y = sin * dx + cos * dy + axis.y + Vector3( + closeToInsignificance(x), + closeToInsignificance(y), + point.z + ) + } - Vector3(x, y, point.z) + /** + * Find the center between two points. + * @param p1 the first point + * @param p2 the second point + * @return the point that is the mean position directly between the first point and the second point + */ + def midpoint(p1: Vector3, p2: Vector3): Vector3 = { + Vector3((p1.x + p2.x) / 2, (p1.y + p2.y) / 2, (p1.z + p2.z) / 2) + } + + /** + * Given a `Vector3` element composed of Euler angles, + * find a standard unit vector that points in the direction of "up" after rotating by the Euler angles. + * Compass direction rules apply (North is 0 degrees, East is 90 degrees, etc.). + * @see `Vector3.relativeUp(Vector3, Vector3)` + * @param orient three Euler angles representing rotation + * @return a mathematical vector representing a relative "up" direction + */ + def relativeUp(orient: Vector3): Vector3 = { + relativeUp(orient, Vector3(0,0,1)) //world up + } + + /** + * Given a `Vector3` element composed of Euler angles + * and a `Vector3` element in the direction of "up", + * find a standard unit vector that points in the direction of "up" after rotating by the Euler angles. + * Compass direction rules apply (North is 0 degrees, East is 90 degrees, etc.). + * @see `Vector3.Rx(Float)` + * @see `Vector3.Ry(Float)` + * @see `Vector3.Rz(Float)` + * @param orient three Euler angles representing rotation + * @param up a mathematical vector representing "up" + * @return a mathematical vector representing a relative "up" direction + */ + def relativeUp(orient: Vector3, up: Vector3): Vector3 = { + //TODO is the missing calculation before Rz(Rx(Ry(v, x), y), z) or after Rz(Ry(Rx(v, y), x), z)? + Rz(Rx(up, orient.y), (orient.z + 180) % 360f) } } diff --git a/src/test/scala/Vector3Test.scala b/src/test/scala/Vector3Test.scala index 0566066e1..ef39a2bbc 100644 --- a/src/test/scala/Vector3Test.scala +++ b/src/test/scala/Vector3Test.scala @@ -240,5 +240,35 @@ class Vector3Test extends Specification { val A: Vector3 = Vector3(1, 0, 0) A.Rz(45) mustEqual Vector3(0.70710677f, 0.70710677f, 0) } + + "find a relative up (identity)" in { + val euler: Vector3 = Vector3(0, 0, 0) + Vector3.relativeUp(euler) mustEqual Vector3(0,0,1) + } + + "find a relative up (z-rot)" in { + val up = Vector3(0,0,1) + Vector3.relativeUp(Vector3(0, 0, 0)) mustEqual up + Vector3.relativeUp(Vector3(0, 0, 90)) mustEqual up + Vector3.relativeUp(Vector3(0, 0, 180)) mustEqual up + Vector3.relativeUp(Vector3(0, 0, 270)) mustEqual up + Vector3.relativeUp(Vector3(0, 0, 360)) mustEqual up + } + + "find a relative up (y-rot)" in { + Vector3.relativeUp(Vector3(0, 0, 0)) mustEqual Vector3(0,0,1) //up + Vector3.relativeUp(Vector3(0, 90, 0)) mustEqual Vector3(0,-1,0) //north + Vector3.relativeUp(Vector3(0, 180, 0)) mustEqual Vector3(0,0,-1) //down + Vector3.relativeUp(Vector3(0, 270, 0)) mustEqual Vector3(0,1,0) //south + Vector3.relativeUp(Vector3(0, 360, 0)) mustEqual Vector3(0,0,1) //up + } + + "find a relative up (combined y,z)" in { + Vector3.relativeUp(Vector3(0, 0, 90)) mustEqual Vector3(0,0,1) //up + Vector3.relativeUp(Vector3(0, 90, 90)) mustEqual Vector3(-1,0,0) //west + Vector3.relativeUp(Vector3(0, 180, 90)) mustEqual Vector3(0,0,-1) //down + Vector3.relativeUp(Vector3(0, 270, 90)) mustEqual Vector3(1,0,0) //east + Vector3.relativeUp(Vector3(0, 360, 90)) mustEqual Vector3(0,0,1) //up + } } } diff --git a/src/test/scala/objects/AutoRepairIntegrationTest.scala b/src/test/scala/objects/AutoRepairIntegrationTest.scala index 188847cad..0a64b8a16 100644 --- a/src/test/scala/objects/AutoRepairIntegrationTest.scala +++ b/src/test/scala/objects/AutoRepairIntegrationTest.scala @@ -6,14 +6,17 @@ import akka.testkit.TestProbe import base.FreedContextActorTest import net.psforever.actors.zone.BuildingActor import net.psforever.objects.avatar.Avatar -import net.psforever.objects.ballistics.{Projectile, ProjectileResolution, ResolvedProjectile, SourceEntry} +import net.psforever.objects.ballistics.{Projectile, SourceEntry} import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.serverobject.resourcesilo.{ResourceSilo, ResourceSiloControl} import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType} import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal, TerminalControl} import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.base.DamageResolution import net.psforever.objects.vital.damage.DamageProfile +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.{GlobalDefinitions, Player, Tool} import net.psforever.services.galaxy.GalaxyService @@ -26,53 +29,51 @@ class AutoRepairFacilityIntegrationTest extends FreedContextActorTest { import akka.actor.typed.scaladsl.adapter._ system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id) ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService](), "galaxy") - expectNoMessage(200 milliseconds) + expectNoMessage(1000 milliseconds) + val guid = new NumberPoolHub(new MaxNumberSource(max = 10)) + val avatarProbe = new TestProbe(system) + val zone = new Zone("test", new ZoneMap("test"), 0) { + override def SetupNumberPools() = {} + GUID(guid) + override def AvatarEvents = avatarProbe.ref + } + val building = Building.Structure(StructureType.Facility)(name = "integ-fac-test-building", guid = 6, map_id = 0, zone, context) + building.Invalidate() val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) player.Spawn() val weapon = new Tool(GlobalDefinitions.suppressor) val terminal = new Terminal(AutoRepairIntegrationTest.terminal_definition) val silo = new ResourceSilo() - val guid = new NumberPoolHub(new MaxNumberSource(max = 10)) - val zone = new Zone("test", new ZoneMap("test"), 0) { - override def SetupNumberPools() = {} - GUID(guid) - } - val avatarProbe = new TestProbe(system) - zone.AvatarEvents = avatarProbe.ref - guid.register(player, number = 1) guid.register(weapon, number = 2) guid.register(weapon.AmmoSlot.Box, number = 3) guid.register(terminal, number = 4) guid.register(silo, number = 5) - - val building = Building.Structure(StructureType.Facility)(name = "test-building", guid = 6, map_id = 0, zone, context) - building.Invalidate() guid.register(building, number = 6) + building.Amenities = silo building.Amenities = terminal - terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal") - silo.NtuCapacitor = 1000 silo.Actor = system.actorOf(Props(classOf[ResourceSiloControl], silo), "test-silo") silo.Actor ! "startup" - building.Actor ! BuildingActor.PowerOn() //artificial val wep_fmode = weapon.FireMode val wep_prof = wep_fmode.Add val proj = weapon.Projectile val proj_prof = proj.asInstanceOf[DamageProfile] val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resolved = DamageInteraction( SourceEntry(terminal), - terminal.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + terminal.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() "AutoRepair" should { "should activate on damage and trade NTU from the facility's resource silo for repairs" in { @@ -80,7 +81,7 @@ class AutoRepairFacilityIntegrationTest extends FreedContextActorTest { assert(terminal.Health == terminal.MaxHealth) terminal.Actor ! Vitality.Damage(applyDamageTo) - avatarProbe.receiveOne(max = 200 milliseconds) //health update event + avatarProbe.receiveOne(max = 1000 milliseconds) //health update event assert(terminal.Health < terminal.MaxHealth) var i = 0 //safety counter while(terminal.Health < terminal.MaxHealth && i < 100) { @@ -97,45 +98,47 @@ class AutoRepairTowerIntegrationTest extends FreedContextActorTest { import akka.actor.typed.scaladsl.adapter._ system.spawn(InterstellarClusterService(Nil), InterstellarClusterService.InterstellarClusterServiceKey.id) ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService](), "galaxy") - expectNoMessage(200 milliseconds) + expectNoMessage(1000 milliseconds) + val guid = new NumberPoolHub(new MaxNumberSource(max = 10)) + val avatarProbe = new TestProbe(system) + val zone = new Zone("test", new ZoneMap("test"), 0) { + override def SetupNumberPools() = {} + GUID(guid) + override def AvatarEvents = avatarProbe.ref + } + val building = Building.Structure(StructureType.Tower)(name = "integ-twr-test-building", guid = 6, map_id = 0, zone, context) + building.Invalidate() val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) player.Spawn() val weapon = new Tool(GlobalDefinitions.suppressor) val terminal = new Terminal(AutoRepairIntegrationTest.terminal_definition) - val guid = new NumberPoolHub(new MaxNumberSource(max = 10)) - val zone = new Zone("test", new ZoneMap("test"), 0) { - override def SetupNumberPools() = {} - GUID(guid) - } - val avatarProbe = new TestProbe(system) - zone.AvatarEvents = avatarProbe.ref - + terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal") guid.register(player, number = 1) guid.register(weapon, number = 2) guid.register(weapon.AmmoSlot.Box, number = 3) guid.register(terminal, number = 4) - - val building = Building.Structure(StructureType.Tower)(name = "test-building", guid = 6, map_id = 0, zone, context) - building.Invalidate() guid.register(building, number = 6) - building.Amenities = terminal - terminal.Actor = context.actorOf(Props(classOf[TerminalControl], terminal), name = "test-terminal") + building.Amenities = terminal + building.Actor ! BuildingActor.SuppliedWithNtu() //artificial + building.Actor ! BuildingActor.PowerOn() //artificial val wep_fmode = weapon.FireMode val wep_prof = wep_fmode.Add val proj = weapon.Projectile val proj_prof = proj.asInstanceOf[DamageProfile] val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resolved = DamageInteraction( SourceEntry(terminal), - terminal.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + terminal.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() "AutoRepair" should { "should activate on damage and trade NTU from the tower for repairs" in { diff --git a/src/test/scala/objects/AutoRepairTest.scala b/src/test/scala/objects/AutoRepairTest.scala index d175ca2c1..5f1374078 100644 --- a/src/test/scala/objects/AutoRepairTest.scala +++ b/src/test/scala/objects/AutoRepairTest.scala @@ -7,13 +7,16 @@ import base.FreedContextActorTest import net.psforever.actors.commands.NtuCommand import net.psforever.actors.zone.BuildingActor import net.psforever.objects.avatar.Avatar -import net.psforever.objects.ballistics.{Projectile, ProjectileResolution, ResolvedProjectile, SourceEntry} +import net.psforever.objects.ballistics.{Projectile, SourceEntry} import net.psforever.objects.guid.NumberPoolHub import net.psforever.objects.guid.source.MaxNumberSource import net.psforever.objects.serverobject.structures.{AutoRepairStats, Building, StructureType} import net.psforever.objects.serverobject.terminals.{OrderTerminalDefinition, Terminal, TerminalControl} import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.base.DamageResolution import net.psforever.objects.vital.damage.DamageProfile +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.{GlobalDefinitions, Player, Tool} import net.psforever.services.ServiceManager @@ -54,14 +57,17 @@ class AutoRepairRequestNtuTest extends FreedContextActorTest { val proj = weapon.Projectile val proj_prof = proj.asInstanceOf[DamageProfile] val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resolved = DamageInteraction( + DamageResolution.Hit, SourceEntry(terminal), - terminal.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + terminal.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() "AutoRepair" should { "asks owning building for NTU after damage" in { @@ -114,14 +120,17 @@ class AutoRepairRequestNtuRepeatTest extends FreedContextActorTest { val proj = weapon.Projectile val proj_prof = proj.asInstanceOf[DamageProfile] val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resolved = DamageInteraction( + DamageResolution.Hit, SourceEntry(terminal), - terminal.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + terminal.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() "AutoRepair" should { "repeatedly asks owning building for NTU after damage" in { @@ -176,14 +185,17 @@ class AutoRepairNoRequestNtuTest extends FreedContextActorTest { val proj = weapon.Projectile val proj_prof = proj.asInstanceOf[DamageProfile] val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resolved = DamageInteraction( + DamageResolution.Hit, SourceEntry(terminal), - terminal.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + terminal.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() "AutoRepair" should { "not ask for NTU after damage if it expects no NTU" in { @@ -231,14 +243,17 @@ class AutoRepairRestoreRequestNtuTest extends FreedContextActorTest { val proj = weapon.Projectile val proj_prof = proj.asInstanceOf[DamageProfile] val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resolved = DamageInteraction( + DamageResolution.Hit, SourceEntry(terminal), - terminal.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + terminal.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() "AutoRepair" should { "ask for NTU after damage if its expectation of NTU is restored" in { @@ -295,14 +310,17 @@ class AutoRepairRepairWithNtuTest extends FreedContextActorTest { val proj = weapon.Projectile val proj_prof = proj.asInstanceOf[DamageProfile] val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resolved = DamageInteraction( + DamageResolution.Hit, SourceEntry(terminal), - terminal.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + terminal.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() "AutoRepair" should { "repair some of the damage when it receives NTU" in { @@ -354,14 +372,17 @@ class AutoRepairRepairWithNtuUntilDoneTest extends FreedContextActorTest { val proj = weapon.Projectile val proj_prof = proj.asInstanceOf[DamageProfile] val projectile = Projectile(proj, weapon.Definition, wep_fmode, player, Vector3(2, 0, 0), Vector3.Zero) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resolved = DamageInteraction( + DamageResolution.Hit, SourceEntry(terminal), - terminal.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + terminal.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() "AutoRepair" should { "ask for NTU after damage and repair some of the damage when it receives NTU, until fully-repaired" in { diff --git a/src/test/scala/objects/DamageModelTests.scala b/src/test/scala/objects/DamageModelTests.scala index 648bdf757..352c2fbd1 100644 --- a/src/test/scala/objects/DamageModelTests.scala +++ b/src/test/scala/objects/DamageModelTests.scala @@ -2,7 +2,8 @@ package objects import net.psforever.objects._ -import net.psforever.objects.vital.damage.{DamageCalculations, DamageModifiers, DamageProfile} +import net.psforever.objects.vital.damage.{DamageProfile, _} +import net.psforever.objects.vital.projectile._ import DamageCalculations._ import net.psforever.objects.vital.resistance.ResistanceCalculations import ResistanceCalculations._ @@ -10,11 +11,13 @@ import net.psforever.objects.vital.resolution.ResolutionCalculations import ResolutionCalculations._ import net.psforever.objects.ballistics._ import net.psforever.objects.definition.{ProjectileDefinition, VehicleDefinition} -import net.psforever.objects.vital.{DamageType, Vitality} +import net.psforever.objects.vital.Vitality import net.psforever.packet.game.objectcreate.ObjectClass import net.psforever.types._ import org.specs2.mutable.Specification import net.psforever.objects.avatar.Avatar +import net.psforever.objects.vital.base._ +import net.psforever.objects.vital.interaction.DamageInteraction class DamageCalculationsTests extends Specification { "DamageCalculations" should { @@ -27,11 +30,13 @@ class DamageCalculationsTests extends Specification { val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) val target = Vehicle(GlobalDefinitions.fury) target.Position = Vector3(10, 0, 0) - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target.DamageModel + ), Vector3(15, 0, 0) ) @@ -60,173 +65,198 @@ class DamageCalculationsTests extends Specification { } "no degrade damage modifier" in { - DamageModifiers.SameHit.Calculate(100, resprojectile) mustEqual 100 + SameHit.calculate(100, resprojectile) mustEqual 100 } "degrade over distance damage modifier (no degrade)" in { - DamageModifiers.DistanceDegrade.Calculate(100, resprojectile) == 100 mustEqual true + DistanceDegrade.calculate(100, resprojectile) == 100 mustEqual true } "cut off damage at max distance (no cutoff)" in { - DamageModifiers.MaxDistanceCutoff.Calculate(100, resprojectile) == 100 mustEqual true + MaxDistanceCutoff.calculate(100, resprojectile) == 100 mustEqual true } "cut off damage at max distance (cutoff)" in { - val cutoffprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val cutoffprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target.DamageModel + ), Vector3(1500, 0, 0) ) - DamageModifiers.MaxDistanceCutoff.Calculate(100, cutoffprojectile) == 0 mustEqual true + val damage = MaxDistanceCutoff.calculate(100, cutoffprojectile) + damage == 0 mustEqual true } "cut off damage at custom distance (no cutoff)" in { - DamageModifiers.CustomDistanceCutoff(10).Calculate(100, resprojectile) == 0 mustEqual true + CustomDistanceCutoff(10).calculate(100, resprojectile) == 0 mustEqual true } "cut off damage at custom distance (cutoff)" in { - val coffprojectile = ResolvedProjectile( - ProjectileResolution.Splash, - projectile, + val coffprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Splash, + projectile, + target.DamageModel + ), Vector3(10, 0, 0) ) - DamageModifiers.CustomDistanceCutoff(2).Calculate(100, coffprojectile) == 0 mustEqual true + CustomDistanceCutoff(2).calculate(100, coffprojectile) == 0 mustEqual true } "degrade over distance damage modifier (some degrade)" in { - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Splash, - projectile, + val resprojectile2 = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Splash, + projectile, + target.DamageModel + ), Vector3(100, 0, 0) ) - val damage = DamageModifiers.DistanceDegrade.Calculate(100, resprojectile2) + val damage = DistanceDegrade.calculate(100, resprojectile2) damage < 100 && damage > 0 mustEqual true } "degrade over distance damage modifier (zero'd)" in { - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Splash, - projectile, + val resprojectile2 = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Splash, + projectile, + target.DamageModel + ), Vector3(1000, 0, 0) ) - DamageModifiers.DistanceDegrade.Calculate(100, resprojectile2) == 0 mustEqual true + DistanceDegrade.calculate(100, resprojectile2) == 0 mustEqual true } "degrade at radial distance damage modifier (no degrade)" in { - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Splash, - projectile, + val resprojectile2 = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Splash, + projectile, + target.DamageModel + ), Vector3(10, 0, 0) ) - DamageModifiers.RadialDegrade.Calculate(100, resprojectile2) == 100 mustEqual true + RadialDegrade.calculate(100, resprojectile2) == 100 mustEqual true } "degrade at radial distance damage modifier (some degrade)" in { - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Splash, - projectile, + val resprojectile2 = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Splash, + projectile, + target.DamageModel + ), Vector3(12, 0, 0) ) - val damage = DamageModifiers.RadialDegrade.Calculate(100, resprojectile2) + val damage = RadialDegrade.calculate(100, resprojectile2) damage < 100 && damage > 0 mustEqual true } "degrade at radial distance damage modifier (zero'd)" in { - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Splash, - projectile, + val resprojectile2 = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Splash, + projectile, + target.DamageModel + ), Vector3(100, 0, 0) ) - DamageModifiers.RadialDegrade.Calculate(100, resprojectile2) == 0 mustEqual true + RadialDegrade.calculate(100, resprojectile2) == 0 mustEqual true } "lash degrade (no lash; too close)" in { - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Lash, - projectile, + val resprojectile2 = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Lash, + projectile, + target.DamageModel + ), Vector3(5, 0, 0) //compared to Vector3(2, 2, 0) ) - DamageModifiers.Lash.Calculate(100, resprojectile2) == 0 mustEqual true + Lash.calculate(100, resprojectile2) == 0 mustEqual true } "lash degrade (lash)" in { - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Lash, - projectile, + val resprojectile2 = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Lash, + projectile, + target.DamageModel + ), Vector3(20, 0, 0) ) - val damage = DamageModifiers.Lash.Calculate(100, resprojectile2) + val damage = Lash.calculate(100, resprojectile2) damage < 100 && damage > 0 mustEqual true } "lash degrade (no lash; too far)" in { - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Lash, - projectile, + val resprojectile2 = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Lash, + projectile, + target.DamageModel + ), Vector3(1000, 0, 0) ) - DamageModifiers.Lash.Calculate(100, resprojectile2) == 0 mustEqual true + Lash.calculate(100, resprojectile2) == 0 mustEqual true } "fireball aggravated damage (aggravated splash burn" in { - val burnWeapon = Tool(GlobalDefinitions.flamethrower) - val burnProjectile = Projectile( - burnWeapon.Projectile, - burnWeapon.Definition, - burnWeapon.FireMode, - player, - Vector3(2, 2, 0), - Vector3.Zero - ) - val burnRes = ResolvedProjectile( - ProjectileResolution.AggravatedSplashBurn, - projectile, +// val burnWeapon = Tool(GlobalDefinitions.flamethrower) +// val burnProjectile = Projectile( +// burnWeapon.Projectile, +// burnWeapon.Definition, +// burnWeapon.FireMode, +// player, +// Vector3(2, 2, 0), +// Vector3.Zero +// ) + val burnRes = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.AggravatedSplashBurn, + projectile, + target.DamageModel + ), Vector3(15, 0, 0) ) - val resistance = burnRes.damage_model.ResistUsing(burnRes)(burnRes) - DamageModifiers.FireballAggravatedBurn.Calculate(100, burnRes) == (1 + resistance) mustEqual true + val resistance = target.DamageModel.ResistUsing(burnRes)(burnRes) + FireballAggravatedBurn.calculate(100, burnRes) == (1 + resistance) mustEqual true } "fireball aggravated damage (noral splash, no modification)" in { - val burnWeapon = Tool(GlobalDefinitions.flamethrower) - val burnProjectile = Projectile( - burnWeapon.Projectile, - burnWeapon.Definition, - burnWeapon.FireMode, - player, - Vector3(2, 2, 0), - Vector3.Zero - ) - val burnRes = ResolvedProjectile( - ProjectileResolution.Splash, - projectile, +// val burnWeapon = Tool(GlobalDefinitions.flamethrower) +// val burnProjectile = Projectile( +// burnWeapon.Projectile, +// burnWeapon.Definition, +// burnWeapon.FireMode, +// player, +// Vector3(2, 2, 0), +// Vector3.Zero +// ) + val burnRes = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Splash, + projectile, + target.DamageModel + ), Vector3(15, 0, 0) ) - DamageModifiers.FireballAggravatedBurn.Calculate(100, burnRes) == 100 mustEqual true + FireballAggravatedBurn.calculate(100, burnRes) == 100 mustEqual true } val charge_weapon = Tool(GlobalDefinitions.spiker) @@ -243,42 +273,52 @@ class DamageCalculationsTests extends Specification { "charge (none)" in { val cprojectile = charge_projectile.quality(ProjectileQuality.Modified(0)) - val rescprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - cprojectile, + val rescprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + cprojectile, + target.DamageModel + ), Vector3(15, 0, 0) ) - val damage = DamageModifiers.SpikerChargeDamage.Calculate(chargeBaseDamage, rescprojectile) - val calcDam = minDamageBase + math.floor(chargeBaseDamage * rescprojectile.projectile.quality.mod) + val damage = SpikerChargeDamage.calculate(chargeBaseDamage, rescprojectile) + val calcDam = minDamageBase + math.floor( + chargeBaseDamage * rescprojectile.cause.asInstanceOf[ProjectileReason].projectile.quality.mod + ) damage mustEqual calcDam } "charge (half)" in { val cprojectile = charge_projectile.quality(ProjectileQuality.Modified(0.5f)) - val rescprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - cprojectile, + val rescprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + cprojectile, + target.DamageModel + ), Vector3(15, 0, 0) ) - val damage = DamageModifiers.SpikerChargeDamage.Calculate(chargeBaseDamage, rescprojectile) - val calcDam = minDamageBase + math.floor(chargeBaseDamage * rescprojectile.projectile.quality.mod) + val damage = SpikerChargeDamage.calculate(chargeBaseDamage, rescprojectile) + val calcDam = minDamageBase + math.floor( + chargeBaseDamage * rescprojectile.cause.asInstanceOf[ProjectileReason].projectile.quality.mod + ) damage mustEqual calcDam } "charge (full)" in { val cprojectile = charge_projectile.quality(ProjectileQuality.Modified(1)) - val rescprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - cprojectile, + val rescprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + cprojectile, + target.DamageModel + ), Vector3(15, 0, 0) ) - val damage = DamageModifiers.SpikerChargeDamage.Calculate(chargeBaseDamage, rescprojectile) + val damage = SpikerChargeDamage.calculate(chargeBaseDamage, rescprojectile) val calcDam = minDamageBase + chargeBaseDamage damage mustEqual calcDam } @@ -294,124 +334,142 @@ class DamageCalculationsTests extends Specification { ) "flak hit (resolution is splash, no degrade)" in { - val resfprojectile = ResolvedProjectile( - ProjectileResolution.Splash, - flak_projectile, + val resfprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Splash, + flak_projectile, + target.DamageModel + ), Vector3(10, 0, 0) ) - val damage = DamageModifiers.FlakHit.Calculate(100, resfprojectile) + val damage = FlakHit.calculate(100, resfprojectile) damage == 100 mustEqual true } "flak hit (resolution is hit, no degrade)" in { - val resfprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - flak_projectile, + val resfprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + flak_projectile, + target.DamageModel + ), Vector3(10, 0, 0) ) - val damage = DamageModifiers.FlakHit.Calculate(100, resfprojectile) + val damage = FlakHit.calculate(100, resfprojectile) damage == 100 mustEqual true } "flak burst (resolution is hit)" in { - val resfprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - flak_projectile, + val resfprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + flak_projectile, + target.DamageModel + ), Vector3(15, 0, 0) ) - val damage = DamageModifiers.FlakBurst.Calculate(100, resfprojectile) + val damage = FlakBurst.calculate(100, resfprojectile) damage == 100 mustEqual true } "flak burst (resolution is splash, no degrade)" in { - val resfprojectile = ResolvedProjectile( - ProjectileResolution.Splash, - flak_projectile, + val resfprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Splash, + flak_projectile, + target.DamageModel + ), Vector3(10, 0, 0) ) - val damage = DamageModifiers.FlakBurst.Calculate(100, resfprojectile) + val damage = FlakBurst.calculate(100, resfprojectile) damage == 100 mustEqual true } "flak burst (resolution is splash, some degrade)" in { - val resfprojectile = ResolvedProjectile( - ProjectileResolution.Splash, - flak_projectile, + val resfprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Splash, + flak_projectile, + target.DamageModel + ), Vector3(5, 0, 0) ) - val damage = DamageModifiers.FlakBurst.Calculate(100, resfprojectile) + val damage = FlakBurst.calculate(100, resfprojectile) damage < 100 mustEqual true } "galaxy gunship reduction (target is galaxy_gunship, no shields)" in { val vehicle = Vehicle(GlobalDefinitions.galaxy_gunship) - val resfprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resfprojectile = DamageInteraction( SourceEntry(vehicle), - vehicle.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + vehicle.DamageModel + ), Vector3(5, 0, 0) ) - val damage = DamageModifiers.GalaxyGunshipReduction(0.63f).Calculate(100, resfprojectile) + val damage = GalaxyGunshipReduction(0.63f).calculate(100, resfprojectile) damage == 63 mustEqual true } "galaxy gunship reduction (target is galaxy_gunship)" in { val vehicle = Vehicle(GlobalDefinitions.galaxy_gunship) vehicle.Shields = 1 - val resfprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resfprojectile = DamageInteraction( SourceEntry(vehicle), - vehicle.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + vehicle.DamageModel + ), Vector3(5, 0, 0) ) - val damage = DamageModifiers.GalaxyGunshipReduction(0.63f).Calculate(100, resfprojectile) + val damage = GalaxyGunshipReduction(0.63f).calculate(100, resfprojectile) damage == 100 mustEqual true } "galaxy gunship reduction (target is vehicle, but not a galaxy_gunship)" in { - val resfprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resfprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target.DamageModel + ), Vector3(5, 0, 0) ) - val damage = DamageModifiers.GalaxyGunshipReduction(0.63f).Calculate(100, resfprojectile) + val damage = GalaxyGunshipReduction(0.63f).calculate(100, resfprojectile) damage == 100 mustEqual true } "galaxy gunship reduction (target is not a vehicle)" in { val tplayer = Player(Avatar(0, "TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) - val resfprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resfprojectile = DamageInteraction( SourceEntry(tplayer), - tplayer.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + tplayer.DamageModel + ), Vector3(5, 0, 0) ) - val damage = DamageModifiers.GalaxyGunshipReduction(0.63f).Calculate(100, resfprojectile) + val damage = GalaxyGunshipReduction(0.63f).calculate(100, resfprojectile) damage == 100 mustEqual true } "extract a complete damage profile" in { - val result1 = DamageModifiers.RadialDegrade.Calculate( + val result1 = RadialDegrade.calculate( AgainstVehicle(proj_prof) + AgainstVehicle(wep_prof), resprojectile ) - val result2 = DamageCalculations.DamageWithModifiers(AgainstVehicle, resprojectile) + val result2 = DamageCalculations.WithModifiers(AgainstVehicle, resprojectile) result1 mustEqual result2 } } @@ -427,11 +485,13 @@ class ResistanceCalculationsTests extends Specification { "ResistanceCalculations" should { "ignore all targets" in { val target = Vehicle(GlobalDefinitions.fury) - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target.DamageModel + ), Vector3.Zero ) InvalidTarget(resprojectile).isFailure mustEqual true @@ -439,11 +499,13 @@ class ResistanceCalculationsTests extends Specification { "discern standard infantry targets" in { val target = player - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target.DamageModel + ), Vector3.Zero ) ValidInfantryTarget(resprojectile).isSuccess mustEqual true @@ -455,11 +517,13 @@ class ResistanceCalculationsTests extends Specification { "discern mechanized infantry targets" in { val target = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) target.ExoSuit = ExoSuitType.MAX - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target.DamageModel + ), Vector3.Zero ) ValidInfantryTarget(resprojectile).isSuccess mustEqual false @@ -470,11 +534,13 @@ class ResistanceCalculationsTests extends Specification { "discern ground vehicle targets" in { val target = Vehicle(GlobalDefinitions.fury) - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target.DamageModel + ), Vector3.Zero ) ValidInfantryTarget(resprojectile).isSuccess mustEqual false @@ -485,11 +551,13 @@ class ResistanceCalculationsTests extends Specification { "discern flying vehicle targets" in { val target = Vehicle(GlobalDefinitions.mosquito) - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target.DamageModel + ), Vector3.Zero ) ValidInfantryTarget(resprojectile).isSuccess mustEqual false @@ -531,11 +599,13 @@ class ResolutionCalculationsTests extends Specification { "ResolutionCalculations" should { "calculate no damage" in { val target = player - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target.DamageModel + ), Vector3.Zero ) ResolutionCalculations.NoDamage(resprojectile)(50, 50) mustEqual 0 @@ -543,21 +613,25 @@ class ResolutionCalculationsTests extends Specification { "calculate no infantry damage for vehicles" in { val target1 = Vehicle(GlobalDefinitions.fury) //! - val resprojectile1 = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile1 = DamageInteraction( SourceEntry(target1), - target1.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target1.DamageModel + ), Vector3.Zero ) InfantryDamage(resprojectile1)(50, 10) mustEqual (0, 0) val target2 = player - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile2 = DamageInteraction( SourceEntry(target2), - target2.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target2.DamageModel + ), Vector3.Zero ) InfantryDamage(resprojectile2)(50, 10) mustEqual (40, 10) @@ -587,21 +661,25 @@ class ResolutionCalculationsTests extends Specification { player2.Spawn() "calculate no max damage for vehicles" in { val target1 = Vehicle(GlobalDefinitions.fury) //! - val resprojectile1 = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile1 = DamageInteraction( SourceEntry(target1), - target1.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target1.DamageModel + ), Vector3.Zero ) MaxDamage(resprojectile1)(50, 10) mustEqual (0, 0) val target2 = player2 - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile2 = DamageInteraction( SourceEntry(target2), - target2.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target2.DamageModel + ), Vector3.Zero ) MaxDamage(resprojectile2)(50, 10) mustEqual (0, 40) @@ -623,21 +701,25 @@ class ResolutionCalculationsTests extends Specification { "do not care if target is infantry for vehicle calculations" in { val target1 = player - val resprojectile1 = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile1 = DamageInteraction( SourceEntry(target1), - target1.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target1.DamageModel + ), Vector3.Zero ) VehicleDamageAfterResist(resprojectile1)(50, 10) mustEqual 40 val target2 = Vehicle(GlobalDefinitions.fury) //! - val resprojectile2 = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile2 = DamageInteraction( SourceEntry(target2), - target2.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + target2.DamageModel + ), Vector3.Zero ) VehicleDamageAfterResist(resprojectile2)(50, 10) mustEqual 40 @@ -690,14 +772,16 @@ class DamageModelTests extends Specification { tplayer.Health mustEqual 100 tplayer.Armor mustEqual 50 - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(tplayer), - tplayer.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + tplayer.DamageModel + ), Vector3.Zero ) - val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile) + val func = resprojectile.calculate() func(tplayer) tplayer.Health mustEqual 65 tplayer.Armor mustEqual 35 @@ -710,15 +794,16 @@ class DamageModelTests extends Specification { tplayer.Health mustEqual 100 tplayer.Armor mustEqual 50 - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(tplayer), - tplayer.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + tplayer.DamageModel + ), Vector3.Zero ) - val func: Any => ResolvedProjectile = - resprojectile.damage_model.Calculate(resprojectile, DamageType.Splash) + val func = resprojectile.calculate(DamageType.Splash) func(tplayer) tplayer.Health mustEqual 65 tplayer.Armor mustEqual 35 @@ -731,14 +816,16 @@ class DamageModelTests extends Specification { tplayer.Health mustEqual 100 tplayer.Armor mustEqual 50 - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(tplayer), - tplayer.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + tplayer.DamageModel + ), Vector3.Zero ) - val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile) + val func = resprojectile.calculate() tplayer.Armor = 0 func(tplayer) @@ -750,14 +837,16 @@ class DamageModelTests extends Specification { val vehicle = Vehicle(DamageModelTests.vehicle) vehicle.Health mustEqual 650 - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(vehicle), - vehicle.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + vehicle.DamageModel + ), Vector3.Zero ) - val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile) + val func = resprojectile.calculate() func(vehicle) vehicle.Health mustEqual 518 @@ -769,14 +858,16 @@ class DamageModelTests extends Specification { vehicle.Health mustEqual 650 vehicle.Shields mustEqual 10 - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(vehicle), - vehicle.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + vehicle.DamageModel + ), Vector3.Zero ) - val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile) + val func = resprojectile.calculate() func(vehicle) vehicle.Health mustEqual 528 @@ -789,14 +880,16 @@ class DamageModelTests extends Specification { vehicle.Health mustEqual 650 vehicle.Shields mustEqual 10 - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(vehicle), - vehicle.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + vehicle.DamageModel + ), Vector3.Zero ) - val func: Any => ResolvedProjectile = resprojectile.damage_model.Calculate(resprojectile) + val func = resprojectile.calculate() func(vehicle) vehicle.Health mustEqual 528 @@ -810,15 +903,16 @@ class DamageModelTests extends Specification { val vehicle = Vehicle(DamageModelTests.vehicle) vehicle.Health mustEqual 650 - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(vehicle), - vehicle.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + vehicle.DamageModel + ), Vector3.Zero ) - val func: Any => ResolvedProjectile = - resprojectile.damage_model.Calculate(resprojectile, DamageType.Splash) + val func = resprojectile.calculate(DamageType.Splash) func(vehicle) vehicle.Health mustEqual 518 @@ -841,7 +935,7 @@ object DamageModelTests { InitialVelocity = 75 Lifespan = 5f ProjectileDefinition.CalculateDerivedFields(pdef = this) - Modifiers = DamageModifiers.RadialDegrade + Modifiers = RadialDegrade } final val vehicle = new VehicleDefinition(ObjectClass.fury) { diff --git a/src/test/scala/objects/DamageableTest.scala b/src/test/scala/objects/DamageableTest.scala index b80730360..3c8fcff72 100644 --- a/src/test/scala/objects/DamageableTest.scala +++ b/src/test/scala/objects/DamageableTest.scala @@ -27,8 +27,12 @@ import net.psforever.services.support.SupportActor import net.psforever.services.vehicle.support.TurretUpgrader import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import org.specs2.mutable.Specification + import scala.concurrent.duration._ import net.psforever.objects.avatar.Avatar +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.base.DamageResolution +import net.psforever.objects.vital.projectile.ProjectileReason class DamageableTest extends Specification { val player1 = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) @@ -39,11 +43,13 @@ class DamageableTest extends Specification { "Damageable" should { "permit damage" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + val resolved = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + target.DamageModel + ), Vector3.Zero ) @@ -52,19 +58,13 @@ class DamageableTest extends Specification { "ignore attempts at non-zero damage" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectileA, - weaponA.Definition, - weaponA.FireMode, - PlayerSource(player1), - 0, - Vector3.Zero, - Vector3.Zero - ), + val resolved = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + target.DamageModel + ), Vector3.Zero ) @@ -77,21 +77,32 @@ class DamageableTest extends Specification { new Building("test-building", 0, 0, Zone.Nowhere, StructureType.Building, GlobalDefinitions.building) { Faction = player1.Faction } - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + + val resolvedFF = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + target.DamageModel + ), Vector3.Zero ) - target.Definition.DamageableByFriendlyFire mustEqual false target.Faction == player1.Faction mustEqual true - Damageable.CanDamage(target, projectileA.Damage0, resolved) mustEqual false + Damageable.CanDamage(target, projectileA.Damage0, resolvedFF) mustEqual false target.Owner.Faction = PlanetSideEmpire.NC + val resolvedNonFF = DamageInteraction( + SourceEntry(target), + ProjectileReason( + DamageResolution.Hit, + Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + target.DamageModel + ), + Vector3.Zero + ) target.Faction != player1.Faction mustEqual true - Damageable.CanDamage(target, projectileA.Damage0, resolved) mustEqual true + Damageable.CanDamage(target, projectileA.Damage0, resolvedNonFF) mustEqual true } "ignore attempts at damaging a target that is not damageable" in { @@ -100,11 +111,13 @@ class DamageableTest extends Specification { new Building("test-building", 0, 0, Zone.Nowhere, StructureType.Building, GlobalDefinitions.building) { Faction = PlanetSideEmpire.NC } - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + val resolved = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + target.DamageModel + ), Vector3.Zero ) @@ -127,11 +140,13 @@ class DamageableTest extends Specification { new Building("test-building", 0, 0, Zone.Nowhere, StructureType.Building, GlobalDefinitions.building) { Faction = player1.Faction } - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + val resolved = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + target.DamageModel + ), Vector3.Zero ) @@ -151,60 +166,74 @@ class DamageableTest extends Specification { "permit jamming" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + val resolved = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + target.DamageModel + ), Vector3.Zero ) - resolved.projectile.profile.JammerProjectile mustEqual true + resolved.cause.source.HasJammedEffectDuration mustEqual true Damageable.CanJammer(target, resolved) mustEqual true } "ignore attempts at jamming if the projectile is does not cause the effect" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + val resolved = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectileA, weaponA.Definition, weaponA.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + target.DamageModel + ), Vector3.Zero ) //decimator - resolved.projectile.profile.JammerProjectile mustEqual false + resolved.cause.source.HasJammedEffectDuration mustEqual false Damageable.CanJammer(target, resolved) mustEqual false } "ignore attempts at jamming friendly targets" in { val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) target.Faction = player1.Faction - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + val resolved = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + target.DamageModel + ), Vector3.Zero ) - resolved.projectile.profile.JammerProjectile mustEqual true - resolved.projectile.owner.Faction == target.Faction mustEqual true + resolved.cause.source.HasJammedEffectDuration mustEqual true + resolved.adversarial match { + case Some(adversarial) => adversarial.attacker.Faction mustEqual adversarial.defender.Faction + case None => ko + } Damageable.CanJammer(target, resolved) mustEqual false } "ignore attempts at jamming targets that are not jammable" in { val target = new TrapDeployable(GlobalDefinitions.tank_traps) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + val resolved = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + target.DamageModel + ), Vector3.Zero ) - resolved.projectile.profile.JammerProjectile mustEqual true - resolved.projectile.owner.Faction == target.Faction mustEqual false + resolved.cause.source.HasJammedEffectDuration mustEqual true + resolved.adversarial match { + case Some(adversarial) => adversarial.attacker.Faction mustNotEqual adversarial.defender.Faction + case None => ko + } target.isInstanceOf[JammableUnit] mustEqual false Damageable.CanJammer(target, resolved) mustEqual false } @@ -215,16 +244,21 @@ class DamageableTest extends Specification { player2.GUID = PlanetSideGUID(1) val target = new SensorDeployable(GlobalDefinitions.motionalarmsensor) target.Faction = player1.Faction - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + val resolved = DamageInteraction( SourceEntry(target), - target.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectileB, weaponB.Definition, weaponB.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + target.DamageModel, + ), Vector3.Zero ) - resolved.projectile.profile.JammerProjectile mustEqual true - resolved.projectile.owner.Faction == target.Faction mustEqual true + resolved.cause.source.HasJammedEffectDuration mustEqual true + resolved.adversarial match { + case Some(adversarial) => adversarial.attacker.Faction mustEqual adversarial.defender.Faction + case None => ko + } target.isInstanceOf[JammableUnit] mustEqual true target.HackedBy.nonEmpty mustEqual false Damageable.CanJammer(target, resolved) mustEqual false @@ -270,22 +304,16 @@ class DamageableEntityDamageTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(gen), - gen.DamageModel, - Vector3(1, 0, 0) + ProjectileReason( + DamageResolution.Hit, + Projectile(projectile, weapon.Definition, weapon.FireMode, PlayerSource(player1), 0, Vector3(2, 0, 0), Vector3(-1, 0, 0)), + gen.DamageModel + ), + Vector3(1,0,0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) "DamageableEntity" should { @@ -341,22 +369,24 @@ class DamageableEntityDestroyedTest extends ActorTest { guid.register(player1, 3) val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(mech), - mech.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + mech.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -415,22 +445,24 @@ class DamageableEntityNotDestroyTwice extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(gen), - gen.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + gen.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -486,22 +518,24 @@ class DamageableAmenityTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(term), - term.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + term.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -579,22 +613,24 @@ class DamageableMountableDamageTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(mech), - mech.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + mech.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() mech.Seats(0).Occupant = player2 //seat the player player2.VehicleSeated = Some(mech.GUID) //seat the player expectNoMessage(200 milliseconds) @@ -674,22 +710,24 @@ class DamageableMountableDestroyTest extends ActorTest { building.Actor = buildingProbe.ref val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(mech), - mech.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + mech.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() mech.Seats(0).Occupant = player2 //seat the player player2.VehicleSeated = Some(mech.GUID) //seat the player expectNoMessage(200 milliseconds) @@ -719,7 +757,7 @@ class DamageableMountableDestroyTest extends ActorTest { ) assert( msg3 match { - case Player.Die() => true + case Player.Die(_) => true case _ => false } ) @@ -764,23 +802,25 @@ class DamageableWeaponTurretDamageTest extends ActorTest { val weapon = Tool(GlobalDefinitions.suppressor) val projectile = weapon.Projectile - val turretSource = SourceEntry(turret) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) + val pSource = PlayerSource(player1) + val resolved = DamageInteraction( + SourceEntry(turret), + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + pSource, + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + turret.DamageModel ), - turretSource, - turret.DamageModel, Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -801,8 +841,8 @@ class DamageableWeaponTurretDamageTest extends ActorTest { assert( msg3 match { case activity: Zone.HotSpot.Activity => - activity.attacker == PlayerSource(player1) && - activity.defender == turretSource && + activity.attacker == pSource && + activity.defender == SourceEntry(turret) && activity.location == Vector3(1, 0, 0) case _ => false } @@ -864,22 +904,24 @@ class DamageableWeaponTurretJammerTest extends ActorTest { val weapon = Tool(GlobalDefinitions.jammer_grenade) val projectile = weapon.Projectile val turretSource = SourceEntry(turret) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( turretSource, - turret.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + turret.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -966,41 +1008,46 @@ class DamageableWeaponTurretDestructionTest extends ActorTest { val turretSource = SourceEntry(turret) val weaponA = Tool(GlobalDefinitions.jammer_grenade) val projectileA = weaponA.Projectile - val resolvedA = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectileA, - weaponA.Definition, - weaponA.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolvedA = DamageInteraction( turretSource, - turret.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectileA, + weaponA.Definition, + weaponA.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + turret.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageToA = resolvedA.damage_model.Calculate(resolvedA) + val applyDamageToA = resolvedA.calculate() val weaponB = Tool(GlobalDefinitions.phoenix) //decimator val projectileB = weaponB.Projectile - val resolvedB = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectileB, - weaponB.Definition, - weaponB.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolvedB = DamageInteraction( + turretSource, - turret.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectileB, + weaponB.Definition, + weaponB.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + turret.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageToB = resolvedB.damage_model.Calculate(resolvedB) + val applyDamageToB = resolvedB.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -1037,7 +1084,7 @@ class DamageableWeaponTurretDestructionTest extends ActorTest { ) assert( msg3 match { - case Player.Die() => true + case Player.Die(_) => true case _ => false } ) @@ -1107,22 +1154,24 @@ class DamageableVehicleDamageTest extends ActorTest { val weapon = Tool(GlobalDefinitions.suppressor) val projectile = weapon.Projectile val vehicleSource = SourceEntry(atv) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( vehicleSource, - atv.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + atv.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -1152,7 +1201,7 @@ class DamageableVehicleDamageTest extends ActorTest { msg3 match { case activity: Zone.HotSpot.Activity => activity.attacker == PlayerSource(player1) && - activity.defender == vehicleSource && + activity.defender == VehicleSource(atv) && activity.location == Vector3(1, 0, 0) case _ => false } @@ -1234,23 +1283,25 @@ class DamageableVehicleDamageMountedTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val vehicleSource = SourceEntry(lodestar) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) + val pSource = PlayerSource(player1) + val resolved = DamageInteraction( + SourceEntry(lodestar), + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + pSource, + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + lodestar.DamageModel ), - vehicleSource, - lodestar.DamageModel, Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -1281,8 +1332,8 @@ class DamageableVehicleDamageMountedTest extends ActorTest { assert( msg3 match { case activity: Zone.HotSpot.Activity => - activity.attacker == PlayerSource(player1) && - activity.defender == vehicleSource && + activity.attacker == pSource && + activity.defender == SourceEntry(lodestar) && activity.location == Vector3(1, 0, 0) case _ => false } @@ -1379,22 +1430,24 @@ class DamageableVehicleJammeringMountedTest extends ActorTest { val vehicleSource = SourceEntry(lodestar) val weapon = Tool(GlobalDefinitions.jammer_grenade) val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( vehicleSource, - lodestar.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + lodestar.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -1469,22 +1522,24 @@ class DamageableVehicleDestroyTest extends ActorTest { val weapon = Tool(GlobalDefinitions.suppressor) val projectile = weapon.Projectile val vehicleSource = SourceEntry(atv) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( vehicleSource, - atv.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + atv.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -1513,7 +1568,7 @@ class DamageableVehicleDestroyTest extends ActorTest { ) assert( msg3 match { - case Player.Die() => true + case Player.Die(_) => true case _ => false } ) @@ -1531,18 +1586,6 @@ class DamageableVehicleDestroyTest extends ActorTest { } class DamageableVehicleDestroyMountedTest extends ActorTest { - val guid = new NumberPoolHub(new MaxNumberSource(15)) - val zone = new Zone("test", new ZoneMap("test"), 0) { - override def SetupNumberPools() = {} - GUID(guid) - } - val activityProbe = TestProbe() - val avatarProbe = TestProbe() - val vehicleProbe = TestProbe() - zone.Activity = activityProbe.ref - zone.AvatarEvents = avatarProbe.ref - zone.VehicleEvents = vehicleProbe.ref - val atv = Vehicle(GlobalDefinitions.quadassault) //guid=1 atv.Actor = system.actorOf(Props(classOf[VehicleControl], atv), "atv-control") atv.Position = Vector3(1, 0, 0) @@ -1568,6 +1611,20 @@ class DamageableVehicleDestroyMountedTest extends ActorTest { val player3Probe = TestProbe() player3.Actor = player3Probe.ref + + val guid = new NumberPoolHub(new MaxNumberSource(15)) + val zone = new Zone("test", new ZoneMap("test"), 0) { + override def SetupNumberPools() = {} + GUID(guid) + override def LivePlayers = List(player1, player2, player3) + } + val activityProbe = TestProbe() + val avatarProbe = TestProbe() + val vehicleProbe = TestProbe() + zone.Activity = activityProbe.ref + zone.AvatarEvents = avatarProbe.ref + zone.VehicleEvents = vehicleProbe.ref + guid.register(atv, 1) guid.register(atvWeapon, 2) guid.register(atvWeapon.AmmoSlot.Box, 3) @@ -1595,41 +1652,45 @@ class DamageableVehicleDestroyMountedTest extends ActorTest { val vehicleSource = SourceEntry(lodestar) val weaponA = Tool(GlobalDefinitions.jammer_grenade) val projectileA = weaponA.Projectile - val resolvedA = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectileA, - weaponA.Definition, - weaponA.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolvedA = DamageInteraction( vehicleSource, - lodestar.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectileA, + weaponA.Definition, + weaponA.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + lodestar.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageToA = resolvedA.damage_model.Calculate(resolvedA) + val applyDamageToA = resolvedA.calculate() val weaponB = Tool(GlobalDefinitions.phoenix) //decimator val projectileB = weaponB.Projectile - val resolvedB = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectileB, - weaponB.Definition, - weaponB.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolvedB = DamageInteraction( vehicleSource, - lodestar.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectileB, + weaponB.Definition, + weaponB.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + lodestar.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageToB = resolvedB.damage_model.Calculate(resolvedB) + val applyDamageToB = resolvedB.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -1699,13 +1760,13 @@ class DamageableVehicleDestroyMountedTest extends ActorTest { ) assert( msg_player2 match { - case Player.Die() => true + case Player.Die(_) => true case _ => false } ) assert( msg_player3 match { - case Player.Die() => true + case Player.Die(_) => true case _ => false } ) diff --git a/src/test/scala/objects/DeployableTest.scala b/src/test/scala/objects/DeployableTest.scala index c734efead..1fa1db7b5 100644 --- a/src/test/scala/objects/DeployableTest.scala +++ b/src/test/scala/objects/DeployableTest.scala @@ -20,6 +20,9 @@ import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.support.SupportActor import net.psforever.objects.avatar.Avatar +import net.psforever.objects.vital.base.DamageResolution +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.projectile.ProjectileReason import scala.concurrent.duration._ @@ -333,14 +336,16 @@ class ExplosiveDeployableJammerTest extends ActorTest { val jMineSource = SourceEntry(j_mine) val pSource = PlayerSource(player1) val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + val resolved = DamageInteraction( jMineSource, - j_mine.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + j_mine.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageToJ = resolved.damage_model.Calculate(resolved) + val applyDamageToJ = resolved.calculate() "ExplosiveDeployable" should { "handle being jammered appropriately (no detonation)" in { @@ -428,17 +433,18 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest { h_mine.Faction = PlanetSideEmpire.NC h_mine.Actor = system.actorOf(Props(classOf[ExplosiveDeployableControl], h_mine), "h-mine-control") - val hMineSource = SourceEntry(h_mine) val pSource = PlayerSource(player1) val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), - hMineSource, - h_mine.DamageModel, + val resolved = DamageInteraction( + SourceEntry(h_mine), + ProjectileReason( + DamageResolution.Hit, + Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + h_mine.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageToH = resolved.damage_model.Calculate(resolved) + val applyDamageToH = resolved.calculate() "ExplosiveDeployable" should { "handle being jammered appropriately (detonation)" in { @@ -498,7 +504,7 @@ class ExplosiveDeployableJammerExplodeTest extends ActorTest { ) assert( msg_activity match { - case Zone.HotSpot.Activity(target, attacker, _) => (target eq hMineSource) && (attacker eq pSource) + case Zone.HotSpot.Conflict(target, attacker, _) => (target.Definition eq h_mine.Definition) && (attacker eq pSource) case _ => false } ) @@ -541,14 +547,16 @@ class ExplosiveDeployableDestructionTest extends ActorTest { val hMineSource = SourceEntry(h_mine) val pSource = PlayerSource(player1) val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + val resolved = DamageInteraction( hMineSource, - h_mine.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), + h_mine.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() "ExplosiveDeployable" should { "handle being destroyed" in { diff --git a/src/test/scala/objects/GeneratorTest.scala b/src/test/scala/objects/GeneratorTest.scala index 436164ec8..8a9516998 100644 --- a/src/test/scala/objects/GeneratorTest.scala +++ b/src/test/scala/objects/GeneratorTest.scala @@ -14,6 +14,10 @@ import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.generator.{Generator, GeneratorControl, GeneratorDefinition} import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.base.DamageResolution +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.projectile.ProjectileReason +import net.psforever.objects.vital.prop.DamageWithPosition import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.packet.game.{InventoryStateMessage, RepairMessage, TriggerEffectMessage} import net.psforever.types._ @@ -80,22 +84,24 @@ class GeneratorControlDamageTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(gen), - gen.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + gen.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -161,22 +167,24 @@ class GeneratorControlCriticalTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(gen), - gen.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + gen.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() val halfHealth = gen.Definition.MaxHealth / 2 expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -245,22 +253,24 @@ class GeneratorControlDestroyedTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(gen), - gen.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + gen.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -328,25 +338,6 @@ class GeneratorControlDestroyedTest extends ActorTest { } class GeneratorControlKillsTest extends ActorTest { - /* - to perform this test, players need to be added to the SOI organization of the test base in proximity of the generator - under normal player scenario, this is an automatic process - extending from the act of players being in a zone - and players being within the SOI radius from the center of a facility on a periodic check - the test base being used has no established SOI region or automatic SOI check refresh, - but its SOI information can be loaded with the players manually - the players need something to catch the die message - */ - val guid = new NumberPoolHub(new MaxNumberSource(5)) - val zone = new Zone("test", new ZoneMap("test"), 0) { - override def SetupNumberPools() = {} - GUID(guid) - } - val avatarProbe = TestProbe() - zone.AvatarEvents = avatarProbe.ref - val activityProbe = TestProbe() - zone.Activity = activityProbe.ref - val gen = Generator(GeneratorTest.generator_definition) //guid=2 gen.Position = Vector3(1, 0, 0) gen.Actor = system.actorOf(Props(classOf[GeneratorControl], gen), "generator-control") @@ -359,16 +350,24 @@ class GeneratorControlKillsTest extends ActorTest { player1.Actor = player1Probe.ref val player2 = Player(Avatar(0, "TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Female, 1, CharacterVoice.Mute)) //guid=4 - player2.Position = Vector3(15, 0, 0) //>14m from generator; lives + player2.Position = Vector3(25, 0, 0) //>14m from generator; lives player2.Spawn() val player2Probe = TestProbe() player2.Actor = player2Probe.ref + val avatarProbe = TestProbe() + val activityProbe = TestProbe() + val guid = new NumberPoolHub(new MaxNumberSource(5)) + val zone = new Zone("test", new ZoneMap("test"), 0) { + override def SetupNumberPools() = {} + GUID(guid) + override def LivePlayers = List(player1, player2) + override def AvatarEvents = avatarProbe.ref + override def Activity = activityProbe.ref + } val building = Building("test-building", 1, 1, zone, StructureType.Facility) //guid=1 building.Position = Vector3(1, 0, 0) - building.Zone = zone building.Amenities = gen - building.PlayersInSOI = List(player1, player2) val buildingProbe = TestProbe() building.Actor = buildingProbe.ref @@ -379,27 +378,29 @@ class GeneratorControlKillsTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(gen), - gen.DamageModel, - Vector3(1, 0, 0) + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(25, 0, 0), + Vector3(-1, 0, 0) + ), + gen.DamageModel + ), + Vector3(2, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct "GeneratorControl" should { - "kill players when the generator is destroyed" in { + "damages (kills) players when the generator is destroyed" in { gen.Health = 1 //no matter what, the next shot destroys the generator assert(gen.Health == 1) assert(!gen.Destroyed) @@ -458,11 +459,11 @@ class GeneratorControlKillsTest extends ActorTest { assert(gen.Destroyed) assert(gen.Condition == PlanetSideGeneratorState.Destroyed) - val msg_player1 = player1Probe.receiveOne(100 milliseconds) + val msg_player1 = player1Probe.receiveOne(200 milliseconds) player2Probe.expectNoMessage(200 milliseconds) assert( msg_player1 match { - case _ @Player.Die() => true + case _ @ Vitality.Damage(_) => true case _ => false } ) @@ -499,22 +500,24 @@ class GeneratorControlNotDestroyTwice extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(gen), - gen.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + gen.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -589,22 +592,24 @@ class GeneratorControlNotDamageIfExplodingTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(gen), - gen.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + gen.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() expectNoMessage(200 milliseconds) //we're not testing that the math is correct @@ -686,22 +691,24 @@ class GeneratorControlNotRepairIfExplodingTest extends ActorTest { val weapon = Tool(GlobalDefinitions.phoenix) //decimator val projectile = weapon.Projectile - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - weapon.Definition, - weapon.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( SourceEntry(gen), - gen.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + weapon.Definition, + weapon.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + gen.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() val tool = Tool(GlobalDefinitions.nano_dispenser) //4 & 5 guid.register(tool, 4) @@ -851,6 +858,10 @@ object GeneratorTest { Repairable = true RepairDistance = 13.5f RepairIfDestroyed = true + explodes = true + innateDamage = new DamageWithPosition { + DamageRadius = 14 + } //note: no auto-repair } } diff --git a/src/test/scala/objects/PlayerControlTest.scala b/src/test/scala/objects/PlayerControlTest.scala index 45a48d5ab..c258de4d4 100644 --- a/src/test/scala/objects/PlayerControlTest.scala +++ b/src/test/scala/objects/PlayerControlTest.scala @@ -377,22 +377,24 @@ class PlayerControlDamageTest extends ActorTest { val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4 val projectile = tool.Projectile val playerSource = SourceEntry(player2) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile( - projectile, - tool.Definition, - tool.FireMode, - PlayerSource(player1), - 0, - Vector3(2, 0, 0), - Vector3(-1, 0, 0) - ), + val resolved = DamageInteraction( playerSource, - player1.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile( + projectile, + tool.Definition, + tool.FireMode, + PlayerSource(player1), + 0, + Vector3(2, 0, 0), + Vector3(-1, 0, 0) + ), + player1.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() guid.register(player1, 1) guid.register(player2, 2) guid.register(tool, 3) @@ -476,14 +478,16 @@ class PlayerControlDeathStandingTest extends ActorTest { val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4 val projectile = tool.Projectile val player1Source = SourceEntry(player1) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectile, tool.Definition, tool.FireMode, player1Source, 0, Vector3(2, 0, 0), Vector3(-1, 0, 0)), + val resolved = DamageInteraction( SourceEntry(player2), - player2.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectile, tool.Definition, tool.FireMode, player1Source, 0, Vector3(2, 0, 0), Vector3(-1, 0, 0)), + player2.DamageModel + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() guid.register(player1, 1) guid.register(player2, 2) guid.register(tool, 3) @@ -605,14 +609,16 @@ class PlayerControlDeathSeatedTest extends ActorTest { val tool = Tool(GlobalDefinitions.suppressor) //guid 3 & 4 val projectile = tool.Projectile val player1Source = SourceEntry(player1) - val resolved = ResolvedProjectile( - ProjectileResolution.Hit, - Projectile(projectile, tool.Definition, tool.FireMode, player1Source, 0, Vector3(2, 0, 0), Vector3(-1, 0, 0)), + val resolved = DamageInteraction( SourceEntry(player2), - player2.DamageModel, + ProjectileReason( + DamageResolution.Hit, + Projectile(projectile, tool.Definition, tool.FireMode, player1Source, 0, Vector3(2, 0, 0), Vector3(-1, 0, 0)), + player2.DamageMode + ), Vector3(1, 0, 0) ) - val applyDamageTo = resolved.damage_model.Calculate(resolved) + val applyDamageTo = resolved.calculate() guid.register(player1, 1) guid.register(player2, 2) guid.register(tool, 3) diff --git a/src/test/scala/objects/ProjectileTest.scala b/src/test/scala/objects/ProjectileTest.scala index 8689c77b0..418a28839 100644 --- a/src/test/scala/objects/ProjectileTest.scala +++ b/src/test/scala/objects/ProjectileTest.scala @@ -6,7 +6,9 @@ import net.psforever.objects.avatar.Avatar import net.psforever.objects.ballistics._ import net.psforever.objects.definition.ProjectileDefinition import net.psforever.objects.serverobject.mblocker.Locker -import net.psforever.objects.vital.DamageType +import net.psforever.objects.vital.base.{DamageResolution, DamageType} +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.types.{PlanetSideGUID, _} import org.specs2.mutable.Specification @@ -328,7 +330,7 @@ class ProjectileTest extends Specification { } } - "ResolvedProjectile" should { + "Projectile DamageInteraction" should { //TODO wrong place for this test? val beamer_wep = Tool(GlobalDefinitions.beamer) val p_source = PlayerSource(player) val player2 = Player(Avatar(0, "TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) @@ -345,17 +347,20 @@ class ProjectileTest extends Specification { val fury_dm = fury.DamageModel "construct" in { - val obj = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val obj = DamageInteraction( + DamageResolution.Hit, PlayerSource(player2), - fury_dm, + ProjectileReason( + DamageResolution.Hit, + projectile, + fury_dm + ), Vector3(1.2f, 3.4f, 5.6f) ) - obj.projectile mustEqual projectile + obj.cause.asInstanceOf[ProjectileReason].projectile mustEqual projectile obj.target mustEqual p2_source - obj.damage_model mustEqual fury.DamageModel - obj.hit_pos mustEqual Vector3(1.2f, 3.4f, 5.6f) + obj.cause.asInstanceOf[ProjectileReason].damageModel mustEqual fury.DamageModel + obj.hitPos mustEqual Vector3(1.2f, 3.4f, 5.6f) } } } diff --git a/src/test/scala/objects/VehicleTest.scala b/src/test/scala/objects/VehicleTest.scala index 0d8852659..ad34959f1 100644 --- a/src/test/scala/objects/VehicleTest.scala +++ b/src/test/scala/objects/VehicleTest.scala @@ -962,7 +962,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(projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f)) +// val obj = DamageInteraction(p_source, ProjectileReason(DamageResolution.Hit, projectile, fury_dm), Vector3(1.2f, 3.4f, 5.6f)) // // "not charge vehicle shields if recently damaged" in { // assert(vehicle.Shields == 0) diff --git a/src/test/scala/objects/VitalityTest.scala b/src/test/scala/objects/VitalityTest.scala index ee459f060..d097232c1 100644 --- a/src/test/scala/objects/VitalityTest.scala +++ b/src/test/scala/objects/VitalityTest.scala @@ -5,6 +5,9 @@ import net.psforever.objects.ballistics._ import net.psforever.objects._ import net.psforever.objects.avatar.Avatar import net.psforever.objects.vital._ +import net.psforever.objects.vital.base.DamageResolution +import net.psforever.objects.vital.interaction.DamageInteraction +import net.psforever.objects.vital.projectile.ProjectileReason import net.psforever.types._ import org.specs2.mutable.Specification @@ -20,16 +23,19 @@ class VitalityTest extends Specification { val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) val pSource = PlayerSource(player) val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(player), - player.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + player.DamageModel + ), Vector3(50, 50, 0) ) + val result = resprojectile.calculate()(player) - player.History(resprojectile) //ResolvedProjectile, straight-up - player.History(DamageFromProjectile(resprojectile)) + player.History(result) //DamageResult, straight-up + player.History(DamageFromProjectile(result)) player.History(HealFromKit(pSource, 10, GlobalDefinitions.medkit)) player.History(HealFromTerm(pSource, 10, 0, GlobalDefinitions.order_terminal)) player.History(HealFromImplant(pSource, 10, ImplantType.AdvancedRegen)) @@ -68,15 +74,18 @@ class VitalityTest extends Specification { val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) val pSource = PlayerSource(player) val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) - val resprojectile = ResolvedProjectile( - ProjectileResolution.Hit, - projectile, + val resprojectile = DamageInteraction( SourceEntry(player), - player.DamageModel, + ProjectileReason( + DamageResolution.Hit, + projectile, + player.DamageModel + ), Vector3(50, 50, 0) ) + val result = resprojectile.calculate()(player) - player.History(DamageFromProjectile(resprojectile)) + player.History(DamageFromProjectile(result)) player.History(HealFromKit(pSource, 10, GlobalDefinitions.medkit)) player.History(HealFromTerm(pSource, 10, 0, GlobalDefinitions.order_terminal)) player.History(HealFromImplant(pSource, 10, ImplantType.AdvancedRegen)) @@ -87,7 +96,10 @@ class VitalityTest extends Specification { player.LastShot match { case Some(resolved_projectile) => - resolved_projectile.projectile mustEqual projectile + resolved_projectile.interaction.cause match { + case o: ProjectileReason => o.projectile mustEqual projectile + case _ => ko + } case None => ko }