mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-07-13 15:34:42 +00:00
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
This commit is contained in:
parent
fc78d53ecb
commit
7901f66324
52 changed files with 3045 additions and 267 deletions
19
.codecov.yml
19
.codecov.yml
|
|
@ -2,19 +2,35 @@
|
||||||
comment: off
|
comment: off
|
||||||
|
|
||||||
ignore:
|
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/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/Ammo.scala"
|
||||||
- "common/src/main/scala/net/psforever/objects/equipment/CItem.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/EquipmentSize.scala"
|
||||||
- "common/src/main/scala/net/psforever/objects/equipment/Kits.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/equipment/SItem.scala"
|
||||||
- "common/src/main/scala/net/psforever/objects/guid/AvailabilityPolicy.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/turret/TurretUpgrade.scala"
|
||||||
- "common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.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/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/SeatArmoRestriction.scala"
|
||||||
- "common/src/main/scala/net/psforever/objects/vehicles/Turrets.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/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/crypto"
|
||||||
- "common/src/main/scala/net/psforever/packet/game/objectcreate/DrawnSlot.scala"
|
- "common/src/main/scala/net/psforever/packet/game/objectcreate/DrawnSlot.scala"
|
||||||
- "common/src/main/scala/net/psforever/packet/game/objectcreate/DriveState.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/ControlPacketOpcode.scala"
|
||||||
- "common/src/main/scala/net/psforever/packet/CryptoPacketOpcode.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/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/Angular.scala"
|
||||||
- "common/src/main/scala/net/psforever/types/CertificationType.scala"
|
- "common/src/main/scala/net/psforever/types/CertificationType.scala"
|
||||||
- "common/src/main/scala/net/psforever/types/ChatMessageType.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/net/psforever/types/TransactionType.scala"
|
||||||
- "common/src/main/scala/services/avatar/AvatarAction.scala"
|
- "common/src/main/scala/services/avatar/AvatarAction.scala"
|
||||||
- "common/src/main/scala/services/avatar/AvatarResponse.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/LocalAction.scala"
|
||||||
- "common/src/main/scala/services/local/LocalResponse.scala"
|
- "common/src/main/scala/services/local/LocalResponse.scala"
|
||||||
- "common/src/main/scala/services/vehicle/VehicleAction.scala"
|
- "common/src/main/scala/services/vehicle/VehicleAction.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
|
Merit Commendations and Ribbons
|
||||||
|
|
@ -210,8 +210,6 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
|
||||||
}
|
}
|
||||||
|
|
||||||
object Avatar {
|
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 = {
|
def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : CharacterVoice.Value) : Avatar = {
|
||||||
new Avatar(name, faction, sex, head, voice)
|
new Avatar(name, faction, sex, head, voice)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ package net.psforever.objects
|
||||||
|
|
||||||
import net.psforever.objects.equipment.EquipmentSize
|
import net.psforever.objects.equipment.EquipmentSize
|
||||||
import net.psforever.objects.inventory.InventoryTile
|
import net.psforever.objects.inventory.InventoryTile
|
||||||
|
import net.psforever.objects.vital._
|
||||||
|
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
||||||
import net.psforever.types.ExoSuitType
|
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.
|
* 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
|
* @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 permission : Int = 0 //TODO certification type?
|
||||||
protected var maxArmor : Int = 0
|
protected var maxArmor : Int = 0
|
||||||
protected val holsters : Array[EquipmentSize.Value] = Array.fill[EquipmentSize.Value](5)(EquipmentSize.Blocked)
|
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 inventoryScale : InventoryTile = InventoryTile.Tile11 //override with custom InventoryTile
|
||||||
protected var inventoryOffset : Int = 0
|
protected var inventoryOffset : Int = 0
|
||||||
|
Damage = StandardInfantryDamage
|
||||||
|
Resistance = StandardInfantryResistance
|
||||||
|
Model = StandardResolutions.Infantry
|
||||||
|
|
||||||
def SuitType : ExoSuitType.Value = suitType
|
def SuitType : ExoSuitType.Value = suitType
|
||||||
|
|
||||||
|
|
@ -79,6 +85,12 @@ class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends
|
||||||
obj.MaxArmor = MaxArmor
|
obj.MaxArmor = MaxArmor
|
||||||
obj.InventoryScale = InventoryScale
|
obj.InventoryScale = InventoryScale
|
||||||
obj.InventoryOffset = InventoryOffset
|
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)) })
|
(0 until 5).foreach(index => { obj.Holster(index, Holster(index)) })
|
||||||
obj
|
obj
|
||||||
}
|
}
|
||||||
|
|
@ -109,6 +121,9 @@ object ExoSuitDefinition {
|
||||||
Standard.Holster(0, EquipmentSize.Pistol)
|
Standard.Holster(0, EquipmentSize.Pistol)
|
||||||
Standard.Holster(2, EquipmentSize.Rifle)
|
Standard.Holster(2, EquipmentSize.Rifle)
|
||||||
Standard.Holster(4, EquipmentSize.Melee)
|
Standard.Holster(4, EquipmentSize.Melee)
|
||||||
|
Standard.ResistanceDirectHit = 4
|
||||||
|
Standard.ResistanceSplash = 15
|
||||||
|
Standard.ResistanceAggravated = 8
|
||||||
|
|
||||||
final val Agile = ExoSuitDefinition(ExoSuitType.Agile)
|
final val Agile = ExoSuitDefinition(ExoSuitType.Agile)
|
||||||
Agile.MaxArmor = 100
|
Agile.MaxArmor = 100
|
||||||
|
|
@ -118,6 +133,9 @@ object ExoSuitDefinition {
|
||||||
Agile.Holster(1, EquipmentSize.Pistol)
|
Agile.Holster(1, EquipmentSize.Pistol)
|
||||||
Agile.Holster(2, EquipmentSize.Rifle)
|
Agile.Holster(2, EquipmentSize.Rifle)
|
||||||
Agile.Holster(4, EquipmentSize.Melee)
|
Agile.Holster(4, EquipmentSize.Melee)
|
||||||
|
Agile.ResistanceDirectHit = 6
|
||||||
|
Agile.ResistanceSplash = 25
|
||||||
|
Agile.ResistanceAggravated = 10
|
||||||
|
|
||||||
final val Reinforced = ExoSuitDefinition(ExoSuitType.Reinforced)
|
final val Reinforced = ExoSuitDefinition(ExoSuitType.Reinforced)
|
||||||
Reinforced.permission = 1
|
Reinforced.permission = 1
|
||||||
|
|
@ -129,6 +147,9 @@ object ExoSuitDefinition {
|
||||||
Reinforced.Holster(2, EquipmentSize.Rifle)
|
Reinforced.Holster(2, EquipmentSize.Rifle)
|
||||||
Reinforced.Holster(3, EquipmentSize.Rifle)
|
Reinforced.Holster(3, EquipmentSize.Rifle)
|
||||||
Reinforced.Holster(4, EquipmentSize.Melee)
|
Reinforced.Holster(4, EquipmentSize.Melee)
|
||||||
|
Reinforced.ResistanceDirectHit = 10
|
||||||
|
Reinforced.ResistanceSplash = 35
|
||||||
|
Reinforced.ResistanceAggravated = 12
|
||||||
|
|
||||||
final val Infiltration = ExoSuitDefinition(ExoSuitType.Infiltration)
|
final val Infiltration = ExoSuitDefinition(ExoSuitType.Infiltration)
|
||||||
Infiltration.permission = 1
|
Infiltration.permission = 1
|
||||||
|
|
@ -145,6 +166,11 @@ object ExoSuitDefinition {
|
||||||
MAX.InventoryOffset = 6
|
MAX.InventoryOffset = 6
|
||||||
MAX.Holster(0, EquipmentSize.Max)
|
MAX.Holster(0, EquipmentSize.Max)
|
||||||
MAX.Holster(4, EquipmentSize.Melee)
|
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 = {
|
def apply(suitType : ExoSuitType.Value) : ExoSuitDefinition = {
|
||||||
new ExoSuitDefinition(suitType)
|
new ExoSuitDefinition(suitType)
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -6,13 +6,19 @@ import net.psforever.objects.equipment.{Equipment, EquipmentSize}
|
||||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
|
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
|
||||||
import net.psforever.objects.loadouts.Loadout
|
import net.psforever.objects.loadouts.Loadout
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
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.packet.game.PlanetSideGUID
|
||||||
import net.psforever.types._
|
import net.psforever.types._
|
||||||
|
|
||||||
import scala.annotation.tailrec
|
import scala.annotation.tailrec
|
||||||
import scala.util.{Success, Try}
|
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 alive : Boolean = false
|
||||||
private var backpack : Boolean = false
|
private var backpack : Boolean = false
|
||||||
private var health : Int = 0
|
private var health : Int = 0
|
||||||
|
|
@ -42,7 +48,6 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio
|
||||||
//SouNourS things
|
//SouNourS things
|
||||||
/** Last medkituse. */
|
/** Last medkituse. */
|
||||||
var lastMedkit : Long = 0
|
var lastMedkit : Long = 0
|
||||||
var death_by : Int = 0
|
|
||||||
var lastSeenStreamMessage : Array[Long] = Array.fill[Long](65535)(0L)
|
var lastSeenStreamMessage : Array[Long] = Array.fill[Long](65535)(0L)
|
||||||
var lastShotSeq_time : Int = -1
|
var lastShotSeq_time : Int = -1
|
||||||
/** From PlanetsideAttributeMessage */
|
/** From PlanetsideAttributeMessage */
|
||||||
|
|
@ -276,6 +281,14 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio
|
||||||
ChangeSpecialAbility()
|
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 LoadLoadout(line : Int) : Option[Loadout] = core.LoadLoadout(line)
|
||||||
|
|
||||||
def BEP : Long = core.BEP
|
def BEP : Long = core.BEP
|
||||||
|
|
@ -475,6 +488,8 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio
|
||||||
Continent
|
Continent
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def DamageModel = exosuit.asInstanceOf[DamageResistanceModel]
|
||||||
|
|
||||||
def Definition : AvatarDefinition = core.Definition
|
def Definition : AvatarDefinition = core.Definition
|
||||||
|
|
||||||
def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
|
def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.objects.serverobject.deploy.Deployment
|
import net.psforever.objects.serverobject.deploy.Deployment
|
||||||
import net.psforever.objects.vehicles._
|
import net.psforever.objects.vehicles._
|
||||||
|
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
|
||||||
import net.psforever.packet.game.PlanetSideGUID
|
import net.psforever.packet.game.PlanetSideGUID
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
|
|
||||||
|
|
@ -67,6 +68,8 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
|
||||||
with Mountable
|
with Mountable
|
||||||
with MountedWeapons
|
with MountedWeapons
|
||||||
with Deployment
|
with Deployment
|
||||||
|
with Vitality
|
||||||
|
with StandardResistanceProfile
|
||||||
with Container {
|
with Container {
|
||||||
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR
|
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR
|
||||||
private var owner : Option[PlanetSideGUID] = None
|
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] = {
|
def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = {
|
||||||
owner match {
|
owner match {
|
||||||
case Some(_) =>
|
case Some(_) =>
|
||||||
if(Definition.CanBeOwned) { //e.g., base turrets
|
if(Definition.CanBeOwned) {
|
||||||
this.owner = owner
|
this.owner = owner
|
||||||
}
|
}
|
||||||
case None =>
|
case None =>
|
||||||
|
|
@ -152,11 +155,11 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
|
||||||
}
|
}
|
||||||
|
|
||||||
def Health : Int = {
|
def Health : Int = {
|
||||||
this.health
|
health
|
||||||
}
|
}
|
||||||
|
|
||||||
def Health_=(health : Int) : Int = {
|
def Health_=(assignHealth : Int) : Int = {
|
||||||
this.health = health
|
health = math.min(math.max(0, assignHealth), MaxHealth)
|
||||||
health
|
health
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -165,11 +168,11 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
|
||||||
}
|
}
|
||||||
|
|
||||||
def Shields : Int = {
|
def Shields : Int = {
|
||||||
this.shields
|
shields
|
||||||
}
|
}
|
||||||
|
|
||||||
def Shields_=(strength : Int) : Int = {
|
def Shields_=(strength : Int) : Int = {
|
||||||
this.shields = strength
|
shields = math.min(math.max(0, strength), MaxShields)
|
||||||
Shields
|
Shields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -178,12 +181,12 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
|
||||||
}
|
}
|
||||||
|
|
||||||
def Decal : Int = {
|
def Decal : Int = {
|
||||||
this.decal
|
decal
|
||||||
}
|
}
|
||||||
|
|
||||||
def Decal_=(decal : Int) : Int = {
|
def Decal_=(logo : Int) : Int = {
|
||||||
this.decal = decal
|
decal = logo
|
||||||
decal
|
Decal
|
||||||
}
|
}
|
||||||
|
|
||||||
def Jammered : Boolean = jammered
|
def Jammered : Boolean = jammered
|
||||||
|
|
@ -493,6 +496,8 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
|
||||||
*/
|
*/
|
||||||
def TrunkLockState : VehicleLockState.Value = groupPermissions(3)
|
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`.
|
* This is the definition entry that is used to store and unload pertinent information about the `Vehicle`.
|
||||||
* @return the vehicle's definition entry
|
* @return the vehicle's definition entry
|
||||||
|
|
@ -534,6 +539,20 @@ object Vehicle {
|
||||||
*/
|
*/
|
||||||
final case class Reactivate()
|
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.
|
* Overloaded constructor.
|
||||||
* @param vehicleDef the vehicle's definition entry
|
* @param vehicleDef the vehicle's definition entry
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,60 +1,59 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.ballistics
|
package net.psforever.objects.ballistics
|
||||||
|
|
||||||
|
import net.psforever.objects.PlanetSideGameObject
|
||||||
import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition}
|
import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition}
|
||||||
import net.psforever.objects.entity.SimpleWorldEntity
|
import net.psforever.objects.entity.SimpleWorldEntity
|
||||||
|
import net.psforever.objects.equipment.FireModeDefinition
|
||||||
|
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||||
import net.psforever.types.Vector3
|
import net.psforever.types.Vector3
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A summation of weapon (`Tool`) discharge.
|
* A summation of weapon (`Tool`) discharge.
|
||||||
|
* @see `ProjectileDefinition`<br>
|
||||||
|
* `ToolDefinition`<br>
|
||||||
|
* `FireModeDefinition`<br>
|
||||||
|
* `SourceEntry`<br>
|
||||||
|
* `PlayerSource`
|
||||||
* @param profile an explanation of the damage that can be performed by this discharge
|
* @param profile an explanation of the damage that can be performed by this discharge
|
||||||
* @param tool_def the weapon that caused 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_origin where the projectile started
|
||||||
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
* @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;
|
* @param fire_time when the weapon discharged was recorded;
|
||||||
* defaults to `System.nanoTime`
|
* defaults to `System.nanoTime`
|
||||||
* @param hit_time when the discharge had its resolution status updated
|
|
||||||
*/
|
*/
|
||||||
final case class Projectile(profile : ProjectileDefinition,
|
final case class Projectile(profile : ProjectileDefinition,
|
||||||
tool_def : ToolDefinition,
|
tool_def : ToolDefinition,
|
||||||
|
fire_mode : FireModeDefinition,
|
||||||
|
owner : SourceEntry,
|
||||||
|
attribute_to : Int,
|
||||||
shot_origin : Vector3,
|
shot_origin : Vector3,
|
||||||
shot_angle : Vector3,
|
shot_angle : Vector3,
|
||||||
resolution : ProjectileResolution.Value,
|
fire_time: Long = System.nanoTime) {
|
||||||
fire_time : Long = System.nanoTime,
|
|
||||||
hit_time : Long = 0) {
|
|
||||||
/** Information about the current world coordinates and orientation of the projectile */
|
/** Information about the current world coordinates and orientation of the projectile */
|
||||||
val current : SimpleWorldEntity = new SimpleWorldEntity()
|
val current : SimpleWorldEntity = new SimpleWorldEntity()
|
||||||
|
private var resolved : ProjectileResolution.Value = ProjectileResolution.Unresolved
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Give the projectile the suggested resolution status.
|
* Mark the projectile as being "encountered" or "managed" at least once.
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
def Resolve(pos : Vector3, ang : Vector3, resolution : ProjectileResolution.Value) : Projectile = {
|
def Resolve() : Unit = {
|
||||||
val obj = Resolve(resolution)
|
resolved = ProjectileResolution.Resolved
|
||||||
obj.current.Position = pos
|
|
||||||
obj.current.Orientation = ang
|
|
||||||
obj
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
def Miss() : Unit = {
|
||||||
* Give the projectile the suggested resolution status.
|
resolved = ProjectileResolution.MissedShot
|
||||||
* @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 isResolved : Boolean = resolved == ProjectileResolution.Resolved || resolved == ProjectileResolution.MissedShot
|
||||||
|
|
||||||
|
def isMiss : Boolean = resolved == ProjectileResolution.MissedShot
|
||||||
}
|
}
|
||||||
|
|
||||||
object Projectile {
|
object Projectile {
|
||||||
|
|
@ -68,11 +67,28 @@ object Projectile {
|
||||||
* Overloaded constructor for an `Unresolved` projectile.
|
* Overloaded constructor for an `Unresolved` projectile.
|
||||||
* @param profile an explanation of the damage that can be performed by this discharge
|
* @param profile an explanation of the damage that can be performed by this discharge
|
||||||
* @param tool_def the weapon that caused 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_origin where the projectile started
|
||||||
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
||||||
* @return the `Projectile` object
|
* @return the `Projectile` object
|
||||||
*/
|
*/
|
||||||
def apply(profile : ProjectileDefinition, tool_def : ToolDefinition, shot_origin : Vector3, shot_angle : Vector3) : Projectile = {
|
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, shot_origin, shot_angle, ProjectileResolution.Unresolved)
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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(" ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,17 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.definition
|
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.
|
* 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.
|
* `Tool` objects emit `ProjectileDefinition` objects and that is later wrapped into a `Projectile` object.
|
||||||
* @param objectId the object's identifier number
|
* @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 val projectileType : Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException
|
||||||
private var damage0 : Int = 0
|
private var damage0 : Int = 0
|
||||||
private var damage1 : Option[Int] = None
|
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 damageAtEdge : Float = 1f
|
||||||
private var damageRadius : Float = 1f
|
private var damageRadius : Float = 1f
|
||||||
private var useDamage1Subtract : Boolean = false
|
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"
|
Name = "projectile"
|
||||||
|
|
||||||
def ProjectileType : Projectiles.Value = projectileType
|
def ProjectileType : Projectiles.Value = projectileType
|
||||||
|
|
@ -157,10 +165,43 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) wi
|
||||||
this.damageRadius = damageRadius
|
this.damageRadius = 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 {
|
object ProjectileDefinition {
|
||||||
def apply(projectileType : Projectiles.Value) : ProjectileDefinition = {
|
def apply(projectileType : Projectiles.Value) : ProjectileDefinition = {
|
||||||
new ProjectileDefinition(projectileType.id)
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,17 +3,22 @@ package net.psforever.objects.definition
|
||||||
|
|
||||||
import net.psforever.objects.definition.converter.VehicleConverter
|
import net.psforever.objects.definition.converter.VehicleConverter
|
||||||
import net.psforever.objects.inventory.InventoryTile
|
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.collection.mutable
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object definition system used to construct and retain the parameters of various vehicles.
|
* 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
|
private var maxHealth : Int = 100
|
||||||
|
/** vehicle shields offered through amp station facility benefits (generally: 20% of health + 1) */
|
||||||
private var maxShields : Int = 0
|
private var maxShields : Int = 0
|
||||||
/* key - seat index, value - seat object */
|
/* key - seat index, value - seat object */
|
||||||
private val seats : mutable.HashMap[Int, SeatDefinition] = mutable.HashMap[Int, SeatDefinition]()
|
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 serverVehicleOverrideSpeeds : (Int, Int) = (0, 0)
|
||||||
private var deconTime : Option[FiniteDuration] = None
|
private var deconTime : Option[FiniteDuration] = None
|
||||||
private var maximumCapacitor : Int = 0
|
private var maximumCapacitor : Int = 0
|
||||||
|
private var destroyedModel : Option[DestroyedVehicle.Value] = None
|
||||||
Name = "vehicle"
|
Name = "vehicle"
|
||||||
Packet = VehicleDefinition.converter
|
Packet = VehicleDefinition.converter
|
||||||
|
Damage = StandardVehicleDamage
|
||||||
|
Resistance = StandardVehicleResistance
|
||||||
|
Model = StandardResolutions.Vehicle
|
||||||
|
|
||||||
def MaxHealth : Int = maxHealth
|
def MaxHealth : Int = maxHealth
|
||||||
|
|
||||||
|
|
@ -138,6 +147,13 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
||||||
maximumCapacitor = maxCapacitor
|
maximumCapacitor = maxCapacitor
|
||||||
MaximumCapacitor
|
MaximumCapacitor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def DestroyedModel : Option[DestroyedVehicle.Value] = destroyedModel
|
||||||
|
|
||||||
|
def DestroyedModel_=(model : Option[DestroyedVehicle.Value]) : Option[DestroyedVehicle.Value] = {
|
||||||
|
destroyedModel = model
|
||||||
|
DestroyedModel
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
object VehicleDefinition {
|
object VehicleDefinition {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -4,7 +4,8 @@ package net.psforever.objects.definition.converter
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.objects.Vehicle
|
import net.psforever.objects.Vehicle
|
||||||
import net.psforever.packet.game.PlanetSideGUID
|
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}
|
import scala.util.{Failure, Success, Try}
|
||||||
|
|
||||||
|
|
@ -14,31 +15,57 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
||||||
|
|
||||||
override def ConstructorData(obj : Vehicle) : Try[VehicleData] = {
|
override def ConstructorData(obj : Vehicle) : Try[VehicleData] = {
|
||||||
val health = 255 * obj.Health / obj.MaxHealth //TODO not precise
|
val health = 255 * obj.Health / obj.MaxHealth //TODO not precise
|
||||||
Success(
|
if(health > 3) { //active
|
||||||
VehicleData(
|
Success(
|
||||||
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
|
VehicleData(
|
||||||
obj.Faction,
|
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
|
||||||
bops = false,
|
obj.Faction,
|
||||||
destroyed = health < 3,
|
bops = false,
|
||||||
unk1 = 0,
|
destroyed = false,
|
||||||
obj.Jammered,
|
unk1 = 0,
|
||||||
unk2 = false,
|
obj.Jammered,
|
||||||
obj.Owner match {
|
unk2 = false,
|
||||||
case Some(owner) => owner
|
obj.Owner match {
|
||||||
case None => PlanetSideGUID(0)
|
case Some(owner) => owner
|
||||||
},
|
case None => PlanetSideGUID(0)
|
||||||
unk3 = false,
|
},
|
||||||
health,
|
unk3 = false,
|
||||||
unk4 = false,
|
health,
|
||||||
no_mount_points = false,
|
unk4 = false,
|
||||||
obj.DeploymentState,
|
no_mount_points = false,
|
||||||
unk5 = false,
|
obj.DeploymentState,
|
||||||
unk6 = false,
|
unk5 = false,
|
||||||
obj.Cloaked,
|
unk6 = false,
|
||||||
SpecificFormatData(obj),
|
obj.Cloaked,
|
||||||
Some(InventoryData(MakeDriverSeat(obj) ++ MakeUtilities(obj) ++ MakeMountings(obj)))
|
SpecificFormatData(obj),
|
||||||
)(SpecificFormatModifier)
|
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] = {
|
private def MakeDriverSeat(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
package net.psforever.objects.equipment
|
package net.psforever.objects.equipment
|
||||||
|
|
||||||
import net.psforever.objects.Tool
|
import net.psforever.objects.Tool
|
||||||
import net.psforever.objects.ballistics.DamageProfile
|
import net.psforever.objects.vital.damage.DamageProfile
|
||||||
|
|
||||||
import scala.collection.mutable
|
import scala.collection.mutable
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -96,18 +96,16 @@ class VehicleSpawnControl(pad : VehicleSpawnPad) extends VehicleSpawnControlBase
|
||||||
}
|
}
|
||||||
|
|
||||||
case VehicleSpawnControl.ProcessControl.Flush =>
|
case VehicleSpawnControl.ProcessControl.Flush =>
|
||||||
if(!periodicReminder.isCancelled) {
|
periodicReminder.cancel
|
||||||
periodicReminder.cancel
|
orders.foreach { VehicleSpawnControl.CancelOrder(_, Continent) }
|
||||||
orders.foreach { VehicleSpawnControl.CancelOrder(_, Continent) }
|
orders = Nil
|
||||||
orders = Nil
|
trackedOrder match {
|
||||||
trackedOrder match {
|
case Some(entry) =>
|
||||||
case Some(entry) =>
|
VehicleSpawnControl.CancelOrder(entry, Continent)
|
||||||
VehicleSpawnControl.CancelOrder(entry, Continent)
|
case None => ;
|
||||||
case None => ;
|
|
||||||
}
|
|
||||||
trackedOrder = None
|
|
||||||
concealPlayer ! akka.actor.Kill //will cause the actor to restart, which will abort any trapped messages
|
|
||||||
}
|
}
|
||||||
|
trackedOrder = None
|
||||||
|
concealPlayer ! akka.actor.Kill //will cause the actor to restart, which will abort any trapped messages
|
||||||
|
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -3,9 +3,11 @@ package net.psforever.objects.vehicles
|
||||||
|
|
||||||
import akka.actor.Actor
|
import akka.actor.Actor
|
||||||
import net.psforever.objects.Vehicle
|
import net.psforever.objects.Vehicle
|
||||||
|
import net.psforever.objects.ballistics.VehicleSource
|
||||||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||||
import net.psforever.objects.serverobject.deploy.DeploymentBehavior
|
import net.psforever.objects.serverobject.deploy.DeploymentBehavior
|
||||||
|
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
|
||||||
import net.psforever.types.ExoSuitType
|
import net.psforever.types.ExoSuitType
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -59,6 +61,22 @@ class VehicleControl(vehicle : Vehicle) extends Actor
|
||||||
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, seat_num))
|
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) =>
|
case FactionAffinity.ConvertFactionAffinity(faction) =>
|
||||||
val originalAffinity = vehicle.Faction
|
val originalAffinity = vehicle.Faction
|
||||||
if(originalAffinity != (vehicle.Faction = faction)) {
|
if(originalAffinity != (vehicle.Faction = faction)) {
|
||||||
|
|
@ -81,3 +99,24 @@ class VehicleControl(vehicle : Vehicle) extends Actor
|
||||||
case _ => ;
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.<br>
|
||||||
|
* <br>
|
||||||
|
* A vital object can be hurt or damaged or healed or repaired (HDHR).
|
||||||
|
* The actual implementation of how that works is left to the specific object and its interfaces, however.
|
||||||
|
* 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.<br>
|
||||||
|
* <br>
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.objects.ballistics
|
package net.psforever.objects.vital
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An `Enumeration` of the damage type.
|
* An `Enumeration` of the damage type.
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
110
common/src/main/scala/net/psforever/objects/vital/Vitality.scala
Normal file
110
common/src/main/scala/net/psforever/objects/vital/Vitality.scala
Normal file
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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.<br>
|
||||||
|
* <br>
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// 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.
|
* The different values for five common types of damage that can be dealt, based on target and application.
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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.<br>
|
||||||
|
* <br>
|
||||||
|
* Implementing functionality of the children is the product of two user-defined processes
|
||||||
|
* and information for the calculation is extracted from the to-be-provided weapon discharge information.
|
||||||
|
* Specifically, the information is found as the `target` object which is a member of the said information.
|
||||||
|
* The specific functions passed into this object typically operate simultaneously normally
|
||||||
|
* and are related to the target and the kind of interaction the weapon discharge had with the target.
|
||||||
|
* @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
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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 _ => ;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package net.psforever.packet.game
|
package net.psforever.packet.game
|
||||||
|
|
||||||
|
import net.psforever.objects.ballistics.SourceEntry
|
||||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||||
import net.psforever.types.PlanetSideEmpire
|
import net.psforever.types.PlanetSideEmpire
|
||||||
import scodec.Codec
|
import scodec.Codec
|
||||||
|
|
@ -15,47 +16,43 @@ import scodec.codecs._
|
||||||
* 3) victim information<br>
|
* 3) victim information<br>
|
||||||
* In the case of a player kill, the player's name will be attributed directly.
|
* 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 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).
|
* In the case of a suicide, the player attributed is the player who was killed (message format displays only the victim).<br>
|
||||||
* The victim's name is byte-aligned with a 5-bit buffer.<br>
|
|
||||||
* <br>
|
* <br>
|
||||||
* The four bytes that follow each name seems to be important to the identification of the associated player.
|
* 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."
|
* The same value will be seen in every `DestroyDisplayMessage` that includes that player,
|
||||||
* This holds true for every entry within thie same login session, at least.
|
* with respect to whether they are listed as the "killer" or as the "victim."
|
||||||
* Blanking these values out does not change anything about the format of the event message.
|
* This holds true for every entry within the same login session, at least.
|
||||||
* In the case of absentee kills, for example, where there is no killer listed, this field has been zero'd (`00000000`).<br>
|
* 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.<br>
|
||||||
* <br>
|
* <br>
|
||||||
* The faction affiliation is different from the normal way `PlanetSideEmpire` values are recorded.
|
* When marked as being in a vehicle or a turret, the player's name will be enclosed within square brackets.
|
||||||
* The higher nibble will reflect the first part of the `PlanetSideEmpire` value.
|
* The length of the player's name found at the start of the character string does not reflect
|
||||||
* An extra `20` will be added if the player is in a vehicle or turret at the time.
|
* whether or not there will be square brackets (fortunately).
|
||||||
* When marked as being in a vehicle or turret, the player's name will be enclosed within square brackets.
|
* The color of the resulting icon is borrowed from the attributed killer's faction affiliation if it can be determined
|
||||||
* 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).<br>
|
* and the type of icon is the same as an object id.
|
||||||
* <br>
|
* An unidentified method or a missing icon defaults to a skull and crossbones.
|
||||||
* 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.
|
|
||||||
* @param killer the name of the player who did the killing
|
* @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_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 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 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 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_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,
|
final case class DestroyDisplayMessage(killer : String,
|
||||||
killer_charId : Long,
|
killer_charId : Long,
|
||||||
killer_empire : PlanetSideEmpire.Value,
|
killer_empire : PlanetSideEmpire.Value,
|
||||||
killer_inVehicle : Boolean,
|
killer_in_vehicle : Boolean,
|
||||||
unk : Int,
|
unk : Int,
|
||||||
method : Int,
|
method : Int,
|
||||||
victim : String,
|
victim : String,
|
||||||
victim_charId : Long,
|
victim_charId : Long,
|
||||||
victim_empire : PlanetSideEmpire.Value,
|
victim_empire : PlanetSideEmpire.Value,
|
||||||
victim_inVehicle : Boolean
|
victim_in_vehicle : Boolean
|
||||||
)
|
)
|
||||||
extends PlanetSideGamePacket {
|
extends PlanetSideGamePacket {
|
||||||
type Packet = DestroyDisplayMessage
|
type Packet = DestroyDisplayMessage
|
||||||
|
|
@ -68,12 +65,12 @@ object DestroyDisplayMessage extends Marshallable[DestroyDisplayMessage] {
|
||||||
("killer" | PacketHelpers.encodedWideString) ::
|
("killer" | PacketHelpers.encodedWideString) ::
|
||||||
("killer_charId" | ulongL(32)) ::
|
("killer_charId" | ulongL(32)) ::
|
||||||
("killer_empire" | PlanetSideEmpire.codec) ::
|
("killer_empire" | PlanetSideEmpire.codec) ::
|
||||||
("killer_inVehicle" | bool) ::
|
("killer_in_vehicle" | bool) ::
|
||||||
("unk" | uint16L) ::
|
("unk" | uint16L) ::
|
||||||
("method" | uint16L) ::
|
("method" | uint16L) ::
|
||||||
("victim" | PacketHelpers.encodedWideStringAligned(5)) ::
|
("victim" | PacketHelpers.encodedWideStringAligned(5)) ::
|
||||||
("victim_charId" | ulongL(32)) ::
|
("victim_charId" | ulongL(32)) ::
|
||||||
("victim_empire" | PlanetSideEmpire.codec) ::
|
("victim_empire" | PlanetSideEmpire.codec) ::
|
||||||
("victim_inVehicle" | bool)
|
("victim_in_vehicle" | bool)
|
||||||
).as[DestroyDisplayMessage]
|
).as[DestroyDisplayMessage]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,10 @@ final case class PlanetsideAttributeMessage(player_guid : PlanetSideGUID,
|
||||||
}
|
}
|
||||||
|
|
||||||
object PlanetsideAttributeMessage extends Marshallable[PlanetsideAttributeMessage] {
|
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] = (
|
implicit val codec : Codec[PlanetsideAttributeMessage] = (
|
||||||
("player_guid" | PlanetSideGUID.codec) ::
|
("player_guid" | PlanetSideGUID.codec) ::
|
||||||
("attribute_type" | uint8L) ::
|
("attribute_type" | uint8L) ::
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package services.avatar
|
package services.avatar
|
||||||
|
|
||||||
|
import net.psforever.objects.ballistics.SourceEntry
|
||||||
import net.psforever.objects.{PlanetSideGameObject, Player}
|
import net.psforever.objects.{PlanetSideGameObject, Player}
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.objects.inventory.Container
|
import net.psforever.objects.inventory.Container
|
||||||
import net.psforever.objects.zones.Zone
|
import net.psforever.objects.zones.Zone
|
||||||
import net.psforever.packet.PlanetSideGamePacket
|
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.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent}
|
||||||
import net.psforever.types.ExoSuitType
|
import net.psforever.types.{ExoSuitType, Vector3}
|
||||||
|
|
||||||
import scala.concurrent.duration.FiniteDuration
|
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_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 ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
|
||||||
final case class ConcealPlayer(player_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 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 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 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 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 ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action
|
||||||
final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) 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 PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||||
// final case class DestroyDisplay(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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,12 @@
|
||||||
package services.avatar
|
package services.avatar
|
||||||
|
|
||||||
import net.psforever.objects.Player
|
import net.psforever.objects.Player
|
||||||
|
import net.psforever.objects.ballistics.SourceEntry
|
||||||
import net.psforever.objects.equipment.Equipment
|
import net.psforever.objects.equipment.Equipment
|
||||||
import net.psforever.packet.PlanetSideGamePacket
|
import net.psforever.packet.PlanetSideGamePacket
|
||||||
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID, PlayerStateMessageUpstream}
|
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID, PlayerStateMessageUpstream}
|
||||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||||
import net.psforever.types.ExoSuitType
|
import net.psforever.types.{ExoSuitType, Vector3}
|
||||||
|
|
||||||
object AvatarResponse {
|
object AvatarResponse {
|
||||||
trait Response
|
trait Response
|
||||||
|
|
@ -17,8 +18,13 @@ object AvatarResponse {
|
||||||
final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response
|
final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response
|
||||||
final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response
|
final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response
|
||||||
final case class ConcealPlayer() 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 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 LoadPlayer(pkt : ObjectCreateMessage) extends Response
|
||||||
final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response
|
final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response
|
||||||
final case class ObjectHeld(slot : 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 SendResponse(msg: PlanetSideGamePacket) extends Response
|
||||||
// final case class PlayerStateShift(itemID : PlanetSideGUID) 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
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,18 @@ class AvatarService extends Actor {
|
||||||
AvatarEvents.publish(
|
AvatarEvents.publish(
|
||||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer())
|
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) =>
|
case AvatarAction.DropItem(player_guid, item, zone) =>
|
||||||
val definition = item.Definition
|
val definition = item.Definition
|
||||||
val objectData = DroppedItemData(
|
val objectData = DroppedItemData(
|
||||||
|
|
@ -86,6 +98,14 @@ class AvatarService extends Actor {
|
||||||
AvatarResponse.EquipmentInHand(ObjectCreateMessage(definition.ObjectId, item.GUID, containerData, objectData))
|
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) =>
|
case AvatarAction.LoadPlayer(player_guid, object_id, target_guid, cdata, pdata) =>
|
||||||
val pkt = pdata match {
|
val pkt = pdata match {
|
||||||
case Some(data) =>
|
case Some(data) =>
|
||||||
|
|
@ -169,22 +189,6 @@ class AvatarService extends Actor {
|
||||||
AvatarServiceReply.PlayerStateShift(killer)
|
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 =>
|
case msg =>
|
||||||
log.warn(s"Unhandled message $msg from $sender")
|
log.warn(s"Unhandled message $msg from $sender")
|
||||||
|
|
|
||||||
|
|
@ -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 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 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 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 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 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 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
|
final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) extends Action
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package services.vehicle
|
package services.vehicle
|
||||||
|
|
||||||
import net.psforever.objects.equipment.Equipment
|
|
||||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||||
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
|
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
|
||||||
import net.psforever.packet.PlanetSideGamePacket
|
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 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 DismountVehicle(bailType : BailType.Value , unk2 : Boolean) extends Response
|
||||||
final case class EquipmentInSlot(pkt : ObjectCreateMessage) 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 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 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 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 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 MountVehicle(object_guid : PlanetSideGUID, seat : Int) extends Response
|
||||||
final case class Ownership(vehicle_guid : PlanetSideGUID) 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 ResetSpawnPad(pad_guid : PlanetSideGUID) extends Response
|
||||||
final case class RevealPlayer(player_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
|
final case class SeatPermissions(vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Response
|
||||||
|
|
|
||||||
|
|
@ -92,6 +92,10 @@ class VehicleService extends Actor {
|
||||||
VehicleEvents.publish(
|
VehicleEvents.publish(
|
||||||
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.Ownership(vehicle_guid))
|
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) =>
|
case VehicleAction.SeatPermissions(player_guid, vehicle_guid, seat_group, permission) =>
|
||||||
VehicleEvents.publish(
|
VehicleEvents.publish(
|
||||||
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission))
|
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission))
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package objects
|
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._
|
||||||
import net.psforever.objects.definition._
|
import net.psforever.objects.definition._
|
||||||
import net.psforever.objects.equipment.CItem.{DeployedItem, Unit}
|
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.inventory.InventoryTile
|
||||||
import net.psforever.objects.serverobject.terminals.Terminal
|
import net.psforever.objects.serverobject.terminals.Terminal
|
||||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||||
|
import net.psforever.objects.vehicles.DestroyedVehicle
|
||||||
import net.psforever.packet.game.PlanetSideGUID
|
import net.psforever.packet.game.PlanetSideGUID
|
||||||
import net.psforever.packet.game.objectcreate._
|
import net.psforever.packet.game.objectcreate._
|
||||||
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
|
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
|
||||||
|
|
@ -368,7 +369,40 @@ class ConverterTest extends Specification {
|
||||||
ams.Utilities(4)().GUID = PlanetSideGUID(417)
|
ams.Utilities(4)().GUID = PlanetSideGUID(417)
|
||||||
|
|
||||||
ams.Definition.Packet.ConstructorData(ams).isSuccess mustEqual true
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
416
common/src/test/scala/objects/DamageModelTests.scala
Normal file
416
common/src/test/scala/objects/DamageModelTests.scala
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,19 +1,29 @@
|
||||||
// Copyright (c) 2017 PSForever
|
// Copyright (c) 2017 PSForever
|
||||||
package objects
|
package objects
|
||||||
|
|
||||||
import net.psforever.objects.{GlobalDefinitions, LocalProjectile, Tool}
|
import net.psforever.objects._
|
||||||
import net.psforever.objects.ballistics.{DamageType, Projectile, ProjectileResolution, Projectiles}
|
import net.psforever.objects.ballistics._
|
||||||
import net.psforever.objects.definition.ProjectileDefinition
|
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
|
import org.specs2.mutable.Specification
|
||||||
|
|
||||||
class ProjectileTest extends Specification {
|
class ProjectileTest extends Specification {
|
||||||
|
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
|
||||||
|
val fury = Vehicle(GlobalDefinitions.fury)
|
||||||
|
|
||||||
"LocalProjectile" should {
|
"LocalProjectile" should {
|
||||||
"construct" in {
|
"construct" in {
|
||||||
val obj = new LocalProjectile() //since they're just placeholders, they only need to construct
|
val obj = new LocalProjectile() //since they're just placeholders, they only need to construct
|
||||||
obj.Definition.ObjectId mustEqual 0
|
obj.Definition.ObjectId mustEqual 0
|
||||||
obj.Definition.Name mustEqual "projectile"
|
obj.Definition.Name mustEqual "projectile"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"local projectile range" in {
|
||||||
|
Projectile.BaseUID < Projectile.RangeUID mustEqual true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"ProjectileDefinition" should {
|
"ProjectileDefinition" should {
|
||||||
|
|
@ -151,48 +161,156 @@ class ProjectileTest extends Specification {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"Projectile" should {
|
"SourceEntry" should {
|
||||||
"construct" in {
|
"construct for players" in {
|
||||||
val beamer_wep = Tool(GlobalDefinitions.beamer)
|
SourceEntry(player) match {
|
||||||
val projectile = beamer_wep.Projectile
|
case o : PlayerSource =>
|
||||||
val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
|
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
|
"construct for vehicles" in {
|
||||||
obj.tool_def mustEqual GlobalDefinitions.beamer
|
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_origin mustEqual Vector3(1.2f, 3.4f, 5.6f)
|
||||||
obj.shot_angle mustEqual Vector3(0.2f, 0.4f, 0.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.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 {
|
"resolve" in {
|
||||||
val beamer_wep = Tool(GlobalDefinitions.beamer)
|
val obj = Projectile(projectile, beamer_def, firemode, PlayerSource(player), beamer_def.ObjectId, Vector3.Zero, Vector3.Zero)
|
||||||
val projectile = beamer_wep.Projectile
|
obj.isResolved mustEqual false
|
||||||
val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
|
obj.isMiss mustEqual false
|
||||||
val obj2 = obj.Resolve(ProjectileResolution.MissedShot)
|
|
||||||
|
|
||||||
obj.resolution mustEqual ProjectileResolution.Unresolved
|
obj.Resolve()
|
||||||
obj.fire_time <= System.nanoTime mustEqual true
|
obj.isResolved mustEqual true
|
||||||
obj.hit_time mustEqual 0
|
obj.isMiss mustEqual false
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"resolve, with coordinates" in {
|
"missed" in {
|
||||||
val beamer_wep = Tool(GlobalDefinitions.beamer)
|
val obj = Projectile(projectile, beamer_def, firemode, PlayerSource(player), beamer_def.ObjectId, Vector3.Zero, Vector3.Zero)
|
||||||
val projectile = beamer_wep.Projectile
|
obj.isResolved mustEqual false
|
||||||
val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
|
obj.isMiss mustEqual false
|
||||||
val obj2 = obj.Resolve(Vector3(7.2f, 8.4f, 9.6f), Vector3(1.2f, 1.4f, 1.6f), ProjectileResolution.Resolved)
|
|
||||||
|
|
||||||
obj.resolution mustEqual ProjectileResolution.Unresolved
|
obj.Miss()
|
||||||
obj.current.Position mustEqual Vector3.Zero
|
obj.isResolved mustEqual true
|
||||||
obj.current.Orientation mustEqual Vector3.Zero
|
obj.isMiss mustEqual true
|
||||||
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)
|
|
||||||
|
"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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,16 @@ package objects
|
||||||
import akka.actor.Props
|
import akka.actor.Props
|
||||||
import base.ActorTest
|
import base.ActorTest
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
|
import net.psforever.objects.ballistics.{PlayerSource, Projectile, ProjectileResolution, ResolvedProjectile}
|
||||||
import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition}
|
import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition}
|
||||||
import net.psforever.objects.serverobject.mount.Mountable
|
import net.psforever.objects.serverobject.mount.Mountable
|
||||||
import net.psforever.objects.vehicles._
|
import net.psforever.objects.vehicles._
|
||||||
|
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
|
||||||
import net.psforever.packet.game.PlanetSideGUID
|
import net.psforever.packet.game.PlanetSideGUID
|
||||||
import net.psforever.types.{CharacterVoice, ExoSuitType}
|
import net.psforever.types._
|
||||||
import org.specs2.mutable._
|
import org.specs2.mutable._
|
||||||
|
|
||||||
import scala.concurrent.duration.Duration
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class VehicleTest extends Specification {
|
class VehicleTest extends Specification {
|
||||||
import VehicleTest._
|
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 {
|
object VehicleTest {
|
||||||
import net.psforever.objects.Avatar
|
import net.psforever.objects.Avatar
|
||||||
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
|
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
|
||||||
|
|
|
||||||
83
common/src/test/scala/objects/VitalityTest.scala
Normal file
83
common/src/test/scala/objects/VitalityTest.scala
Normal file
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,34 +15,34 @@ import services.{RemoverActor, ServiceManager}
|
||||||
|
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
|
|
||||||
class StandardRemoverActorTest extends ActorTest {
|
//class StandardRemoverActorTest extends ActorTest {
|
||||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||||
|
//
|
||||||
"RemoverActor" should {
|
// "RemoverActor" should {
|
||||||
"handle a simple task" in {
|
// "handle a simple task" in {
|
||||||
expectNoMsg(500 milliseconds)
|
// expectNoMsg(500 milliseconds)
|
||||||
val probe = TestProbe()
|
// val probe = TestProbe()
|
||||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere)
|
// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere)
|
||||||
|
//
|
||||||
val reply1 = probe.receiveOne(200 milliseconds)
|
// val reply1 = probe.receiveOne(200 milliseconds)
|
||||||
assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert])
|
// assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert])
|
||||||
val reply2 = probe.receiveOne(200 milliseconds)
|
// val reply2 = probe.receiveOne(200 milliseconds)
|
||||||
assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert])
|
// assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert])
|
||||||
probe.expectNoMsg(1 seconds) //delay
|
// probe.expectNoMsg(1 seconds) //delay
|
||||||
val reply3 = probe.receiveOne(300 milliseconds)
|
// val reply3 = probe.receiveOne(300 milliseconds)
|
||||||
assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
// assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||||
val reply4 = probe.receiveOne(300 milliseconds)
|
// val reply4 = probe.receiveOne(300 milliseconds)
|
||||||
assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
// assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||||
val reply5 = probe.receiveOne(300 milliseconds)
|
// val reply5 = probe.receiveOne(300 milliseconds)
|
||||||
assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
// assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||||
val reply6 = probe.receiveOne(500 milliseconds)
|
// val reply6 = probe.receiveOne(500 milliseconds)
|
||||||
assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
// assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||||
val reply7 = probe.receiveOne(500 milliseconds)
|
// val reply7 = probe.receiveOne(500 milliseconds)
|
||||||
assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
// assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
||||||
//class DelayedRemoverActorTest extends ActorTest {
|
//class DelayedRemoverActorTest extends ActorTest {
|
||||||
// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||||
|
|
|
||||||
|
|
@ -13,8 +13,9 @@ import csr.{CSRWarp, CSRZone, Traveler}
|
||||||
import net.psforever.objects.GlobalDefinitions._
|
import net.psforever.objects.GlobalDefinitions._
|
||||||
import services.ServiceManager.Lookup
|
import services.ServiceManager.Lookup
|
||||||
import net.psforever.objects._
|
import net.psforever.objects._
|
||||||
|
import net.psforever.objects.ballistics._
|
||||||
import net.psforever.objects.definition.ToolDefinition
|
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.equipment._
|
||||||
import net.psforever.objects.loadouts._
|
import net.psforever.objects.loadouts._
|
||||||
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
|
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._
|
||||||
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
|
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
|
||||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
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.objects.zones.{InterstellarCluster, Zone}
|
||||||
import net.psforever.packet.game.objectcreate._
|
import net.psforever.packet.game.objectcreate._
|
||||||
import net.psforever.types._
|
import net.psforever.types._
|
||||||
|
|
@ -53,8 +56,6 @@ import scala.concurrent.Future
|
||||||
import scala.concurrent.duration._
|
import scala.concurrent.duration._
|
||||||
import scala.util.Success
|
import scala.util.Success
|
||||||
import akka.pattern.ask
|
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 {
|
class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
import WorldSessionActor._
|
import WorldSessionActor._
|
||||||
|
|
@ -109,6 +110,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0
|
implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0
|
||||||
|
|
||||||
override def postStop() = {
|
override def postStop() = {
|
||||||
|
//TODO normally, player avatar persists a minute or so after disconnect; we are subject to the SessionReaper
|
||||||
clientKeepAlive.cancel
|
clientKeepAlive.cancel
|
||||||
reviveTimer.cancel
|
reviveTimer.cancel
|
||||||
respawnTimer.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) {
|
if(player.isAlive) {
|
||||||
//actually being alive or manually deconstructing
|
//actually being alive or manually deconstructing
|
||||||
DismountVehicleOnLogOut()
|
player.Position = Vector3.Zero
|
||||||
continent.Population ! Zone.Population.Release(avatar)
|
if(player.VehicleSeated.nonEmpty) {
|
||||||
player.Position = Vector3.Zero //save character before doing this
|
//quickly and briefly kill player to avoid disembark animation
|
||||||
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectDelete(player_guid, player_guid))
|
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)
|
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)) {
|
else if(continent.LivePlayers.contains(player) && !continent.Corpses.contains(player)) {
|
||||||
//player disconnected while waiting for a revive
|
//player disconnected while waiting for a revive
|
||||||
//similar to handling ReleaseAvatarRequestMessage
|
//similar to handling ReleaseAvatarRequestMessage
|
||||||
player.Release
|
player.Release
|
||||||
continent.Population ! Zone.Population.Release(avatar)
|
|
||||||
player.VehicleSeated match {
|
player.VehicleSeated match {
|
||||||
case None =>
|
case None =>
|
||||||
FriskCorpse(player) //TODO eliminate dead letters
|
FriskCorpse(player) //TODO eliminate dead letters
|
||||||
|
|
@ -189,7 +194,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
case Some(vehicle : Vehicle) =>
|
case Some(vehicle : Vehicle) =>
|
||||||
vehicle.Seat(vehicle.PassengerInSeat(player).get).get.Occupant = None
|
vehicle.Seat(vehicle.PassengerInSeat(player).get).get.Occupant = None
|
||||||
if(vehicle.Seats.values.count(_.isOccupied) == 0) {
|
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}"))
|
vehicleService ! Service.Leave(Some(s"${vehicle.Actor}"))
|
||||||
|
|
||||||
|
|
@ -392,7 +397,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
sendResponse(GenericObjectActionMessage(guid, 36))
|
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) {
|
if(tplayer_guid != guid) {
|
||||||
sendResponse(pkt)
|
sendResponse(pkt)
|
||||||
}
|
}
|
||||||
|
|
@ -402,6 +430,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
sendResponse(pkt)
|
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) =>
|
case AvatarResponse.LoadPlayer(pkt) =>
|
||||||
if(tplayer_guid != guid) {
|
if(tplayer_guid != guid) {
|
||||||
sendResponse(pkt)
|
sendResponse(pkt)
|
||||||
|
|
@ -544,7 +591,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) }
|
val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) }
|
||||||
reply match {
|
reply match {
|
||||||
case VehicleResponse.Ownership(vehicle_guid) =>
|
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) =>
|
case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) =>
|
||||||
sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3))
|
sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3))
|
||||||
|
|
@ -577,6 +624,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
sendResponse(pkt)
|
sendResponse(pkt)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case VehicleResponse.HitHint(source_guid) =>
|
||||||
|
sendResponse(HitHint(source_guid, player.GUID))
|
||||||
|
|
||||||
case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) =>
|
case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) =>
|
||||||
if(tplayer_guid != guid) {
|
if(tplayer_guid != guid) {
|
||||||
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
|
//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))
|
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) =>
|
case VehicleResponse.ResetSpawnPad(pad_guid) =>
|
||||||
sendResponse(GenericObjectActionMessage(pad_guid, 92))
|
sendResponse(GenericObjectActionMessage(pad_guid, 92))
|
||||||
|
|
||||||
|
|
@ -787,6 +842,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
|
|
||||||
case Mountable.CanMount(obj : Vehicle, seat_num) =>
|
case Mountable.CanMount(obj : Vehicle, seat_num) =>
|
||||||
val obj_guid : PlanetSideGUID = obj.GUID
|
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
|
if(seat_num == 0) { //simplistic vehicle ownership management
|
||||||
obj.Owner match {
|
obj.Owner match {
|
||||||
case Some(owner_guid) =>
|
case Some(owner_guid) =>
|
||||||
|
|
@ -807,7 +869,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
//update mounted weapon belonging to seat
|
//update mounted weapon belonging to seat
|
||||||
weapon.AmmoSlots.foreach(slot => { //update the magazine(s) in the weapon, specifically
|
weapon.AmmoSlots.foreach(slot => { //update the magazine(s) in the weapon, specifically
|
||||||
val magazine = slot.Box
|
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
|
case _ => ; //no weapons to update
|
||||||
}
|
}
|
||||||
|
|
@ -859,6 +921,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
case Terminal.TerminalMessage(tplayer, msg, order) =>
|
case Terminal.TerminalMessage(tplayer, msg, order) =>
|
||||||
order match {
|
order match {
|
||||||
case Terminal.BuyExosuit(exosuit, subtype) => //refresh armor points
|
case Terminal.BuyExosuit(exosuit, subtype) => //refresh armor points
|
||||||
|
tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit))
|
||||||
if(tplayer.ExoSuit == exosuit) {
|
if(tplayer.ExoSuit == exosuit) {
|
||||||
if(exosuit == ExoSuitType.MAX) {
|
if(exosuit == ExoSuitType.MAX) {
|
||||||
//special MAX case - clear any special state
|
//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
|
//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}")
|
log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}")
|
||||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true))
|
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true))
|
||||||
|
tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit))
|
||||||
//ensure arm is down
|
//ensure arm is down
|
||||||
tplayer.DrawnSlot = Player.HandsDownSlot
|
tplayer.DrawnSlot = Player.HandsDownSlot
|
||||||
sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
|
sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
|
||||||
|
|
@ -1105,7 +1169,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//accommodate as much of inventory as possible
|
//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
|
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory) //dropped items can be forgotten
|
||||||
stow
|
stow
|
||||||
}
|
}
|
||||||
|
|
@ -1121,7 +1184,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
if(!tplayer.Certifications.contains(cert)) {
|
if(!tplayer.Certifications.contains(cert)) {
|
||||||
log.info(s"$tplayer is learning the $cert certification for $cost points")
|
log.info(s"$tplayer is learning the $cert certification for $cost points")
|
||||||
avatar.Certifications += cert
|
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))
|
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -1133,7 +1196,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
if(tplayer.Certifications.contains(cert)) {
|
if(tplayer.Certifications.contains(cert)) {
|
||||||
log.info(s"$tplayer is forgetting the $cert certification for $cost points")
|
log.info(s"$tplayer is forgetting the $cert certification for $cost points")
|
||||||
avatar.Certifications -= cert
|
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))
|
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true))
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -1289,9 +1352,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) =>
|
case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) =>
|
||||||
val vehicle_guid = vehicle.GUID
|
val vehicle_guid = vehicle.GUID
|
||||||
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on
|
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)
|
ReloadVehicleAccessPermissions(vehicle)
|
||||||
ServerVehicleLock(vehicle)
|
ServerVehicleLock(vehicle)
|
||||||
|
|
||||||
|
|
@ -1551,6 +1611,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list
|
sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list
|
||||||
sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil))
|
sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil))
|
||||||
sendResponse(FriendsResponse(FriendAction.InitializeIgnoreList, 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")
|
cluster ! InterstellarCluster.GetWorld("z6")
|
||||||
|
|
||||||
case InterstellarCluster.GiveWorld(zoneId, zone) =>
|
case InterstellarCluster.GiveWorld(zoneId, zone) =>
|
||||||
|
|
@ -1750,6 +1811,50 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
case DelayedProximityUnitStop(terminal) =>
|
case DelayedProximityUnitStop(terminal) =>
|
||||||
StopUsingProximityUnit(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) =>
|
case ResponseToSelf(pkt) =>
|
||||||
log.info(s"Received a direct message: $pkt")
|
log.info(s"Received a direct message: $pkt")
|
||||||
sendResponse(pkt)
|
sendResponse(pkt)
|
||||||
|
|
@ -1780,7 +1885,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
//TODO begin temp player character auto-loading; remove later
|
//TODO begin temp player character auto-loading; remove later
|
||||||
import net.psforever.objects.GlobalDefinitions._
|
import net.psforever.objects.GlobalDefinitions._
|
||||||
import net.psforever.types.CertificationType._
|
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 += StandardAssault
|
||||||
avatar.Certifications += MediumAssault
|
avatar.Certifications += MediumAssault
|
||||||
avatar.Certifications += StandardExoSuit
|
avatar.Certifications += StandardExoSuit
|
||||||
|
|
@ -1814,6 +1919,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
avatar.Certifications += AdvancedEngineering
|
avatar.Certifications += AdvancedEngineering
|
||||||
avatar.Certifications += FortificationEngineering
|
avatar.Certifications += FortificationEngineering
|
||||||
avatar.Certifications += AssaultEngineering
|
avatar.Certifications += AssaultEngineering
|
||||||
|
this.avatar = avatar
|
||||||
AwardBattleExperiencePoints(avatar, 1000000L)
|
AwardBattleExperiencePoints(avatar, 1000000L)
|
||||||
player = new Player(avatar)
|
player = new Player(avatar)
|
||||||
//player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C
|
//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 {
|
continent.Corpses.foreach {
|
||||||
TurnPlayerIntoCorpse
|
TurnPlayerIntoCorpse
|
||||||
}
|
}
|
||||||
//load active vehicles in zone
|
//load vehicles in zone
|
||||||
continent.Vehicles.foreach(vehicle => {
|
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 vehicle_guid = vehicle.GUID
|
||||||
val vdefinition = vehicle.Definition
|
val vdefinition = vehicle.Definition
|
||||||
sendResponse(ObjectCreateMessage(vdefinition.ObjectId, vehicle_guid, vdefinition.Packet.ConstructorData(vehicle).get))
|
sendResponse(ObjectCreateMessage(vdefinition.ObjectId, vehicle_guid, vdefinition.Packet.ConstructorData(vehicle).get))
|
||||||
|
|
@ -2014,9 +2122,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
})
|
})
|
||||||
ReloadVehicleAccessPermissions(vehicle)
|
ReloadVehicleAccessPermissions(vehicle)
|
||||||
})
|
})
|
||||||
|
//Loop over vehicles again to add cargohold occupants after all vehicles have been created on the local client
|
||||||
// Loop over vehicles again to add cargohold occupants after all vehicles have been created on the local client
|
vehicles.foreach(vehicle => {
|
||||||
continent.Vehicles.foreach(vehicle => {
|
|
||||||
vehicle.CargoHolds.foreach({ case (cargo_num, cargo) => {
|
vehicle.CargoHolds.foreach({ case (cargo_num, cargo) => {
|
||||||
cargo.Occupant match {
|
cargo.Occupant match {
|
||||||
case Some(cargo_vehicle) =>
|
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
|
//implant terminals
|
||||||
continent.Map.TerminalToInterface.foreach({ case ((terminal_guid, interface_guid)) =>
|
continent.Map.TerminalToInterface.foreach({ case ((terminal_guid, interface_guid)) =>
|
||||||
|
|
@ -2198,8 +2315,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
}
|
}
|
||||||
|
|
||||||
case Some(_) =>
|
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
|
val player_guid = player.GUID
|
||||||
sendResponse(ObjectDeleteMessage(player_guid, 0))
|
sendResponse(ObjectDeleteMessage(player_guid, 0))
|
||||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, 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
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
context.system.scheduler.scheduleOnce(50 milliseconds, self, UnregisterCorpseOnVehicleDisembark(player))
|
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) =>
|
case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) =>
|
||||||
|
|
@ -2289,7 +2402,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
}
|
}
|
||||||
if(messagetype == ChatMessageType.CMT_SUICIDE) {
|
if(messagetype == ChatMessageType.CMT_SUICIDE) {
|
||||||
if(player.isAlive && deadState != DeadState.Release) {
|
if(player.isAlive && deadState != DeadState.Release) {
|
||||||
KillPlayer(player)
|
Suicide(player)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(messagetype == ChatMessageType.CMT_DESTROY) {
|
if(messagetype == ChatMessageType.CMT_DESTROY) {
|
||||||
|
|
@ -2726,10 +2839,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
case Some(_ : LocalProjectile) =>
|
case Some(_ : LocalProjectile) =>
|
||||||
FindProjectileEntry(object_guid) match {
|
FindProjectileEntry(object_guid) match {
|
||||||
case Some(projectile) =>
|
case Some(projectile) =>
|
||||||
if(projectile.resolution != ProjectileResolution.Unresolved) {
|
if(projectile.isResolved) {
|
||||||
log.warn(s"RequestDestroy: tried to clean up missed projectile ${object_guid.guid} but it was already resolved")
|
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 =>
|
case None =>
|
||||||
log.warn(s"RequestDestroy: projectile ${object_guid.guid} has never been fired")
|
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(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
|
||||||
sendResponse(ObjectDeleteMessage(kit.GUID, 0))
|
sendResponse(ObjectDeleteMessage(kit.GUID, 0))
|
||||||
taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
|
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
|
player.Health = player.Health + 25
|
||||||
sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health))
|
sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health))
|
||||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(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
|
else { //shooting
|
||||||
tool.Discharge
|
tool.Discharge
|
||||||
val projectileIndex = projectile_guid.guid - Projectile.BaseUID
|
val projectileIndex = projectile_guid.guid - Projectile.BaseUID
|
||||||
val ang = obj match {
|
val projectilePlace = projectiles(projectileIndex)
|
||||||
case _ : Player =>
|
if(projectilePlace match {
|
||||||
obj.Orientation //TODO upper body facing
|
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 =>
|
case _ : Vehicle =>
|
||||||
tool.Orientation //TODO this is too simplistic
|
(tool.Orientation, obj.Definition.ObjectId) //TODO this is too simplistic to find proper angle
|
||||||
case _ =>
|
case _ =>
|
||||||
Vector3.Zero
|
(obj.Orientation, obj.Definition.ObjectId)
|
||||||
}
|
}
|
||||||
projectiles(projectileIndex) =
|
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 _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
|
|
@ -3243,15 +3365,62 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
|
|
||||||
case msg @ HitMessage(seq_time, projectile_guid, unk1, hit_info, unk2, unk3, unk4) =>
|
case msg @ HitMessage(seq_time, projectile_guid, unk1, hit_info, unk2, unk3, unk4) =>
|
||||||
log.info(s"Hit: $msg")
|
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) =>
|
case msg @ SplashHitMessage(seq_time, projectile_guid, explosion_pos, direct_victim_uid, unk3, projectile_vel, unk4, targets) =>
|
||||||
log.info(s"Splash: $msg")
|
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) =>
|
case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, pos, unk1) =>
|
||||||
log.info(s"Lash: $msg")
|
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) =>
|
case msg @ AvatarFirstTimeEventMessage(avatar_guid, object_guid, unk1, event_name) =>
|
||||||
log.info("AvatarFirstTimeEvent: " + msg)
|
log.info("AvatarFirstTimeEvent: " + msg)
|
||||||
|
|
@ -3422,9 +3591,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
}
|
}
|
||||||
|
|
||||||
case msg @ FacilityBenefitShieldChargeRequestMessage(guid) =>
|
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)
|
log.info("Battleplan: "+msg)
|
||||||
|
|
||||||
case msg @ CreateShortcutMessage(player_guid, slot, unk, add, shortcut) =>
|
case msg @ CreateShortcutMessage(player_guid, slot, unk, add, shortcut) =>
|
||||||
|
|
@ -3433,8 +3614,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
case msg @ FriendsRequest(action, friend) =>
|
case msg @ FriendsRequest(action, friend) =>
|
||||||
log.info("FriendsRequest: "+msg)
|
log.info("FriendsRequest: "+msg)
|
||||||
|
|
||||||
case msg @ HitHint(source, player_guid) =>
|
case msg @ HitHint(source_guid, player_guid) =>
|
||||||
log.info("HitHint: "+msg)
|
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) =>
|
case msg @ TargetingImplantRequest(list) =>
|
||||||
log.info("TargetingImplantRequest: "+msg)
|
log.info("TargetingImplantRequest: "+msg)
|
||||||
|
|
@ -3977,7 +4164,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
val vehicle_guid = vehicle.GUID
|
val vehicle_guid = vehicle.GUID
|
||||||
(0 to 3).foreach(group => {
|
(0 to 3).foreach(group => {
|
||||||
sendResponse(
|
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`<br>
|
||||||
|
* `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.<br>
|
* The player has lost all his vitality and must be killed.<br>
|
||||||
* <br>
|
* <br>
|
||||||
|
|
@ -4898,6 +5096,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
continent.GUID(tplayer.VehicleSeated.get) match {
|
continent.GUID(tplayer.VehicleSeated.get) match {
|
||||||
case Some(obj : Vehicle) =>
|
case Some(obj : Vehicle) =>
|
||||||
TotalDriverVehicleControl(obj)
|
TotalDriverVehicleControl(obj)
|
||||||
|
UnAccessContents(obj)
|
||||||
case _ => ;
|
case _ => ;
|
||||||
}
|
}
|
||||||
//make player invisible (if not, the cadaver sticks out the side in a seated position)
|
//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()
|
PlayerActionsToCancel()
|
||||||
CancelAllProximityUnits()
|
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
|
import scala.concurrent.ExecutionContext.Implicits.global
|
||||||
reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer milliseconds, cluster, Zone.Lattice.RequestSpawnPoint(Zones.SanctuaryZoneNumber(tplayer.Faction), tplayer, 7))
|
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 = {
|
def ProximityMedicalTerminal(unit : Terminal with ProximityUnit) : Unit = {
|
||||||
val healthFull : Boolean = if(player.Health < player.MaxHealth) {
|
val healthFull : Boolean = if(player.Health < player.MaxHealth) {
|
||||||
|
player.History(HealFromTerm(PlayerSource(player), 10, 0, unit.Definition))
|
||||||
HealAction(player)
|
HealAction(player)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
val armorFull : Boolean = if(player.Armor < player.MaxArmor) {
|
val armorFull : Boolean = if(player.Armor < player.MaxArmor) {
|
||||||
|
player.History(HealFromTerm(PlayerSource(player), 0, 10, unit.Definition))
|
||||||
ArmorRepairAction(player)
|
ArmorRepairAction(player)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -5216,6 +5440,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
*/
|
*/
|
||||||
def ProximityHealCrystal(unit : Terminal with ProximityUnit) : Unit = {
|
def ProximityHealCrystal(unit : Terminal with ProximityUnit) : Unit = {
|
||||||
val healthFull : Boolean = if(player.Health < player.MaxHealth) {
|
val healthFull : Boolean = if(player.Health < player.MaxHealth) {
|
||||||
|
player.History(HealFromTerm(PlayerSource(player), 10, 0, unit.Definition))
|
||||||
HealAction(player)
|
HealAction(player)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
|
@ -5364,11 +5589,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
* @param resolution the resolution status to promote the projectile
|
* @param resolution the resolution status to promote the projectile
|
||||||
* @return 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 {
|
FindProjectileEntry(projectile_guid) match {
|
||||||
case Some(projectile) =>
|
case Some(projectile) =>
|
||||||
val index = projectile_guid.guid - Projectile.BaseUID
|
val index = projectile_guid.guid - Projectile.BaseUID
|
||||||
ResolveProjectileEntry(projectile, index, resolution)
|
ResolveProjectileEntry(projectile, index, resolution, target, pos)
|
||||||
case None =>
|
case None =>
|
||||||
log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
|
log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
|
||||||
None
|
None
|
||||||
|
|
@ -5385,13 +5610,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
||||||
* @param resolution the resolution status to promote the projectile
|
* @param resolution the resolution status to promote the projectile
|
||||||
* @return a copy of the projectile
|
* @return a copy of the projectile
|
||||||
*/
|
*/
|
||||||
def ResolveProjectileEntry(projectile : Projectile, index : Int, resolution : ProjectileResolution.Value) : Option[Projectile] = {
|
def ResolveProjectileEntry(projectile : Projectile, index : Int, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = {
|
||||||
if(projectiles(index).contains(projectile)) {
|
if(!projectiles(index).contains(projectile)) {
|
||||||
projectiles(index) = Some(projectile.Resolve(ProjectileResolution.Resolved))
|
log.error(s"expected projectile could not be found at $index; can not resolve")
|
||||||
Some(projectile.Resolve(resolution))
|
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 {
|
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))
|
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`<br>
|
||||||
|
* `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) = {
|
def failWithError(error : String) = {
|
||||||
log.error(error)
|
log.error(error)
|
||||||
sendResponse(ConnectionClose())
|
sendResponse(ConnectionClose())
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue