mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
Damage Changes/Explosions (#644)
* created base damage interaction classes and replaced various projectile-based damage that utilized ResolvedProjectile; not refined, maintains redundancy and overloads, but should work * continuing to reduce the exposure of ResolvedProjectile and replacing it with applications of DamageInteraction, DamageResult, and DamageReason * removed ResolvedProjectile from the project; adjusted remaining code paths to work around it * vitals.test became vital.base; no one liked this * lots of inheritance, polymorphism, and other chicanery; moved around files, so it also looks like more files have changed when they have not (even if they did) * codecov file correction * master rebase; vital directory structure changed, so file imports have been modified in several other files; ResolutionSelection has been removed, requiring direct function literal assignment; tests repaired, where necessary; no actual functional change * code comments * DamageResult is its own case class now, wrapping around a before/after target and the interaction used in its calaculations; tests have been corrected * adjusted Player.Die() to demonstrate a damage-based suicide approach * resolved circular inheritance in projectile damage modifiers; better employed explosion reason, damages players around exploding vehicle as example * expanded explosions to other object types; exploding is now a flag and the damage is an innate property of the object type; removed advanced references to properties on the damage source, since the damage source is easily accessible; wrote comments; fixed tests * overhaul to painbox damage to align with normal player damage handling, thus assimilating it properly into the damage system * future development; normal vector from euler angles; custom proximity test * where 'innateDamage' should have not replaced 'explosion' * moved the hitPos for the generator test; attempting to imrpove the reliability of the auto-repair integration tests (didn't ...) * spelling and private val
This commit is contained in:
parent
b3101d9a8d
commit
6c93746767
18
.codecov.yml
18
.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"
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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 =>
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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._
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 _ => ;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<br>
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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).<br>
|
||||
* <br>
|
||||
* 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
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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.<br>
|
||||
* <br>
|
||||
* Although the latter states reflect what sort of damage the projectile might perform - `Hit`, `Splash`, etc. -
|
||||
* the state is more a communication about how that damage is interpreted by the server.
|
||||
* 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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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).<br>
|
||||
* <br>
|
||||
* 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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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.<br>
|
||||
* <br>
|
||||
* 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")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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.<br>
|
||||
* 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.<br>
|
||||
* <br>
|
||||
* 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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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.<br>
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue