From 7901f663242f5df0e061b4aee9aa81c9dd129e17 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 30 Jul 2018 09:28:45 -0400 Subject: [PATCH] Damages (#225) * refactored WSA code handling HitMessage, handling SplashMessage, and handling LashMessage; modified projectiles for future functionality * players can die from being shot now; the damage model is simplistic since main goal was to write around the potential for negative damage ('healed from getting shot'); HitHint works correctly; dedicated AvatarService channel for each avatar helps reduce message spam * vehicle destruction, and replacement with lightweight wreckage objects upon continent join; made flushing vehicle terminal more accessible * simple work on vehicle shield charging (amp station benefit) (that's my commit story and I'm sticking with it) * a flexible calculation workflow that can be applied, grabbing damage information, resistance information, and then combining it with a resolution function; players and vehicles have resistance values; removed redundant damage calculations from WSA * broke up DamageCalculations, ResistanceCalculations, and ResolutionCalculations into packages under vital; fixed an error with exo-suit calculation resistances; events for dealing with synchronized player and vehicle damage calculations and building the papertrail of those damages; updating codecov.yml file for ignore classes * added tests for various components (damage model, destroyed vehicle converter, vitality, etc..) and some functionality improvements * added a field to keep track of how projectiles will be attributed at the time of target death --- .codecov.yml | 19 +- .../scala/net/psforever/objects/Avatar.scala | 4 +- .../psforever/objects/ExoSuitDefinition.scala | 28 +- .../psforever/objects/GlobalDefinitions.scala | 248 ++++++++++- .../scala/net/psforever/objects/Player.scala | 19 +- .../scala/net/psforever/objects/Vehicle.scala | 39 +- .../objects/ballistics/ObjectSource.scala | 31 ++ .../objects/ballistics/PlayerSource.scala | 35 ++ .../objects/ballistics/Projectile.scala | 78 ++-- .../ballistics/ResolvedProjectile.scala | 24 + .../objects/ballistics/SourceEntry.scala | 42 ++ .../objects/ballistics/VehicleSource.scala | 37 ++ .../definition/ProjectileDefinition.scala | 45 +- .../definition/VehicleDefinition.scala | 22 +- .../converter/DestroyedVehicleConverter.scala | 25 ++ .../converter/VehicleConverter.scala | 79 ++-- .../equipment/FireModeDefinition.scala | 2 +- .../pad/VehicleSpawnControl.scala | 20 +- .../objects/vehicles/DestroyedVehicle.scala | 33 ++ .../objects/vehicles/VehicleControl.scala | 39 ++ .../objects/vital/DamageResistanceModel.scala | 80 ++++ .../{ballistics => vital}/DamageType.scala | 2 +- .../objects/vital/StandardDamages.scala | 113 +++++ .../vital/StandardResistanceProfile.scala | 26 ++ .../objects/vital/StandardResistances.scala | 72 +++ .../objects/vital/StandardResolutions.scala | 31 ++ .../psforever/objects/vital/Vitality.scala | 110 +++++ .../vital/damage/DamageCalculations.scala | 129 ++++++ .../damage}/DamageProfile.scala | 2 +- .../vital/damage/DamageSelection.scala | 32 ++ .../projectile/ProjectileCalculations.scala | 20 + .../resistance/ResistanceCalculations.scala | 123 ++++++ .../vital/resistance/ResistanceProfile.scala | 69 +++ .../resistance/ResistanceSelection.scala | 33 ++ .../resolution/DamageResistCalculations.scala | 27 ++ .../resolution/ResolutionCalculations.scala | 160 +++++++ .../resolution/ResolutionSelection.scala | 12 + .../packet/game/DestroyDisplayMessage.scala | 47 +- .../game/PlanetsideAttributeMessage.scala | 4 + .../scala/services/avatar/AvatarAction.scala | 11 +- .../services/avatar/AvatarResponse.scala | 12 +- .../scala/services/avatar/AvatarService.scala | 36 +- .../services/vehicle/VehicleAction.scala | 2 + .../services/vehicle/VehicleResponse.scala | 3 +- .../services/vehicle/VehicleService.scala | 4 + .../test/scala/objects/ConverterTest.scala | 38 +- .../test/scala/objects/DamageModelTests.scala | 416 ++++++++++++++++++ .../test/scala/objects/ProjectileTest.scala | 186 ++++++-- .../src/test/scala/objects/VehicleTest.scala | 103 ++++- .../src/test/scala/objects/VitalityTest.scala | 83 ++++ .../test/scala/service/RemoverActorTest.scala | 56 +-- .../src/main/scala/WorldSessionActor.scala | 401 ++++++++++++++--- 52 files changed, 3045 insertions(+), 267 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/ballistics/ObjectSource.scala create mode 100644 common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala create mode 100644 common/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala create mode 100644 common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala create mode 100644 common/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala create mode 100644 common/src/main/scala/net/psforever/objects/definition/converter/DestroyedVehicleConverter.scala create mode 100644 common/src/main/scala/net/psforever/objects/vehicles/DestroyedVehicle.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala rename common/src/main/scala/net/psforever/objects/{ballistics => vital}/DamageType.scala (90%) create mode 100644 common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/Vitality.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala rename common/src/main/scala/net/psforever/objects/{ballistics => vital/damage}/DamageProfile.scala (92%) create mode 100644 common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/projectile/ProjectileCalculations.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/resolution/DamageResistCalculations.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala create mode 100644 common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionSelection.scala create mode 100644 common/src/test/scala/objects/DamageModelTests.scala create mode 100644 common/src/test/scala/objects/VitalityTest.scala diff --git a/.codecov.yml b/.codecov.yml index 8f90e18ca..f8fb716e4 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -2,19 +2,35 @@ comment: off ignore: + - "common/src/main/scala/net/psforever/objects/ObjectType.scala" - "common/src/main/scala/net/psforever/objects/avatar/Avatars.scala" + - "common/src/main/scala/net/psforever/objects/ballistics/ProjectileResolution.scala" + - "common/src/main/scala/net/psforever/objects/ballistics/Projectiles.scala" - "common/src/main/scala/net/psforever/objects/equipment/Ammo.scala" - "common/src/main/scala/net/psforever/objects/equipment/CItem.scala" - "common/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala" - "common/src/main/scala/net/psforever/objects/equipment/Kits.scala" - "common/src/main/scala/net/psforever/objects/equipment/SItem.scala" - "common/src/main/scala/net/psforever/objects/guid/AvailabilityPolicy.scala" + - "common/src/main/scala/net/psforever/objects/serverobject/pad/AutoDriveControls.scala" + - "common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala" - "common/src/main/scala/net/psforever/objects/serverobject/turret/TurretUpgrade.scala" - "common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala" - "common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala" + - "common/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestiction.scala" + - "common/src/main/scala/net/psforever/objects/vehicles/DestroyedVehicle.scala" - "common/src/main/scala/net/psforever/objects/vehicles/SeatArmoRestriction.scala" - "common/src/main/scala/net/psforever/objects/vehicles/Turrets.scala" - "common/src/main/scala/net/psforever/objects/vehicles/VehicleLockState.scala" + - "common/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala" + - "common/src/main/scala/net/psforever/objects/vital/projectile/ProjectileCalculations.scala" + - "common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala" + - "common/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala" + - "common/src/main/scala/net/psforever/objects/vital/DamageType.scala" + - "common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala" + - "common/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala" + - "common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala" + - "common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala" - "common/src/main/scala/net/psforever/packet/crypto" - "common/src/main/scala/net/psforever/packet/game/objectcreate/DrawnSlot.scala" - "common/src/main/scala/net/psforever/packet/game/objectcreate/DriveState.scala" @@ -24,7 +40,6 @@ ignore: - "common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala" - "common/src/main/scala/net/psforever/packet/CryptoPacketOpcode.scala" - "common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala" - - "common/src/main/scala/net/psforever/objects/ObjectType.scala" - "common/src/main/scala/net/psforever/types/Angular.scala" - "common/src/main/scala/net/psforever/types/CertificationType.scala" - "common/src/main/scala/net/psforever/types/ChatMessageType.scala" @@ -38,6 +53,8 @@ ignore: - "common/src/main/scala/net/psforever/types/TransactionType.scala" - "common/src/main/scala/services/avatar/AvatarAction.scala" - "common/src/main/scala/services/avatar/AvatarResponse.scala" + - "common/src/main/scala/services/galaxy/GalaxyAction.scala" + - "common/src/main/scala/services/galaxy/GalaxyResponse.scala" - "common/src/main/scala/services/local/LocalAction.scala" - "common/src/main/scala/services/local/LocalResponse.scala" - "common/src/main/scala/services/vehicle/VehicleAction.scala" diff --git a/common/src/main/scala/net/psforever/objects/Avatar.scala b/common/src/main/scala/net/psforever/objects/Avatar.scala index 3166d5b64..8c2f4cd47 100644 --- a/common/src/main/scala/net/psforever/objects/Avatar.scala +++ b/common/src/main/scala/net/psforever/objects/Avatar.scala @@ -177,7 +177,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : } } - def Definition : AvatarDefinition = Avatar.definition + def Definition : AvatarDefinition = GlobalDefinitions.avatar /* Merit Commendations and Ribbons @@ -210,8 +210,6 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : } object Avatar { - final private val definition : AvatarDefinition = new AvatarDefinition(121) - def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : CharacterVoice.Value) : Avatar = { new Avatar(name, faction, sex, head, voice) } diff --git a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala index fa36256df..2a2539a58 100644 --- a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala @@ -3,6 +3,8 @@ package net.psforever.objects import net.psforever.objects.equipment.EquipmentSize import net.psforever.objects.inventory.InventoryTile +import net.psforever.objects.vital._ +import net.psforever.objects.vital.resistance.ResistanceProfileMutators import net.psforever.types.ExoSuitType /** @@ -10,12 +12,16 @@ import net.psforever.types.ExoSuitType * Players are influenced by the exo-suit they wear in a variety of ways, with speed and available equipment slots being major differences. * @param suitType the `Enumeration` corresponding to this exo-suit */ -class ExoSuitDefinition(private val suitType : ExoSuitType.Value) { +class ExoSuitDefinition(private val suitType : ExoSuitType.Value) extends ResistanceProfileMutators + with DamageResistanceModel { protected var permission : Int = 0 //TODO certification type? protected var maxArmor : Int = 0 protected val holsters : Array[EquipmentSize.Value] = Array.fill[EquipmentSize.Value](5)(EquipmentSize.Blocked) protected var inventoryScale : InventoryTile = InventoryTile.Tile11 //override with custom InventoryTile protected var inventoryOffset : Int = 0 + Damage = StandardInfantryDamage + Resistance = StandardInfantryResistance + Model = StandardResolutions.Infantry def SuitType : ExoSuitType.Value = suitType @@ -79,6 +85,12 @@ class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends obj.MaxArmor = MaxArmor obj.InventoryScale = InventoryScale obj.InventoryOffset = InventoryOffset + obj.ResistanceDirectHit = ResistanceDirectHit + obj.ResistanceSplash = ResistanceSplash + obj.ResistanceAggravated = ResistanceAggravated + obj.Damage = Damage + obj.Resistance = Resistance + obj.Model = Model (0 until 5).foreach(index => { obj.Holster(index, Holster(index)) }) obj } @@ -109,6 +121,9 @@ object ExoSuitDefinition { Standard.Holster(0, EquipmentSize.Pistol) Standard.Holster(2, EquipmentSize.Rifle) Standard.Holster(4, EquipmentSize.Melee) + Standard.ResistanceDirectHit = 4 + Standard.ResistanceSplash = 15 + Standard.ResistanceAggravated = 8 final val Agile = ExoSuitDefinition(ExoSuitType.Agile) Agile.MaxArmor = 100 @@ -118,6 +133,9 @@ object ExoSuitDefinition { Agile.Holster(1, EquipmentSize.Pistol) Agile.Holster(2, EquipmentSize.Rifle) Agile.Holster(4, EquipmentSize.Melee) + Agile.ResistanceDirectHit = 6 + Agile.ResistanceSplash = 25 + Agile.ResistanceAggravated = 10 final val Reinforced = ExoSuitDefinition(ExoSuitType.Reinforced) Reinforced.permission = 1 @@ -129,6 +147,9 @@ object ExoSuitDefinition { Reinforced.Holster(2, EquipmentSize.Rifle) Reinforced.Holster(3, EquipmentSize.Rifle) Reinforced.Holster(4, EquipmentSize.Melee) + Reinforced.ResistanceDirectHit = 10 + Reinforced.ResistanceSplash = 35 + Reinforced.ResistanceAggravated = 12 final val Infiltration = ExoSuitDefinition(ExoSuitType.Infiltration) Infiltration.permission = 1 @@ -145,6 +166,11 @@ object ExoSuitDefinition { MAX.InventoryOffset = 6 MAX.Holster(0, EquipmentSize.Max) MAX.Holster(4, EquipmentSize.Melee) + MAX.ResistanceDirectHit = 6 + MAX.ResistanceSplash = 35 + MAX.ResistanceAggravated = 10 + MAX.Damage = StandardMaxDamage + MAX.Model = StandardResolutions.Max def apply(suitType : ExoSuitType.Value) : ExoSuitDefinition = { new ExoSuitDefinition(suitType) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 9a0559e48..eec948f9c 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1,12 +1,13 @@ // Copyright (c) 2017 PSForever package net.psforever.objects +import net.psforever.objects.ballistics.Projectiles import net.psforever.objects.definition._ import net.psforever.objects.definition.converter._ -import net.psforever.objects.serverobject.doors.DoorDefinition import net.psforever.objects.equipment.CItem.DeployedItem import net.psforever.objects.equipment._ import net.psforever.objects.inventory.InventoryTile +import net.psforever.objects.serverobject.doors.DoorDefinition import net.psforever.objects.serverobject.implantmech.ImplantTerminalMechDefinition import net.psforever.objects.serverobject.locks.IFFLockDefinition import net.psforever.objects.serverobject.mblocker.LockerDefinition @@ -14,15 +15,19 @@ import net.psforever.objects.serverobject.pad.VehicleSpawnPadDefinition import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.serverobject.tube.SpawnTubeDefinition import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition -import net.psforever.objects.ballistics.{DamageType, Projectiles} import net.psforever.objects.serverobject.turret.{MannedTurretDefinition, TurretUpgrade} -import net.psforever.objects.vehicles.{SeatArmorRestriction, UtilityType} +import net.psforever.objects.vehicles.{DestroyedVehicle, SeatArmorRestriction, UtilityType} +import net.psforever.objects.vital.DamageType import net.psforever.types.PlanetSideEmpire import scala.collection.mutable import scala.concurrent.duration._ object GlobalDefinitions { + /* + characters + */ + val avatar = new AvatarDefinition(121) /* Implants */ @@ -1551,6 +1556,7 @@ object GlobalDefinitions { bullet_105mm_projectile.ProjectileDamageType = DamageType.Splash bullet_105mm_projectile.InitialVelocity = 100 bullet_105mm_projectile.Lifespan = 4f + ProjectileDefinition.CalculateDerivedFields(bullet_105mm_projectile) bullet_12mm_projectile.Name= "12mmbullet_projectile" bullet_12mm_projectile.Damage0 = 25 @@ -1563,6 +1569,7 @@ object GlobalDefinitions { bullet_12mm_projectile.DegradeMultiplier = 0.5f bullet_12mm_projectile.InitialVelocity = 500 bullet_12mm_projectile.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(bullet_12mm_projectile) bullet_12mm_projectileb.Name = "12mmbullet_projectileb" // TODO for later, maybe : set_resource_parent 12mmbullet_projectileb game_objects 12mmbullet_projectile @@ -1576,6 +1583,7 @@ object GlobalDefinitions { bullet_12mm_projectileb.DegradeMultiplier = 0.5f bullet_12mm_projectileb.InitialVelocity = 500 bullet_12mm_projectileb.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(bullet_12mm_projectileb) bullet_150mm_projectile.Name = "150mmbullet_projectile" bullet_150mm_projectile.Damage0 = 150 @@ -1588,6 +1596,7 @@ object GlobalDefinitions { bullet_150mm_projectile.ProjectileDamageType = DamageType.Splash bullet_150mm_projectile.InitialVelocity = 100 bullet_150mm_projectile.Lifespan = 4f + ProjectileDefinition.CalculateDerivedFields(bullet_150mm_projectile) bullet_15mm_apc_projectile.Name = "15mmbullet_apc_projectile" // TODO for later, maybe : set_resource_parent 15mmbullet_apc_projectile game_objects 15mmbullet_projectile @@ -1601,6 +1610,7 @@ object GlobalDefinitions { bullet_15mm_apc_projectile.DegradeMultiplier = 0.5f bullet_15mm_apc_projectile.InitialVelocity = 500 bullet_15mm_apc_projectile.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(bullet_15mm_apc_projectile) bullet_15mm_projectile.Name = "15mmbullet_projectile" bullet_15mm_projectile.Damage0 = 21 @@ -1613,6 +1623,7 @@ object GlobalDefinitions { bullet_15mm_projectile.DegradeMultiplier = 0.5f bullet_15mm_projectile.InitialVelocity = 500 bullet_15mm_projectile.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(bullet_15mm_projectile) bullet_20mm_apc_projectile.Name = "20mmbullet_apc_projectile" // TODO for later, maybe : set_resource_parent 20mmbullet_apc_projectile game_objects 20mmbullet_projectile @@ -1626,6 +1637,7 @@ object GlobalDefinitions { bullet_20mm_apc_projectile.DegradeMultiplier = 0.5f bullet_20mm_apc_projectile.InitialVelocity = 500 bullet_20mm_apc_projectile.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(bullet_20mm_apc_projectile) bullet_20mm_projectile.Name = "20mmbullet_projectile" bullet_20mm_projectile.Damage0 = 20 @@ -1638,6 +1650,7 @@ object GlobalDefinitions { bullet_20mm_projectile.DegradeMultiplier = 0.5f bullet_20mm_projectile.InitialVelocity = 500 bullet_20mm_projectile.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(bullet_20mm_projectile) bullet_25mm_projectile.Name = "25mmbullet_projectile" bullet_25mm_projectile.Damage0 = 25 @@ -1648,6 +1661,7 @@ object GlobalDefinitions { bullet_25mm_projectile.DegradeMultiplier = 0.5f bullet_25mm_projectile.InitialVelocity = 500 bullet_25mm_projectile.Lifespan = 0.6f + ProjectileDefinition.CalculateDerivedFields(bullet_25mm_projectile) bullet_35mm_projectile.Name = "35mmbullet_projectile" bullet_35mm_projectile.Damage0 = 40 @@ -1658,6 +1672,7 @@ object GlobalDefinitions { bullet_35mm_projectile.DegradeMultiplier = 0.5f bullet_35mm_projectile.InitialVelocity = 200 bullet_35mm_projectile.Lifespan = 1.5f + ProjectileDefinition.CalculateDerivedFields(bullet_35mm_projectile) bullet_75mm_apc_projectile.Name = "75mmbullet_apc_projectile" // TODO for later, maybe : set_resource_parent 75mmbullet_apc_projectile game_objects 75mmbullet_projectile @@ -1668,6 +1683,7 @@ object GlobalDefinitions { bullet_75mm_apc_projectile.ProjectileDamageType = DamageType.Splash bullet_75mm_apc_projectile.InitialVelocity = 100 bullet_75mm_apc_projectile.Lifespan = 4f + ProjectileDefinition.CalculateDerivedFields(bullet_75mm_apc_projectile) bullet_75mm_projectile.Name = "75mmbullet_projectile" bullet_75mm_projectile.Damage0 = 75 @@ -1677,6 +1693,7 @@ object GlobalDefinitions { bullet_75mm_projectile.ProjectileDamageType = DamageType.Splash bullet_75mm_projectile.InitialVelocity = 100 bullet_75mm_projectile.Lifespan = 4f + ProjectileDefinition.CalculateDerivedFields(bullet_75mm_projectile) bullet_9mm_AP_projectile.Name = "9mmbullet_AP_projectile" // TODO for later, maybe : set_resource_parent 9mmbullet_AP_projectile game_objects 9mmbullet_projectile @@ -1688,6 +1705,7 @@ object GlobalDefinitions { bullet_9mm_AP_projectile.InitialVelocity = 500 bullet_9mm_AP_projectile.Lifespan = 0.4f bullet_9mm_AP_projectile.UseDamage1Subtract = true + ProjectileDefinition.CalculateDerivedFields(bullet_9mm_AP_projectile) bullet_9mm_projectile.Name = "9mmbullet_projectile" bullet_9mm_projectile.Damage0 = 18 @@ -1698,6 +1716,7 @@ object GlobalDefinitions { bullet_9mm_projectile.InitialVelocity = 500 bullet_9mm_projectile.Lifespan = 0.4f bullet_9mm_projectile.UseDamage1Subtract = true + ProjectileDefinition.CalculateDerivedFields(bullet_9mm_projectile) anniversary_projectilea.Name = "anniversary_projectilea" anniversary_projectilea.Damage0 = 30 @@ -1710,6 +1729,7 @@ object GlobalDefinitions { anniversary_projectilea.DegradeMultiplier = 0.2f anniversary_projectilea.InitialVelocity = 500 anniversary_projectilea.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(anniversary_projectilea) anniversary_projectileb.Name = "anniversary_projectileb" // TODO for later, maybe : set_resource_parent anniversary_projectileb game_objects anniversary_projectilea @@ -1723,6 +1743,7 @@ object GlobalDefinitions { anniversary_projectileb.DegradeMultiplier = 0.2f anniversary_projectileb.InitialVelocity = 500 anniversary_projectileb.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(anniversary_projectileb) aphelion_immolation_cannon_projectile.Name = "aphelion_immolation_cannon_projectile" aphelion_immolation_cannon_projectile.Damage0 = 55 @@ -1735,6 +1756,7 @@ object GlobalDefinitions { aphelion_immolation_cannon_projectile.ProjectileDamageType = DamageType.Splash aphelion_immolation_cannon_projectile.InitialVelocity = 250 aphelion_immolation_cannon_projectile.Lifespan = 1.4f + ProjectileDefinition.CalculateDerivedFields(aphelion_immolation_cannon_projectile) aphelion_laser_projectile.Name = "aphelion_laser_projectile" aphelion_laser_projectile.Damage0 = 3 @@ -1747,6 +1769,7 @@ object GlobalDefinitions { aphelion_laser_projectile.DegradeMultiplier = 0.5f aphelion_laser_projectile.InitialVelocity = 500 aphelion_laser_projectile.Lifespan = 0.35f + ProjectileDefinition.CalculateDerivedFields(aphelion_laser_projectile) aphelion_plasma_rocket_projectile.Name = "aphelion_plasma_rocket_projectile" aphelion_plasma_rocket_projectile.Damage0 = 38 @@ -1761,6 +1784,7 @@ object GlobalDefinitions { aphelion_plasma_rocket_projectile.ProjectileDamageType = DamageType.Splash aphelion_plasma_rocket_projectile.InitialVelocity = 75 aphelion_plasma_rocket_projectile.Lifespan = 5f + ProjectileDefinition.CalculateDerivedFields(aphelion_plasma_rocket_projectile) aphelion_ppa_projectile.Name = "aphelion_ppa_projectile" // TODO for later, maybe : set_resource_parent aphelion_ppa_projectile game_objects ppa_projectile @@ -1776,6 +1800,7 @@ object GlobalDefinitions { aphelion_ppa_projectile.DegradeMultiplier = 0.55f aphelion_ppa_projectile.InitialVelocity = 350 aphelion_ppa_projectile.Lifespan = .7f + ProjectileDefinition.CalculateDerivedFields(aphelion_ppa_projectile) aphelion_starfire_projectile.Name = "aphelion_starfire_projectile" // TODO for later, maybe : set_resource_parent aphelion_starfire_projectile game_objects starfire_projectile @@ -1789,6 +1814,7 @@ object GlobalDefinitions { aphelion_starfire_projectile.InitialVelocity = 45 aphelion_starfire_projectile.Lifespan = 7f aphelion_starfire_projectile.ProjectileDamageType = DamageType.Aggravated + ProjectileDefinition.CalculateDerivedFields(aphelion_starfire_projectile) bolt_projectile.Name = "bolt_projectile" bolt_projectile.Damage0 = 100 @@ -1799,6 +1825,7 @@ object GlobalDefinitions { bolt_projectile.ProjectileDamageType = DamageType.Splash bolt_projectile.InitialVelocity = 500 bolt_projectile.Lifespan = 1.0f + ProjectileDefinition.CalculateDerivedFields(bolt_projectile) burster_projectile.Name = "burster_projectile" burster_projectile.Damage0 = 18 @@ -1810,6 +1837,7 @@ object GlobalDefinitions { burster_projectile.ProjectileDamageTypeSecondary = DamageType.Splash burster_projectile.InitialVelocity = 125 burster_projectile.Lifespan = 4f + ProjectileDefinition.CalculateDerivedFields(burster_projectile) chainblade_projectile.Name = "chainblade_projectile" // TODO for later, maybe : set_resource_parent chainblade_projectile game_objects melee_ammo_projectile @@ -1818,6 +1846,7 @@ object GlobalDefinitions { chainblade_projectile.ProjectileDamageType = DamageType.Direct chainblade_projectile.InitialVelocity = 100 chainblade_projectile.Lifespan = .02f + ProjectileDefinition.CalculateDerivedFields(chainblade_projectile) colossus_100mm_projectile.Name = "colossus_100mm_projectile" colossus_100mm_projectile.Damage0 = 58 @@ -1830,6 +1859,7 @@ object GlobalDefinitions { colossus_100mm_projectile.ProjectileDamageType = DamageType.Splash colossus_100mm_projectile.InitialVelocity = 100 colossus_100mm_projectile.Lifespan = 4f + ProjectileDefinition.CalculateDerivedFields(colossus_100mm_projectile) colossus_burster_projectile.Name = "colossus_burster_projectile" // TODO for later, maybe : set_resource_parent colossus_burster_projectile game_objects burster_projectile @@ -1844,6 +1874,7 @@ object GlobalDefinitions { colossus_burster_projectile.ProjectileDamageTypeSecondary = DamageType.Splash colossus_burster_projectile.InitialVelocity = 175 colossus_burster_projectile.Lifespan = 2.5f + ProjectileDefinition.CalculateDerivedFields(colossus_burster_projectile) colossus_chaingun_projectile.Name = "colossus_chaingun_projectile" // TODO for later, maybe : set_resource_parent colossus_chaingun_projectile game_objects 35mmbullet_projectile @@ -1857,6 +1888,7 @@ object GlobalDefinitions { colossus_chaingun_projectile.DegradeMultiplier = 0.44f colossus_chaingun_projectile.InitialVelocity = 500 colossus_chaingun_projectile.Lifespan = .50f + ProjectileDefinition.CalculateDerivedFields(colossus_chaingun_projectile) colossus_cluster_bomb_projectile.Name = "colossus_cluster_bomb_projectile" colossus_cluster_bomb_projectile.Damage0 = 40 @@ -1869,6 +1901,7 @@ object GlobalDefinitions { colossus_cluster_bomb_projectile.ProjectileDamageType = DamageType.Splash colossus_cluster_bomb_projectile.InitialVelocity = 75 colossus_cluster_bomb_projectile.Lifespan = 5f + ProjectileDefinition.CalculateDerivedFields(colossus_cluster_bomb_projectile) colossus_tank_cannon_projectile.Name = "colossus_tank_cannon_projectile" // TODO for later, maybe : set_resource_parent colossus_tank_cannon_projectile game_objects 75mmbullet_projectile @@ -1882,6 +1915,7 @@ object GlobalDefinitions { colossus_tank_cannon_projectile.ProjectileDamageType = DamageType.Splash colossus_tank_cannon_projectile.InitialVelocity = 165 colossus_tank_cannon_projectile.Lifespan = 2f + ProjectileDefinition.CalculateDerivedFields(colossus_tank_cannon_projectile) comet_projectile.Name = "comet_projectile" comet_projectile.Damage0 = 15 @@ -1896,6 +1930,7 @@ object GlobalDefinitions { comet_projectile.ProjectileDamageType = DamageType.Aggravated comet_projectile.InitialVelocity = 80 comet_projectile.Lifespan = 3.1f + ProjectileDefinition.CalculateDerivedFields(comet_projectile) dualcycler_projectile.Name = "dualcycler_projectile" dualcycler_projectile.Damage0 = 18 @@ -1905,6 +1940,7 @@ object GlobalDefinitions { dualcycler_projectile.DegradeMultiplier = .5f dualcycler_projectile.InitialVelocity = 500 dualcycler_projectile.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(dualcycler_projectile) dynomite_projectile.Name = "dynomite_projectile" // TODO for later, maybe : set_resource_parent dynomite_projectile game_objects frag_grenade_projectile_enh @@ -1915,6 +1951,7 @@ object GlobalDefinitions { dynomite_projectile.ProjectileDamageType = DamageType.Splash dynomite_projectile.InitialVelocity = 30 dynomite_projectile.Lifespan = 3f + ProjectileDefinition.CalculateDerivedFields(dynomite_projectile) energy_cell_projectile.Name = "energy_cell_projectile" energy_cell_projectile.Damage0 = 18 @@ -1925,6 +1962,7 @@ object GlobalDefinitions { energy_cell_projectile.InitialVelocity = 500 energy_cell_projectile.Lifespan = .4f energy_cell_projectile.UseDamage1Subtract = true + ProjectileDefinition.CalculateDerivedFields(energy_cell_projectile) energy_gun_nc_projectile.Name = "energy_gun_nc_projectile" energy_gun_nc_projectile.Damage0 = 10 @@ -1932,6 +1970,7 @@ object GlobalDefinitions { energy_gun_nc_projectile.ProjectileDamageType = DamageType.Direct energy_gun_nc_projectile.InitialVelocity = 500 energy_gun_nc_projectile.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(energy_gun_nc_projectile) energy_gun_tr_projectile.Name = "energy_gun_tr_projectile" energy_gun_tr_projectile.Damage0 = 14 @@ -1941,6 +1980,7 @@ object GlobalDefinitions { energy_gun_tr_projectile.DegradeMultiplier = .5f energy_gun_tr_projectile.InitialVelocity = 500 energy_gun_tr_projectile.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(energy_gun_tr_projectile) energy_gun_vs_projectile.Name = "energy_gun_vs_projectile" energy_gun_vs_projectile.Damage0 = 25 @@ -1950,6 +1990,7 @@ object GlobalDefinitions { energy_gun_vs_projectile.DegradeMultiplier = 0.5f energy_gun_vs_projectile.InitialVelocity = 500 energy_gun_vs_projectile.Lifespan = .5f + ProjectileDefinition.CalculateDerivedFields(energy_gun_vs_projectile) enhanced_energy_cell_projectile.Name = "enhanced_energy_cell_projectile" // TODO for later, maybe : set_resource_parent enhanced_energy_cell_projectile game_objects energy_cell_projectile @@ -1961,6 +2002,7 @@ object GlobalDefinitions { enhanced_energy_cell_projectile.InitialVelocity = 500 enhanced_energy_cell_projectile.Lifespan = .4f enhanced_energy_cell_projectile.UseDamage1Subtract = true + ProjectileDefinition.CalculateDerivedFields(enhanced_energy_cell_projectile) enhanced_quasar_projectile.Name = "enhanced_quasar_projectile" // TODO for later, maybe : set_resource_parent enhanced_quasar_projectile game_objects quasar_projectile @@ -1971,6 +2013,7 @@ object GlobalDefinitions { enhanced_quasar_projectile.DegradeMultiplier = 0.5f enhanced_quasar_projectile.InitialVelocity = 500 enhanced_quasar_projectile.Lifespan = .4f + ProjectileDefinition.CalculateDerivedFields(enhanced_quasar_projectile) falcon_projectile.Name = "falcon_projectile" falcon_projectile.Damage0 = 35 @@ -1985,6 +2028,7 @@ object GlobalDefinitions { falcon_projectile.ProjectileDamageType = DamageType.Splash falcon_projectile.InitialVelocity = 120 falcon_projectile.Lifespan = 2.1f + ProjectileDefinition.CalculateDerivedFields(falcon_projectile) firebird_missile_projectile.Name = "firebird_missile_projectile" firebird_missile_projectile.Damage0 = 125 @@ -1999,6 +2043,7 @@ object GlobalDefinitions { firebird_missile_projectile.ProjectileDamageType = DamageType.Splash firebird_missile_projectile.InitialVelocity = 75 firebird_missile_projectile.Lifespan = 5f + ProjectileDefinition.CalculateDerivedFields(firebird_missile_projectile) flail_projectile.Name = "flail_projectile" flail_projectile.Damage0 = 75 @@ -2013,6 +2058,7 @@ object GlobalDefinitions { flail_projectile.DegradeMultiplier = 5f flail_projectile.InitialVelocity = 75 flail_projectile.Lifespan = 40f + ProjectileDefinition.CalculateDerivedFields(flail_projectile) flamethrower_fireball.Name = "flamethrower_fireball" flamethrower_fireball.Damage0 = 30 @@ -2025,6 +2071,7 @@ object GlobalDefinitions { flamethrower_fireball.ProjectileDamageType = DamageType.Aggravated flamethrower_fireball.InitialVelocity = 15 flamethrower_fireball.Lifespan = 1.2f + ProjectileDefinition.CalculateDerivedFields(flamethrower_fireball) flamethrower_projectile.Name = "flamethrower_projectile" flamethrower_projectile.Damage0 = 10 @@ -2039,6 +2086,7 @@ object GlobalDefinitions { flamethrower_projectile.DegradeMultiplier = 0.5f flamethrower_projectile.InitialVelocity = 10 flamethrower_projectile.Lifespan = 2.0f + ProjectileDefinition.CalculateDerivedFields(flamethrower_projectile) flux_cannon_apc_projectile.Name = "flux_cannon_apc_projectile" // TODO for later, maybe : set_resource_parent flux_cannon_apc_projectile game_objects flux_cannon_thresher_projectile @@ -2052,6 +2100,7 @@ object GlobalDefinitions { flux_cannon_apc_projectile.ProjectileDamageType = DamageType.Direct flux_cannon_apc_projectile.InitialVelocity = 300 flux_cannon_apc_projectile.Lifespan = 1f + ProjectileDefinition.CalculateDerivedFields(flux_cannon_apc_projectile) flux_cannon_thresher_projectile.Name = "flux_cannon_thresher_projectile" flux_cannon_thresher_projectile.Damage0 = 30 @@ -2064,6 +2113,7 @@ object GlobalDefinitions { flux_cannon_thresher_projectile.ProjectileDamageType = DamageType.Splash flux_cannon_thresher_projectile.InitialVelocity = 75 flux_cannon_thresher_projectile.Lifespan = 3f + ProjectileDefinition.CalculateDerivedFields(flux_cannon_thresher_projectile) fluxpod_projectile.Name = "fluxpod_projectile" fluxpod_projectile.Damage0 = 110 @@ -2076,6 +2126,7 @@ object GlobalDefinitions { fluxpod_projectile.ProjectileDamageType = DamageType.Splash fluxpod_projectile.InitialVelocity = 80 fluxpod_projectile.Lifespan = 4f + ProjectileDefinition.CalculateDerivedFields(fluxpod_projectile) forceblade_projectile.Name = "forceblade_projectile" // TODO for later, maybe : set_resource_parent forceblade_projectile game_objects melee_ammo_projectile @@ -2084,6 +2135,7 @@ object GlobalDefinitions { forceblade_projectile.ProjectileDamageType = DamageType.Direct forceblade_projectile.InitialVelocity = 100 forceblade_projectile.Lifespan = .02f + ProjectileDefinition.CalculateDerivedFields(forceblade_projectile) frag_cartridge_projectile.Name = "frag_cartridge_projectile" // TODO for later, maybe : set_resource_parent frag_cartridge_projectile game_objects frag_grenade_projectile @@ -2094,6 +2146,7 @@ object GlobalDefinitions { frag_cartridge_projectile.ProjectileDamageType = DamageType.Splash frag_cartridge_projectile.InitialVelocity = 30 frag_cartridge_projectile.Lifespan = 15f + ProjectileDefinition.CalculateDerivedFields(frag_cartridge_projectile) frag_cartridge_projectile_b.Name = "frag_cartridge_projectile_b" // TODO for later, maybe : set_resource_parent frag_cartridge_projectile_b game_objects frag_grenade_projectile_enh @@ -2104,6 +2157,7 @@ object GlobalDefinitions { frag_cartridge_projectile_b.ProjectileDamageType = DamageType.Splash frag_cartridge_projectile_b.InitialVelocity = 30 frag_cartridge_projectile_b.Lifespan = 2f + ProjectileDefinition.CalculateDerivedFields(frag_cartridge_projectile_b) frag_grenade_projectile.Name = "frag_grenade_projectile" frag_grenade_projectile.Damage0 = 75 @@ -2113,6 +2167,7 @@ object GlobalDefinitions { frag_grenade_projectile.ProjectileDamageType = DamageType.Splash frag_grenade_projectile.InitialVelocity = 30 frag_grenade_projectile.Lifespan = 15f + ProjectileDefinition.CalculateDerivedFields(frag_grenade_projectile) frag_grenade_projectile_enh.Name = "frag_grenade_projectile_enh" // TODO for later, maybe : set_resource_parent frag_grenade_projectile_enh game_objects frag_grenade_projectile @@ -2123,6 +2178,7 @@ object GlobalDefinitions { frag_grenade_projectile_enh.ProjectileDamageType = DamageType.Splash frag_grenade_projectile_enh.InitialVelocity = 30 frag_grenade_projectile_enh.Lifespan = 2f + ProjectileDefinition.CalculateDerivedFields(frag_grenade_projectile_enh) galaxy_gunship_gun_projectile.Name = "galaxy_gunship_gun_projectile" // TODO for later, maybe : set_resource_parent galaxy_gunship_gun_projectile game_objects 35mmbullet_projectile @@ -2134,6 +2190,7 @@ object GlobalDefinitions { galaxy_gunship_gun_projectile.DegradeMultiplier = 0.6f galaxy_gunship_gun_projectile.InitialVelocity = 400 galaxy_gunship_gun_projectile.Lifespan = 0.8f + ProjectileDefinition.CalculateDerivedFields(galaxy_gunship_gun_projectile) gauss_cannon_projectile.Name = "gauss_cannon_projectile" gauss_cannon_projectile.Damage0 = 190 @@ -2146,6 +2203,7 @@ object GlobalDefinitions { gauss_cannon_projectile.ProjectileDamageType = DamageType.Splash gauss_cannon_projectile.InitialVelocity = 150 gauss_cannon_projectile.Lifespan = 2.67f + ProjectileDefinition.CalculateDerivedFields(gauss_cannon_projectile) grenade_projectile.Name = "grenade_projectile" grenade_projectile.Damage0 = 50 @@ -2154,6 +2212,7 @@ object GlobalDefinitions { grenade_projectile.ProjectileDamageType = DamageType.Splash grenade_projectile.InitialVelocity = 15 grenade_projectile.Lifespan = 15f + ProjectileDefinition.CalculateDerivedFields(grenade_projectile) heavy_grenade_projectile.Name = "heavy_grenade_projectile" heavy_grenade_projectile.Damage0 = 50 @@ -2166,6 +2225,7 @@ object GlobalDefinitions { heavy_grenade_projectile.ProjectileDamageType = DamageType.Splash heavy_grenade_projectile.InitialVelocity = 75 heavy_grenade_projectile.Lifespan = 5f + ProjectileDefinition.CalculateDerivedFields(heavy_grenade_projectile) heavy_rail_beam_projectile.Name = "heavy_rail_beam_projectile" heavy_rail_beam_projectile.Damage0 = 75 @@ -2178,6 +2238,7 @@ object GlobalDefinitions { heavy_rail_beam_projectile.ProjectileDamageType = DamageType.Splash heavy_rail_beam_projectile.InitialVelocity = 600 heavy_rail_beam_projectile.Lifespan = .5f + ProjectileDefinition.CalculateDerivedFields(heavy_rail_beam_projectile) heavy_sniper_projectile.Name = "heavy_sniper_projectile" heavy_sniper_projectile.Damage0 = 55 @@ -2188,6 +2249,7 @@ object GlobalDefinitions { heavy_sniper_projectile.ProjectileDamageType = DamageType.Splash heavy_sniper_projectile.InitialVelocity = 500 heavy_sniper_projectile.Lifespan = 1.0f + ProjectileDefinition.CalculateDerivedFields(heavy_sniper_projectile) hellfire_projectile.Name = "hellfire_projectile" hellfire_projectile.Damage0 = 50 @@ -2202,6 +2264,7 @@ object GlobalDefinitions { hellfire_projectile.ProjectileDamageType = DamageType.Splash hellfire_projectile.InitialVelocity = 125 hellfire_projectile.Lifespan = 1.5f + ProjectileDefinition.CalculateDerivedFields(hellfire_projectile) hunter_seeker_missile_dumbfire.Name = "hunter_seeker_missile_dumbfire" hunter_seeker_missile_dumbfire.Damage0 = 50 @@ -2214,6 +2277,7 @@ object GlobalDefinitions { hunter_seeker_missile_dumbfire.ProjectileDamageType = DamageType.Splash hunter_seeker_missile_dumbfire.InitialVelocity = 40 hunter_seeker_missile_dumbfire.Lifespan = 6.3f + ProjectileDefinition.CalculateDerivedFields(hunter_seeker_missile_dumbfire) hunter_seeker_missile_projectile.Name = "hunter_seeker_missile_projectile" hunter_seeker_missile_projectile.Damage0 = 50 @@ -2226,6 +2290,7 @@ object GlobalDefinitions { hunter_seeker_missile_projectile.ProjectileDamageType = DamageType.Splash hunter_seeker_missile_projectile.InitialVelocity = 40 hunter_seeker_missile_projectile.Lifespan = 6.3f + ProjectileDefinition.CalculateDerivedFields(hunter_seeker_missile_projectile) jammer_cartridge_projectile.Name = "jammer_cartridge_projectile" // TODO for later, maybe : set_resource_parent jammer_cartridge_projectile game_objects jammer_grenade_projectile @@ -2236,6 +2301,7 @@ object GlobalDefinitions { jammer_cartridge_projectile.ProjectileDamageType = DamageType.Splash jammer_cartridge_projectile.InitialVelocity = 30 jammer_cartridge_projectile.Lifespan = 15f + ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile) jammer_cartridge_projectile_b.Name = "jammer_cartridge_projectile_b" // TODO for later, maybe : set_resource_parent jammer_cartridge_projectile_b game_objects jammer_grenade_projectile_enh @@ -2246,6 +2312,7 @@ object GlobalDefinitions { jammer_cartridge_projectile_b.ProjectileDamageType = DamageType.Splash jammer_cartridge_projectile_b.InitialVelocity = 30 jammer_cartridge_projectile_b.Lifespan = 2f + ProjectileDefinition.CalculateDerivedFields(jammer_cartridge_projectile_b) jammer_grenade_projectile.Name = "jammer_grenade_projectile" jammer_grenade_projectile.Damage0 = 0 @@ -2255,6 +2322,7 @@ object GlobalDefinitions { jammer_grenade_projectile.ProjectileDamageType = DamageType.Splash jammer_grenade_projectile.InitialVelocity = 30 jammer_grenade_projectile.Lifespan = 15f + ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile) jammer_grenade_projectile_enh.Name = "jammer_grenade_projectile_enh" // TODO for later, maybe : set_resource_parent jammer_grenade_projectile_enh game_objects jammer_grenade_projectile @@ -2265,6 +2333,7 @@ object GlobalDefinitions { jammer_grenade_projectile_enh.ProjectileDamageType = DamageType.Splash jammer_grenade_projectile_enh.InitialVelocity = 30 jammer_grenade_projectile_enh.Lifespan = 3f + ProjectileDefinition.CalculateDerivedFields(jammer_grenade_projectile_enh) katana_projectile.Name = "katana_projectile" katana_projectile.Damage0 = 25 @@ -2272,6 +2341,7 @@ object GlobalDefinitions { katana_projectile.ProjectileDamageType = DamageType.Direct katana_projectile.InitialVelocity = 100 katana_projectile.Lifespan = .03f + ProjectileDefinition.CalculateDerivedFields(katana_projectile) katana_projectileb.Name = "katana_projectileb" // TODO for later, maybe : set_resource_parent katana_projectileb game_objects katana_projectile @@ -2280,6 +2350,7 @@ object GlobalDefinitions { katana_projectileb.ProjectileDamageType = DamageType.Direct katana_projectileb.InitialVelocity = 100 katana_projectileb.Lifespan = .03f + ProjectileDefinition.CalculateDerivedFields(katana_projectileb) lancer_projectile.Name = "lancer_projectile" lancer_projectile.Damage0 = 25 @@ -2290,6 +2361,7 @@ object GlobalDefinitions { lancer_projectile.ProjectileDamageType = DamageType.Direct lancer_projectile.InitialVelocity = 500 lancer_projectile.Lifespan = 0.6f + ProjectileDefinition.CalculateDerivedFields(lancer_projectile) lasher_projectile.Name = "lasher_projectile" lasher_projectile.Damage0 = 30 @@ -2302,6 +2374,7 @@ object GlobalDefinitions { lasher_projectile.DegradeMultiplier = 0.3f lasher_projectile.InitialVelocity = 120 lasher_projectile.Lifespan = 0.75f + ProjectileDefinition.CalculateDerivedFields(lasher_projectile) lasher_projectile_ap.Name = "lasher_projectile_ap" lasher_projectile_ap.Damage0 = 12 @@ -2314,6 +2387,7 @@ object GlobalDefinitions { lasher_projectile_ap.DegradeMultiplier = 0.3f lasher_projectile_ap.InitialVelocity = 120 lasher_projectile_ap.Lifespan = 0.75f + ProjectileDefinition.CalculateDerivedFields(lasher_projectile_ap) liberator_bomb_cluster_bomblet_projectile.Name = "liberator_bomb_cluster_bomblet_projectile" liberator_bomb_cluster_bomblet_projectile.Damage0 = 75 @@ -2323,6 +2397,7 @@ object GlobalDefinitions { liberator_bomb_cluster_bomblet_projectile.ProjectileDamageType = DamageType.Splash liberator_bomb_cluster_bomblet_projectile.InitialVelocity = 0 liberator_bomb_cluster_bomblet_projectile.Lifespan = 30f + ProjectileDefinition.CalculateDerivedFields(liberator_bomb_cluster_bomblet_projectile) liberator_bomb_cluster_projectile.Name = "liberator_bomb_cluster_projectile" liberator_bomb_cluster_projectile.Damage0 = 75 @@ -2332,6 +2407,7 @@ object GlobalDefinitions { liberator_bomb_cluster_projectile.ProjectileDamageType = DamageType.Direct liberator_bomb_cluster_projectile.InitialVelocity = 0 liberator_bomb_cluster_projectile.Lifespan = 30f + ProjectileDefinition.CalculateDerivedFields(liberator_bomb_cluster_projectile) liberator_bomb_projectile.Name = "liberator_bomb_projectile" liberator_bomb_projectile.Damage0 = 250 @@ -2344,6 +2420,7 @@ object GlobalDefinitions { liberator_bomb_projectile.ProjectileDamageType = DamageType.Splash liberator_bomb_projectile.InitialVelocity = 0 liberator_bomb_projectile.Lifespan = 30f + ProjectileDefinition.CalculateDerivedFields(liberator_bomb_projectile) maelstrom_grenade_projectile.Name = "maelstrom_grenade_projectile" maelstrom_grenade_projectile.Damage0 = 32 @@ -2352,6 +2429,7 @@ object GlobalDefinitions { maelstrom_grenade_projectile.ProjectileDamageType = DamageType.Direct maelstrom_grenade_projectile.InitialVelocity = 30 maelstrom_grenade_projectile.Lifespan = 2f + ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile) maelstrom_grenade_projectile_contact.Name = "maelstrom_grenade_projectile_contact" // TODO for later, maybe : set_resource_parent maelstrom_grenade_projectile_contact game_objects maelstrom_grenade_projectile @@ -2361,6 +2439,7 @@ object GlobalDefinitions { maelstrom_grenade_projectile_contact.ProjectileDamageType = DamageType.Direct maelstrom_grenade_projectile_contact.InitialVelocity = 30 maelstrom_grenade_projectile_contact.Lifespan = 15f + ProjectileDefinition.CalculateDerivedFields(maelstrom_grenade_projectile_contact) maelstrom_stream_projectile.Name = "maelstrom_stream_projectile" maelstrom_stream_projectile.Damage0 = 15 @@ -2370,6 +2449,7 @@ object GlobalDefinitions { maelstrom_stream_projectile.DegradeMultiplier = 0.5f maelstrom_stream_projectile.InitialVelocity = 200 maelstrom_stream_projectile.Lifespan = 0.2f + ProjectileDefinition.CalculateDerivedFields(maelstrom_stream_projectile) magcutter_projectile.Name = "magcutter_projectile" // TODO for later, maybe : set_resource_parent magcutter_projectile game_objects melee_ammo_projectile @@ -2378,6 +2458,7 @@ object GlobalDefinitions { magcutter_projectile.ProjectileDamageType = DamageType.Direct magcutter_projectile.InitialVelocity = 100 magcutter_projectile.Lifespan = .02f + ProjectileDefinition.CalculateDerivedFields(magcutter_projectile) melee_ammo_projectile.Name = "melee_ammo_projectile" melee_ammo_projectile.Damage0 = 25 @@ -2385,12 +2466,14 @@ object GlobalDefinitions { melee_ammo_projectile.ProjectileDamageType = DamageType.Direct melee_ammo_projectile.InitialVelocity = 100 melee_ammo_projectile.Lifespan = .02f + ProjectileDefinition.CalculateDerivedFields(melee_ammo_projectile) meteor_common.Name = "meteor_common" meteor_common.DamageAtEdge = .1f meteor_common.ProjectileDamageType = DamageType.Splash meteor_common.InitialVelocity = 0 meteor_common.Lifespan = 40 + ProjectileDefinition.CalculateDerivedFields(meteor_common) meteor_projectile_b_large.Name = "meteor_projectile_b_large" // TODO for later, maybe : set_resource_parent meteor_projectile_b_large game_objects meteor_common @@ -2401,6 +2484,7 @@ object GlobalDefinitions { meteor_projectile_b_large.ProjectileDamageType = DamageType.Splash meteor_projectile_b_large.InitialVelocity = 0 meteor_projectile_b_large.Lifespan = 40 + ProjectileDefinition.CalculateDerivedFields(meteor_projectile_b_large) meteor_projectile_b_medium.Name = "meteor_projectile_b_medium" // TODO for later, maybe : set_resource_parent meteor_projectile_b_medium game_objects meteor_common @@ -2411,6 +2495,7 @@ object GlobalDefinitions { meteor_projectile_b_medium.ProjectileDamageType = DamageType.Splash meteor_projectile_b_medium.InitialVelocity = 0 meteor_projectile_b_medium.Lifespan = 40 + ProjectileDefinition.CalculateDerivedFields(meteor_projectile_b_medium) meteor_projectile_b_small.Name = "meteor_projectile_b_small" // TODO for later, maybe : set_resource_parent meteor_projectile_b_small game_objects meteor_common @@ -2421,6 +2506,7 @@ object GlobalDefinitions { meteor_projectile_b_small.ProjectileDamageType = DamageType.Splash meteor_projectile_b_small.InitialVelocity = 0 meteor_projectile_b_small.Lifespan = 40 + ProjectileDefinition.CalculateDerivedFields(meteor_projectile_b_small) meteor_projectile_large.Name = "meteor_projectile_large" // TODO for later, maybe : set_resource_parent meteor_projectile_large game_objects meteor_common @@ -2431,6 +2517,7 @@ object GlobalDefinitions { meteor_projectile_large.ProjectileDamageType = DamageType.Splash meteor_projectile_large.InitialVelocity = 0 meteor_projectile_large.Lifespan = 40 + ProjectileDefinition.CalculateDerivedFields(meteor_projectile_large) meteor_projectile_medium.Name = "meteor_projectile_medium" // TODO for later, maybe : set_resource_parent meteor_projectile_medium game_objects meteor_common @@ -2441,6 +2528,7 @@ object GlobalDefinitions { meteor_projectile_medium.ProjectileDamageType = DamageType.Splash meteor_projectile_medium.InitialVelocity = 0 meteor_projectile_medium.Lifespan = 40 + ProjectileDefinition.CalculateDerivedFields(meteor_projectile_medium) meteor_projectile_small.Name = "meteor_projectile_small" // TODO for later, maybe : set_resource_parent meteor_projectile_small game_objects meteor_common @@ -2451,10 +2539,12 @@ object GlobalDefinitions { meteor_projectile_small.ProjectileDamageType = DamageType.Splash meteor_projectile_small.InitialVelocity = 0 meteor_projectile_small.Lifespan = 40 + ProjectileDefinition.CalculateDerivedFields(meteor_projectile_small) mine_projectile.Name = "mine_projectile" mine_projectile.Lifespan = 0.01f mine_projectile.InitialVelocity = 300 + ProjectileDefinition.CalculateDerivedFields(mine_projectile) mine_sweeper_projectile.Name = "mine_sweeper_projectile" mine_sweeper_projectile.Damage0 = 0 @@ -2464,6 +2554,7 @@ object GlobalDefinitions { mine_sweeper_projectile.ProjectileDamageType = DamageType.Splash mine_sweeper_projectile.InitialVelocity = 30 mine_sweeper_projectile.Lifespan = 15f + ProjectileDefinition.CalculateDerivedFields(mine_sweeper_projectile) mine_sweeper_projectile_enh.Name = "mine_sweeper_projectile_enh" mine_sweeper_projectile_enh.Damage0 = 0 @@ -2472,6 +2563,7 @@ object GlobalDefinitions { mine_sweeper_projectile_enh.DamageRadius = 25f mine_sweeper_projectile_enh.InitialVelocity = 30 mine_sweeper_projectile_enh.Lifespan = 3f + ProjectileDefinition.CalculateDerivedFields(mine_sweeper_projectile_enh) oicw_projectile.Name = "oicw_projectile" oicw_projectile.Damage0 = 50 @@ -2483,6 +2575,7 @@ object GlobalDefinitions { oicw_projectile.ProjectileDamageType = DamageType.Splash oicw_projectile.InitialVelocity = 5 oicw_projectile.Lifespan = 6.1f + ProjectileDefinition.CalculateDerivedFields(oicw_projectile) pellet_gun_projectile.Name = "pellet_gun_projectile" // TODO for later, maybe : set_resource_parent pellet_gun_projectile game_objects shotgun_shell_projectile @@ -2492,6 +2585,7 @@ object GlobalDefinitions { pellet_gun_projectile.InitialVelocity = 400 pellet_gun_projectile.Lifespan = 0.1875f pellet_gun_projectile.UseDamage1Subtract = false + ProjectileDefinition.CalculateDerivedFields(pellet_gun_projectile) peregrine_dual_machine_gun_projectile.Name = "peregrine_dual_machine_gun_projectile" // TODO for later, maybe : set_resource_parent peregrine_dual_machine_gun_projectile game_objects 35mmbullet_projectile @@ -2505,6 +2599,7 @@ object GlobalDefinitions { peregrine_dual_machine_gun_projectile.DegradeMultiplier = 0.65f peregrine_dual_machine_gun_projectile.InitialVelocity = 250 peregrine_dual_machine_gun_projectile.Lifespan = 1.1f + ProjectileDefinition.CalculateDerivedFields(peregrine_dual_machine_gun_projectile) peregrine_mechhammer_projectile.Name = "peregrine_mechhammer_projectile" peregrine_mechhammer_projectile.Damage0 = 5 @@ -2515,6 +2610,7 @@ object GlobalDefinitions { peregrine_mechhammer_projectile.ProjectileDamageType = DamageType.Direct peregrine_mechhammer_projectile.InitialVelocity = 500 peregrine_mechhammer_projectile.Lifespan = 0.4f + ProjectileDefinition.CalculateDerivedFields(peregrine_mechhammer_projectile) peregrine_particle_cannon_projectile.Name = "peregrine_particle_cannon_projectile" peregrine_particle_cannon_projectile.Damage0 = 70 @@ -2527,6 +2623,7 @@ object GlobalDefinitions { peregrine_particle_cannon_projectile.ProjectileDamageType = DamageType.Splash peregrine_particle_cannon_projectile.InitialVelocity = 500 peregrine_particle_cannon_projectile.Lifespan = .6f + ProjectileDefinition.CalculateDerivedFields(peregrine_particle_cannon_projectile) peregrine_rocket_pod_projectile.Name = "peregrine_rocket_pod_projectile" peregrine_rocket_pod_projectile.Damage0 = 30 @@ -2541,6 +2638,7 @@ object GlobalDefinitions { peregrine_rocket_pod_projectile.ProjectileDamageType = DamageType.Splash peregrine_rocket_pod_projectile.InitialVelocity = 200 peregrine_rocket_pod_projectile.Lifespan = 1.85f + ProjectileDefinition.CalculateDerivedFields(peregrine_rocket_pod_projectile) peregrine_sparrow_projectile.Name = "peregrine_sparrow_projectile" // TODO for later, maybe : set_resource_parent peregrine_sparrow_projectile game_objects sparrow_projectile @@ -2556,6 +2654,7 @@ object GlobalDefinitions { peregrine_sparrow_projectile.ProjectileDamageType = DamageType.Splash peregrine_sparrow_projectile.InitialVelocity = 45 peregrine_sparrow_projectile.Lifespan = 7.5f + ProjectileDefinition.CalculateDerivedFields(peregrine_sparrow_projectile) phalanx_av_projectile.Name = "phalanx_av_projectile" phalanx_av_projectile.Damage0 = 60 @@ -2565,6 +2664,7 @@ object GlobalDefinitions { phalanx_av_projectile.ProjectileDamageType = DamageType.Splash phalanx_av_projectile.InitialVelocity = 100 phalanx_av_projectile.Lifespan = 4f + ProjectileDefinition.CalculateDerivedFields(phalanx_av_projectile) phalanx_flak_projectile.Name = "phalanx_flak_projectile" phalanx_flak_projectile.Damage0 = 15 @@ -2576,6 +2676,7 @@ object GlobalDefinitions { phalanx_flak_projectile.ProjectileDamageTypeSecondary = DamageType.Splash phalanx_flak_projectile.InitialVelocity = 100 phalanx_flak_projectile.Lifespan = 5f + ProjectileDefinition.CalculateDerivedFields(phalanx_flak_projectile) phalanx_projectile.Name = "phalanx_projectile" phalanx_projectile.Damage0 = 20 @@ -2588,6 +2689,7 @@ object GlobalDefinitions { phalanx_projectile.DegradeMultiplier = 0.25f phalanx_projectile.InitialVelocity = 400 phalanx_projectile.Lifespan = 1f + ProjectileDefinition.CalculateDerivedFields(phalanx_projectile) phoenix_missile_guided_projectile.Name = "phoenix_missile_guided_projectile" // TODO for later, maybe : set_resource_parent phoenix_missile_guided_projectile game_objects phoenix_missile_projectile @@ -2603,6 +2705,7 @@ object GlobalDefinitions { phoenix_missile_guided_projectile.ProjectileDamageType = DamageType.Splash phoenix_missile_guided_projectile.InitialVelocity = 0 phoenix_missile_guided_projectile.Lifespan = 3f + ProjectileDefinition.CalculateDerivedFields(phoenix_missile_guided_projectile) phoenix_missile_projectile.Name = "phoenix_missile_projectile" phoenix_missile_projectile.Damage0 = 80 @@ -2617,6 +2720,7 @@ object GlobalDefinitions { phoenix_missile_projectile.ProjectileDamageType = DamageType.Splash phoenix_missile_projectile.InitialVelocity = 0 phoenix_missile_projectile.Lifespan = 3f + ProjectileDefinition.CalculateDerivedFields(phoenix_missile_projectile) plasma_cartridge_projectile.Name = "plasma_cartridge_projectile" // TODO for later, maybe : set_resource_parent plasma_cartridge_projectile game_objects plasma_grenade_projectile @@ -2627,6 +2731,7 @@ object GlobalDefinitions { plasma_cartridge_projectile.ProjectileDamageType = DamageType.Aggravated plasma_cartridge_projectile.InitialVelocity = 30 plasma_cartridge_projectile.Lifespan = 15f + ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile) plasma_cartridge_projectile_b.Name = "plasma_cartridge_projectile_b" // TODO for later, maybe : set_resource_parent plasma_cartridge_projectile_b game_objects plasma_grenade_projectile_B @@ -2637,6 +2742,7 @@ object GlobalDefinitions { plasma_cartridge_projectile_b.ProjectileDamageType = DamageType.Aggravated plasma_cartridge_projectile_b.InitialVelocity = 30 plasma_cartridge_projectile_b.Lifespan = 2f + ProjectileDefinition.CalculateDerivedFields(plasma_cartridge_projectile_b) plasma_grenade_projectile.Name = "plasma_grenade_projectile" plasma_grenade_projectile.Damage0 = 40 @@ -2646,6 +2752,7 @@ object GlobalDefinitions { plasma_grenade_projectile.ProjectileDamageType = DamageType.Aggravated plasma_grenade_projectile.InitialVelocity = 30 plasma_grenade_projectile.Lifespan = 15f + ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile) plasma_grenade_projectile_B.Name = "plasma_grenade_projectile_B" // TODO for later, maybe : set_resource_parent plasma_grenade_projectile_B game_objects plasma_grenade_projectile @@ -2656,6 +2763,7 @@ object GlobalDefinitions { plasma_grenade_projectile_B.ProjectileDamageType = DamageType.Aggravated plasma_grenade_projectile_B.InitialVelocity = 30 plasma_grenade_projectile_B.Lifespan = 3f + ProjectileDefinition.CalculateDerivedFields(plasma_grenade_projectile_B) pounder_projectile.Name = "pounder_projectile" pounder_projectile.Damage0 = 31 @@ -2668,6 +2776,7 @@ object GlobalDefinitions { pounder_projectile.ProjectileDamageType = DamageType.Splash pounder_projectile.InitialVelocity = 120 pounder_projectile.Lifespan = 2.5f + ProjectileDefinition.CalculateDerivedFields(pounder_projectile) pounder_projectile_enh.Name = "pounder_projectile_enh" // TODO for later, maybe : set_resource_parent pounder_projectile_enh game_objects pounder_projectile @@ -2681,6 +2790,7 @@ object GlobalDefinitions { pounder_projectile_enh.ProjectileDamageType = DamageType.Splash pounder_projectile_enh.InitialVelocity = 120 pounder_projectile_enh.Lifespan = 3.2f + ProjectileDefinition.CalculateDerivedFields(pounder_projectile_enh) ppa_projectile.Name = "ppa_projectile" ppa_projectile.Damage0 = 20 @@ -2691,6 +2801,7 @@ object GlobalDefinitions { ppa_projectile.ProjectileDamageType = DamageType.Direct ppa_projectile.InitialVelocity = 400 ppa_projectile.Lifespan = .5f + ProjectileDefinition.CalculateDerivedFields(ppa_projectile) pulsar_ap_projectile.Name = "pulsar_ap_projectile" // TODO for later, maybe : set_resource_parent pulsar_ap_projectile game_objects pulsar_projectile @@ -2702,6 +2813,7 @@ object GlobalDefinitions { pulsar_ap_projectile.InitialVelocity = 500 pulsar_ap_projectile.Lifespan = .4f pulsar_ap_projectile.UseDamage1Subtract = true + ProjectileDefinition.CalculateDerivedFields(pulsar_ap_projectile) pulsar_projectile.Name = "pulsar_projectile" pulsar_projectile.Damage0 = 20 @@ -2712,6 +2824,7 @@ object GlobalDefinitions { pulsar_projectile.InitialVelocity = 500 pulsar_projectile.Lifespan = .4f pulsar_projectile.UseDamage1Subtract = true + ProjectileDefinition.CalculateDerivedFields(pulsar_projectile) quasar_projectile.Name = "quasar_projectile" quasar_projectile.Damage0 = 18 @@ -2721,17 +2834,20 @@ object GlobalDefinitions { quasar_projectile.DegradeMultiplier = 0.5f quasar_projectile.InitialVelocity = 500 quasar_projectile.Lifespan = .4f + ProjectileDefinition.CalculateDerivedFields(quasar_projectile) radiator_grenade_projectile.Name = "radiator_grenade_projectile" // Todo : Radiator damages ? radiator_grenade_projectile.ProjectileDamageType = DamageType.Direct radiator_grenade_projectile.InitialVelocity = 30 radiator_grenade_projectile.Lifespan = 3f + ProjectileDefinition.CalculateDerivedFields(radiator_grenade_projectile) radiator_sticky_projectile.Name = "radiator_sticky_projectile" // TODO for later, maybe : set_resource_parent radiator_sticky_projectile game_objects radiator_grenade_projectile radiator_sticky_projectile.ProjectileDamageType = DamageType.Direct radiator_sticky_projectile.InitialVelocity = 30 radiator_sticky_projectile.Lifespan = 4f + ProjectileDefinition.CalculateDerivedFields(radiator_sticky_projectile) reaver_rocket_projectile.Name = "reaver_rocket_projectile" reaver_rocket_projectile.Damage0 = 25 @@ -2746,6 +2862,7 @@ object GlobalDefinitions { reaver_rocket_projectile.ProjectileDamageType = DamageType.Splash reaver_rocket_projectile.InitialVelocity = 100 reaver_rocket_projectile.Lifespan = 2.1f + ProjectileDefinition.CalculateDerivedFields(reaver_rocket_projectile) rocket_projectile.Name = "rocket_projectile" rocket_projectile.Damage0 = 50 @@ -2760,6 +2877,7 @@ object GlobalDefinitions { rocket_projectile.ProjectileDamageType = DamageType.Splash rocket_projectile.InitialVelocity = 50 rocket_projectile.Lifespan = 8f + ProjectileDefinition.CalculateDerivedFields(rocket_projectile) rocklet_flak_projectile.Name = "rocklet_flak_projectile" rocklet_flak_projectile.Damage0 = 20 @@ -2773,6 +2891,7 @@ object GlobalDefinitions { rocklet_flak_projectile.ProjectileDamageTypeSecondary = DamageType.Splash rocklet_flak_projectile.InitialVelocity = 60 rocklet_flak_projectile.Lifespan = 3.2f + ProjectileDefinition.CalculateDerivedFields(rocklet_flak_projectile) rocklet_jammer_projectile.Name = "rocklet_jammer_projectile" rocklet_jammer_projectile.Damage0 = 0 @@ -2783,6 +2902,7 @@ object GlobalDefinitions { rocklet_jammer_projectile.ProjectileDamageType = DamageType.Splash rocklet_jammer_projectile.InitialVelocity = 50 rocklet_jammer_projectile.Lifespan = 8f + ProjectileDefinition.CalculateDerivedFields(rocklet_jammer_projectile) scattercannon_projectile.Name = "scattercannon_projectile" scattercannon_projectile.Damage0 = 11 @@ -2790,6 +2910,7 @@ object GlobalDefinitions { scattercannon_projectile.ProjectileDamageType = DamageType.Direct scattercannon_projectile.InitialVelocity = 400 scattercannon_projectile.Lifespan = 0.25f + ProjectileDefinition.CalculateDerivedFields(scattercannon_projectile) scythe_projectile.Name = "scythe_projectile" scythe_projectile.Damage0 = 30 @@ -2799,10 +2920,12 @@ object GlobalDefinitions { scythe_projectile.DegradeMultiplier = 0.35f scythe_projectile.InitialVelocity = 60 scythe_projectile.Lifespan = 3f + ProjectileDefinition.CalculateDerivedFields(scythe_projectile) scythe_projectile_slave.Name = "scythe_projectile_slave" // Todo how does it work ? scythe_projectile_slave.InitialVelocity = 30 scythe_projectile_slave.Lifespan = 3f + ProjectileDefinition.CalculateDerivedFields(scythe_projectile_slave) shotgun_shell_AP_projectile.Name = "shotgun_shell_AP_projectile" // TODO for later, maybe : set_resource_parent shotgun_shell_AP_projectile game_objects shotgun_shell_projectile @@ -2812,6 +2935,7 @@ object GlobalDefinitions { shotgun_shell_AP_projectile.InitialVelocity = 400 shotgun_shell_AP_projectile.Lifespan = 0.25f shotgun_shell_AP_projectile.UseDamage1Subtract = true + ProjectileDefinition.CalculateDerivedFields(shotgun_shell_AP_projectile) shotgun_shell_projectile.Name = "shotgun_shell_projectile" shotgun_shell_projectile.Damage0 = 12 @@ -2820,6 +2944,7 @@ object GlobalDefinitions { shotgun_shell_projectile.InitialVelocity = 400 shotgun_shell_projectile.Lifespan = 0.25f shotgun_shell_projectile.UseDamage1Subtract = true + ProjectileDefinition.CalculateDerivedFields(shotgun_shell_projectile) six_shooter_projectile.Name = "six_shooter_projectile" // TODO for later, maybe : set_resource_parent six_shooter_projectile game_objects 9mmbullet_projectile @@ -2831,6 +2956,7 @@ object GlobalDefinitions { six_shooter_projectile.InitialVelocity = 500 six_shooter_projectile.Lifespan = 0.4f six_shooter_projectile.UseDamage1Subtract = false + ProjectileDefinition.CalculateDerivedFields(six_shooter_projectile) skyguard_flak_cannon_projectile.Name = "skyguard_flak_cannon_projectile" skyguard_flak_cannon_projectile.Damage0 = 15 @@ -2842,6 +2968,7 @@ object GlobalDefinitions { skyguard_flak_cannon_projectile.ProjectileDamageTypeSecondary = DamageType.Splash skyguard_flak_cannon_projectile.InitialVelocity = 100 skyguard_flak_cannon_projectile.Lifespan = 5f + ProjectileDefinition.CalculateDerivedFields(skyguard_flak_cannon_projectile) sparrow_projectile.Name = "sparrow_projectile" sparrow_projectile.Damage0 = 35 @@ -2854,6 +2981,7 @@ object GlobalDefinitions { sparrow_projectile.ProjectileDamageType = DamageType.Splash sparrow_projectile.InitialVelocity = 60 sparrow_projectile.Lifespan = 5.85f + ProjectileDefinition.CalculateDerivedFields(sparrow_projectile) sparrow_secondary_projectile.Name = "sparrow_secondary_projectile" // TODO for later, maybe : set_resource_parent sparrow_secondary_projectile game_objects sparrow_projectile @@ -2867,6 +2995,7 @@ object GlobalDefinitions { sparrow_secondary_projectile.ProjectileDamageType = DamageType.Splash sparrow_secondary_projectile.InitialVelocity = 60 sparrow_secondary_projectile.Lifespan = 5.85f + ProjectileDefinition.CalculateDerivedFields(sparrow_secondary_projectile) spiker_projectile.Name = "spiker_projectile" // spiker_projectile.Damage0 = 75 @@ -2881,6 +3010,7 @@ object GlobalDefinitions { spiker_projectile.ProjectileDamageType = DamageType.Splash spiker_projectile.InitialVelocity = 40 spiker_projectile.Lifespan = 5f + ProjectileDefinition.CalculateDerivedFields(spiker_projectile) spitfire_aa_ammo_projectile.Name = "spitfire_aa_ammo_projectile" spitfire_aa_ammo_projectile.Damage0 = 5 @@ -2894,6 +3024,7 @@ object GlobalDefinitions { spitfire_aa_ammo_projectile.ProjectileDamageTypeSecondary = DamageType.Splash spitfire_aa_ammo_projectile.InitialVelocity = 100 spitfire_aa_ammo_projectile.Lifespan = 5f + ProjectileDefinition.CalculateDerivedFields(spitfire_aa_ammo_projectile) spitfire_ammo_projectile.Name = "spitfire_ammo_projectile" spitfire_ammo_projectile.Damage0 = 15 @@ -2903,6 +3034,7 @@ object GlobalDefinitions { spitfire_ammo_projectile.DegradeMultiplier = 0.5f spitfire_ammo_projectile.InitialVelocity = 100 spitfire_ammo_projectile.Lifespan = .5f + ProjectileDefinition.CalculateDerivedFields(spitfire_ammo_projectile) starfire_projectile.Name = "starfire_projectile" starfire_projectile.Damage0 = 16 @@ -2913,6 +3045,7 @@ object GlobalDefinitions { starfire_projectile.ProjectileDamageType = DamageType.Aggravated starfire_projectile.InitialVelocity = 45 starfire_projectile.Lifespan = 7.8f + ProjectileDefinition.CalculateDerivedFields(starfire_projectile) striker_missile_projectile.Name = "striker_missile_projectile" striker_missile_projectile.Damage0 = 35 @@ -2927,6 +3060,7 @@ object GlobalDefinitions { striker_missile_projectile.ProjectileDamageType = DamageType.Splash striker_missile_projectile.InitialVelocity = 30 striker_missile_projectile.Lifespan = 4.2f + ProjectileDefinition.CalculateDerivedFields(striker_missile_projectile) striker_missile_targeting_projectile.Name = "striker_missile_targeting_projectile" // TODO for later, maybe : set_resource_parent striker_missile_targeting_projectile game_objects striker_missile_projectile @@ -2942,6 +3076,7 @@ object GlobalDefinitions { striker_missile_targeting_projectile.ProjectileDamageType = DamageType.Splash striker_missile_targeting_projectile.InitialVelocity = 30 striker_missile_targeting_projectile.Lifespan = 4.2f + ProjectileDefinition.CalculateDerivedFields(striker_missile_targeting_projectile) trek_projectile.Name = "trek_projectile" trek_projectile.Damage0 = 0 @@ -2954,6 +3089,7 @@ object GlobalDefinitions { trek_projectile.ProjectileDamageType = DamageType.Direct trek_projectile.InitialVelocity = 40 trek_projectile.Lifespan = 7f + ProjectileDefinition.CalculateDerivedFields(trek_projectile) vanu_sentry_turret_projectile.Name = "vanu_sentry_turret_projectile" vanu_sentry_turret_projectile.Damage0 = 25 @@ -2964,6 +3100,7 @@ object GlobalDefinitions { vanu_sentry_turret_projectile.ProjectileDamageType = DamageType.Splash vanu_sentry_turret_projectile.InitialVelocity = 240 vanu_sentry_turret_projectile.Lifespan = 1.3f + ProjectileDefinition.CalculateDerivedFields(vanu_sentry_turret_projectile) vulture_bomb_projectile.Name = "vulture_bomb_projectile" vulture_bomb_projectile.Damage0 = 175 @@ -2976,6 +3113,7 @@ object GlobalDefinitions { vulture_bomb_projectile.ProjectileDamageType = DamageType.Splash vulture_bomb_projectile.InitialVelocity = 0 vulture_bomb_projectile.Lifespan = 30f + ProjectileDefinition.CalculateDerivedFields(vulture_bomb_projectile) vulture_nose_bullet_projectile.Name = "vulture_nose_bullet_projectile" vulture_nose_bullet_projectile.Damage0 = 12 @@ -2988,6 +3126,7 @@ object GlobalDefinitions { vulture_nose_bullet_projectile.DegradeMultiplier = 0.7f vulture_nose_bullet_projectile.InitialVelocity = 500 vulture_nose_bullet_projectile.Lifespan = 0.46f + ProjectileDefinition.CalculateDerivedFields(vulture_nose_bullet_projectile) vulture_tail_bullet_projectile.Name = "vulture_tail_bullet_projectile" vulture_tail_bullet_projectile.Damage0 = 25 @@ -2998,6 +3137,7 @@ object GlobalDefinitions { vulture_tail_bullet_projectile.DegradeMultiplier = 0.5f vulture_tail_bullet_projectile.InitialVelocity = 500 vulture_tail_bullet_projectile.Lifespan = 0.6f + ProjectileDefinition.CalculateDerivedFields(vulture_tail_bullet_projectile) wasp_gun_projectile.Name = "wasp_gun_projectile" wasp_gun_projectile.Damage0 = 10 @@ -3010,6 +3150,7 @@ object GlobalDefinitions { wasp_gun_projectile.DegradeMultiplier = 0.5f wasp_gun_projectile.InitialVelocity = 500 wasp_gun_projectile.Lifespan = 0.5f + ProjectileDefinition.CalculateDerivedFields(wasp_gun_projectile) wasp_rocket_projectile.Name = "wasp_rocket_projectile" wasp_rocket_projectile.Damage0 = 35 @@ -3022,6 +3163,7 @@ object GlobalDefinitions { wasp_rocket_projectile.ProjectileDamageType = DamageType.Splash wasp_rocket_projectile.InitialVelocity = 60 wasp_rocket_projectile.Lifespan = 6.5f + ProjectileDefinition.CalculateDerivedFields(wasp_rocket_projectile) winchester_projectile.Name = "winchester_projectile" // TODO for later, maybe : set_resource_parent winchester_projectile game_objects bolt_projectile @@ -3033,6 +3175,7 @@ object GlobalDefinitions { winchester_projectile.ProjectileDamageType = DamageType.Direct winchester_projectile.InitialVelocity = 500 winchester_projectile.Lifespan = 0.6f + ProjectileDefinition.CalculateDerivedFields(winchester_projectile) } /** @@ -4370,6 +4513,8 @@ object GlobalDefinitions { */ private def init_vehicles() : Unit = { fury.Name = "fury" + fury.MaxHealth = 650 + fury.MaxShields = 130 + 1 fury.Seats += 0 -> new SeatDefinition() fury.Seats(0).Bailable = true fury.Seats(0).ControlledWeapon = 1 @@ -4379,8 +4524,11 @@ object GlobalDefinitions { fury.TrunkSize = InventoryTile.Tile1111 fury.TrunkOffset = 30 fury.AutoPilotSpeeds = (24, 10) + fury.DestroyedModel = Some(DestroyedVehicle.QuadAssault) quadassault.Name = "quadassault" + quadassault.MaxHealth = 650 + quadassault.MaxShields = 130 + 1 quadassault.Seats += 0 -> new SeatDefinition() quadassault.Seats(0).Bailable = true quadassault.Seats(0).ControlledWeapon = 1 @@ -4390,8 +4538,11 @@ object GlobalDefinitions { quadassault.TrunkSize = InventoryTile.Tile1111 quadassault.TrunkOffset = 30 quadassault.AutoPilotSpeeds = (24, 10) + quadassault.DestroyedModel = Some(DestroyedVehicle.QuadAssault) quadstealth.Name = "quadstealth" + quadstealth.MaxHealth = 650 + quadstealth.MaxShields = 130 + 1 quadstealth.CanCloak = true quadstealth.Seats += 0 -> new SeatDefinition() quadstealth.Seats(0).Bailable = true @@ -4401,8 +4552,11 @@ object GlobalDefinitions { quadstealth.TrunkSize = InventoryTile.Tile1111 quadstealth.TrunkOffset = 30 quadstealth.AutoPilotSpeeds = (24, 10) + quadstealth.DestroyedModel = Some(DestroyedVehicle.QuadStealth) two_man_assault_buggy.Name = "two_man_assault_buggy" + two_man_assault_buggy.MaxHealth = 1250 + two_man_assault_buggy.MaxShields = 250 + 1 two_man_assault_buggy.Seats += 0 -> new SeatDefinition() two_man_assault_buggy.Seats(0).Bailable = true two_man_assault_buggy.Seats += 1 -> new SeatDefinition() @@ -4414,8 +4568,11 @@ object GlobalDefinitions { two_man_assault_buggy.TrunkSize = InventoryTile.Tile1511 two_man_assault_buggy.TrunkOffset = 30 two_man_assault_buggy.AutoPilotSpeeds = (22, 8) + two_man_assault_buggy.DestroyedModel = Some(DestroyedVehicle.TwoManAssaultBuggy) skyguard.Name = "skyguard" + skyguard.MaxHealth = 1000 + skyguard.MaxShields = 200 + 1 skyguard.Seats += 0 -> new SeatDefinition() skyguard.Seats(0).Bailable = true skyguard.Seats += 1 -> new SeatDefinition() @@ -4428,8 +4585,11 @@ object GlobalDefinitions { skyguard.TrunkSize = InventoryTile.Tile1511 skyguard.TrunkOffset = 30 skyguard.AutoPilotSpeeds = (22, 8) + skyguard.DestroyedModel = Some(DestroyedVehicle.Skyguard) threemanheavybuggy.Name = "threemanheavybuggy" + threemanheavybuggy.MaxHealth = 1700 + threemanheavybuggy.MaxShields = 340 + 1 threemanheavybuggy.Seats += 0 -> new SeatDefinition() threemanheavybuggy.Seats(0).Bailable = true threemanheavybuggy.Seats += 1 -> new SeatDefinition() @@ -4446,8 +4606,11 @@ object GlobalDefinitions { threemanheavybuggy.TrunkSize = InventoryTile.Tile1511 threemanheavybuggy.TrunkOffset = 30 threemanheavybuggy.AutoPilotSpeeds = (22, 8) + threemanheavybuggy.DestroyedModel = Some(DestroyedVehicle.ThreeManHeavyBuggy) twomanheavybuggy.Name = "twomanheavybuggy" + twomanheavybuggy.MaxHealth = 1800 + twomanheavybuggy.MaxShields = 360 + 1 twomanheavybuggy.Seats += 0 -> new SeatDefinition() twomanheavybuggy.Seats(0).Bailable = true twomanheavybuggy.Seats += 1 -> new SeatDefinition() @@ -4459,8 +4622,11 @@ object GlobalDefinitions { twomanheavybuggy.TrunkSize = InventoryTile.Tile1511 twomanheavybuggy.TrunkOffset = 30 twomanheavybuggy.AutoPilotSpeeds = (22, 8) + twomanheavybuggy.DestroyedModel = Some(DestroyedVehicle.TwoManHeavyBuggy) twomanhoverbuggy.Name = "twomanhoverbuggy" + twomanhoverbuggy.MaxHealth = 1600 + twomanhoverbuggy.MaxShields = 320 + 1 twomanhoverbuggy.Seats += 0 -> new SeatDefinition() twomanhoverbuggy.Seats(0).Bailable = true twomanhoverbuggy.Seats += 1 -> new SeatDefinition() @@ -4472,8 +4638,11 @@ object GlobalDefinitions { twomanhoverbuggy.TrunkSize = InventoryTile.Tile1511 twomanhoverbuggy.TrunkOffset = 30 twomanhoverbuggy.AutoPilotSpeeds = (22, 10) + twomanhoverbuggy.DestroyedModel = Some(DestroyedVehicle.TwoManHoverBuggy) mediumtransport.Name = "mediumtransport" + mediumtransport.MaxHealth = 2500 + mediumtransport.MaxShields = 500 + 1 mediumtransport.Seats += 0 -> new SeatDefinition() mediumtransport.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax mediumtransport.Seats += 1 -> new SeatDefinition() @@ -4492,8 +4661,11 @@ object GlobalDefinitions { mediumtransport.TrunkSize = InventoryTile.Tile1515 mediumtransport.TrunkOffset = 30 mediumtransport.AutoPilotSpeeds = (18, 6) + mediumtransport.DestroyedModel = Some(DestroyedVehicle.MediumTransport) battlewagon.Name = "battlewagon" + battlewagon.MaxHealth = 2500 + battlewagon.MaxShields = 500 + 1 battlewagon.Seats += 0 -> new SeatDefinition() battlewagon.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax battlewagon.Seats += 1 -> new SeatDefinition() @@ -4516,8 +4688,11 @@ object GlobalDefinitions { battlewagon.TrunkSize = InventoryTile.Tile1515 battlewagon.TrunkOffset = 30 battlewagon.AutoPilotSpeeds = (18, 6) + battlewagon.DestroyedModel = Some(DestroyedVehicle.MediumTransport) thunderer.Name = "thunderer" + thunderer.MaxHealth = 2500 + thunderer.MaxShields = 500 + 1 thunderer.Seats += 0 -> new SeatDefinition() thunderer.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax thunderer.Seats += 1 -> new SeatDefinition() @@ -4536,8 +4711,11 @@ object GlobalDefinitions { thunderer.TrunkSize = InventoryTile.Tile1515 thunderer.TrunkOffset = 30 thunderer.AutoPilotSpeeds = (18, 6) + thunderer.DestroyedModel = Some(DestroyedVehicle.MediumTransport) aurora.Name = "aurora" + aurora.MaxHealth = 2500 + aurora.MaxShields = 500 + 1 aurora.Seats += 0 -> new SeatDefinition() aurora.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax aurora.Seats += 1 -> new SeatDefinition() @@ -4556,8 +4734,11 @@ object GlobalDefinitions { aurora.TrunkSize = InventoryTile.Tile1515 aurora.TrunkOffset = 30 aurora.AutoPilotSpeeds = (18, 6) + aurora.DestroyedModel = Some(DestroyedVehicle.MediumTransport) apc_tr.Name = "apc_tr" + apc_tr.MaxHealth = 6000 + apc_tr.MaxShields = 1200 + 1 apc_tr.Seats += 0 -> new SeatDefinition() apc_tr.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax apc_tr.Seats += 1 -> new SeatDefinition() @@ -4599,8 +4780,11 @@ object GlobalDefinitions { apc_tr.TrunkSize = InventoryTile.Tile2016 apc_tr.TrunkOffset = 30 apc_tr.AutoPilotSpeeds = (16, 6) + apc_tr.DestroyedModel = Some(DestroyedVehicle.Apc) apc_nc.Name = "apc_nc" + apc_nc.MaxHealth = 6000 + apc_nc.MaxShields = 1200 + 1 apc_nc.Seats += 0 -> new SeatDefinition() apc_nc.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax apc_nc.Seats += 1 -> new SeatDefinition() @@ -4642,8 +4826,11 @@ object GlobalDefinitions { apc_nc.TrunkSize = InventoryTile.Tile2016 apc_nc.TrunkOffset = 30 apc_nc.AutoPilotSpeeds = (16, 6) + apc_nc.DestroyedModel = Some(DestroyedVehicle.Apc) apc_vs.Name = "apc_vs" + apc_vs.MaxHealth = 6000 + apc_vs.MaxShields = 1200 + 1 apc_vs.Seats += 0 -> new SeatDefinition() apc_vs.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax apc_vs.Seats += 1 -> new SeatDefinition() @@ -4685,8 +4872,11 @@ object GlobalDefinitions { apc_vs.TrunkSize = InventoryTile.Tile2016 apc_vs.TrunkOffset = 30 apc_vs.AutoPilotSpeeds = (16, 6) + apc_vs.DestroyedModel = Some(DestroyedVehicle.Apc) lightning.Name = "lightning" + lightning.MaxHealth = 2000 + lightning.MaxShields = 400 + 1 lightning.Seats += 0 -> new SeatDefinition() lightning.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax lightning.Seats(0).ControlledWeapon = 1 @@ -4696,8 +4886,11 @@ object GlobalDefinitions { lightning.TrunkSize = InventoryTile.Tile1511 lightning.TrunkOffset = 30 lightning.AutoPilotSpeeds = (20, 8) + lightning.DestroyedModel = Some(DestroyedVehicle.Lightning) prowler.Name = "prowler" + prowler.MaxHealth = 4800 + prowler.MaxShields = 960 + 1 prowler.Seats += 0 -> new SeatDefinition() prowler.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax prowler.Seats += 1 -> new SeatDefinition() @@ -4712,8 +4905,11 @@ object GlobalDefinitions { prowler.TrunkSize = InventoryTile.Tile1511 prowler.TrunkOffset = 30 prowler.AutoPilotSpeeds = (14, 6) + prowler.DestroyedModel = Some(DestroyedVehicle.Prowler) vanguard.Name = "vanguard" + vanguard.MaxHealth = 5400 + vanguard.MaxShields = 1080 + 1 vanguard.Seats += 0 -> new SeatDefinition() vanguard.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax vanguard.Seats += 1 -> new SeatDefinition() @@ -4724,8 +4920,11 @@ object GlobalDefinitions { vanguard.TrunkSize = InventoryTile.Tile1511 vanguard.TrunkOffset = 30 vanguard.AutoPilotSpeeds = (16, 6) + vanguard.DestroyedModel = Some(DestroyedVehicle.Vanguard) magrider.Name = "magrider" + magrider.MaxHealth = 4200 + magrider.MaxShields = 840 + 1 magrider.Seats += 0 -> new SeatDefinition() magrider.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax magrider.Seats(0).ControlledWeapon = 2 @@ -4738,9 +4937,12 @@ object GlobalDefinitions { magrider.TrunkSize = InventoryTile.Tile1511 magrider.TrunkOffset = 30 magrider.AutoPilotSpeeds = (18, 6) + magrider.DestroyedModel = Some(DestroyedVehicle.Magrider) val utilityConverter = new UtilityVehicleConverter ant.Name = "ant" + ant.MaxHealth = 2000 + ant.MaxShields = 400 + 1 ant.Seats += 0 -> new SeatDefinition() ant.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax ant.MountPoints += 1 -> 0 @@ -4751,8 +4953,11 @@ object GlobalDefinitions { ant.AutoPilotSpeeds = (18, 6) ant.MaximumCapacitor = 1500 ant.Packet = utilityConverter + ant.DestroyedModel = Some(DestroyedVehicle.Ant) ams.Name = "ams" + ams.MaxHealth = 3000 + ams.MaxShields = 600 + 1 ams.Seats += 0 -> new SeatDefinition() ams.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax ams.MountPoints += 1 -> 0 @@ -4767,9 +4972,12 @@ object GlobalDefinitions { ams.DeconstructionTime = Some(20 minutes) ams.AutoPilotSpeeds = (18, 6) ams.Packet = utilityConverter + ams.DestroyedModel = Some(DestroyedVehicle.Ams) val variantConverter = new VariantVehicleConverter router.Name = "router" + router.MaxHealth = 4000 + router.MaxShields = 800 + 1 router.Seats += 0 -> new SeatDefinition() router.MountPoints += 1 -> 0 router.TrunkSize = InventoryTile.Tile1511 @@ -4780,8 +4988,11 @@ object GlobalDefinitions { router.DeconstructionTime = Duration(20, "minutes") router.AutoPilotSpeeds = (16, 6) router.Packet = variantConverter + router.DestroyedModel = Some(DestroyedVehicle.Router) switchblade.Name = "switchblade" + switchblade.MaxHealth = 1750 + switchblade.MaxShields = 350 + 1 switchblade.Seats += 0 -> new SeatDefinition() switchblade.Seats(0).ControlledWeapon = 1 switchblade.Weapons += 1 -> scythe @@ -4794,8 +5005,11 @@ object GlobalDefinitions { switchblade.UndeployTime = 2000 switchblade.AutoPilotSpeeds = (22, 8) switchblade.Packet = variantConverter + switchblade.DestroyedModel = Some(DestroyedVehicle.Switchblade) flail.Name = "flail" + flail.MaxHealth = 2400 + flail.MaxShields = 480 + 1 flail.Seats += 0 -> new SeatDefinition() flail.Seats(0).ControlledWeapon = 1 flail.Weapons += 1 -> flail_weapon @@ -4807,8 +5021,11 @@ object GlobalDefinitions { flail.UndeployTime = 2000 flail.AutoPilotSpeeds = (14, 6) flail.Packet = variantConverter + flail.DestroyedModel = Some(DestroyedVehicle.Flail) mosquito.Name = "mosquito" + mosquito.MaxHealth = 665 + mosquito.MaxShields = 133 + 1 mosquito.Seats += 0 -> new SeatDefinition() mosquito.Seats(0).Bailable = true mosquito.Seats(0).ControlledWeapon = 1 @@ -4819,8 +5036,11 @@ object GlobalDefinitions { mosquito.TrunkOffset = 30 mosquito.AutoPilotSpeeds = (0, 6) mosquito.Packet = variantConverter + mosquito.DestroyedModel = Some(DestroyedVehicle.Mosquito) lightgunship.Name = "lightgunship" + lightgunship.MaxHealth = 1000 + lightgunship.MaxShields = 200 + 1 lightgunship.Seats += 0 -> new SeatDefinition() lightgunship.Seats(0).Bailable = true lightgunship.Seats(0).ControlledWeapon = 1 @@ -4831,8 +5051,11 @@ object GlobalDefinitions { lightgunship.TrunkOffset = 30 lightgunship.AutoPilotSpeeds = (0, 4) lightgunship.Packet = variantConverter + lightgunship.DestroyedModel = Some(DestroyedVehicle.LightGunship) wasp.Name = "wasp" + wasp.MaxHealth = 515 + wasp.MaxShields = 103 + 1 wasp.Seats += 0 -> new SeatDefinition() wasp.Seats(0).Bailable = true wasp.Seats(0).ControlledWeapon = 1 @@ -4843,8 +5066,11 @@ object GlobalDefinitions { wasp.TrunkOffset = 30 wasp.AutoPilotSpeeds = (0, 6) wasp.Packet = variantConverter + wasp.DestroyedModel = Some(DestroyedVehicle.Mosquito) //set_resource_parent wasp game_objects mosquito liberator.Name = "liberator" + liberator.MaxHealth = 2500 + liberator.MaxShields = 500 + 1 liberator.Seats += 0 -> new SeatDefinition() liberator.Seats(0).ControlledWeapon = 3 liberator.Seats += 1 -> new SeatDefinition() @@ -4862,8 +5088,11 @@ object GlobalDefinitions { liberator.TrunkOffset = 30 liberator.AutoPilotSpeeds = (0, 4) liberator.Packet = variantConverter + liberator.DestroyedModel = Some(DestroyedVehicle.Liberator) vulture.Name = "vulture" + vulture.MaxHealth = 2500 + vulture.MaxShields = 500 + 1 vulture.Seats += 0 -> new SeatDefinition() vulture.Seats(0).ControlledWeapon = 3 vulture.Seats += 1 -> new SeatDefinition() @@ -4881,8 +5110,11 @@ object GlobalDefinitions { vulture.TrunkOffset = 30 vulture.AutoPilotSpeeds = (0, 4) vulture.Packet = variantConverter + vulture.DestroyedModel = Some(DestroyedVehicle.Liberator) //add_property vulture destroyedphysics liberator_destroyed dropship.Name = "dropship" + dropship.MaxHealth = 5000 + dropship.MaxShields = 1000 + 1 dropship.Seats += 0 -> new SeatDefinition() dropship.Seats += 1 -> new SeatDefinition() dropship.Seats(1).Bailable = true @@ -4932,8 +5164,11 @@ object GlobalDefinitions { dropship.TrunkOffset = 30 dropship.AutoPilotSpeeds = (0, 4) dropship.Packet = variantConverter + dropship.DestroyedModel = Some(DestroyedVehicle.Dropship) galaxy_gunship.Name = "galaxy_gunship" + galaxy_gunship.MaxHealth = 6000 + galaxy_gunship.MaxShields = 1200 + 1 galaxy_gunship.Seats += 0 -> new SeatDefinition() galaxy_gunship.Seats += 1 -> new SeatDefinition() galaxy_gunship.Seats(1).ControlledWeapon = 6 @@ -4960,8 +5195,11 @@ object GlobalDefinitions { galaxy_gunship.TrunkOffset = 30 galaxy_gunship.AutoPilotSpeeds = (0, 4) galaxy_gunship.Packet = variantConverter + galaxy_gunship.DestroyedModel = Some(DestroyedVehicle.Dropship) //the adb calls out a galaxy_gunship_destroyed but no such asset exists lodestar.Name = "lodestar" + lodestar.MaxHealth = 5000 + lodestar.MaxShields = 1000 + 1 lodestar.Seats += 0 -> new SeatDefinition() lodestar.MountPoints += 1 -> 0 lodestar.MountPoints += 2 -> 1 @@ -4970,8 +5208,11 @@ object GlobalDefinitions { lodestar.TrunkOffset = 30 lodestar.AutoPilotSpeeds = (0, 4) lodestar.Packet = variantConverter + lodestar.DestroyedModel = Some(DestroyedVehicle.Lodestar) phantasm.Name = "phantasm" + phantasm.MaxHealth = 2500 + phantasm.MaxShields = 500 + 1 phantasm.CanCloak = true phantasm.Seats += 0 -> new SeatDefinition() phantasm.Seats += 1 -> new SeatDefinition() @@ -4991,5 +5232,6 @@ object GlobalDefinitions { phantasm.TrunkOffset = 30 phantasm.AutoPilotSpeeds = (0, 6) phantasm.Packet = variantConverter + phantasm.DestroyedModel = None //the adb calls out a phantasm_destroyed but no such asset exists } } diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 71398c5ed..8586b0a0a 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -6,13 +6,19 @@ import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.loadouts.Loadout import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.vital.resistance.ResistanceProfile +import net.psforever.objects.vital.{DamageResistanceModel, Vitality} import net.psforever.packet.game.PlanetSideGUID import net.psforever.types._ import scala.annotation.tailrec import scala.util.{Success, Try} -class Player(private val core : Avatar) extends PlanetSideGameObject with FactionAffinity with Container { +class Player(private val core : Avatar) extends PlanetSideGameObject + with FactionAffinity + with Vitality + with ResistanceProfile + with Container { private var alive : Boolean = false private var backpack : Boolean = false private var health : Int = 0 @@ -42,7 +48,6 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio //SouNourS things /** Last medkituse. */ var lastMedkit : Long = 0 - var death_by : Int = 0 var lastSeenStreamMessage : Array[Long] = Array.fill[Long](65535)(0L) var lastShotSeq_time : Int = -1 /** From PlanetsideAttributeMessage */ @@ -276,6 +281,14 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio ChangeSpecialAbility() } + def ResistanceDirectHit = exosuit.ResistanceDirectHit + + def ResistanceSplash = exosuit.ResistanceSplash + + def ResistanceAggravated = exosuit.ResistanceAggravated + + def RadiationShielding = exosuit.RadiationShielding + def LoadLoadout(line : Int) : Option[Loadout] = core.LoadLoadout(line) def BEP : Long = core.BEP @@ -475,6 +488,8 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio Continent } + def DamageModel = exosuit.asInstanceOf[DamageResistanceModel] + def Definition : AvatarDefinition = core.Definition def canEqual(other: Any): Boolean = other.isInstanceOf[Player] diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index 3d56eb4c1..9306468dd 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -9,6 +9,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.deploy.Deployment import net.psforever.objects.vehicles._ +import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality} import net.psforever.packet.game.PlanetSideGUID import net.psforever.types.PlanetSideEmpire @@ -67,6 +68,8 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ with Mountable with MountedWeapons with Deployment + with Vitality + with StandardResistanceProfile with Container { private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR private var owner : Option[PlanetSideGUID] = None @@ -142,7 +145,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = { owner match { case Some(_) => - if(Definition.CanBeOwned) { //e.g., base turrets + if(Definition.CanBeOwned) { this.owner = owner } case None => @@ -152,11 +155,11 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ } def Health : Int = { - this.health + health } - def Health_=(health : Int) : Int = { - this.health = health + def Health_=(assignHealth : Int) : Int = { + health = math.min(math.max(0, assignHealth), MaxHealth) health } @@ -165,11 +168,11 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ } def Shields : Int = { - this.shields + shields } def Shields_=(strength : Int) : Int = { - this.shields = strength + shields = math.min(math.max(0, strength), MaxShields) Shields } @@ -178,12 +181,12 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ } def Decal : Int = { - this.decal + decal } - def Decal_=(decal : Int) : Int = { - this.decal = decal - decal + def Decal_=(logo : Int) : Int = { + decal = logo + Decal } def Jammered : Boolean = jammered @@ -493,6 +496,8 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ */ def TrunkLockState : VehicleLockState.Value = groupPermissions(3) + def DamageModel = Definition.asInstanceOf[DamageResistanceModel] + /** * This is the definition entry that is used to store and unload pertinent information about the `Vehicle`. * @return the vehicle's definition entry @@ -534,6 +539,20 @@ object Vehicle { */ final case class Reactivate() + /** + * A request has been made to charge this vehicle's shields. + * @see `FacilityBenefitShieldChargeRequestMessage` + * @param amount the number of points to charge + */ + final case class ChargeShields(amount : Int) + + /** + * Following a successful shield charge tick, display the results of the update. + * @see `FacilityBenefitShieldChargeRequestMessage` + * @param vehicle the updated vehicle + */ + final case class UpdateShieldsCharge(vehicle : Vehicle) + /** * Overloaded constructor. * @param vehicleDef the vehicle's definition entry diff --git a/common/src/main/scala/net/psforever/objects/ballistics/ObjectSource.scala b/common/src/main/scala/net/psforever/objects/ballistics/ObjectSource.scala new file mode 100644 index 000000000..e73365df0 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/ballistics/ObjectSource.scala @@ -0,0 +1,31 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.ballistics + +import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.types.{PlanetSideEmpire, Vector3} + +final case class ObjectSource(obj : PlanetSideGameObject with FactionAffinity, + faction : PlanetSideEmpire.Value, + position : Vector3, + orientation : Vector3, + velocity : Option[Vector3] = None) extends SourceEntry { + override def Name = SourceEntry.NameFormat(obj.Definition.Name) + override def Faction = faction + def Definition = obj.Definition + def Position = position + def Orientation = orientation + def Velocity = velocity +} + +object ObjectSource { + def apply(obj : PlanetSideGameObject with FactionAffinity) : ObjectSource = { + ObjectSource( + obj, + obj.Faction, + obj.Position, + obj.Orientation, + obj.Velocity + ) + } +} diff --git a/common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala b/common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala new file mode 100644 index 000000000..495c01365 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala @@ -0,0 +1,35 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.ballistics + +import net.psforever.objects.Player +import net.psforever.objects.definition.ObjectDefinition +import net.psforever.types.{ExoSuitType, PlanetSideEmpire, Vector3} + +final case class PlayerSource(name : String, + obj_def : ObjectDefinition, + faction : PlanetSideEmpire.Value, + exosuit : ExoSuitType.Value, + seated : Boolean, + health : Int, + armor : Int, + position : Vector3, + orientation : Vector3, + velocity : Option[Vector3] = None) extends SourceEntry { + override def Name = name + override def Faction = faction + def Definition = obj_def + def ExoSuit = exosuit + def Seated = seated + def Health = health + def Armor = armor + def Position = position + def Orientation = orientation + def Velocity = velocity +} + +object PlayerSource { + def apply(tplayer : Player) : PlayerSource = { + PlayerSource(tplayer.Name, tplayer.Definition, tplayer.Faction, tplayer.ExoSuit, tplayer.VehicleSeated.nonEmpty, + tplayer.Health, tplayer.Armor, tplayer.Position, tplayer.Orientation, tplayer.Velocity) + } +} diff --git a/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala b/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala index 790a4227d..fdc9ad8e9 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala +++ b/common/src/main/scala/net/psforever/objects/ballistics/Projectile.scala @@ -1,60 +1,59 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.ballistics +import net.psforever.objects.PlanetSideGameObject 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.types.Vector3 /** * A summation of weapon (`Tool`) discharge. + * @see `ProjectileDefinition`
+ * `ToolDefinition`
+ * `FireModeDefinition`
+ * `SourceEntry`
+ * `PlayerSource` * @param profile an explanation of the damage that can be performed by this discharge * @param tool_def the weapon that caused this discharge + * @param fire_mode the current fire mode of the tool used + * @param owner the agency that caused the weapon to produce this projectile; + * most often a player (`PlayerSource`) + * @param attribute_to an object ID that refers to the method of death that would be reported; + * usually the same as `tool_def.ObjectId`; + * if not, then it is a type of vehicle (and owner should have a positive `seated` field) * @param shot_origin where the projectile started * @param shot_angle in which direction the projectile was aimed when it was discharged - * @param resolution whether this projectile has encountered a target or wall; - * defaults to `Unresolved` * @param fire_time when the weapon discharged was recorded; * defaults to `System.nanoTime` - * @param hit_time when the discharge had its resolution status updated */ final case class Projectile(profile : ProjectileDefinition, tool_def : ToolDefinition, + fire_mode : FireModeDefinition, + owner : SourceEntry, + attribute_to : Int, shot_origin : Vector3, shot_angle : Vector3, - resolution : ProjectileResolution.Value, - fire_time : Long = System.nanoTime, - hit_time : Long = 0) { + fire_time: Long = System.nanoTime) { /** Information about the current world coordinates and orientation of the projectile */ val current : SimpleWorldEntity = new SimpleWorldEntity() + private var resolved : ProjectileResolution.Value = ProjectileResolution.Unresolved /** - * Give the projectile the suggested resolution status. - * Update the world coordinates and orientation. - * @param pos the current position - * @param ang the current orientation - * @param resolution the resolution status - * @return a new projectile with the suggested resolution status, or the original projectile + * Mark the projectile as being "encountered" or "managed" at least once. */ - def Resolve(pos : Vector3, ang : Vector3, resolution : ProjectileResolution.Value) : Projectile = { - val obj = Resolve(resolution) - obj.current.Position = pos - obj.current.Orientation = ang - obj + def Resolve() : Unit = { + resolved = ProjectileResolution.Resolved } - /** - * Give the projectile the suggested resolution status. - * @param resolution the resolution status - * @return a new projectile with the suggested resolution status, or the original projectile - */ - def Resolve(resolution : ProjectileResolution.Value) : Projectile = { - resolution match { - case ProjectileResolution.Unresolved => - this - case _ => - Projectile(profile, tool_def, shot_origin, shot_angle, resolution, fire_time, System.nanoTime) - } + def Miss() : Unit = { + resolved = ProjectileResolution.MissedShot } + + def isResolved : Boolean = resolved == ProjectileResolution.Resolved || resolved == ProjectileResolution.MissedShot + + def isMiss : Boolean = resolved == ProjectileResolution.MissedShot } object Projectile { @@ -68,11 +67,28 @@ object Projectile { * Overloaded constructor for an `Unresolved` projectile. * @param profile an explanation of the damage that can be performed by this discharge * @param tool_def the weapon that caused this discharge + * @param fire_mode the current fire mode of the tool used + * @param owner the agency that caused the weapon to produce this projectile * @param shot_origin where the projectile started * @param shot_angle in which direction the projectile was aimed when it was discharged * @return the `Projectile` object */ - def apply(profile : ProjectileDefinition, tool_def : ToolDefinition, shot_origin : Vector3, shot_angle : Vector3) : Projectile = { - Projectile(profile, tool_def, shot_origin, shot_angle, ProjectileResolution.Unresolved) + def apply(profile : ProjectileDefinition, tool_def : ToolDefinition, fire_mode : FireModeDefinition, owner : PlanetSideGameObject with FactionAffinity, shot_origin : Vector3, shot_angle : Vector3) : Projectile = { + Projectile(profile, tool_def, fire_mode, SourceEntry(owner), tool_def.ObjectId, shot_origin, shot_angle) + } + + /** + * Overloaded constructor for an `Unresolved` projectile. + * @param profile an explanation of the damage that can be performed by this discharge + * @param tool_def the weapon that caused this discharge + * @param fire_mode the current fire mode of the tool used + * @param owner the agency that caused the weapon to produce this projectile + * @param attribute_to an object ID that refers to the method of death that would be reported + * @param shot_origin where the projectile started + * @param shot_angle in which direction the projectile was aimed when it was discharged + * @return the `Projectile` object + */ + def apply(profile : ProjectileDefinition, tool_def : ToolDefinition, fire_mode : FireModeDefinition, owner : PlanetSideGameObject with FactionAffinity, attribute_to : Int, shot_origin : Vector3, shot_angle : Vector3) : Projectile = { + Projectile(profile, tool_def, fire_mode, SourceEntry(owner), attribute_to, shot_origin, shot_angle) } } diff --git a/common/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala b/common/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala new file mode 100644 index 000000000..79b24590a --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala @@ -0,0 +1,24 @@ +// 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 resolution how the projectile hit was executed + * @param projectile the original projectile + * @param target what the projectile hit + * @param damage_model the kind of damage model to which the `target` is/was subject + * @param hit_pos where the projectile hit + * @param hit_time the sequence timing when the projectile hit the target + */ +final case class ResolvedProjectile(resolution : ProjectileResolution.Value, + projectile : Projectile, + target : SourceEntry, + damage_model : DamageResistanceModel, + hit_pos : Vector3, + hit_time : Long = System.nanoTime) diff --git a/common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala b/common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala new file mode 100644 index 000000000..5c2eed857 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala @@ -0,0 +1,42 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.ballistics + +import net.psforever.objects.definition.ObjectDefinition +import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle} +import net.psforever.objects.entity.WorldEntity +import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.types.{PlanetSideEmpire, Vector3} + +trait SourceEntry extends WorldEntity { + def Name : String = "" + def Definition : ObjectDefinition + def CharId : Long = 0L + def Faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL + def Position_=(pos : Vector3) = Position + def Orientation_=(pos : Vector3) = Position + def Velocity_=(pos : Option[Vector3]) = Velocity +} + +object SourceEntry { + final val None = new SourceEntry() { + def Definition = null + def Position = Vector3.Zero + def Orientation = Vector3.Zero + def Velocity = Some(Vector3.Zero) + } + + def apply(target : PlanetSideGameObject with FactionAffinity) : SourceEntry = { + target match { + case obj : Player => PlayerSource(obj) + case obj : Vehicle => VehicleSource(obj) + case _ => ObjectSource(target) + } + } + + def NameFormat(name : String) : String = { + name.replace("_", " ") + .split(" ") + .map(_.capitalize) + .mkString(" ") + } +} diff --git a/common/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala b/common/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala new file mode 100644 index 000000000..ccd7db404 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala @@ -0,0 +1,37 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.ballistics + +import net.psforever.objects.Vehicle +import net.psforever.objects.definition.VehicleDefinition +import net.psforever.types.{PlanetSideEmpire, Vector3} + +final case class VehicleSource(obj_def : VehicleDefinition, + faction : PlanetSideEmpire.Value, + health : Int, + shields : Int, + position : Vector3, + orientation : Vector3, + velocity : Option[Vector3] = None) extends SourceEntry { + override def Name = SourceEntry.NameFormat(obj_def.Name) + override def Faction = faction + def Definition : VehicleDefinition = obj_def + def Health = health + def Shields = shields + def Position = position + def Orientation = orientation + def Velocity = velocity +} + +object VehicleSource { + def apply(obj : Vehicle) : VehicleSource = { + VehicleSource( + obj.Definition, + obj.Faction, + obj.Health, + obj.Shields, + obj.Position, + obj.Orientation, + obj.Velocity + ) + } +} diff --git a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala index 892e0f21a..043ccb77b 100644 --- a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala @@ -1,14 +1,17 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.definition -import net.psforever.objects.ballistics.{DamageProfile, DamageType, Projectiles} +import net.psforever.objects.ballistics.Projectiles +import net.psforever.objects.vital.DamageType +import net.psforever.objects.vital.damage.DamageProfile /** * The definition that outlines the damage-dealing characteristics of any projectile. * `Tool` objects emit `ProjectileDefinition` objects and that is later wrapped into a `Projectile` object. * @param objectId the object's identifier number */ -class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) with DamageProfile { +class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) + with DamageProfile { private val projectileType : Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException private var damage0 : Int = 0 private var damage1 : Option[Int] = None @@ -26,6 +29,11 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) wi private var damageAtEdge : Float = 1f private var damageRadius : Float = 1f private var useDamage1Subtract : Boolean = false + //derived calculations + private var distanceMax : Float = 0f + private var distanceFromAcceleration : Float = 0f + private var distanceNoDegrade : Float = 0f + private var finalVelocity : Float = 0f Name = "projectile" def ProjectileType : Projectiles.Value = projectileType @@ -157,10 +165,43 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) wi this.damageRadius = damageRadius DamageRadius } + + def DistanceMax : Float = distanceMax //accessor only + + def DistanceFromAcceleration : Float = distanceFromAcceleration //accessor only + + def DistanceNoDegrade : Float = distanceNoDegrade //accessor only + + def FinalVelocity : Float = finalVelocity //accessor only } object ProjectileDefinition { def apply(projectileType : Projectiles.Value) : ProjectileDefinition = { new ProjectileDefinition(projectileType.id) } + + def CalculateDerivedFields(pdef : ProjectileDefinition) : Unit = { + val (distanceMax, distanceFromAcceleration, finalVelocity) : (Float, Float, Float) = if(pdef.Acceleration == 0f) { + (pdef.InitialVelocity * pdef.Lifespan, 0, pdef.InitialVelocity) + } + else { + val distanceFromAcceleration = (pdef.AccelerationUntil * pdef.InitialVelocity) + (0.5f * pdef.Acceleration * pdef.AccelerationUntil * pdef.AccelerationUntil) + val finalVelocity = pdef.InitialVelocity + pdef.Acceleration * pdef.AccelerationUntil + val distanceAfterAcceleration = finalVelocity * (pdef.Lifespan - pdef.AccelerationUntil) + (distanceFromAcceleration + distanceAfterAcceleration, distanceFromAcceleration, finalVelocity) + } + pdef.distanceMax = distanceMax + pdef.distanceFromAcceleration = distanceFromAcceleration + pdef.finalVelocity = finalVelocity + + pdef.distanceNoDegrade = if(pdef.DegradeDelay == 0f) { + pdef.distanceMax + } + else if(pdef.DegradeDelay < pdef.AccelerationUntil) { + (pdef.DegradeDelay * pdef.InitialVelocity) + (0.5f * pdef.Acceleration * pdef.DegradeDelay * pdef.DegradeDelay) + } + else { + pdef.distanceFromAcceleration + pdef.finalVelocity * (pdef.DegradeDelay - pdef.AccelerationUntil) + } + } } diff --git a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala index ab3c5da9b..7cc959dca 100644 --- a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala @@ -3,17 +3,22 @@ package net.psforever.objects.definition import net.psforever.objects.definition.converter.VehicleConverter import net.psforever.objects.inventory.InventoryTile -import net.psforever.objects.vehicles.UtilityType +import net.psforever.objects.vehicles.{DestroyedVehicle, UtilityType} +import net.psforever.objects.vital._ +import net.psforever.objects.vital.resistance.ResistanceProfileMutators import scala.collection.mutable import scala.concurrent.duration._ /** * An object definition system used to construct and retain the parameters of various vehicles. - * @param objectId the object id the is associated with this sort of `Vehicle` + * @param objectId the object id that is associated with this sort of `Vehicle` */ -class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) { +class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) + with ResistanceProfileMutators + with DamageResistanceModel { private var maxHealth : Int = 100 + /** vehicle shields offered through amp station facility benefits (generally: 20% of health + 1) */ private var maxShields : Int = 0 /* key - seat index, value - seat object */ private val seats : mutable.HashMap[Int, SeatDefinition] = mutable.HashMap[Int, SeatDefinition]() @@ -33,8 +38,12 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) { private var serverVehicleOverrideSpeeds : (Int, Int) = (0, 0) private var deconTime : Option[FiniteDuration] = None private var maximumCapacitor : Int = 0 + private var destroyedModel : Option[DestroyedVehicle.Value] = None Name = "vehicle" Packet = VehicleDefinition.converter + Damage = StandardVehicleDamage + Resistance = StandardVehicleResistance + Model = StandardResolutions.Vehicle def MaxHealth : Int = maxHealth @@ -138,6 +147,13 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) { maximumCapacitor = maxCapacitor MaximumCapacitor } + + def DestroyedModel : Option[DestroyedVehicle.Value] = destroyedModel + + def DestroyedModel_=(model : Option[DestroyedVehicle.Value]) : Option[DestroyedVehicle.Value] = { + destroyedModel = model + DestroyedModel + } } object VehicleDefinition { diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/DestroyedVehicleConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/DestroyedVehicleConverter.scala new file mode 100644 index 000000000..6df53256e --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/definition/converter/DestroyedVehicleConverter.scala @@ -0,0 +1,25 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.definition.converter + +import net.psforever.objects.Vehicle +import net.psforever.packet.game.objectcreate.{DestroyedVehicleData, PlacementData} + +import scala.util.{Failure, Success, Try} + +class DestroyedVehicleConverter extends ObjectCreateConverter[Vehicle]() { + override def DetailedConstructorData(obj : Vehicle) : Try[DestroyedVehicleData] = + Failure(new Exception("DestroyedVehicleConverter should not be used to generate detailed DestroyedVehicleData (nothing should)")) + + override def ConstructorData(obj : Vehicle) : Try[DestroyedVehicleData] = { + if(obj.Health > 0) { + Failure(new Exception("Vehicle used on DestroyedVehicleConverter has not yet been destroyed (Health == 0)")) + } + else { + Success(DestroyedVehicleData(PlacementData(obj.Position, obj.Orientation))) + } + } +} + +object DestroyedVehicleConverter { + final val converter = new DestroyedVehicleConverter +} diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala index f88c98b38..91b4a7343 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala @@ -4,7 +4,8 @@ package net.psforever.objects.definition.converter import net.psforever.objects.equipment.Equipment import net.psforever.objects.Vehicle import net.psforever.packet.game.PlanetSideGUID -import net.psforever.packet.game.objectcreate.{InventoryItemData, _} +import net.psforever.packet.game.objectcreate.{InventoryData, InventoryItemData, ObjectClass, PlacementData, SpecificVehicleData, VehicleData, VehicleFormat} +import net.psforever.types.DriveState import scala.util.{Failure, Success, Try} @@ -14,31 +15,57 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { override def ConstructorData(obj : Vehicle) : Try[VehicleData] = { val health = 255 * obj.Health / obj.MaxHealth //TODO not precise - Success( - VehicleData( - PlacementData(obj.Position, obj.Orientation, obj.Velocity), - obj.Faction, - bops = false, - destroyed = health < 3, - unk1 = 0, - obj.Jammered, - unk2 = false, - obj.Owner match { - case Some(owner) => owner - case None => PlanetSideGUID(0) - }, - unk3 = false, - health, - unk4 = false, - no_mount_points = false, - obj.DeploymentState, - unk5 = false, - unk6 = false, - obj.Cloaked, - SpecificFormatData(obj), - Some(InventoryData(MakeDriverSeat(obj) ++ MakeUtilities(obj) ++ MakeMountings(obj))) - )(SpecificFormatModifier) - ) + if(health > 3) { //active + Success( + VehicleData( + PlacementData(obj.Position, obj.Orientation, obj.Velocity), + obj.Faction, + bops = false, + destroyed = false, + unk1 = 0, + obj.Jammered, + unk2 = false, + obj.Owner match { + case Some(owner) => owner + case None => PlanetSideGUID(0) + }, + unk3 = false, + health, + unk4 = false, + no_mount_points = false, + obj.DeploymentState, + unk5 = false, + unk6 = false, + obj.Cloaked, + SpecificFormatData(obj), + Some(InventoryData(MakeDriverSeat(obj) ++ MakeUtilities(obj) ++ MakeMountings(obj))) + )(SpecificFormatModifier) + ) + } + else { //destroyed + Success( + VehicleData( + PlacementData(obj.Position, obj.Orientation), + obj.Faction, + bops = false, + destroyed = true, + unk1 = 0, + jammered = false, + unk2 = false, + owner_guid = PlanetSideGUID(0), + unk3 = false, + health = 0, + unk4 = false, + no_mount_points = true, + driveState = DriveState.Mobile, + unk5 = false, + unk6 = false, + cloak = false, + SpecificFormatData(obj), + inventory = None + )(SpecificFormatModifier) + ) + } } private def MakeDriverSeat(obj : Vehicle) : List[InventoryItemData.InventoryItem] = { diff --git a/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala b/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala index 3b010e48b..a38a1015f 100644 --- a/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/equipment/FireModeDefinition.scala @@ -2,7 +2,7 @@ package net.psforever.objects.equipment import net.psforever.objects.Tool -import net.psforever.objects.ballistics.DamageProfile +import net.psforever.objects.vital.damage.DamageProfile import scala.collection.mutable diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala index 4229cc61a..52b1cad29 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala @@ -96,18 +96,16 @@ class VehicleSpawnControl(pad : VehicleSpawnPad) extends VehicleSpawnControlBase } case VehicleSpawnControl.ProcessControl.Flush => - if(!periodicReminder.isCancelled) { - periodicReminder.cancel - orders.foreach { VehicleSpawnControl.CancelOrder(_, Continent) } - orders = Nil - trackedOrder match { - case Some(entry) => - VehicleSpawnControl.CancelOrder(entry, Continent) - case None => ; - } - trackedOrder = None - concealPlayer ! akka.actor.Kill //will cause the actor to restart, which will abort any trapped messages + periodicReminder.cancel + orders.foreach { VehicleSpawnControl.CancelOrder(_, Continent) } + orders = Nil + trackedOrder match { + case Some(entry) => + VehicleSpawnControl.CancelOrder(entry, Continent) + case None => ; } + trackedOrder = None + concealPlayer ! akka.actor.Kill //will cause the actor to restart, which will abort any trapped messages case _ => ; } diff --git a/common/src/main/scala/net/psforever/objects/vehicles/DestroyedVehicle.scala b/common/src/main/scala/net/psforever/objects/vehicles/DestroyedVehicle.scala new file mode 100644 index 000000000..4927030c4 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vehicles/DestroyedVehicle.scala @@ -0,0 +1,33 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vehicles + +/** + * + */ +object DestroyedVehicle extends Enumeration { + type Type = Value + + val Ams = Value(47) + val Ant = Value(61) + val Apc = Value(65) + val Dropship = Value(260) + val Flail = Value(295) + val Liberator = Value(439) + val LightGunship = Value(442) + val Lightning = Value(447) + val Lodestar = Value(460) + val Magrider = Value(471) + val Mosquito = Value(573) + val MediumTransport = Value(533) + val Prowler = Value(698) + val QuadAssault = Value(708) + val QuadStealth = Value(711) + val Router = Value(742) + val Skyguard = Value(785) + val Switchblade = Value(848) + val ThreeManHeavyBuggy = Value(863) + val TwoManAssaultBuggy = Value(897) + val TwoManHeavyBuggy = Value(899) + val TwoManHoverBuggy = Value(901) + val Vanguard = Value(924) +} diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index dc114e68c..e773696e2 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -3,9 +3,11 @@ package net.psforever.objects.vehicles import akka.actor.Actor import net.psforever.objects.Vehicle +import net.psforever.objects.ballistics.VehicleSource import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.deploy.DeploymentBehavior +import net.psforever.objects.vital.{VehicleShieldCharge, Vitality} import net.psforever.types.ExoSuitType /** @@ -59,6 +61,22 @@ class VehicleControl(vehicle : Vehicle) extends Actor sender ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, seat_num)) } + case Vitality.Damage(damage_func) => + if(vehicle.Health > 0) { + damage_func(vehicle) + sender ! Vitality.DamageResolution(vehicle) + } + + case Vehicle.ChargeShields(amount) => + val now : Long = System.nanoTime + //make certain vehicle doesn't charge shields too quickly + if(vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields && + !vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now))) { + vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount)) + vehicle.Shields = vehicle.Shields + amount + sender ! Vehicle.UpdateShieldsCharge(vehicle) + } + case FactionAffinity.ConvertFactionAffinity(faction) => val originalAffinity = vehicle.Faction if(originalAffinity != (vehicle.Faction = faction)) { @@ -81,3 +99,24 @@ class VehicleControl(vehicle : Vehicle) extends Actor case _ => ; } } + +object VehicleControl { + import net.psforever.objects.vital.{DamageFromProjectile, VehicleShieldCharge, VitalsActivity} + import scala.concurrent.duration._ + + /** + * Determine if a given activity entry would invalidate the act of charging vehicle shields this tick. + * @param now the current time (in nanoseconds) + * @param act a `VitalsActivity` entry to test + * @return `true`, if the vehicle took damage in the last five seconds or + * charged shields in the last second; + * `false`, otherwise + */ + 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 _ => false + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala b/common/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala new file mode 100644 index 000000000..7d98bc7ff --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala @@ -0,0 +1,80 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital + +import net.psforever.objects.ballistics.{ProjectileResolution, ResolvedProjectile} +import net.psforever.objects.vital.damage.DamageSelection +import net.psforever.objects.vital.projectile.ProjectileCalculations +import net.psforever.objects.vital.resistance.ResistanceSelection +import net.psforever.objects.vital.resolution.ResolutionCalculations + +/** + * The functionality that is necessary for interaction of a vital game object with the rest of the game world.
+ *
+ * A vital object can be hurt or damaged or healed or repaired (HDHR). + * The actual implementation of how that works is left to the specific object and its interfaces, however. + * The more involved values that are applied to the vital object are calculated by a series of functions + * that contribute different values, e.g., the value for being damaged. + * "Being damaged" is also not the same for all valid targets: + * some targets don't utilize the same kinds of values in the same way as another, + * and some targets utilize a different assortment of values than either of the first two examples. + * The damage model is a common interface for producing those values + * and reconciling those values with a valid target object + * without much fuss.
+ *
+ * By default, nothing should do anything of substance. + * @see `Vitality` + */ +trait DamageResistanceModel { + /** the functionality that processes damage; required */ + private var damage : DamageSelection = NoDamageSelection + + /** the functionality that processes resistance; optional */ + private var resistance : ResistanceSelection = NoResistanceSelection + + /** the functionality that prepares for damage application actions; required */ + private var model : ResolutionCalculations.Form = NoResolutions.Calculate + + def Damage : DamageSelection = damage + + def Damage_=(selector : DamageSelection) : DamageSelection = { + damage = selector + Damage + } + + def Resistance : ResistanceSelection = resistance + + def Resistance_=(selector : ResistanceSelection) : ResistanceSelection = { + resistance = selector + Resistance + } + + def Model : ResolutionCalculations.Form = model + + def Model_=(selector : ResolutionCalculations.Form) : ResolutionCalculations.Form = { + model = selector + Model + } + + /** + * Magic stuff. + * @param data the historical `ResolvedProjectile` information + * @return a function literal that encapsulates delayed modification instructions for certain objects + */ + def Calculate(data : ResolvedProjectile) : ResolutionCalculations.Output = { + val dam : ProjectileCalculations.Form = Damage(data) + val res : ProjectileCalculations.Form = Resistance(data) + Model(dam, res, data) + } + + /** + * Magic stuff. + * @param data the historical `ResolvedProjectile` information + * @param resolution an explicit damage resolution overriding the one in the `ResolvedProjectile` object + * @return a function literal that encapsulates delayed modification instructions for certain objects + */ + def Calculate(data : ResolvedProjectile, resolution : ProjectileResolution.Value) : ResolutionCalculations.Output = { + val dam : ProjectileCalculations.Form = Damage(resolution) + val res : ProjectileCalculations.Form = Resistance(resolution) + Model(dam, res, data) + } +} diff --git a/common/src/main/scala/net/psforever/objects/ballistics/DamageType.scala b/common/src/main/scala/net/psforever/objects/vital/DamageType.scala similarity index 90% rename from common/src/main/scala/net/psforever/objects/ballistics/DamageType.scala rename to common/src/main/scala/net/psforever/objects/vital/DamageType.scala index 8f2722b06..f0070f21b 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/DamageType.scala +++ b/common/src/main/scala/net/psforever/objects/vital/DamageType.scala @@ -1,5 +1,5 @@ // Copyright (c) 2017 PSForever -package net.psforever.objects.ballistics +package net.psforever.objects.vital /** * An `Enumeration` of the damage type. diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala b/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala new file mode 100644 index 000000000..9c7ebbaee --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala @@ -0,0 +1,113 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital + +import net.psforever.objects.ballistics.ResolvedProjectile +import net.psforever.objects.vital.damage._ +import net.psforever.objects.vital.damage.DamageCalculations._ + +/** + * A protected super class for calculating "no damage." + * Used for `NoDamage` but also for the base of `*LashDamage` calculation objects + * to maintain the polymorphic identity of `DamageCalculations`. + */ +protected class NoDamageBase extends DamageCalculations( + DamageCalculations.NoDamage, + DamageWithModifiers(NoDamageAgainst), + TooFar +) + +object NoDamage extends NoDamageBase + +object InfantryHitDamage extends DamageCalculations( + DirectHitDamageWithDegrade, + DamageWithModifiers(DamageAgainstExoSuit), + DistanceBetweenTargetandSource +) + +object MaxHitDamage extends DamageCalculations( + DirectHitDamageWithDegrade, + DamageWithModifiers(DamageAgainstMaxSuit), + DistanceBetweenTargetandSource +) + +object VehicleHitDamage extends DamageCalculations( + DirectHitDamageWithDegrade, + DamageWithModifiers(DamageAgainstVehicle), + DistanceBetweenTargetandSource +) + +object AircraftHitDamage extends DamageCalculations( + DirectHitDamageWithDegrade, + DamageWithModifiers(DamageAgainstAircraft), + DistanceBetweenTargetandSource +) + +object InfantrySplashDamage extends DamageCalculations( + SplashDamageWithRadialDegrade, + DamageWithModifiers(DamageAgainstExoSuit), + DistanceFromExplosionToTarget +) + +object MaxSplashDamage extends DamageCalculations( + SplashDamageWithRadialDegrade, + DamageWithModifiers(DamageAgainstMaxSuit), + DistanceFromExplosionToTarget +) + +object VehicleSplashDamage extends DamageCalculations( + SplashDamageWithRadialDegrade, + DamageWithModifiers(DamageAgainstVehicle), + DistanceFromExplosionToTarget +) + +object AircraftSplashDamage extends DamageCalculations( + SplashDamageWithRadialDegrade, + DamageWithModifiers(DamageAgainstAircraft), + DistanceFromExplosionToTarget +) + +object InfantryLashDamage extends NoDamageBase { + override def Calculate(data : ResolvedProjectile) : Int = (InfantryHitDamage.Calculate(data) * 0.2f).toInt +} + +object MaxLashDamage extends NoDamageBase { + override def Calculate(data : ResolvedProjectile) : Int = (MaxHitDamage.Calculate(data) * 0.2f).toInt +} + +object VehicleLashDamage extends NoDamageBase { + override def Calculate(data : ResolvedProjectile) : Int = (VehicleHitDamage.Calculate(data) * 0.2f).toInt +} + +object AircraftLashDamage extends NoDamageBase { + override def Calculate(data : ResolvedProjectile) : Int = (AircraftHitDamage.Calculate(data) * 0.2f).toInt +} + +object NoDamageSelection extends DamageSelection { + def Direct = None + def Splash = None + def Lash = None +} + +object StandardInfantryDamage extends DamageSelection { + def Direct = InfantryHitDamage.Calculate + def Splash = InfantrySplashDamage.Calculate + def Lash = InfantryLashDamage.Calculate +} + +object StandardMaxDamage extends DamageSelection { + def Direct = MaxHitDamage.Calculate + def Splash = MaxSplashDamage.Calculate + def Lash = MaxLashDamage.Calculate +} + +object StandardVehicleDamage extends DamageSelection { + def Direct = VehicleHitDamage.Calculate + def Splash = VehicleSplashDamage.Calculate + def Lash = VehicleLashDamage.Calculate +} + +object StandardAircraftDamage extends DamageSelection { + def Direct = AircraftHitDamage.Calculate + def Splash = AircraftSplashDamage.Calculate + def Lash = AircraftLashDamage.Calculate +} diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala b/common/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala new file mode 100644 index 000000000..d419c02c3 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala @@ -0,0 +1,26 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital + +import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.vital.resistance.ResistanceProfile + +/** + * The different values for four common methods of modifying incoming damage. + * Two of the four resistances are directly paired with forms of incoming damage. + * This is for defining pure accessor functions, + * based on the assumption that the implementing object's `Definition` is the primary `ResistanceProfile`. + */ +trait StandardResistanceProfile extends ResistanceProfile { + this : PlanetSideGameObject => + //actually check that this will work for this implementing class + assert(Definition.isInstanceOf[ResistanceProfile], s"$this object definition must extend ResistanceProfile") + private val resistDef = Definition.asInstanceOf[ResistanceProfile] //cast only once + + def ResistanceDirectHit : Int = resistDef.ResistanceDirectHit + + def ResistanceSplash : Int = resistDef.ResistanceDirectHit + + def ResistanceAggravated : Int = resistDef.ResistanceDirectHit + + def RadiationShielding : Float = resistDef.ResistanceDirectHit +} diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala b/common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala new file mode 100644 index 000000000..5aaaa6f70 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala @@ -0,0 +1,72 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital + +import net.psforever.objects.ballistics.{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, + ResistanceCalculations.NoResistExtractor +) + +object InfantryHitResistance extends ResistanceCalculations[PlayerSource]( + ResistanceCalculations.ValidInfantryTarget, + ResistanceCalculations.ExoSuitDirectExtractor +) + +object InfantrySplashResistance extends ResistanceCalculations[PlayerSource]( + ResistanceCalculations.ValidInfantryTarget, + ResistanceCalculations.ExoSuitSplashExtractor +) + +object InfantryLashResistance extends ResistanceCalculations[PlayerSource]( + ResistanceCalculations.ValidInfantryTarget, + ResistanceCalculations.NoResistExtractor +) + +object InfantryAggravatedResistance extends ResistanceCalculations[PlayerSource]( + ResistanceCalculations.ValidInfantryTarget, + ResistanceCalculations.ExoSuitAggravatedExtractor +) + +object VehicleHitResistance extends ResistanceCalculations[VehicleSource]( + ResistanceCalculations.ValidVehicleTarget, + ResistanceCalculations.VehicleDirectExtractor +) + +object VehicleSplashResistance extends ResistanceCalculations[VehicleSource]( + ResistanceCalculations.ValidVehicleTarget, + ResistanceCalculations.VehicleSplashExtractor +) + +object VehicleLashResistance extends ResistanceCalculations[VehicleSource]( + ResistanceCalculations.ValidVehicleTarget, + ResistanceCalculations.NoResistExtractor +) + +object VehicleAggravatedResistance extends ResistanceCalculations[VehicleSource]( + ResistanceCalculations.ValidVehicleTarget, + ResistanceCalculations.VehicleAggravatedExtractor +) + +object NoResistanceSelection extends ResistanceSelection { + def Direct : ProjectileCalculations.Form = None + def Splash : ProjectileCalculations.Form = None + def Lash : ProjectileCalculations.Form = None + def Aggravated : ProjectileCalculations.Form = None +} + +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 +} + +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 +} diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala b/common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala new file mode 100644 index 000000000..6a25af3ee --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala @@ -0,0 +1,31 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital + +import net.psforever.objects.vital.resolution._ + +object NoResolutions extends DamageResistCalculations( + ResolutionCalculations.NoDamage, + ResolutionCalculations.NoApplication +) + +object InfantryResolutions extends DamageResistCalculations( + ResolutionCalculations.InfantryDamageAfterResist, + ResolutionCalculations.InfantryApplication +) + +object MaxResolutions extends DamageResistCalculations( + ResolutionCalculations.MaxDamageAfterResist, + ResolutionCalculations.InfantryApplication +) + +object VehicleResolutions extends DamageResistCalculations( + ResolutionCalculations.VehicleDamageAfterResist, + ResolutionCalculations.VehicleApplication +) + +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 +} diff --git a/common/src/main/scala/net/psforever/objects/vital/Vitality.scala b/common/src/main/scala/net/psforever/objects/vital/Vitality.scala new file mode 100644 index 000000000..a90522123 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/Vitality.scala @@ -0,0 +1,110 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital + +import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry, VehicleSource} +import net.psforever.objects.definition.KitDefinition +import net.psforever.objects.serverobject.terminals.TerminalDefinition +import net.psforever.types.{ExoSuitType, ImplantType} + +abstract class VitalsActivity(target : SourceEntry) { + def Target : SourceEntry = target + val t : Long = System.nanoTime //??? + + def time : Long = t +} + +abstract class HealingActivity(target : SourceEntry) extends VitalsActivity(target) + +abstract class DamagingActivity(target : SourceEntry) extends VitalsActivity(target) + +final case class HealFromKit(target : PlayerSource, amount : Int, kit_def : KitDefinition) extends HealingActivity(target) + +final case class HealFromTerm(target : PlayerSource, health : Int, armor : Int, term_def : TerminalDefinition) extends HealingActivity(target) + +final case class HealFromImplant(target : PlayerSource, amount : Int, implant : ImplantType.Value) extends HealingActivity(target) + +final case class HealFromExoSuitChange(target : PlayerSource, exosuit : ExoSuitType.Value) extends HealingActivity(target) + +final case class RepairFromTerm(target : VehicleSource, amount : Int, term_def : TerminalDefinition) extends HealingActivity(target) + +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 PlayerSuicide(target : PlayerSource) extends DamagingActivity(target) + +/** + * A vital object can be hurt or damaged or healed or repaired (HDHR). + * The amount of HDHR is controlled by the damage model of this vital object reacting to stimulus. + * A history of the previous changes in vital statistics of the underlying object is recorded + * in reverse chronological order. + * The damage model is also provided. + */ +trait Vitality { + this : PlanetSideGameObject => + + /** a reverse-order list of chronological events that have occurred to these vital statistics */ + private var vitalHistory : List[VitalsActivity] = List.empty[VitalsActivity] + + def History : List[VitalsActivity] = vitalHistory + + /** + * A `VitalsActivity` event must be recorded. + * Add new entry to the front of the list (for recent activity). + * @param action the fully-informed entry + * @return the list of previous changes to this object's vital statistics + */ + def History(action : VitalsActivity) : List[VitalsActivity] = { + vitalHistory = action +: vitalHistory + vitalHistory + } + + /** + * Very common example of a `VitalsActivity` event involving weapon discharge. + * @param projectile the fully-informed entry of discharge of a weapon + * @return the list of previous changes to this object's vital statistics + */ + def History(projectile : ResolvedProjectile) : List[VitalsActivity] = { + vitalHistory = DamageFromProjectile(projectile) +: vitalHistory + vitalHistory + } + + /** + * Find, specifically, the last instance of a weapon discharge vital statistics change. + * @return information about the discharge + */ + def LastShot : Option[ResolvedProjectile] = { + vitalHistory.find({p => p.isInstanceOf[DamageFromProjectile]}) match { + case Some(entry : DamageFromProjectile) => + Some(entry.data) + case _ => + None + } + } + + def ClearHistory() : List[VitalsActivity] = { + val out = vitalHistory + vitalHistory = List.empty[VitalsActivity] + out + } + + def DamageModel : DamageResistanceModel +} + +object Vitality { + + /** + * Provide the damage model-generated functionality + * that would properly enact the calculated changes of a vital statistics event + * upon a given vital object. + * @param func a function literal + */ + final case class Damage(func : (Any)=>Unit) + + /** + * Report that a vitals object must be updated due to damage. + * @param obj the vital object + */ + final case class DamageResolution(obj : Vitality) +} diff --git a/common/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala new file mode 100644 index 000000000..7e48430a5 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala @@ -0,0 +1,129 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital.damage + +import net.psforever.types.Vector3 +import net.psforever.objects.ballistics.{Projectile, ResolvedProjectile} +import net.psforever.objects.vital.projectile.ProjectileCalculations +import DamageCalculations._ + +/** + * The base class for function literal description related to calculating damage information.
+ *
+ * Implementing functionality of the children is the product of three user-defined processes + * and information for the calculation is extracted from the to-be-provided weapon discharge 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. + * @param damages function by which damage is modified by distance + * @param extractor function that recovers damage information + * @param distanceFunc a function to calculate the distance for scaling the damage, if valid + */ +abstract class DamageCalculations(damages : DamagesType, + extractor : DamageWithModifiersType, + distanceFunc : DistanceType) extends ProjectileCalculations { + /** + * Combine the damage and distance data extracted from the `ResolvedProjectile` entry. + * @param data the historical `ResolvedProjectile` information + * @return the damage value + */ + def Calculate(data : ResolvedProjectile) : Int = { + val projectile = data.projectile + damages( + projectile, + extractor(projectile.profile, List(projectile.fire_mode.Modifiers)), + distanceFunc(data) + ) + } +} + +object DamageCalculations { + //types + type DamagesType = (Projectile, Int, Float)=>Int + type DamageWithModifiersType = (DamageProfile, List[DamageProfile])=>Int + type DistanceType = (ResolvedProjectile)=>Float + + //raw damage selectors + def NoDamageAgainst(profile : DamageProfile) : Int = 0 + + def DamageAgainstExoSuit(profile : DamageProfile) : Int = profile.Damage0 + + def DamageAgainstVehicle(profile : DamageProfile) : Int = profile.Damage1 + + def DamageAgainstAircraft(profile : DamageProfile) : Int = profile.Damage2 + + def DamageAgainstMaxSuit(profile : DamageProfile) : Int = profile.Damage3 + + def DamageAgainstUnknown(profile : DamageProfile) : Int = profile.Damage4 + + //raw damage selection functions + /** + * Get damage information from a series of profiles related to the weapon discharge. + * @param extractor the function that recovers the damage value + * @param base the profile from which primary damage is to be selected + * @param modifiers alternate profiles that will modify the base damage value + * @return the accumulated damage value + */ + //TODO modifiers come from various sources; expand this part of the calculation model in the future + def DamageWithModifiers(extractor : (DamageProfile)=>Int)(base : DamageProfile, modifiers : List[DamageProfile]) : Int = { + extractor(base) + modifiers.foldLeft(0)(_ + extractor(_)) + } + + //damage calculation functions + def NoDamage(projectile : Projectile, rawDamage : Int, distance : Float) : Int = 0 + + /** + * Modify the base damage based on the degrade distance of the projectile type + * and its maximum effective distance. + * Calls out "direct hit" damage but is recycled for other damage types as well. + * @param projectile information about the weapon discharge (itself) + * @param rawDamage the accumulated amount of damage + * @param distance how far the source was from the target + * @return the modified damage value + */ + def DirectHitDamageWithDegrade(projectile : Projectile, rawDamage: Int, distance: Float): Int = { + val profile = projectile.profile + if(distance <= profile.DistanceMax) { + if(profile.DistanceNoDegrade == profile.DistanceMax || distance <= profile.DistanceNoDegrade) { + rawDamage + } + else { + rawDamage - ((rawDamage - profile.DegradeMultiplier * rawDamage) * ((distance - profile.DistanceNoDegrade) / (profile.DistanceMax - profile.DistanceNoDegrade))).toInt + } + } + else { + 0 + } + } + + /** + * Modify the base damage based on the radial distance of the target from the center of an explosion. + * Calls out "splash" damage exclusively. + * @param projectile information about the weapon discharge (itself) + * @param rawDamage the accumulated amount of damage + * @param distance how far the origin of the explosion was from the target + * @return the modified damage value + */ + def SplashDamageWithRadialDegrade(projectile : Projectile, rawDamage : Int, distance : Float) : Int = { + val radius = projectile.profile.DamageRadius + if(distance <= radius) { + val base : Float = projectile.profile.DamageAtEdge + val degrade : Float = (1 - base) * ((radius - distance) / radius) + base + rawDamage + (rawDamage * degrade).toInt + } + else { + 0 + } + } + + //distance functions + def NoDistance(data : ResolvedProjectile) : Float = 0 + + def TooFar(data : ResolvedProjectile) : Float = Float.MaxValue + + def DistanceBetweenTargetandSource(data : ResolvedProjectile) : Float = { + Vector3.Distance(data.target.Position, data.projectile.owner.Position) + } + + def DistanceFromExplosionToTarget(data : ResolvedProjectile) : Float = { + Vector3.Distance(data.target.Position, data.hit_pos) + } +} diff --git a/common/src/main/scala/net/psforever/objects/ballistics/DamageProfile.scala b/common/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala similarity index 92% rename from common/src/main/scala/net/psforever/objects/ballistics/DamageProfile.scala rename to common/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala index 1d2e877e1..95f99ffc5 100644 --- a/common/src/main/scala/net/psforever/objects/ballistics/DamageProfile.scala +++ b/common/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala @@ -1,5 +1,5 @@ // Copyright (c) 2017 PSForever -package net.psforever.objects.ballistics +package net.psforever.objects.vital.damage /** * The different values for five common types of damage that can be dealt, based on target and application. diff --git a/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala b/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala new file mode 100644 index 000000000..9ecd9b41c --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/damage/DamageSelection.scala @@ -0,0 +1,32 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital.damage + +import net.psforever.objects.ballistics.{ProjectileResolution, ResolvedProjectile} +import net.psforever.objects.vital.NoDamage +import net.psforever.objects.vital.projectile.ProjectileCalculations + +/** + * Maintain information about three primary forms of damage calculation + * and a means to test which calculation is valid in a given situation. + */ +trait DamageSelection { + final def None : ProjectileCalculations.Form = NoDamage.Calculate + + def Direct : ProjectileCalculations.Form + def Splash : ProjectileCalculations.Form + def Lash : ProjectileCalculations.Form + + def apply(data : ResolvedProjectile) : ProjectileCalculations.Form = data.resolution match { + case ProjectileResolution.Hit => Direct + case ProjectileResolution.Splash => Splash + case ProjectileResolution.Lash => Lash + case _ => None + } + + def apply(res : ProjectileResolution.Value) : ProjectileCalculations.Form = res match { + case ProjectileResolution.Hit => Direct + case ProjectileResolution.Splash => Splash + case ProjectileResolution.Lash => Lash + case _ => None + } +} diff --git a/common/src/main/scala/net/psforever/objects/vital/projectile/ProjectileCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/projectile/ProjectileCalculations.scala new file mode 100644 index 000000000..28f8d0609 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/projectile/ProjectileCalculations.scala @@ -0,0 +1,20 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital.projectile + +import net.psforever.objects.ballistics.ResolvedProjectile + +/** + * The base for all projectile-induced damage calculation function literals. + */ +trait ProjectileCalculations { + /** + * The exposed entry for the calculation function literal defined by this base. + * @param data the historical `ResolvedProjectile` information + * @return the calculated value + */ + def Calculate(data : ResolvedProjectile) : Int +} + +object ProjectileCalculations { + type Form = (ResolvedProjectile)=>Int +} diff --git a/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala new file mode 100644 index 000000000..82b08dee5 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala @@ -0,0 +1,123 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital.resistance + +import net.psforever.objects.{ExoSuitDefinition, GlobalDefinitions} +import net.psforever.objects.ballistics._ +import net.psforever.objects.vital.projectile.ProjectileCalculations +import net.psforever.types.ExoSuitType + +import scala.util.{Failure, Success, Try} + +/** + * The base class for function literal description related to calculating resistance information.
+ *
+ * Implementing functionality of the children is the product of two user-defined processes + * and information for the calculation is extracted from the to-be-provided weapon discharge information. + * Specifically, the information is found as the `target` object which is a member of the said information. + * The specific functions passed into this object typically operate simultaneously normally + * and are related to the target and the kind of interaction the weapon discharge had with the target. + * @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 + * @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 + */ +abstract class ResistanceCalculations[TargetType](validate : (ResolvedProjectile)=>Try[TargetType], + extractor : (TargetType)=>Int) extends ProjectileCalculations { + /** + * Get resistance valuess. + * @param data the historical `ResolvedProjectile` information + * @return the damage value + */ + def Calculate(data : ResolvedProjectile) : Int = { + validate(data) match { + case Success(target) => + extractor(target) + case _ => + 0 + } + } +} + +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 ValidInfantryTarget(data : ResolvedProjectile) : Try[PlayerSource] = { + data.target match { + case target : PlayerSource => + if(target.ExoSuit != ExoSuitType.MAX) { //max is not counted as an official infantry exo-suit type + Success(target) + } + else { + failure("infantry") + } + case _ => + failure("infantry") + } + } + + def ValidMaxTarget(data : ResolvedProjectile) : Try[PlayerSource] = { + data.target match { + case target : PlayerSource => + if(target.ExoSuit == ExoSuitType.MAX) { + Success(target) + } + else { + failure("max") + } + case _ => + failure("max") + } + } + + def ValidVehicleTarget(data : ResolvedProjectile) : Try[VehicleSource] = { + data.target match { + case target : VehicleSource => + if(!GlobalDefinitions.isFlightVehicle(target.Definition)) { + Success(target) + } + else { + failure("vehicle") + } + case _ => + failure("vehicle") + } + } + + def ValidAircraftTarget(data : ResolvedProjectile) : Try[VehicleSource] = { + data.target match { + case target : VehicleSource => + if(GlobalDefinitions.isFlightVehicle(target.Definition)) { + Success(target) + } + else { + failure("aircraft") + } + case _ => + failure("aircraft") + } + } + + //extractors + def NoResistExtractor(target : SourceEntry) : Int = 0 + + def ExoSuitDirectExtractor(target : PlayerSource) : Int = ExoSuitDefinition.Select(target.ExoSuit).ResistanceDirectHit + + def ExoSuitSplashExtractor(target : PlayerSource) : Int = ExoSuitDefinition.Select(target.ExoSuit).ResistanceSplash + + def ExoSuitAggravatedExtractor(target : PlayerSource) : Int = ExoSuitDefinition.Select(target.ExoSuit).ResistanceAggravated + + def ExoSuitRadiationExtractor(target : PlayerSource) : Float = ExoSuitDefinition.Select(target.ExoSuit).RadiationShielding + + def VehicleDirectExtractor(target : VehicleSource) : Int = target.Definition.ResistanceDirectHit + + def VehicleSplashExtractor(target : VehicleSource) : Int = target.Definition.ResistanceSplash + + def VehicleAggravatedExtractor(target : VehicleSource) : Int = target.Definition.ResistanceAggravated + + def VehicleRadiationExtractor(target : VehicleSource) : Float = target.Definition.RadiationShielding +} diff --git a/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala new file mode 100644 index 000000000..695b9c033 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala @@ -0,0 +1,69 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital.resistance + +import net.psforever.objects.vital.DamageType + +/** + * The different values for four common methods of modifying incoming damage. + * Two of the four resistances are directly paired with forms of incoming damage. + * This is for defining pure accessor functions. + */ +trait ResistanceProfile { + def ResistanceDirectHit : Int + + def ResistanceSplash : Int + + def ResistanceAggravated : Int + + def RadiationShielding : Float + + def Resist(dtype : DamageType.Value) : Float = { + dtype match { + case DamageType.Direct => ResistanceDirectHit + case DamageType.Splash => ResistanceSplash + case DamageType.Aggravated => ResistanceAggravated + case DamageType.Radiation => RadiationShielding + case _ => 0 + } + } +} + +/** + * The different values for four common methods of modifying incoming damage. + * Two of the four resistances are directly paired with forms of incoming damage. + * This is for defining both accessor and mutator functions. + */ +trait ResistanceProfileMutators extends ResistanceProfile { + private var resistanceDirectHit : Int = 0 + private var resistanceSplash : Int = 0 + private var resistanceAggravated : Int = 0 + private var radiationShielding : Float = 0f + + def ResistanceDirectHit : Int = resistanceDirectHit + + def ResistanceDirectHit_=(resist : Int) : Int = { + resistanceDirectHit = resist + ResistanceDirectHit + } + + def ResistanceSplash : Int = resistanceSplash + + def ResistanceSplash_=(resist : Int) : Int = { + resistanceSplash = resist + ResistanceSplash + } + + def ResistanceAggravated : Int = resistanceAggravated + + def ResistanceAggravated_=(resist : Int) : Int = { + resistanceAggravated = resist + ResistanceAggravated + } + + def RadiationShielding : Float = radiationShielding + + def RadiationShielding_=(resist : Float) : Float = { + radiationShielding = resist + RadiationShielding + } +} diff --git a/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala new file mode 100644 index 000000000..36c9736e5 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceSelection.scala @@ -0,0 +1,33 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital.resistance + +import net.psforever.objects.ballistics._ +import net.psforever.objects.vital.NoResistance +import net.psforever.objects.vital.projectile.ProjectileCalculations + +/** + * 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 : ProjectileCalculations.Form + def Splash : ProjectileCalculations.Form + def Lash : ProjectileCalculations.Form + def Aggravated : ProjectileCalculations.Form + + def apply(data : ResolvedProjectile) : ProjectileCalculations.Form = data.resolution match { + case ProjectileResolution.Hit => Direct + case ProjectileResolution.Splash => Splash + case ProjectileResolution.Lash => Lash + case _ => None + } + + def apply(res : ProjectileResolution.Value) : ProjectileCalculations.Form = res match { + case ProjectileResolution.Hit => Direct + case ProjectileResolution.Splash => Splash + case ProjectileResolution.Lash => Lash + case _ => None + } +} diff --git a/common/src/main/scala/net/psforever/objects/vital/resolution/DamageResistCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/resolution/DamageResistCalculations.scala new file mode 100644 index 000000000..e46fb46aa --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/resolution/DamageResistCalculations.scala @@ -0,0 +1,27 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital.resolution + +import net.psforever.objects.ballistics.ResolvedProjectile +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 : ProjectileCalculations.Form, resistances : ProjectileCalculations.Form, data : ResolvedProjectile) : ResolutionCalculations.Output = { + val dam : Int = damages(data) + val res : Int = resistances(data) + val mod = calcFunc(data) + val modDam = mod(dam, res) + applyFunc(modDam, data) + } +} diff --git a/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala new file mode 100644 index 000000000..3b143ba09 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala @@ -0,0 +1,160 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.vital.resolution + +import net.psforever.objects.{Player, Vehicle} +import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} +import net.psforever.objects.vital.projectile.ProjectileCalculations + +/** + * 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 + * @return a function literal that encapsulates delayed modification instructions for certain objects + */ + def Calculate(damages : ProjectileCalculations.Form, resistances : ProjectileCalculations.Form, data : ResolvedProjectile) : ResolutionCalculations.Output +} + +object ResolutionCalculations { + type Output = (Any)=>Unit + type Form = (ProjectileCalculations.Form, ProjectileCalculations.Form, ResolvedProjectile)=>Output + + def NoDamage(data : ResolvedProjectile)(a : Int, b : Int) : Int = 0 + + def InfantryDamageAfterResist(data : ResolvedProjectile) : (Int, Int)=>(Int, Int) = { + data.target match { + case target : PlayerSource => + InfantryDamageAfterResist(target.health, target.armor) + case _ => + InfantryDamageAfterResist(0, 0) + } + } + + def InfantryDamageAfterResist(currentHP : Int, currentArmor : Int)(damages : Int, resistance : Int) : (Int, Int) = { + if(damages > 0 && currentHP > 0) { + if(currentArmor <= 0) { + (damages, 0) //no armor; health damage + } + else if(damages > resistance) { + val resistedDam = damages - resistance + //(resistedDam, resistance) + if(resistance <= currentArmor) { + (resistedDam, resistance) //armor and health damage + } + else { + (resistedDam + (resistance - currentArmor), currentArmor) //deplete armor; health damage + bonus + } + } + else { + (0, damages) //too weak; armor damage (less than resistance) + } + } + else { + (0, 0) //no damage + } + } + + def MaxDamageAfterResist(data : ResolvedProjectile) : (Int, Int)=>(Int, Int) = { + data.target match { + case target : PlayerSource => + MaxDamageAfterResist(target.health, target.armor) + case _ => + MaxDamageAfterResist(0, 0) + } + } + + def MaxDamageAfterResist(currentHP : Int, currentArmor : Int)(damages : Int, resistance : Int) : (Int, Int) = { + val resistedDam = damages - resistance + if(resistedDam > 0 && currentHP > 0) { + if(currentArmor <= 0) { + (resistedDam, 0) //no armor; health damage + } + else if(resistedDam >= currentArmor) { + (resistedDam - currentArmor, currentArmor) //deplete armor; health damage + } + else { + (0, resistedDam) //too weak; armor damage (less than resistance) + } + } + else { + (0, 0) //no damage + } + } + + /** + * 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 + * @return a function literal for dealing with damage values and resistance values together + */ + def VehicleDamageAfterResist(data : ResolvedProjectile) : (Int, Int)=>Int = { + VehicleDamageAfterResist + } + + def VehicleDamageAfterResist(damages : Int, resistance : Int) : Int = { + if(damages > resistance) { + damages - resistance + } + else { + damages + } + } + + def NoApplication(damageValue : Int, data : ResolvedProjectile)(target : Any) : Unit = { } + + /** + * The expanded `(Any)=>Unit` function for infantry. + * Apply the damage values to the 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 target the `Player` object to be affected by these damage values (at some point) + */ + def InfantryApplication(damageValues : (Int, Int), data : ResolvedProjectile)(target : Any) : Unit = target match { + case player : Player => + val (a, b) = damageValues + //TODO Personal Shield implant test should go here and modify the values a and b + if(player.isAlive && !(a == 0 && b == 0)) { + player.History(data) + if(player.Armor - b < 0) { + player.Health = player.Health - a - (b - player.Armor) + player.Armor = 0 + } + else { + player.Armor = player.Armor - b + player.Health = player.Health - a + } + } + case _ => + } + + /** + * 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 target the `Vehicle` object to be affected by these damage values (at some point) + */ + def VehicleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : Unit = target match { + case vehicle : Vehicle => + if(vehicle.Health > 0) { + vehicle.History(data) + val shields = vehicle.Shields + if(shields > damage) { + vehicle.Shields = shields - damage + } + else if(shields > 0) { + vehicle.Health = vehicle.Health - (damage - shields) + vehicle.Shields = 0 + } + else { + vehicle.Health = vehicle.Health - damage + } + } + case _ => ; + } +} diff --git a/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionSelection.scala b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionSelection.scala new file mode 100644 index 000000000..824a9faef --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionSelection.scala @@ -0,0 +1,12 @@ +// 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 +} diff --git a/common/src/main/scala/net/psforever/packet/game/DestroyDisplayMessage.scala b/common/src/main/scala/net/psforever/packet/game/DestroyDisplayMessage.scala index db2fc6eda..f8dc51d83 100644 --- a/common/src/main/scala/net/psforever/packet/game/DestroyDisplayMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/DestroyDisplayMessage.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.packet.game +import net.psforever.objects.ballistics.SourceEntry import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import net.psforever.types.PlanetSideEmpire import scodec.Codec @@ -15,47 +16,43 @@ import scodec.codecs._ * 3) victim information
* In the case of a player kill, the player's name will be attributed directly. * In the case of an absentee kill, a description of the method of death will be attributed. - * In the case of a suicide, the player attributed is the player who was killed (message format displays only the victim). - * The victim's name is byte-aligned with a 5-bit buffer.
+ * In the case of a suicide, the player attributed is the player who was killed (message format displays only the victim).
*
* The four bytes that follow each name seems to be important to the identification of the associated player. - * The same value will be seen in every `DestroyDisplayMessage` that includes the player, with respect to whether they are listed as the "killer" or as the "victim." - * This holds true for every entry within thie same login session, at least. - * Blanking these values out does not change anything about the format of the event message. - * In the case of absentee kills, for example, where there is no killer listed, this field has been zero'd (`00000000`).
+ * The same value will be seen in every `DestroyDisplayMessage` that includes that player, + * with respect to whether they are listed as the "killer" or as the "victim." + * This holds true for every entry within the same login session, at least. + * Blanking either of these values out does not change anything about the format of the event message. + * If the two ids match, the packet will interpreted as the "suicide" format, even if the names do not match. + * In the case of absentee kills where there is no killer listed, this field is zero'd.
*
- * The faction affiliation is different from the normal way `PlanetSideEmpire` values are recorded. - * The higher nibble will reflect the first part of the `PlanetSideEmpire` value. - * An extra `20` will be added if the player is in a vehicle or turret at the time. - * When marked as being in a vehicle or turret, the player's name will be enclosed within square brackets. - * The length of the player's name found at the start of the wide character string does not reflect whether or not there will be square brackets (fortunately).
- *
- * The two bytes in between the killer section and the victim section are the method of homicide or suicide. - * The color of the resulting icon is borrowed from the attributed killer's faction affiliation if it can be determined. - * An unidentified method defaults to a skull and crossbones icon. - * The exact range of unique and valid icon values for this parameter is currently unknown. - * It is also unknown what the two bytes preceding `method` specify, as changing them does nothing to the displayed message. + * When marked as being in a vehicle or a turret, the player's name will be enclosed within square brackets. + * The length of the player's name found at the start of the character string does not reflect + * whether or not there will be square brackets (fortunately). + * The color of the resulting icon is borrowed from the attributed killer's faction affiliation if it can be determined + * and the type of icon is the same as an object id. + * An unidentified method or a missing icon defaults to a skull and crossbones. * @param killer the name of the player who did the killing - * @param killer_charId Same as CharacterInfoMessage + * @param killer_charId same as CharacterInfoMessage * @param killer_empire the empire affiliation of the killer - * @param killer_inVehicle true, if the killer was in a vehicle at the time of the kill; false, otherwise + * @param killer_in_vehicle true, if the killer was in a vehicle at the time of the kill; false, otherwise * @param unk na; but does not like being set to 0 * @param method modifies the icon in the message, related to the way the victim was killed * @param victim the name of the player who was killed - * @param victim_charId Same as CharacterInfoMessage + * @param victim_charId same as CharacterInfoMessage * @param victim_empire the empire affiliation of the victim - * @param victim_inVehicle true, if the victim was in a vehicle when he was killed; false, otherwise + * @param victim_in_vehicle true, if the victim was in a vehicle when he was killed; false, otherwise */ final case class DestroyDisplayMessage(killer : String, killer_charId : Long, killer_empire : PlanetSideEmpire.Value, - killer_inVehicle : Boolean, + killer_in_vehicle : Boolean, unk : Int, method : Int, victim : String, victim_charId : Long, victim_empire : PlanetSideEmpire.Value, - victim_inVehicle : Boolean + victim_in_vehicle : Boolean ) extends PlanetSideGamePacket { type Packet = DestroyDisplayMessage @@ -68,12 +65,12 @@ object DestroyDisplayMessage extends Marshallable[DestroyDisplayMessage] { ("killer" | PacketHelpers.encodedWideString) :: ("killer_charId" | ulongL(32)) :: ("killer_empire" | PlanetSideEmpire.codec) :: - ("killer_inVehicle" | bool) :: + ("killer_in_vehicle" | bool) :: ("unk" | uint16L) :: ("method" | uint16L) :: ("victim" | PacketHelpers.encodedWideStringAligned(5)) :: ("victim_charId" | ulongL(32)) :: ("victim_empire" | PlanetSideEmpire.codec) :: - ("victim_inVehicle" | bool) + ("victim_in_vehicle" | bool) ).as[DestroyDisplayMessage] } diff --git a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala index 1dc999bf2..09b6d289e 100644 --- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala @@ -156,6 +156,10 @@ final case class PlanetsideAttributeMessage(player_guid : PlanetSideGUID, } object PlanetsideAttributeMessage extends Marshallable[PlanetsideAttributeMessage] { + def apply(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Int) : PlanetsideAttributeMessage = { + PlanetsideAttributeMessage(player_guid, attribute_type, attribute_value.toLong) + } + implicit val codec : Codec[PlanetsideAttributeMessage] = ( ("player_guid" | PlanetSideGUID.codec) :: ("attribute_type" | uint8L) :: diff --git a/common/src/main/scala/services/avatar/AvatarAction.scala b/common/src/main/scala/services/avatar/AvatarAction.scala index 8d3a0ea28..310bad7e6 100644 --- a/common/src/main/scala/services/avatar/AvatarAction.scala +++ b/common/src/main/scala/services/avatar/AvatarAction.scala @@ -1,14 +1,15 @@ // Copyright (c) 2017 PSForever package services.avatar +import net.psforever.objects.ballistics.SourceEntry import net.psforever.objects.{PlanetSideGameObject, Player} import net.psforever.objects.equipment.Equipment import net.psforever.objects.inventory.Container import net.psforever.objects.zones.Zone import net.psforever.packet.PlanetSideGamePacket -import net.psforever.packet.game.{CargoMountPointStatusMessage, PlanetSideGUID, PlayerStateMessageUpstream} +import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream} import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent} -import net.psforever.types.ExoSuitType +import net.psforever.types.{ExoSuitType, Vector3} import scala.concurrent.duration.FiniteDuration @@ -21,9 +22,14 @@ object AvatarAction { final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action + final case class Damage(player_guid : PlanetSideGUID, target : Player, resolution_function : (Any)=>Unit) extends Action + final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Action + final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) extends Action final case class DropItem(player_guid : PlanetSideGUID, item : Equipment, zone : Zone) extends Action final case class EquipmentInHand(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action + final case class HitHint(source_guid : PlanetSideGUID, player_guid : PlanetSideGUID) extends Action final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action + final case class KilledWhileInVehicle(player_guid : PlanetSideGUID) extends Action final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action final case class ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action @@ -38,5 +44,4 @@ object AvatarAction { // final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action // final case class DestroyDisplay(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action -// final case class HitHintReturn(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action } diff --git a/common/src/main/scala/services/avatar/AvatarResponse.scala b/common/src/main/scala/services/avatar/AvatarResponse.scala index b56d92cf6..03647d5dd 100644 --- a/common/src/main/scala/services/avatar/AvatarResponse.scala +++ b/common/src/main/scala/services/avatar/AvatarResponse.scala @@ -2,11 +2,12 @@ package services.avatar import net.psforever.objects.Player +import net.psforever.objects.ballistics.SourceEntry import net.psforever.objects.equipment.Equipment import net.psforever.packet.PlanetSideGamePacket import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID, PlayerStateMessageUpstream} import net.psforever.packet.game.objectcreate.ConstructorData -import net.psforever.types.ExoSuitType +import net.psforever.types.{ExoSuitType, Vector3} object AvatarResponse { trait Response @@ -17,8 +18,13 @@ object AvatarResponse { final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response final case class ConcealPlayer() extends Response - final case class EquipmentInHand(pkt : ObjectCreateMessage) extends Response + final case class DamageResolution(target : Player, resolution_function : (Any)=>Unit) extends Response + final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Response + final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int) extends Response final case class DropItem(pkt : ObjectCreateMessage) extends Response + final case class EquipmentInHand(pkt : ObjectCreateMessage) extends Response + final case class HitHint(source_guid : PlanetSideGUID) extends Response + final case class KilledWhileInVehicle() extends Response final case class LoadPlayer(pkt : ObjectCreateMessage) extends Response final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response final case class ObjectHeld(slot : Int) extends Response @@ -31,6 +37,4 @@ object AvatarResponse { final case class SendResponse(msg: PlanetSideGamePacket) extends Response // final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response - // final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response - // final case class HitHintReturn(itemID : PlanetSideGUID) extends Response } diff --git a/common/src/main/scala/services/avatar/AvatarService.scala b/common/src/main/scala/services/avatar/AvatarService.scala index f74fec1d1..c703b3601 100644 --- a/common/src/main/scala/services/avatar/AvatarService.scala +++ b/common/src/main/scala/services/avatar/AvatarService.scala @@ -65,6 +65,18 @@ class AvatarService extends Actor { AvatarEvents.publish( AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer()) ) + case AvatarAction.Damage(player_guid, target, resolution_function) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.DamageResolution(target, resolution_function)) + ) + case AvatarAction.Destroy(victim, killer, weapon, pos) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", victim, AvatarResponse.Destroy(victim, killer, weapon, pos)) + ) + case AvatarAction.DestroyDisplay(killer, victim, method, unk) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", Service.defaultPlayerGUID, AvatarResponse.DestroyDisplay(killer, victim, method, unk)) + ) case AvatarAction.DropItem(player_guid, item, zone) => val definition = item.Definition val objectData = DroppedItemData( @@ -86,6 +98,14 @@ class AvatarService extends Actor { AvatarResponse.EquipmentInHand(ObjectCreateMessage(definition.ObjectId, item.GUID, containerData, objectData)) ) ) + case AvatarAction.HitHint(source_guid, player_guid) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.HitHint(source_guid)) + ) + case AvatarAction.KilledWhileInVehicle(player_guid) => + AvatarEvents.publish( + AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.KilledWhileInVehicle()) + ) case AvatarAction.LoadPlayer(player_guid, object_id, target_guid, cdata, pdata) => val pkt = pdata match { case Some(data) => @@ -169,22 +189,6 @@ class AvatarService extends Actor { AvatarServiceReply.PlayerStateShift(killer) )) } - case AvatarService.DestroyDisplay(killer, victim) => - val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(victim) - if (playerOpt.isDefined) { - val player: PlayerAvatar = playerOpt.get - AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, victim, - AvatarServiceReply.DestroyDisplay(killer) - )) - } - case AvatarService.HitHintReturn(source_guid,victim_guid) => - val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(source_guid) - if (playerOpt.isDefined) { - val player: PlayerAvatar = playerOpt.get - AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, victim_guid, - AvatarServiceReply.DestroyDisplay(source_guid) - )) - } */ case msg => log.warn(s"Unhandled message $msg from $sender") diff --git a/common/src/main/scala/services/vehicle/VehicleAction.scala b/common/src/main/scala/services/vehicle/VehicleAction.scala index 604256cf2..1ae95ea81 100644 --- a/common/src/main/scala/services/vehicle/VehicleAction.scala +++ b/common/src/main/scala/services/vehicle/VehicleAction.scala @@ -21,7 +21,9 @@ object VehicleAction { final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Action final case class LoadVehicle(player_guid : PlanetSideGUID, vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Action final case class MountVehicle(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, seat : Int) extends Action + final case class ObjectDelete(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action final case class Ownership(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID) extends Action + final case class PlanetsideAttribute(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action final case class SeatPermissions(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Action final case class StowEquipment(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) extends Action diff --git a/common/src/main/scala/services/vehicle/VehicleResponse.scala b/common/src/main/scala/services/vehicle/VehicleResponse.scala index e083bd247..5c972bf63 100644 --- a/common/src/main/scala/services/vehicle/VehicleResponse.scala +++ b/common/src/main/scala/services/vehicle/VehicleResponse.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package services.vehicle -import net.psforever.objects.equipment.Equipment import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.{PlanetSideGameObject, Vehicle} import net.psforever.packet.PlanetSideGamePacket @@ -19,12 +18,14 @@ object VehicleResponse { final case class DetachFromRails(vehicle_guid : PlanetSideGUID, rails_guid : PlanetSideGUID, rails_pos : Vector3, rails_rot : Float) extends Response final case class DismountVehicle(bailType : BailType.Value , unk2 : Boolean) extends Response final case class EquipmentInSlot(pkt : ObjectCreateMessage) extends Response + final case class HitHint(source_guid : PlanetSideGUID) extends Response final case class InventoryState(obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Response final case class InventoryState2(obj_guid : PlanetSideGUID, parent_guid : PlanetSideGUID, value : Int) extends Response final case class KickPassenger(seat_num : Int, kickedByDriver : Boolean, vehicle_guid : PlanetSideGUID) extends Response final case class LoadVehicle(vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Response final case class MountVehicle(object_guid : PlanetSideGUID, seat : Int) extends Response final case class Ownership(vehicle_guid : PlanetSideGUID) extends Response + final case class PlanetsideAttribute(vehicle_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Response final case class ResetSpawnPad(pad_guid : PlanetSideGUID) extends Response final case class RevealPlayer(player_guid : PlanetSideGUID) extends Response final case class SeatPermissions(vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Response diff --git a/common/src/main/scala/services/vehicle/VehicleService.scala b/common/src/main/scala/services/vehicle/VehicleService.scala index 6f347da79..cacfae0d1 100644 --- a/common/src/main/scala/services/vehicle/VehicleService.scala +++ b/common/src/main/scala/services/vehicle/VehicleService.scala @@ -92,6 +92,10 @@ class VehicleService extends Actor { VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.Ownership(vehicle_guid)) ) + case VehicleAction.PlanetsideAttribute(exclude_guid, target_guid, attribute_type, attribute_value) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", exclude_guid, VehicleResponse.PlanetsideAttribute(target_guid, attribute_type, attribute_value)) + ) case VehicleAction.SeatPermissions(player_guid, vehicle_guid, seat_group, permission) => VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission)) diff --git a/common/src/test/scala/objects/ConverterTest.scala b/common/src/test/scala/objects/ConverterTest.scala index b863499a2..7021914f6 100644 --- a/common/src/test/scala/objects/ConverterTest.scala +++ b/common/src/test/scala/objects/ConverterTest.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package objects -import net.psforever.objects.definition.converter.{ACEConverter, CharacterSelectConverter, REKConverter} +import net.psforever.objects.definition.converter.{ACEConverter, CharacterSelectConverter, DestroyedVehicleConverter, REKConverter} import net.psforever.objects._ import net.psforever.objects.definition._ import net.psforever.objects.equipment.CItem.{DeployedItem, Unit} @@ -9,6 +9,7 @@ import net.psforever.objects.equipment._ import net.psforever.objects.inventory.InventoryTile import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.serverobject.tube.SpawnTube +import net.psforever.objects.vehicles.DestroyedVehicle import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.objectcreate._ import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3} @@ -368,7 +369,40 @@ class ConverterTest extends Specification { ams.Utilities(4)().GUID = PlanetSideGUID(417) ams.Definition.Packet.ConstructorData(ams).isSuccess mustEqual true - ok //TODO write more of this test + } + + "convert to packet (3)" in { + val + ams = Vehicle(GlobalDefinitions.ams) + ams.GUID = PlanetSideGUID(413) + ams.Health = 0 //destroyed vehicle + + ams.Definition.Packet.ConstructorData(ams).isSuccess mustEqual true + //did not initialize the utilities, but the converter did not fail + } + } + + "DestroyedVehicle" should { + "not convert a working vehicle" in { + val ams = Vehicle(GlobalDefinitions.ams) + ams.GUID = PlanetSideGUID(413) + ams.Health mustEqual 3000 //not destroyed vehicle + DestroyedVehicleConverter.converter.ConstructorData(ams).isFailure mustEqual true + } + + "convert to packet" in { + val ams = Vehicle(GlobalDefinitions.ams) + ams.GUID = PlanetSideGUID(413) + ams.Health = 0 + DestroyedVehicleConverter.converter.ConstructorData(ams).isSuccess mustEqual true + //did not initialize the utilities, but the converter did not fail + } + + "not convert into a detailed packet" in { + val ams = Vehicle(GlobalDefinitions.ams) + ams.GUID = PlanetSideGUID(413) + ams.Health = 0 + DestroyedVehicleConverter.converter.DetailedConstructorData(ams).isFailure mustEqual true } } } \ No newline at end of file diff --git a/common/src/test/scala/objects/DamageModelTests.scala b/common/src/test/scala/objects/DamageModelTests.scala new file mode 100644 index 000000000..2b4c95b7d --- /dev/null +++ b/common/src/test/scala/objects/DamageModelTests.scala @@ -0,0 +1,416 @@ +// Copyright (c) 2017 PSForever +package objects + +import net.psforever.objects._ +import net.psforever.objects.vital.damage.{DamageCalculations, DamageProfile} +import DamageCalculations._ +import net.psforever.objects.vital.resistance.ResistanceCalculations +import ResistanceCalculations._ +import net.psforever.objects.vital.resolution.ResolutionCalculations +import ResolutionCalculations._ +import net.psforever.objects.ballistics._ +import net.psforever.objects.vital.Vitality +import net.psforever.types._ +import org.specs2.mutable.Specification + +class DamageCalculationsTests extends Specification { + "DamageCalculations" should { + val wep = GlobalDefinitions.galaxy_gunship_cannon + val wep_fmode = Tool(wep).FireMode + val wep_prof = wep_fmode.Modifiers.asInstanceOf[DamageProfile] + val proj = wep.ProjectileTypes.head + val proj_prof = proj.asInstanceOf[DamageProfile] + val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + 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.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3(50, 50, 0)) + "extract no damage numbers" in { + NoDamageAgainst(proj_prof) mustEqual 0 + } + + "extract damage against exosuit target" in { + DamageAgainstExoSuit(proj_prof) mustEqual 50 + } + + "extract damage against MAX target" in { + DamageAgainstMaxSuit(proj_prof) mustEqual 75 + } + + "extract damage against vehicle target" in { + DamageAgainstVehicle(proj_prof) mustEqual 82 + } + + "extract damage against aircraft target" in { + DamageAgainstAircraft(proj_prof) mustEqual 82 + } + + "extract damage against something" in { + DamageAgainstUnknown(proj_prof) mustEqual 66 + } + + "extract a complete damage profile (1)" in { + val result = DamageAgainstVehicle(proj_prof) + DamageAgainstVehicle(wep_prof) + val func : (DamageProfile, List[DamageProfile]) => Int = DamageWithModifiers(DamageAgainstVehicle) + func(proj_prof, List(wep_prof)) mustEqual result + } + + "extract a complete damage profile (2)" in { + val result = 2 * DamageAgainstVehicle(proj_prof) + DamageAgainstVehicle(wep_prof) + val func : (DamageProfile, List[DamageProfile]) => Int = DamageWithModifiers(DamageAgainstVehicle) + func(proj_prof, List(proj_prof, wep_prof)) mustEqual result + } + + "calculate no distance" in { + NoDistance(resprojectile) mustEqual 0 + } + + "calculate too far distance" in { + TooFar(resprojectile) mustEqual Float.MaxValue + } + + "calculate distance between target and source" in { + DistanceBetweenTargetandSource(resprojectile) mustEqual 10 + } + + "calculate distance between target and explosion (splash)" in { + DistanceFromExplosionToTarget(resprojectile) mustEqual 64.03124f + } + + "calculate no damage from components" in { + val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof)) + val distance = DistanceFromExplosionToTarget(resprojectile) + DamageCalculations.NoDamage(projectile, result, distance) mustEqual 0 + } + + "calculate degraded damage from components (near)" in { + val projectile_alt = Projectile(GlobalDefinitions.galaxy_gunship_gun_projectile, //need projectile with degrade + wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) + val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof)) + DirectHitDamageWithDegrade(projectile_alt, result, 0) mustEqual 132 + } + + "calculate degraded damage from components (medium)" in { + val projectile_alt = Projectile(GlobalDefinitions.galaxy_gunship_gun_projectile, //need projectile with degrade + wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) + val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof)) + DirectHitDamageWithDegrade(projectile_alt, result, 250) mustEqual 103 + } + + "calculate degraded damage from components (far)" in { + val projectile_alt = Projectile(GlobalDefinitions.galaxy_gunship_gun_projectile, //need projectile with degrade + wep, wep_fmode, player, Vector3(2, 2, 0), Vector3.Zero) + val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof)) + DirectHitDamageWithDegrade(projectile_alt, result, 1000) mustEqual 0 + } + + "calculate splash damage from components (near)" in { + val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof)) + SplashDamageWithRadialDegrade(projectile, result, 0) mustEqual 264 + } + + "calculate splash damage from components (medium)" in { + val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof)) + SplashDamageWithRadialDegrade(projectile, result, 5) mustEqual 145 + } + + "calculate splash damage from components (far)" in { + val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof)) + SplashDamageWithRadialDegrade(projectile, result, 6) mustEqual 0 + } + } +} + +class ResistanceCalculationsTests extends Specification { + val wep = GlobalDefinitions.galaxy_gunship_cannon + val wep_fmode = Tool(wep).FireMode + val proj = wep.ProjectileTypes.head //GlobalDefinitions.heavy_grenade_projectile + val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2,2,0), Vector3.Zero) + + "ResistanceCalculations" should { + "ignore all targets" in { + val target = Vehicle(GlobalDefinitions.fury) + val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero) + InvalidTarget(resprojectile).isFailure mustEqual true + } + + "discern standard infantry targets" in { + val target = player + val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero) + ValidInfantryTarget(resprojectile).isSuccess mustEqual true + ValidMaxTarget(resprojectile).isSuccess mustEqual false + ValidVehicleTarget(resprojectile).isSuccess mustEqual false + ValidAircraftTarget(resprojectile).isSuccess mustEqual false + } + + "discern mechanized infantry targets" in { + val target = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + target.ExoSuit = ExoSuitType.MAX + val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero) + ValidInfantryTarget(resprojectile).isSuccess mustEqual false + ValidMaxTarget(resprojectile).isSuccess mustEqual true + ValidVehicleTarget(resprojectile).isSuccess mustEqual false + ValidAircraftTarget(resprojectile).isSuccess mustEqual false + } + + "discern ground vehicle targets" in { + val target = Vehicle(GlobalDefinitions.fury) + val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero) + ValidInfantryTarget(resprojectile).isSuccess mustEqual false + ValidMaxTarget(resprojectile).isSuccess mustEqual false + ValidVehicleTarget(resprojectile).isSuccess mustEqual true + ValidAircraftTarget(resprojectile).isSuccess mustEqual false + } + + "discern flying vehicle targets" in { + val target = Vehicle(GlobalDefinitions.mosquito) + val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero) + ValidInfantryTarget(resprojectile).isSuccess mustEqual false + ValidMaxTarget(resprojectile).isSuccess mustEqual false + ValidVehicleTarget(resprojectile).isSuccess mustEqual false + ValidAircraftTarget(resprojectile).isSuccess mustEqual true + } + + "extract no resistance values" in { + NoResistExtractor(SourceEntry(player)) mustEqual 0 + } + + "extract resistance values from exo-suit" in { + val pSource = PlayerSource(player) + ExoSuitDirectExtractor(pSource) mustEqual 4 + ExoSuitSplashExtractor(pSource) mustEqual 15 + ExoSuitAggravatedExtractor(pSource) mustEqual 8 + ExoSuitRadiationExtractor(pSource) mustEqual 0 + } + + "extract resistance values from vehicle" in { + val vSource = VehicleSource(Vehicle(GlobalDefinitions.fury)) + VehicleDirectExtractor(vSource) mustEqual 0 + VehicleSplashExtractor(vSource) mustEqual 0 + VehicleAggravatedExtractor(vSource) mustEqual 0 + VehicleRadiationExtractor(vSource) mustEqual 0 + } + } +} + +class ResolutionCalculationsTests extends Specification { + val wep = GlobalDefinitions.suppressor + val wep_fmode = Tool(wep).FireMode + val proj = wep.ProjectileTypes.head + val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + player.Spawn + val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2,2,0), Vector3.Zero) + + "ResolutionCalculations" should { + "calculate no damage" in { + val target = player + val resprojectile = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target), target.DamageModel, Vector3.Zero) + ResolutionCalculations.NoDamage(resprojectile)(50,50) mustEqual 0 + } + + "calculate no infantry damage for vehicles" in { + val target1 = Vehicle(GlobalDefinitions.fury) //! + val resprojectile1 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target1), target1.DamageModel, Vector3.Zero) + InfantryDamageAfterResist(resprojectile1)(50, 10) mustEqual (0,0) + + val target2 = player + val resprojectile2 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target2), target2.DamageModel, Vector3.Zero) + InfantryDamageAfterResist(resprojectile2)(50, 10) mustEqual (40,10) + } + + "calculate health and armor damage for infantry target" in { + InfantryDamageAfterResist(100,100)(50, 10) mustEqual (40,10) + } + + "calculate health and armor damage, with bonus damage, for infantry target" in { + //health = 100, armor = 5 -> resist 10 but only have 5, so rollover extra -> damages (40+5, 5) + InfantryDamageAfterResist(100,5)(50, 10) mustEqual (45,5) + } + + "calculate health damage for infantry target" in { + //health = 100, armor = 0 + InfantryDamageAfterResist(100,0)(50, 10) mustEqual (50,0) + } + + "calculate armor damage for infantry target" in { + //resistance > damage + InfantryDamageAfterResist(100,100)(50, 60) mustEqual (0,50) + } + + val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + player2.ExoSuit = ExoSuitType.MAX + player2.Spawn + "calculate no max damage for vehicles" in { + val target1 = Vehicle(GlobalDefinitions.fury) //! + val resprojectile1 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target1), target1.DamageModel, Vector3.Zero) + MaxDamageAfterResist(resprojectile1)(50, 10) mustEqual (0,0) + + val target2 = player2 + val resprojectile2 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target2), target2.DamageModel, Vector3.Zero) + MaxDamageAfterResist(resprojectile2)(50, 10) mustEqual (0,40) + } + + "calculate health and armor damage for max target" in { + MaxDamageAfterResist(100,5)(50, 10) mustEqual (35,5) + } + + "calculate health damage for max target" in { + //health = 100, armor = 0 + MaxDamageAfterResist(100,0)(50, 10) mustEqual (40,0) + } + + "calculate armor damage for max target" in { + //resistance > damage + MaxDamageAfterResist(100,100)(50, 10) mustEqual (0,40) + } + + "do not care if target is infantry for vehicle calculations" in { + val target1 = player + val resprojectile1 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target1), target1.DamageModel, Vector3.Zero) + VehicleDamageAfterResist(resprojectile1)(50, 10) mustEqual 40 + + val target2 = Vehicle(GlobalDefinitions.fury) //! + val resprojectile2 = ResolvedProjectile(ProjectileResolution.Splash, projectile, SourceEntry(target2), target2.DamageModel, Vector3.Zero) + VehicleDamageAfterResist(resprojectile2)(50, 10) mustEqual 40 + } + } + + "calculate resisted damage for vehicle target" in { + VehicleDamageAfterResist(50, 10) mustEqual 40 + } + + "calculate un-resisted damage for vehicle target" in { + VehicleDamageAfterResist(50, 0) mustEqual 50 + } +} + +class DamageModelTests extends Specification { + val wep = GlobalDefinitions.suppressor + val wep_tool = Tool(wep) + val wep_fmode = wep_tool.FireMode + val proj = wep.ProjectileTypes.head + val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + player.Spawn + val projectile = Projectile(proj, wep, wep_fmode, player, Vector3(2,2,0), Vector3.Zero) + + "DamageModel" should { + "be a part of vitality" in { + player.isInstanceOf[Vitality] mustEqual true + try { + player.getClass.getDeclaredMethod("DamageModel").hashCode() + } + catch { + case _ : Exception => + ko //the method doesn't exist + } + + wep_tool.isInstanceOf[Vitality] mustEqual false + try { + wep_tool.getClass.getDeclaredMethod("DamageModel").hashCode() + ko + } + catch { + case _ : Exception => + ok //the method doesn't exist + } + ok + } + + "resolve infantry targets" in { + val tplayer = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + tplayer.Spawn + tplayer.Health mustEqual 100 + tplayer.Armor mustEqual 50 + + val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero) + val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile) + + func(tplayer) + tplayer.Health mustEqual 87 + tplayer.Armor mustEqual 46 + } + + "resolve infantry targets in a specific way" in { + val tplayer = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + tplayer.Spawn + tplayer.Health mustEqual 100 + tplayer.Armor mustEqual 50 + + val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero) + val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash) + + func(tplayer) + tplayer.Health mustEqual 81 + tplayer.Armor mustEqual 35 + } + + "resolve infantry targets, with damage overflow" in { + val tplayer = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + tplayer.Spawn + tplayer.Health mustEqual 100 + tplayer.Armor mustEqual 50 + + val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(tplayer), tplayer.DamageModel, Vector3.Zero) + val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile) + tplayer.Armor = 0 + + func(tplayer) + tplayer.Health mustEqual 83 + tplayer.Armor mustEqual 0 + } + + "resolve vehicle targets" in { + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.Health mustEqual 650 + + val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero) + val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile) + + func(vehicle) + vehicle.Health mustEqual 641 + } + + "resolve vehicle targets (with shields)" in { + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.Shields = 10 + vehicle.Health mustEqual 650 + vehicle.Shields mustEqual 10 + + val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero) + val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile) + + func(vehicle) + vehicle.Health mustEqual 650 + vehicle.Shields mustEqual 1 + } + + "resolve vehicle targets (losing shields)" in { + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.Shields = 10 + vehicle.Health mustEqual 650 + vehicle.Shields mustEqual 10 + + val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero) + val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile) + + func(vehicle) + vehicle.Health mustEqual 650 + vehicle.Shields mustEqual 1 + func(vehicle) + vehicle.Health mustEqual 642 + vehicle.Shields mustEqual 0 + } + + "resolve vehicle targets in a specific way" in { + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.Health mustEqual 650 + + val resprojectile = ResolvedProjectile(ProjectileResolution.Hit, projectile, SourceEntry(vehicle), vehicle.DamageModel, Vector3.Zero) + val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash) + + func(vehicle) + vehicle.Health mustEqual 632 + } + } +} diff --git a/common/src/test/scala/objects/ProjectileTest.scala b/common/src/test/scala/objects/ProjectileTest.scala index d28aba917..26d2a87cc 100644 --- a/common/src/test/scala/objects/ProjectileTest.scala +++ b/common/src/test/scala/objects/ProjectileTest.scala @@ -1,19 +1,29 @@ // Copyright (c) 2017 PSForever package objects -import net.psforever.objects.{GlobalDefinitions, LocalProjectile, Tool} -import net.psforever.objects.ballistics.{DamageType, Projectile, ProjectileResolution, Projectiles} +import net.psforever.objects._ +import net.psforever.objects.ballistics._ import net.psforever.objects.definition.ProjectileDefinition -import net.psforever.types.Vector3 +import net.psforever.objects.serverobject.mblocker.Locker +import net.psforever.objects.vital.DamageType +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types._ import org.specs2.mutable.Specification class ProjectileTest extends Specification { + val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + val fury = Vehicle(GlobalDefinitions.fury) + "LocalProjectile" should { "construct" in { val obj = new LocalProjectile() //since they're just placeholders, they only need to construct obj.Definition.ObjectId mustEqual 0 obj.Definition.Name mustEqual "projectile" } + + "local projectile range" in { + Projectile.BaseUID < Projectile.RangeUID mustEqual true + } } "ProjectileDefinition" should { @@ -151,48 +161,156 @@ class ProjectileTest extends Specification { } } - "Projectile" should { - "construct" in { - val beamer_wep = Tool(GlobalDefinitions.beamer) - val projectile = beamer_wep.Projectile - val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f)) + "SourceEntry" should { + "construct for players" in { + SourceEntry(player) match { + case o : PlayerSource => + o.Name mustEqual "TestCharacter" + o.Faction mustEqual PlanetSideEmpire.TR + o.Seated mustEqual false + o.ExoSuit mustEqual ExoSuitType.Standard + o.Health mustEqual 0 + o.Armor mustEqual 0 + o.Definition mustEqual GlobalDefinitions.avatar + o.Position mustEqual Vector3.Zero + o.Orientation mustEqual Vector3.Zero + o.Velocity mustEqual None + case _ => + ko + } + } - obj.profile mustEqual beamer_wep.Projectile - obj.tool_def mustEqual GlobalDefinitions.beamer + "construct for vehicles" in { + SourceEntry(fury) match { + case o : VehicleSource => + o.Name mustEqual "Fury" + o.Faction mustEqual PlanetSideEmpire.TR + o.Definition mustEqual GlobalDefinitions.fury + o.Health mustEqual 650 + o.Shields mustEqual 0 + o.Position mustEqual Vector3.Zero + o.Orientation mustEqual Vector3.Zero + o.Velocity mustEqual None + case _ => + ko + } + } + + "construct for generic object" in { + val obj = Locker() + SourceEntry(obj) match { + case o : ObjectSource => + o.obj mustEqual obj + o.Name mustEqual "Mb Locker" + o.Faction mustEqual PlanetSideEmpire.NEUTRAL + o.Definition mustEqual GlobalDefinitions.mb_locker + o.Position mustEqual Vector3.Zero + o.Orientation mustEqual Vector3.Zero + o.Velocity mustEqual None + case _ => + ko + } + } + + "contain timely information" in { + val obj = Player(Avatar("TestCharacter-alt", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + obj.VehicleSeated = Some(PlanetSideGUID(1)) + obj.Position = Vector3(1.2f, 3.4f, 5.6f) + obj.Orientation = Vector3(2.1f, 4.3f, 6.5f) + obj.Velocity = Some(Vector3(1.1f, 2.2f, 3.3f)) + val sobj = SourceEntry(obj) + obj.VehicleSeated = None + obj.Position = Vector3.Zero + obj.Orientation = Vector3.Zero + obj.Velocity = None + obj.ExoSuit = ExoSuitType.Agile + + sobj match { + case o : PlayerSource => + o.Name mustEqual "TestCharacter-alt" + o.Faction mustEqual PlanetSideEmpire.TR + o.Seated mustEqual true + o.ExoSuit mustEqual ExoSuitType.Standard + o.Definition mustEqual GlobalDefinitions.avatar + o.Position mustEqual Vector3(1.2f, 3.4f, 5.6f) + o.Orientation mustEqual Vector3(2.1f, 4.3f, 6.5f) + o.Velocity mustEqual Some(Vector3(1.1f, 2.2f, 3.3f)) + case _ => + ko + } + } + } + + "Projectile" should { + val beamer_def = GlobalDefinitions.beamer + val beamer_wep = Tool(beamer_def) + val firemode = beamer_wep.FireMode + val projectile = beamer_wep.Projectile + + "construct" in { + val obj = Projectile(beamer_wep.Projectile, beamer_wep.Definition, beamer_wep.FireMode, PlayerSource(player), beamer_def.ObjectId, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f)) + obj.profile mustEqual projectile + obj.tool_def mustEqual beamer_def + obj.fire_mode mustEqual firemode + obj.owner match { + case _ : PlayerSource => + ok + case _ => + ko + } + obj.attribute_to mustEqual obj.tool_def.ObjectId obj.shot_origin mustEqual Vector3(1.2f, 3.4f, 5.6f) obj.shot_angle mustEqual Vector3(0.2f, 0.4f, 0.6f) - obj.resolution mustEqual ProjectileResolution.Unresolved obj.fire_time <= System.nanoTime mustEqual true - obj.hit_time mustEqual 0 + obj.isResolved mustEqual false + } + + "construct (different attribute)" in { + val obj1 = Projectile(beamer_wep.Projectile, beamer_wep.Definition, beamer_wep.FireMode, player, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f)) + obj1.attribute_to mustEqual obj1.tool_def.ObjectId + + val obj2 = Projectile(beamer_wep.Projectile, beamer_wep.Definition, beamer_wep.FireMode, PlayerSource(player), 65, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f)) + obj2.attribute_to == obj2.tool_def.ObjectId mustEqual false + obj2.attribute_to mustEqual 65 } "resolve" in { - val beamer_wep = Tool(GlobalDefinitions.beamer) - val projectile = beamer_wep.Projectile - val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f)) - val obj2 = obj.Resolve(ProjectileResolution.MissedShot) + val obj = Projectile(projectile, beamer_def, firemode, PlayerSource(player), beamer_def.ObjectId, Vector3.Zero, Vector3.Zero) + obj.isResolved mustEqual false + obj.isMiss mustEqual false - obj.resolution mustEqual ProjectileResolution.Unresolved - obj.fire_time <= System.nanoTime mustEqual true - obj.hit_time mustEqual 0 - obj2.resolution mustEqual ProjectileResolution.MissedShot - obj2.fire_time == obj.fire_time mustEqual true - obj2.hit_time <= System.nanoTime mustEqual true - obj2.fire_time <= obj2.hit_time mustEqual true + obj.Resolve() + obj.isResolved mustEqual true + obj.isMiss mustEqual false } - "resolve, with coordinates" in { - val beamer_wep = Tool(GlobalDefinitions.beamer) - val projectile = beamer_wep.Projectile - val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f)) - val obj2 = obj.Resolve(Vector3(7.2f, 8.4f, 9.6f), Vector3(1.2f, 1.4f, 1.6f), ProjectileResolution.Resolved) + "missed" in { + val obj = Projectile(projectile, beamer_def, firemode, PlayerSource(player), beamer_def.ObjectId, Vector3.Zero, Vector3.Zero) + obj.isResolved mustEqual false + obj.isMiss mustEqual false - obj.resolution mustEqual ProjectileResolution.Unresolved - obj.current.Position mustEqual Vector3.Zero - obj.current.Orientation mustEqual Vector3.Zero - obj2.resolution mustEqual ProjectileResolution.Resolved - obj2.current.Position mustEqual Vector3(7.2f, 8.4f, 9.6f) - obj2.current.Orientation mustEqual Vector3(1.2f, 1.4f, 1.6f) + obj.Miss() + obj.isResolved mustEqual true + obj.isMiss mustEqual true + } + } + + "ResolvedProjectile" should { + val beamer_wep = Tool(GlobalDefinitions.beamer) + val p_source = PlayerSource(player) + val player2 = Player(Avatar("TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) + val p2_source = PlayerSource(player2) + val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero) + val fury_dm = fury.DamageModel + + "construct" in { + val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, PlayerSource(player2), fury_dm, Vector3(1.2f, 3.4f, 5.6f), 123456L) + obj.resolution mustEqual ProjectileResolution.Hit + obj.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.hit_time mustEqual 123456L } } } diff --git a/common/src/test/scala/objects/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala index 94c2878d9..0cb21ef96 100644 --- a/common/src/test/scala/objects/VehicleTest.scala +++ b/common/src/test/scala/objects/VehicleTest.scala @@ -4,14 +4,16 @@ package objects import akka.actor.Props import base.ActorTest import net.psforever.objects._ +import net.psforever.objects.ballistics.{PlayerSource, Projectile, ProjectileResolution, ResolvedProjectile} import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition} import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.vehicles._ +import net.psforever.objects.vital.{VehicleShieldCharge, Vitality} import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.{CharacterVoice, ExoSuitType} +import net.psforever.types._ import org.specs2.mutable._ -import scala.concurrent.duration.Duration +import scala.concurrent.duration._ class VehicleTest extends Specification { import VehicleTest._ @@ -611,6 +613,103 @@ class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest { } } +class VehicleControlShieldsChargingTest extends ActorTest { + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.GUID = PlanetSideGUID(10) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + + "charge vehicle shields" in { + assert(vehicle.Shields == 0) + assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) + vehicle.Actor ! Vehicle.ChargeShields(15) + + val msg = receiveOne(500 milliseconds) + assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge]) + assert(vehicle.Shields == 15) + assert(vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) + } +} + +class VehicleControlShieldsNotChargingVehicleDeadTest extends ActorTest { + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.GUID = PlanetSideGUID(10) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + + "not charge vehicle shields if the vehicle is destroyed" in { + assert(vehicle.Health > 0) + vehicle.Health = 0 + assert(vehicle.Health == 0) + assert(vehicle.Shields == 0) + assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) + vehicle.Actor ! Vehicle.ChargeShields(15) + + expectNoMsg(1 seconds) + assert(vehicle.Shields == 0) + assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) + } +} + +class VehicleControlShieldsNotChargingVehicleShieldsFullTest extends ActorTest { + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.GUID = PlanetSideGUID(10) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + + "not charge vehicle shields if the vehicle is destroyed" in { + assert(vehicle.Shields == 0) + vehicle.Shields = vehicle.MaxShields + assert(vehicle.Shields == vehicle.MaxShields) + assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) + vehicle.Actor ! Vehicle.ChargeShields(15) + + expectNoMsg(1 seconds) + assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]})) + } +} + +class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest { + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.GUID = PlanetSideGUID(10) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + + "charge vehicle shields" in { + assert(vehicle.Shields == 0) + vehicle.Actor ! Vehicle.ChargeShields(15) + + val msg = receiveOne(200 milliseconds) + assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge]) + assert(vehicle.Shields == 15) + vehicle.Actor ! Vehicle.ChargeShields(15) + + expectNoMsg(200 milliseconds) + assert(vehicle.Shields == 15) + } +} + +class VehicleControlShieldsNotChargingDamagedTest extends ActorTest { + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.GUID = PlanetSideGUID(10) + vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test") + // + val beamer_wep = Tool(GlobalDefinitions.beamer) + val p_source = PlayerSource( Player(Avatar("TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) ) + val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero) + val fury_dm = Vehicle(GlobalDefinitions.fury).DamageModel + val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f), System.nanoTime) + + "charge vehicle shields" in { + assert(vehicle.Shields == 0) + vehicle.Actor ! Vitality.Damage({case v : Vehicle => v.History(obj)}) + + val msg = receiveOne(200 milliseconds) + assert(msg.isInstanceOf[Vitality.DamageResolution]) + assert(vehicle.Shields == 0) + vehicle.Actor ! Vehicle.ChargeShields(15) + + expectNoMsg(200 milliseconds) + assert(vehicle.Shields == 0) + } +} + object VehicleTest { import net.psforever.objects.Avatar import net.psforever.types.{CharacterGender, PlanetSideEmpire} diff --git a/common/src/test/scala/objects/VitalityTest.scala b/common/src/test/scala/objects/VitalityTest.scala new file mode 100644 index 000000000..ca527ea02 --- /dev/null +++ b/common/src/test/scala/objects/VitalityTest.scala @@ -0,0 +1,83 @@ +// Copyright (c) 2017 PSForever +package objects + +import net.psforever.objects.ballistics._ +import net.psforever.objects._ +import net.psforever.objects.vital._ +import net.psforever.types._ +import org.specs2.mutable.Specification + +class VitalityTest extends Specification { + "Vitality" should { + val wep = GlobalDefinitions.galaxy_gunship_cannon + val wep_fmode = Tool(wep).FireMode + val proj = wep.ProjectileTypes.head + val vehicle = Vehicle(GlobalDefinitions.fury) + val vSource = VehicleSource(vehicle) + + "accept a variety of events" in { + val player = Player(Avatar("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.Splash, projectile, SourceEntry(player), player.DamageModel, Vector3(50, 50, 0)) + + player.History(resprojectile) //ResolvedProjectile, straight-up + player.History(DamageFromProjectile(resprojectile)) + player.History(HealFromKit(pSource, 10, GlobalDefinitions.medkit)) + player.History(HealFromTerm(pSource, 10, 0, GlobalDefinitions.order_terminal)) + player.History(HealFromImplant(pSource, 10, ImplantType.AdvancedRegen)) + player.History(HealFromExoSuitChange(pSource, ExoSuitType.Standard)) + player.History(RepairFromTerm(vSource, 10, GlobalDefinitions.order_terminal)) + player.History(VehicleShieldCharge(vSource, 10)) + player.History(PlayerSuicide(pSource)) + ok + } + + "return and clear the former list of vital activities" in { + val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)) + val pSource = PlayerSource(player) + + player.History(HealFromKit(pSource, 10, GlobalDefinitions.medkit)) + player.History(HealFromTerm(pSource, 10, 0, GlobalDefinitions.order_terminal)) + player.History(HealFromImplant(pSource, 10, ImplantType.AdvancedRegen)) + player.History(HealFromExoSuitChange(pSource, ExoSuitType.Standard)) + player.History(RepairFromTerm(vSource, 10, GlobalDefinitions.order_terminal)) + player.History(VehicleShieldCharge(vSource, 10)) + player.History(PlayerSuicide(pSource)) + player.History.size mustEqual 7 + + val list = player.ClearHistory() + player.History.size mustEqual 0 + list.head.isInstanceOf[PlayerSuicide] mustEqual true + list(1).isInstanceOf[VehicleShieldCharge] mustEqual true + list(2).isInstanceOf[RepairFromTerm] mustEqual true + list(3).isInstanceOf[HealFromExoSuitChange] mustEqual true + list(4).isInstanceOf[HealFromImplant] mustEqual true + list(5).isInstanceOf[HealFromTerm] mustEqual true + list(6).isInstanceOf[HealFromKit] mustEqual true + } + + "get exactly one entry that was caused by projectile damage" in { + val player = Player(Avatar("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.Splash, projectile, SourceEntry(player), player.DamageModel, Vector3(50, 50, 0)) + + player.History(DamageFromProjectile(resprojectile)) + player.History(HealFromKit(pSource, 10, GlobalDefinitions.medkit)) + player.History(HealFromTerm(pSource, 10, 0, GlobalDefinitions.order_terminal)) + player.History(HealFromImplant(pSource, 10, ImplantType.AdvancedRegen)) + player.History(HealFromExoSuitChange(pSource, ExoSuitType.Standard)) + player.History(RepairFromTerm(vSource, 10, GlobalDefinitions.order_terminal)) + player.History(VehicleShieldCharge(vSource, 10)) + player.History(PlayerSuicide(pSource)) + + player.LastShot match { + case Some(resolved_projectile) => + resolved_projectile.projectile mustEqual projectile + case None => + ko + } + } + } +} diff --git a/common/src/test/scala/service/RemoverActorTest.scala b/common/src/test/scala/service/RemoverActorTest.scala index 73f870549..c0cc62340 100644 --- a/common/src/test/scala/service/RemoverActorTest.scala +++ b/common/src/test/scala/service/RemoverActorTest.scala @@ -15,34 +15,34 @@ import services.{RemoverActor, ServiceManager} import scala.concurrent.duration._ -class StandardRemoverActorTest extends ActorTest { - ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") - - "RemoverActor" should { - "handle a simple task" in { - expectNoMsg(500 milliseconds) - val probe = TestProbe() - val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") - remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere) - - val reply1 = probe.receiveOne(200 milliseconds) - assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert]) - val reply2 = probe.receiveOne(200 milliseconds) - assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert]) - probe.expectNoMsg(1 seconds) //delay - val reply3 = probe.receiveOne(300 milliseconds) - assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert]) - val reply4 = probe.receiveOne(300 milliseconds) - assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) - val reply5 = probe.receiveOne(300 milliseconds) - assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert]) - val reply6 = probe.receiveOne(500 milliseconds) - assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) - val reply7 = probe.receiveOne(500 milliseconds) - assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) - } - } -} +//class StandardRemoverActorTest extends ActorTest { +// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") +// +// "RemoverActor" should { +// "handle a simple task" in { +// expectNoMsg(500 milliseconds) +// val probe = TestProbe() +// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover") +// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere) +// +// val reply1 = probe.receiveOne(200 milliseconds) +// assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert]) +// val reply2 = probe.receiveOne(200 milliseconds) +// assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert]) +// probe.expectNoMsg(1 seconds) //delay +// val reply3 = probe.receiveOne(300 milliseconds) +// assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert]) +// val reply4 = probe.receiveOne(300 milliseconds) +// assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert]) +// val reply5 = probe.receiveOne(300 milliseconds) +// assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert]) +// val reply6 = probe.receiveOne(500 milliseconds) +// assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert]) +// val reply7 = probe.receiveOne(500 milliseconds) +// assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert]) +// } +// } +//} //class DelayedRemoverActorTest extends ActorTest { // ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver") diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 18b55d833..39d10935a 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -13,8 +13,9 @@ import csr.{CSRWarp, CSRZone, Traveler} import net.psforever.objects.GlobalDefinitions._ import services.ServiceManager.Lookup import net.psforever.objects._ +import net.psforever.objects.ballistics._ import net.psforever.objects.definition.ToolDefinition -import net.psforever.objects.definition.converter.CorpseConverter +import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter} import net.psforever.objects.equipment._ import net.psforever.objects.loadouts._ import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} @@ -35,7 +36,9 @@ import net.psforever.objects.serverobject.structures.{Building, StructureType, W import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.serverobject.tube.SpawnTube -import net.psforever.objects.vehicles._ +import net.psforever.objects.serverobject.turret.{MannedTurret, TurretUpgrade} +import net.psforever.objects.vehicles.{AccessPermissionGroup, Cargo, Utility, VehicleLockState, _} +import net.psforever.objects.vital._ import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.packet.game.objectcreate._ import net.psforever.types._ @@ -53,8 +56,6 @@ import scala.concurrent.Future import scala.concurrent.duration._ import scala.util.Success import akka.pattern.ask -import net.psforever.objects.ballistics.{Projectile, ProjectileResolution} -import net.psforever.objects.serverobject.turret.{MannedTurret, TurretUpgrade} class WorldSessionActor extends Actor with MDCContextAware { import WorldSessionActor._ @@ -109,6 +110,7 @@ class WorldSessionActor extends Actor with MDCContextAware { implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0 override def postStop() = { + //TODO normally, player avatar persists a minute or so after disconnect; we are subject to the SessionReaper clientKeepAlive.cancel reviveTimer.cancel respawnTimer.cancel @@ -133,20 +135,23 @@ class WorldSessionActor extends Actor with MDCContextAware { } }) + //TODO final character save before doing any of this + continent.Population ! Zone.Population.Release(avatar) if(player.isAlive) { //actually being alive or manually deconstructing - DismountVehicleOnLogOut() - continent.Population ! Zone.Population.Release(avatar) - player.Position = Vector3.Zero //save character before doing this - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectDelete(player_guid, player_guid)) + player.Position = Vector3.Zero + if(player.VehicleSeated.nonEmpty) { + //quickly and briefly kill player to avoid disembark animation + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0)) + DismountVehicleOnLogOut() + } + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid)) taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID) - //TODO normally, the actual player avatar persists a minute or so after the user disconnects } else if(continent.LivePlayers.contains(player) && !continent.Corpses.contains(player)) { //player disconnected while waiting for a revive //similar to handling ReleaseAvatarRequestMessage player.Release - continent.Population ! Zone.Population.Release(avatar) player.VehicleSeated match { case None => FriskCorpse(player) //TODO eliminate dead letters @@ -189,7 +194,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(vehicle : Vehicle) => vehicle.Seat(vehicle.PassengerInSeat(player).get).get.Occupant = None if(vehicle.Seats.values.count(_.isOccupied) == 0) { - vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent), vehicle.Definition.DeconstructionTime) //start vehicle decay + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, vehicle.Definition.DeconstructionTime)) //start vehicle decay } vehicleService ! Service.Leave(Some(s"${vehicle.Actor}")) @@ -392,7 +397,30 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(GenericObjectActionMessage(guid, 36)) } - case msg @ AvatarResponse.DropItem(pkt) => + case AvatarResponse.DamageResolution(target, resolution_function) => + if(player.isAlive) { + resolution_function(target) + + val health = player.Health + val armor = player.Armor + val playerGUID = player.GUID + sendResponse(PlanetsideAttributeMessage(playerGUID, 0, health)) + sendResponse(PlanetsideAttributeMessage(playerGUID, 4, armor)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 0, health)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 4, armor)) + if(health == 0 && player.isAlive) { + KillPlayer(player) + } + } + + case AvatarResponse.Destroy(victim, killer, weapon, pos) => + // guid = victim // killer = killer ;) + sendResponse(DestroyMessage(victim, killer, weapon, pos)) + + case AvatarResponse.DestroyDisplay(killer, victim, method, unk) => + sendResponse(DestroyDisplayMessage(killer, victim, method, unk)) + + case AvatarResponse.DropItem(pkt) => if(tplayer_guid != guid) { sendResponse(pkt) } @@ -402,6 +430,25 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(pkt) } + case AvatarResponse.HitHint(source_guid) => + sendResponse(HitHint(source_guid, guid)) + + case AvatarResponse.KilledWhileInVehicle() => + if(player.isAlive && player.VehicleSeated.nonEmpty) { + continent.GUID(player.VehicleSeated.get) match { + case Some(vehicle : Vehicle) => + if(vehicle.Health == 0) { + vehicle.LastShot match { + case Some(cause) => + player.History(cause) + case None => ; + } + KillPlayer(player) + } + case _ => ; + } + } + case AvatarResponse.LoadPlayer(pkt) => if(tplayer_guid != guid) { sendResponse(pkt) @@ -544,7 +591,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) } reply match { case VehicleResponse.Ownership(vehicle_guid) => - sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid.toLong)) + sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid)) case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) => sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3)) @@ -577,6 +624,9 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(pkt) } + case VehicleResponse.HitHint(source_guid) => + sendResponse(HitHint(source_guid, player.GUID)) + case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) => if(tplayer_guid != guid) { //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? @@ -621,6 +671,11 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ObjectAttachMessage(vehicle_guid, guid, seat)) } + case VehicleResponse.PlanetsideAttribute(vehicle_guid, attribute_type, attribute_value) => + if(tplayer_guid != guid) { + sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type, attribute_value)) + } + case VehicleResponse.ResetSpawnPad(pad_guid) => sendResponse(GenericObjectActionMessage(pad_guid, 92)) @@ -787,6 +842,13 @@ class WorldSessionActor extends Actor with MDCContextAware { case Mountable.CanMount(obj : Vehicle, seat_num) => val obj_guid : PlanetSideGUID = obj.GUID + val player_guid : PlanetSideGUID = tplayer.GUID + log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num") + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) //clear timer + PlayerActionsToCancel() + sendResponse(PlanetsideAttributeMessage(obj_guid, 0, obj.Health)) + sendResponse(PlanetsideAttributeMessage(obj_guid, 68, 0)) //shield health + sendResponse(PlanetsideAttributeMessage(obj_guid, 113, 0)) //capacitor if(seat_num == 0) { //simplistic vehicle ownership management obj.Owner match { case Some(owner_guid) => @@ -807,7 +869,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //update mounted weapon belonging to seat weapon.AmmoSlots.foreach(slot => { //update the magazine(s) in the weapon, specifically val magazine = slot.Box - sendResponse(InventoryStateMessage(magazine.GUID, weapon.GUID, magazine.Capacity.toLong)) + sendResponse(InventoryStateMessage(magazine.GUID, weapon.GUID, magazine.Capacity)) }) case _ => ; //no weapons to update } @@ -859,6 +921,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Terminal.TerminalMessage(tplayer, msg, order) => order match { case Terminal.BuyExosuit(exosuit, subtype) => //refresh armor points + tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit)) if(tplayer.ExoSuit == exosuit) { if(exosuit == ExoSuitType.MAX) { //special MAX case - clear any special state @@ -1005,6 +1068,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO optimizations against replacing Equipment with the exact same Equipment and potentially for recycling existing Equipment log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}") sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true)) + tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit)) //ensure arm is down tplayer.DrawnSlot = Player.HandsDownSlot sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true)) @@ -1105,7 +1169,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } else { //accommodate as much of inventory as possible - //TODO map x,y -> x,y rather than reorganize items val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory) //dropped items can be forgotten stow } @@ -1121,7 +1184,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(!tplayer.Certifications.contains(cert)) { log.info(s"$tplayer is learning the $cert certification for $cost points") avatar.Certifications += cert - sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 24, cert.id.toLong)) + sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 24, cert.id)) sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true)) } else { @@ -1133,7 +1196,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(tplayer.Certifications.contains(cert)) { log.info(s"$tplayer is forgetting the $cert certification for $cost points") avatar.Certifications -= cert - sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 25, cert.id.toLong)) + sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 25, cert.id)) sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true)) } else { @@ -1289,9 +1352,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) => val vehicle_guid = vehicle.GUID sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on - //sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, 10))//vehicle.Definition.MaxHealth)) - sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) // Shield health - sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) // Capacitor (EMP) ReloadVehicleAccessPermissions(vehicle) ServerVehicleLock(vehicle) @@ -1551,6 +1611,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil)) sendResponse(FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true, Nil)) + avatarService ! Service.Join(avatar.name) //will be same as player.Name cluster ! InterstellarCluster.GetWorld("z6") case InterstellarCluster.GiveWorld(zoneId, zone) => @@ -1750,6 +1811,50 @@ class WorldSessionActor extends Actor with MDCContextAware { case DelayedProximityUnitStop(terminal) => StopUsingProximityUnit(terminal) + case Vitality.DamageResolution(target : Vehicle) => + val targetGUID = target.GUID + val playerGUID = player.GUID + val continentId = continent.Id + val players = target.Seats.values.filter(seat => { seat.isOccupied && seat.Occupant.get.isAlive }) + if(target.Health > 0) { + //alert occupants to damage source + players.foreach(seat => { + val tplayer = seat.Occupant.get + avatarService ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(playerGUID, tplayer.GUID)) + }) + } + else { + //alert to vehicle death (hence, occupants' deaths) + players.foreach(seat => { + val tplayer = seat.Occupant.get + val tplayerGUID = tplayer.GUID + avatarService ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID)) + avatarService ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self + }) + //vehicle wreckage has no weapons + target.Weapons.values + .filter { _.Equipment.nonEmpty } + .foreach(slot => { + val wep = slot.Equipment.get + avatarService ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID)) + }) + if(target.Definition == GlobalDefinitions.ams) { + target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying) + ClearCurrentAmsSpawnPoint() + } + avatarService ! AvatarServiceMessage(continentId, AvatarAction.Destroy(targetGUID, playerGUID, playerGUID, target.Position)) + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), continent)) + vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, continent, Some(1 minute))) + } + vehicleService ! VehicleServiceMessage(continentId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health)) + vehicleService ! VehicleServiceMessage(s"${target.Actor}", VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, target.Shields)) + + case Vitality.DamageResolution(target : PlanetSideGameObject) => + log.warn(s"Vital target ${target.Definition.Name} damage resolution not supported using this method") + + case Vehicle.UpdateShieldsCharge(vehicle) => + vehicleService ! VehicleServiceMessage(s"${vehicle.Actor}", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, 68, vehicle.Shields)) + case ResponseToSelf(pkt) => log.info(s"Received a direct message: $pkt") sendResponse(pkt) @@ -1780,7 +1885,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO begin temp player character auto-loading; remove later import net.psforever.objects.GlobalDefinitions._ import net.psforever.types.CertificationType._ - avatar = Avatar("TestCharacter" + sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1) + val avatar = Avatar("TestCharacter" + sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1) avatar.Certifications += StandardAssault avatar.Certifications += MediumAssault avatar.Certifications += StandardExoSuit @@ -1814,6 +1919,7 @@ class WorldSessionActor extends Actor with MDCContextAware { avatar.Certifications += AdvancedEngineering avatar.Certifications += FortificationEngineering avatar.Certifications += AssaultEngineering + this.avatar = avatar AwardBattleExperiencePoints(avatar, 1000000L) player = new Player(avatar) //player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C @@ -1992,8 +2098,10 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.Corpses.foreach { TurnPlayerIntoCorpse } - //load active vehicles in zone - continent.Vehicles.foreach(vehicle => { + //load vehicles in zone + val (wreckages, vehicles) = continent.Vehicles.partition(vehicle => { vehicle.Health == 0 && vehicle.Definition.DestroyedModel.nonEmpty }) + //active vehicles + vehicles.foreach(vehicle => { val vehicle_guid = vehicle.GUID val vdefinition = vehicle.Definition sendResponse(ObjectCreateMessage(vdefinition.ObjectId, vehicle_guid, vdefinition.Packet.ConstructorData(vehicle).get)) @@ -2014,9 +2122,8 @@ class WorldSessionActor extends Actor with MDCContextAware { }) ReloadVehicleAccessPermissions(vehicle) }) - - // Loop over vehicles again to add cargohold occupants after all vehicles have been created on the local client - continent.Vehicles.foreach(vehicle => { + //Loop over vehicles again to add cargohold occupants after all vehicles have been created on the local client + vehicles.foreach(vehicle => { vehicle.CargoHolds.foreach({ case (cargo_num, cargo) => { cargo.Occupant match { case Some(cargo_vehicle) => @@ -2031,6 +2138,16 @@ class WorldSessionActor extends Actor with MDCContextAware { } }}) }) + //vehicle wreckages + wreckages.foreach(vehicle => { + sendResponse( + ObjectCreateMessage( + vehicle.Definition.DestroyedModel.get.id, + vehicle.GUID, + DestroyedVehicleConverter.converter.ConstructorData(vehicle).get + ) + ) + }) //implant terminals continent.Map.TerminalToInterface.foreach({ case ((terminal_guid, interface_guid)) => @@ -2198,8 +2315,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } case Some(_) => - //TODO we do not want to delete the player if he is seated in a vehicle when releasing - //TODO it is necessary for now until we know how to juggle ownership properly val player_guid = player.GUID sendResponse(ObjectDeleteMessage(player_guid, 0)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 0)) @@ -2207,8 +2322,6 @@ class WorldSessionActor extends Actor with MDCContextAware { import scala.concurrent.ExecutionContext.Implicits.global context.system.scheduler.scheduleOnce(50 milliseconds, self, UnregisterCorpseOnVehicleDisembark(player)) - //sendResponse(ObjectDetachMessage(vehicle_guid, player.GUID, Vector3.Zero, 0)) - //sendResponse(PlayerStateShiftMessage(ShiftState(1, Vector3.Zero, 0))) } case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) => @@ -2289,7 +2402,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } if(messagetype == ChatMessageType.CMT_SUICIDE) { if(player.isAlive && deadState != DeadState.Release) { - KillPlayer(player) + Suicide(player) } } if(messagetype == ChatMessageType.CMT_DESTROY) { @@ -2726,10 +2839,12 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(_ : LocalProjectile) => FindProjectileEntry(object_guid) match { case Some(projectile) => - if(projectile.resolution != ProjectileResolution.Unresolved) { - log.warn(s"RequestDestroy: tried to clean up missed projectile ${object_guid.guid} but it was already resolved") + if(projectile.isResolved) { + log.warn(s"RequestDestroy: tried to clean up projectile ${object_guid.guid} but it was already resolved") + } + else { + projectile.Miss() } - ResolveProjectileEntry(object_guid, ProjectileResolution.MissedShot) case None => log.warn(s"RequestDestroy: projectile ${object_guid.guid} has never been fired") } @@ -2901,7 +3016,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) sendResponse(ObjectDeleteMessage(kit.GUID, 0)) taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID) - //TODO better health/damage control workflow + player.History(HealFromKit(PlayerSource(player), 25, kit.Definition)) player.Health = player.Health + 25 sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 0, player.Health)) @@ -3224,16 +3339,23 @@ class WorldSessionActor extends Actor with MDCContextAware { else { //shooting tool.Discharge val projectileIndex = projectile_guid.guid - Projectile.BaseUID - val ang = obj match { - case _ : Player => - obj.Orientation //TODO upper body facing + val projectilePlace = projectiles(projectileIndex) + if(projectilePlace match { + case Some(projectile) => !projectile.isResolved + case None => false + }) { + log.warn(s"WeaponFireMessage: former projectile ${projectile_guid.guid} was not resolved properly; overwriting anyway") + } + val (angle, attribution) = obj match { + case p : Player => + (p.Orientation, tool.Definition.ObjectId) //TODO upper body facing case _ : Vehicle => - tool.Orientation //TODO this is too simplistic + (tool.Orientation, obj.Definition.ObjectId) //TODO this is too simplistic to find proper angle case _ => - Vector3.Zero + (obj.Orientation, obj.Definition.ObjectId) } projectiles(projectileIndex) = - Some(Projectile(tool.Projectile, tool.Definition, shot_origin, ang)) + Some(Projectile(tool.Projectile, tool.Definition, tool.FireMode, player, attribution, shot_origin, angle)) } case _ => ; } @@ -3243,15 +3365,62 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ HitMessage(seq_time, projectile_guid, unk1, hit_info, unk2, unk3, unk4) => log.info(s"Hit: $msg") - ResolveProjectileEntry(projectile_guid, ProjectileResolution.Hit) + (hit_info match { + case Some(hitInfo) => + continent.GUID(hitInfo.hitobject_guid.get) match { + case Some(obj : Player) => + Some((obj, hitInfo.shot_origin, hitInfo.hit_pos)) + case Some(obj : Vehicle) => + Some((obj, hitInfo.shot_origin, hitInfo.hit_pos)) + case _ => + None + } + case None => ; + None + }) match { + case Some((target, shotOrigin, hitPos)) => + ResolveProjectileEntry(projectile_guid, ProjectileResolution.Hit, target, hitPos) match { + case Some(projectile) => + HandleDealingDamage(target, projectile) + case None => ; + } + case None => ; + } case msg @ SplashHitMessage(seq_time, projectile_guid, explosion_pos, direct_victim_uid, unk3, projectile_vel, unk4, targets) => log.info(s"Splash: $msg") - ResolveProjectileEntry(projectile_guid, ProjectileResolution.Splash) + continent.GUID(direct_victim_uid) match { + case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => + ResolveProjectileEntry(projectile_guid, ProjectileResolution.Hit, target, explosion_pos) match { + case Some(projectile) => + HandleDealingDamage(target, projectile) + case None => ; + } + case _ => ; + } + targets.foreach(elem => { + continent.GUID(elem.uid) match { + case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => + ResolveProjectileEntry(projectile_guid, ProjectileResolution.Splash, target, target.Position) match { + case Some(projectile) => + HandleDealingDamage(target, projectile) + case None => ; + } + case _ => ; + } + }) case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, pos, unk1) => log.info(s"Lash: $msg") - ResolveProjectileEntry(projectile_guid, ProjectileResolution.Lash) + continent.GUID(victim_guid) match { + case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => + ResolveProjectileEntry(projectile_guid, ProjectileResolution.Lash, target, pos) match { + case Some(projectile) => + HandleDealingDamage(target, projectile) + case None => ; + } + case _ => ; + } case msg @ AvatarFirstTimeEventMessage(avatar_guid, object_guid, unk1, event_name) => log.info("AvatarFirstTimeEvent: " + msg) @@ -3422,9 +3591,21 @@ class WorldSessionActor extends Actor with MDCContextAware { } case msg @ FacilityBenefitShieldChargeRequestMessage(guid) => - //log.info(s"ShieldChargeRequest: $msg") + player.VehicleSeated match { + case Some(vehicleGUID) => + continent.GUID(vehicleGUID) match { + case Some(obj : Vehicle) => + if(obj.Health > 0) { //vehicle will try to charge even if destroyed + obj.Actor ! Vehicle.ChargeShields(15) + } + case _ => + log.warn(s"FacilityBenefitShieldChargeRequest: can not find vehicle ${vehicleGUID.guid} in zone ${continent.Id}") + } + case None => + log.warn(s"FacilityBenefitShieldChargeRequest: player ${player.Name} is not seated in a vehicle") + } - case msg @ BattleplanMessage(char_id, player_name, zonr_id, diagrams) => + case msg @ BattleplanMessage(char_id, player_name, zone_id, diagrams) => log.info("Battleplan: "+msg) case msg @ CreateShortcutMessage(player_guid, slot, unk, add, shortcut) => @@ -3433,8 +3614,14 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ FriendsRequest(action, friend) => log.info("FriendsRequest: "+msg) - case msg @ HitHint(source, player_guid) => - log.info("HitHint: "+msg) + case msg @ HitHint(source_guid, player_guid) => + log.info(s"HitHint: $msg") + continent.GUID(player_guid) match { + case Some(obj : Player) => + avatarService ! AvatarServiceMessage(obj.Name, AvatarAction.HitHint(source_guid, player_guid)) + case _ => ; + } + case msg @ TargetingImplantRequest(list) => log.info("TargetingImplantRequest: "+msg) @@ -3977,7 +4164,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val vehicle_guid = vehicle.GUID (0 to 3).foreach(group => { sendResponse( - PlanetsideAttributeMessage(vehicle_guid, group + 10, vehicle.PermissionGroup(group).get.id.toLong) + PlanetsideAttributeMessage(vehicle_guid, group + 10, vehicle.PermissionGroup(group).get.id) ) }) } @@ -4869,6 +5056,17 @@ class WorldSessionActor extends Actor with MDCContextAware { }) } + /** + * The player has lost the will to live and must be killed. + * @see `Vitality`
+ * `PlayerSuicide` + * @param tplayer the player to be killed + */ + def Suicide(tplayer : Player) : Unit = { + tplayer.History(PlayerSuicide(PlayerSource(tplayer))) + KillPlayer(tplayer) + } + /** * The player has lost all his vitality and must be killed.
*
@@ -4898,6 +5096,7 @@ class WorldSessionActor extends Actor with MDCContextAware { continent.GUID(tplayer.VehicleSeated.get) match { case Some(obj : Vehicle) => TotalDriverVehicleControl(obj) + UnAccessContents(obj) case _ => ; } //make player invisible (if not, the cadaver sticks out the side in a seated position) @@ -4906,6 +5105,29 @@ class WorldSessionActor extends Actor with MDCContextAware { } PlayerActionsToCancel() CancelAllProximityUnits() + //TODO other methods of death? + val pentry = PlayerSource(tplayer) + (tplayer.History.find({p => p.isInstanceOf[PlayerSuicide]}) match { + case Some(PlayerSuicide(_)) => + None + case _ => + tplayer.LastShot match { + case Some(shot) => + if(System.nanoTime - shot.hit_time < (10 seconds).toNanos) { + Some(shot) + } + else { + None //suicide + } + case None => + None //suicide + } + }) match { + case Some(shot) => + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.DestroyDisplay(shot.projectile.owner, pentry, shot.projectile.attribute_to)) + case None => + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.DestroyDisplay(pentry, pentry, 0)) + } import scala.concurrent.ExecutionContext.Implicits.global reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer milliseconds, cluster, Zone.Lattice.RequestSpawnPoint(Zones.SanctuaryZoneNumber(tplayer.Faction), tplayer, 7)) @@ -5192,12 +5414,14 @@ class WorldSessionActor extends Actor with MDCContextAware { */ def ProximityMedicalTerminal(unit : Terminal with ProximityUnit) : Unit = { val healthFull : Boolean = if(player.Health < player.MaxHealth) { + player.History(HealFromTerm(PlayerSource(player), 10, 0, unit.Definition)) HealAction(player) } else { true } val armorFull : Boolean = if(player.Armor < player.MaxArmor) { + player.History(HealFromTerm(PlayerSource(player), 0, 10, unit.Definition)) ArmorRepairAction(player) } else { @@ -5216,6 +5440,7 @@ class WorldSessionActor extends Actor with MDCContextAware { */ def ProximityHealCrystal(unit : Terminal with ProximityUnit) : Unit = { val healthFull : Boolean = if(player.Health < player.MaxHealth) { + player.History(HealFromTerm(PlayerSource(player), 10, 0, unit.Definition)) HealAction(player) } else { @@ -5364,11 +5589,11 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param resolution the resolution status to promote the projectile * @return the projectile */ - def ResolveProjectileEntry(projectile_guid : PlanetSideGUID, resolution : ProjectileResolution.Value) : Option[Projectile] = { + def ResolveProjectileEntry(projectile_guid : PlanetSideGUID, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = { FindProjectileEntry(projectile_guid) match { case Some(projectile) => val index = projectile_guid.guid - Projectile.BaseUID - ResolveProjectileEntry(projectile, index, resolution) + ResolveProjectileEntry(projectile, index, resolution, target, pos) case None => log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found") None @@ -5385,13 +5610,18 @@ class WorldSessionActor extends Actor with MDCContextAware { * @param resolution the resolution status to promote the projectile * @return a copy of the projectile */ - def ResolveProjectileEntry(projectile : Projectile, index : Int, resolution : ProjectileResolution.Value) : Option[Projectile] = { - if(projectiles(index).contains(projectile)) { - projectiles(index) = Some(projectile.Resolve(ProjectileResolution.Resolved)) - Some(projectile.Resolve(resolution)) + def ResolveProjectileEntry(projectile : Projectile, index : Int, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = { + if(!projectiles(index).contains(projectile)) { + log.error(s"expected projectile could not be found at $index; can not resolve") + None + } + else if(projectile.isMiss) { + log.error(s"expected projectile at $index was already counted as a missed shot; can not resolve any further") + None } else { - None + projectile.Resolve() + Some(ResolvedProjectile(resolution, projectile, SourceEntry(target), target.DamageModel, pos)) } } @@ -5423,6 +5653,65 @@ class WorldSessionActor extends Actor with MDCContextAware { vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, BailType.Normal, false)) } + /** + * 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. + * @see `DamageResistanceModel`
+ * `Vitality` + * @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) + target match { + case obj : Player => + //damage is synchronized on the target player's `WSA` (results distributed from there) + avatarService ! AvatarServiceMessage(obj.Name, AvatarAction.Damage(player.GUID, obj, func)) + case obj : Vehicle => + //damage is synchronized on the vehicle actor (results returned to and distributed from this `WSA`) + obj.Actor ! Vitality.Damage(func) + case _ => ; + } + } + + /** + * Properly format a `DestroyDisplayMessage` packet + * given sufficient information about a target (victim) and an actor (killer). + * For the packet, the `*_charId` field is most important to determining distinction between players. + * The "char id" is not a currently supported field for different players so a name hash is used instead. + * The virtually negligent chance of a name hash collision is covered. + * @param killer the killer's entry + * @param victim the victim's entry + * @param method the manner of death + * @param unk na; + * defaults to 121, the object id of `avatar` + * @return a `DestroyDisplayMessage` packet that is properly formatted + */ + def DestroyDisplayMessage(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) : DestroyDisplayMessage = { + //TODO charId should reflect the player more properly + val killerCharId = math.abs(killer.Name.hashCode) + var victimCharId = math.abs(victim.Name.hashCode) + if(killerCharId == victimCharId && killer.Name != victim.Name) { + //odds of hash collision in a populated zone should be close to odds of being struck by lightning + victimCharId = Int.MaxValue - victimCharId + 1 + } + val killer_seated = killer match { + case obj : PlayerSource => obj.Seated + case _ => false + } + val victim_seated = victim match { + case obj : PlayerSource => obj.Seated + case _ => false + } + new DestroyDisplayMessage( + killer.Name, killerCharId, killer.Faction, killer_seated, + unk, method, + victim.Name, victimCharId, victim.Faction, victim_seated + ) + } + def failWithError(error : String) = { log.error(error) sendResponse(ConnectionClose())