mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-20 02:24:45 +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
19
.codecov.yml
19
.codecov.yml
|
|
@ -2,19 +2,35 @@
|
|||
comment: off
|
||||
|
||||
ignore:
|
||||
- "common/src/main/scala/net/psforever/objects/ObjectType.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/avatar/Avatars.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/ballistics/ProjectileResolution.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/ballistics/Projectiles.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/equipment/Ammo.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/equipment/CItem.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/equipment/Kits.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/equipment/SItem.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/guid/AvailabilityPolicy.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/serverobject/pad/AutoDriveControls.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/serverobject/structures/StructureType.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/serverobject/turret/TurretUpgrade.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vehicles/CargoVehicleRestiction.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vehicles/DestroyedVehicle.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vehicles/SeatArmoRestriction.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vehicles/Turrets.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vehicles/VehicleLockState.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vital/projectile/ProjectileCalculations.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vital/DamageType.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala"
|
||||
- "common/src/main/scala/net/psforever/packet/crypto"
|
||||
- "common/src/main/scala/net/psforever/packet/game/objectcreate/DrawnSlot.scala"
|
||||
- "common/src/main/scala/net/psforever/packet/game/objectcreate/DriveState.scala"
|
||||
|
|
@ -24,7 +40,6 @@ ignore:
|
|||
- "common/src/main/scala/net/psforever/packet/ControlPacketOpcode.scala"
|
||||
- "common/src/main/scala/net/psforever/packet/CryptoPacketOpcode.scala"
|
||||
- "common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala"
|
||||
- "common/src/main/scala/net/psforever/objects/ObjectType.scala"
|
||||
- "common/src/main/scala/net/psforever/types/Angular.scala"
|
||||
- "common/src/main/scala/net/psforever/types/CertificationType.scala"
|
||||
- "common/src/main/scala/net/psforever/types/ChatMessageType.scala"
|
||||
|
|
@ -38,6 +53,8 @@ ignore:
|
|||
- "common/src/main/scala/net/psforever/types/TransactionType.scala"
|
||||
- "common/src/main/scala/services/avatar/AvatarAction.scala"
|
||||
- "common/src/main/scala/services/avatar/AvatarResponse.scala"
|
||||
- "common/src/main/scala/services/galaxy/GalaxyAction.scala"
|
||||
- "common/src/main/scala/services/galaxy/GalaxyResponse.scala"
|
||||
- "common/src/main/scala/services/local/LocalAction.scala"
|
||||
- "common/src/main/scala/services/local/LocalResponse.scala"
|
||||
- "common/src/main/scala/services/vehicle/VehicleAction.scala"
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
|
|||
}
|
||||
}
|
||||
|
||||
def Definition : AvatarDefinition = Avatar.definition
|
||||
def Definition : AvatarDefinition = GlobalDefinitions.avatar
|
||||
|
||||
/*
|
||||
Merit Commendations and Ribbons
|
||||
|
|
@ -210,8 +210,6 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex :
|
|||
}
|
||||
|
||||
object Avatar {
|
||||
final private val definition : AvatarDefinition = new AvatarDefinition(121)
|
||||
|
||||
def apply(name : String, faction : PlanetSideEmpire.Value, sex : CharacterGender.Value, head : Int, voice : CharacterVoice.Value) : Avatar = {
|
||||
new Avatar(name, faction, sex, head, voice)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package net.psforever.objects
|
|||
|
||||
import net.psforever.objects.equipment.EquipmentSize
|
||||
import net.psforever.objects.inventory.InventoryTile
|
||||
import net.psforever.objects.vital._
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
||||
import net.psforever.types.ExoSuitType
|
||||
|
||||
/**
|
||||
|
|
@ -10,12 +12,16 @@ import net.psforever.types.ExoSuitType
|
|||
* Players are influenced by the exo-suit they wear in a variety of ways, with speed and available equipment slots being major differences.
|
||||
* @param suitType the `Enumeration` corresponding to this exo-suit
|
||||
*/
|
||||
class ExoSuitDefinition(private val suitType : ExoSuitType.Value) {
|
||||
class ExoSuitDefinition(private val suitType : ExoSuitType.Value) extends ResistanceProfileMutators
|
||||
with DamageResistanceModel {
|
||||
protected var permission : Int = 0 //TODO certification type?
|
||||
protected var maxArmor : Int = 0
|
||||
protected val holsters : Array[EquipmentSize.Value] = Array.fill[EquipmentSize.Value](5)(EquipmentSize.Blocked)
|
||||
protected var inventoryScale : InventoryTile = InventoryTile.Tile11 //override with custom InventoryTile
|
||||
protected var inventoryOffset : Int = 0
|
||||
Damage = StandardInfantryDamage
|
||||
Resistance = StandardInfantryResistance
|
||||
Model = StandardResolutions.Infantry
|
||||
|
||||
def SuitType : ExoSuitType.Value = suitType
|
||||
|
||||
|
|
@ -79,6 +85,12 @@ class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends
|
|||
obj.MaxArmor = MaxArmor
|
||||
obj.InventoryScale = InventoryScale
|
||||
obj.InventoryOffset = InventoryOffset
|
||||
obj.ResistanceDirectHit = ResistanceDirectHit
|
||||
obj.ResistanceSplash = ResistanceSplash
|
||||
obj.ResistanceAggravated = ResistanceAggravated
|
||||
obj.Damage = Damage
|
||||
obj.Resistance = Resistance
|
||||
obj.Model = Model
|
||||
(0 until 5).foreach(index => { obj.Holster(index, Holster(index)) })
|
||||
obj
|
||||
}
|
||||
|
|
@ -109,6 +121,9 @@ object ExoSuitDefinition {
|
|||
Standard.Holster(0, EquipmentSize.Pistol)
|
||||
Standard.Holster(2, EquipmentSize.Rifle)
|
||||
Standard.Holster(4, EquipmentSize.Melee)
|
||||
Standard.ResistanceDirectHit = 4
|
||||
Standard.ResistanceSplash = 15
|
||||
Standard.ResistanceAggravated = 8
|
||||
|
||||
final val Agile = ExoSuitDefinition(ExoSuitType.Agile)
|
||||
Agile.MaxArmor = 100
|
||||
|
|
@ -118,6 +133,9 @@ object ExoSuitDefinition {
|
|||
Agile.Holster(1, EquipmentSize.Pistol)
|
||||
Agile.Holster(2, EquipmentSize.Rifle)
|
||||
Agile.Holster(4, EquipmentSize.Melee)
|
||||
Agile.ResistanceDirectHit = 6
|
||||
Agile.ResistanceSplash = 25
|
||||
Agile.ResistanceAggravated = 10
|
||||
|
||||
final val Reinforced = ExoSuitDefinition(ExoSuitType.Reinforced)
|
||||
Reinforced.permission = 1
|
||||
|
|
@ -129,6 +147,9 @@ object ExoSuitDefinition {
|
|||
Reinforced.Holster(2, EquipmentSize.Rifle)
|
||||
Reinforced.Holster(3, EquipmentSize.Rifle)
|
||||
Reinforced.Holster(4, EquipmentSize.Melee)
|
||||
Reinforced.ResistanceDirectHit = 10
|
||||
Reinforced.ResistanceSplash = 35
|
||||
Reinforced.ResistanceAggravated = 12
|
||||
|
||||
final val Infiltration = ExoSuitDefinition(ExoSuitType.Infiltration)
|
||||
Infiltration.permission = 1
|
||||
|
|
@ -145,6 +166,11 @@ object ExoSuitDefinition {
|
|||
MAX.InventoryOffset = 6
|
||||
MAX.Holster(0, EquipmentSize.Max)
|
||||
MAX.Holster(4, EquipmentSize.Melee)
|
||||
MAX.ResistanceDirectHit = 6
|
||||
MAX.ResistanceSplash = 35
|
||||
MAX.ResistanceAggravated = 10
|
||||
MAX.Damage = StandardMaxDamage
|
||||
MAX.Model = StandardResolutions.Max
|
||||
|
||||
def apply(suitType : ExoSuitType.Value) : ExoSuitDefinition = {
|
||||
new ExoSuitDefinition(suitType)
|
||||
|
|
|
|||
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.loadouts.Loadout
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import net.psforever.types._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Success, Try}
|
||||
|
||||
class Player(private val core : Avatar) extends PlanetSideGameObject with FactionAffinity with Container {
|
||||
class Player(private val core : Avatar) extends PlanetSideGameObject
|
||||
with FactionAffinity
|
||||
with Vitality
|
||||
with ResistanceProfile
|
||||
with Container {
|
||||
private var alive : Boolean = false
|
||||
private var backpack : Boolean = false
|
||||
private var health : Int = 0
|
||||
|
|
@ -42,7 +48,6 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio
|
|||
//SouNourS things
|
||||
/** Last medkituse. */
|
||||
var lastMedkit : Long = 0
|
||||
var death_by : Int = 0
|
||||
var lastSeenStreamMessage : Array[Long] = Array.fill[Long](65535)(0L)
|
||||
var lastShotSeq_time : Int = -1
|
||||
/** From PlanetsideAttributeMessage */
|
||||
|
|
@ -276,6 +281,14 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio
|
|||
ChangeSpecialAbility()
|
||||
}
|
||||
|
||||
def ResistanceDirectHit = exosuit.ResistanceDirectHit
|
||||
|
||||
def ResistanceSplash = exosuit.ResistanceSplash
|
||||
|
||||
def ResistanceAggravated = exosuit.ResistanceAggravated
|
||||
|
||||
def RadiationShielding = exosuit.RadiationShielding
|
||||
|
||||
def LoadLoadout(line : Int) : Option[Loadout] = core.LoadLoadout(line)
|
||||
|
||||
def BEP : Long = core.BEP
|
||||
|
|
@ -475,6 +488,8 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio
|
|||
Continent
|
||||
}
|
||||
|
||||
def DamageModel = exosuit.asInstanceOf[DamageResistanceModel]
|
||||
|
||||
def Definition : AvatarDefinition = core.Definition
|
||||
|
||||
def canEqual(other: Any): Boolean = other.isInstanceOf[Player]
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
|
|||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.deploy.Deployment
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
|
||||
|
|
@ -67,6 +68,8 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
|
|||
with Mountable
|
||||
with MountedWeapons
|
||||
with Deployment
|
||||
with Vitality
|
||||
with StandardResistanceProfile
|
||||
with Container {
|
||||
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR
|
||||
private var owner : Option[PlanetSideGUID] = None
|
||||
|
|
@ -142,7 +145,7 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
|
|||
def Owner_=(owner : Option[PlanetSideGUID]) : Option[PlanetSideGUID] = {
|
||||
owner match {
|
||||
case Some(_) =>
|
||||
if(Definition.CanBeOwned) { //e.g., base turrets
|
||||
if(Definition.CanBeOwned) {
|
||||
this.owner = owner
|
||||
}
|
||||
case None =>
|
||||
|
|
@ -152,11 +155,11 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
|
|||
}
|
||||
|
||||
def Health : Int = {
|
||||
this.health
|
||||
health
|
||||
}
|
||||
|
||||
def Health_=(health : Int) : Int = {
|
||||
this.health = health
|
||||
def Health_=(assignHealth : Int) : Int = {
|
||||
health = math.min(math.max(0, assignHealth), MaxHealth)
|
||||
health
|
||||
}
|
||||
|
||||
|
|
@ -165,11 +168,11 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
|
|||
}
|
||||
|
||||
def Shields : Int = {
|
||||
this.shields
|
||||
shields
|
||||
}
|
||||
|
||||
def Shields_=(strength : Int) : Int = {
|
||||
this.shields = strength
|
||||
shields = math.min(math.max(0, strength), MaxShields)
|
||||
Shields
|
||||
}
|
||||
|
||||
|
|
@ -178,12 +181,12 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
|
|||
}
|
||||
|
||||
def Decal : Int = {
|
||||
this.decal
|
||||
decal
|
||||
}
|
||||
|
||||
def Decal_=(decal : Int) : Int = {
|
||||
this.decal = decal
|
||||
decal
|
||||
def Decal_=(logo : Int) : Int = {
|
||||
decal = logo
|
||||
Decal
|
||||
}
|
||||
|
||||
def Jammered : Boolean = jammered
|
||||
|
|
@ -493,6 +496,8 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ
|
|||
*/
|
||||
def TrunkLockState : VehicleLockState.Value = groupPermissions(3)
|
||||
|
||||
def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
|
||||
|
||||
/**
|
||||
* This is the definition entry that is used to store and unload pertinent information about the `Vehicle`.
|
||||
* @return the vehicle's definition entry
|
||||
|
|
@ -534,6 +539,20 @@ object Vehicle {
|
|||
*/
|
||||
final case class Reactivate()
|
||||
|
||||
/**
|
||||
* A request has been made to charge this vehicle's shields.
|
||||
* @see `FacilityBenefitShieldChargeRequestMessage`
|
||||
* @param amount the number of points to charge
|
||||
*/
|
||||
final case class ChargeShields(amount : Int)
|
||||
|
||||
/**
|
||||
* Following a successful shield charge tick, display the results of the update.
|
||||
* @see `FacilityBenefitShieldChargeRequestMessage`
|
||||
* @param vehicle the updated vehicle
|
||||
*/
|
||||
final case class UpdateShieldsCharge(vehicle : Vehicle)
|
||||
|
||||
/**
|
||||
* Overloaded constructor.
|
||||
* @param vehicleDef the vehicle's definition entry
|
||||
|
|
|
|||
|
|
@ -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
|
||||
package net.psforever.objects.ballistics
|
||||
|
||||
import net.psforever.objects.PlanetSideGameObject
|
||||
import net.psforever.objects.definition.{ProjectileDefinition, ToolDefinition}
|
||||
import net.psforever.objects.entity.SimpleWorldEntity
|
||||
import net.psforever.objects.equipment.FireModeDefinition
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
/**
|
||||
* A summation of weapon (`Tool`) discharge.
|
||||
* @see `ProjectileDefinition`<br>
|
||||
* `ToolDefinition`<br>
|
||||
* `FireModeDefinition`<br>
|
||||
* `SourceEntry`<br>
|
||||
* `PlayerSource`
|
||||
* @param profile an explanation of the damage that can be performed by this discharge
|
||||
* @param tool_def the weapon that caused this discharge
|
||||
* @param fire_mode the current fire mode of the tool used
|
||||
* @param owner the agency that caused the weapon to produce this projectile;
|
||||
* most often a player (`PlayerSource`)
|
||||
* @param attribute_to an object ID that refers to the method of death that would be reported;
|
||||
* usually the same as `tool_def.ObjectId`;
|
||||
* if not, then it is a type of vehicle (and owner should have a positive `seated` field)
|
||||
* @param shot_origin where the projectile started
|
||||
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
||||
* @param resolution whether this projectile has encountered a target or wall;
|
||||
* defaults to `Unresolved`
|
||||
* @param fire_time when the weapon discharged was recorded;
|
||||
* defaults to `System.nanoTime`
|
||||
* @param hit_time when the discharge had its resolution status updated
|
||||
*/
|
||||
final case class Projectile(profile : ProjectileDefinition,
|
||||
tool_def : ToolDefinition,
|
||||
fire_mode : FireModeDefinition,
|
||||
owner : SourceEntry,
|
||||
attribute_to : Int,
|
||||
shot_origin : Vector3,
|
||||
shot_angle : Vector3,
|
||||
resolution : ProjectileResolution.Value,
|
||||
fire_time : Long = System.nanoTime,
|
||||
hit_time : Long = 0) {
|
||||
fire_time: Long = System.nanoTime) {
|
||||
/** Information about the current world coordinates and orientation of the projectile */
|
||||
val current : SimpleWorldEntity = new SimpleWorldEntity()
|
||||
private var resolved : ProjectileResolution.Value = ProjectileResolution.Unresolved
|
||||
|
||||
/**
|
||||
* Give the projectile the suggested resolution status.
|
||||
* Update the world coordinates and orientation.
|
||||
* @param pos the current position
|
||||
* @param ang the current orientation
|
||||
* @param resolution the resolution status
|
||||
* @return a new projectile with the suggested resolution status, or the original projectile
|
||||
* Mark the projectile as being "encountered" or "managed" at least once.
|
||||
*/
|
||||
def Resolve(pos : Vector3, ang : Vector3, resolution : ProjectileResolution.Value) : Projectile = {
|
||||
val obj = Resolve(resolution)
|
||||
obj.current.Position = pos
|
||||
obj.current.Orientation = ang
|
||||
obj
|
||||
def Resolve() : Unit = {
|
||||
resolved = ProjectileResolution.Resolved
|
||||
}
|
||||
|
||||
/**
|
||||
* Give the projectile the suggested resolution status.
|
||||
* @param resolution the resolution status
|
||||
* @return a new projectile with the suggested resolution status, or the original projectile
|
||||
*/
|
||||
def Resolve(resolution : ProjectileResolution.Value) : Projectile = {
|
||||
resolution match {
|
||||
case ProjectileResolution.Unresolved =>
|
||||
this
|
||||
case _ =>
|
||||
Projectile(profile, tool_def, shot_origin, shot_angle, resolution, fire_time, System.nanoTime)
|
||||
}
|
||||
def Miss() : Unit = {
|
||||
resolved = ProjectileResolution.MissedShot
|
||||
}
|
||||
|
||||
def isResolved : Boolean = resolved == ProjectileResolution.Resolved || resolved == ProjectileResolution.MissedShot
|
||||
|
||||
def isMiss : Boolean = resolved == ProjectileResolution.MissedShot
|
||||
}
|
||||
|
||||
object Projectile {
|
||||
|
|
@ -68,11 +67,28 @@ object Projectile {
|
|||
* Overloaded constructor for an `Unresolved` projectile.
|
||||
* @param profile an explanation of the damage that can be performed by this discharge
|
||||
* @param tool_def the weapon that caused this discharge
|
||||
* @param fire_mode the current fire mode of the tool used
|
||||
* @param owner the agency that caused the weapon to produce this projectile
|
||||
* @param shot_origin where the projectile started
|
||||
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
||||
* @return the `Projectile` object
|
||||
*/
|
||||
def apply(profile : ProjectileDefinition, tool_def : ToolDefinition, shot_origin : Vector3, shot_angle : Vector3) : Projectile = {
|
||||
Projectile(profile, tool_def, shot_origin, shot_angle, ProjectileResolution.Unresolved)
|
||||
def apply(profile : ProjectileDefinition, tool_def : ToolDefinition, fire_mode : FireModeDefinition, owner : PlanetSideGameObject with FactionAffinity, shot_origin : Vector3, shot_angle : Vector3) : Projectile = {
|
||||
Projectile(profile, tool_def, fire_mode, SourceEntry(owner), tool_def.ObjectId, shot_origin, shot_angle)
|
||||
}
|
||||
|
||||
/**
|
||||
* Overloaded constructor for an `Unresolved` projectile.
|
||||
* @param profile an explanation of the damage that can be performed by this discharge
|
||||
* @param tool_def the weapon that caused this discharge
|
||||
* @param fire_mode the current fire mode of the tool used
|
||||
* @param owner the agency that caused the weapon to produce this projectile
|
||||
* @param attribute_to an object ID that refers to the method of death that would be reported
|
||||
* @param shot_origin where the projectile started
|
||||
* @param shot_angle in which direction the projectile was aimed when it was discharged
|
||||
* @return the `Projectile` object
|
||||
*/
|
||||
def apply(profile : ProjectileDefinition, tool_def : ToolDefinition, fire_mode : FireModeDefinition, owner : PlanetSideGameObject with FactionAffinity, attribute_to : Int, shot_origin : Vector3, shot_angle : Vector3) : Projectile = {
|
||||
Projectile(profile, tool_def, fire_mode, SourceEntry(owner), attribute_to, shot_origin, shot_angle)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
package net.psforever.objects.definition
|
||||
|
||||
import net.psforever.objects.ballistics.{DamageProfile, DamageType, Projectiles}
|
||||
import net.psforever.objects.ballistics.Projectiles
|
||||
import net.psforever.objects.vital.DamageType
|
||||
import net.psforever.objects.vital.damage.DamageProfile
|
||||
|
||||
/**
|
||||
* The definition that outlines the damage-dealing characteristics of any projectile.
|
||||
* `Tool` objects emit `ProjectileDefinition` objects and that is later wrapped into a `Projectile` object.
|
||||
* @param objectId the object's identifier number
|
||||
*/
|
||||
class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) with DamageProfile {
|
||||
class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
|
||||
with DamageProfile {
|
||||
private val projectileType : Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException
|
||||
private var damage0 : Int = 0
|
||||
private var damage1 : Option[Int] = None
|
||||
|
|
@ -26,6 +29,11 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) wi
|
|||
private var damageAtEdge : Float = 1f
|
||||
private var damageRadius : Float = 1f
|
||||
private var useDamage1Subtract : Boolean = false
|
||||
//derived calculations
|
||||
private var distanceMax : Float = 0f
|
||||
private var distanceFromAcceleration : Float = 0f
|
||||
private var distanceNoDegrade : Float = 0f
|
||||
private var finalVelocity : Float = 0f
|
||||
Name = "projectile"
|
||||
|
||||
def ProjectileType : Projectiles.Value = projectileType
|
||||
|
|
@ -157,10 +165,43 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) wi
|
|||
this.damageRadius = damageRadius
|
||||
DamageRadius
|
||||
}
|
||||
|
||||
def DistanceMax : Float = distanceMax //accessor only
|
||||
|
||||
def DistanceFromAcceleration : Float = distanceFromAcceleration //accessor only
|
||||
|
||||
def DistanceNoDegrade : Float = distanceNoDegrade //accessor only
|
||||
|
||||
def FinalVelocity : Float = finalVelocity //accessor only
|
||||
}
|
||||
|
||||
object ProjectileDefinition {
|
||||
def apply(projectileType : Projectiles.Value) : ProjectileDefinition = {
|
||||
new ProjectileDefinition(projectileType.id)
|
||||
}
|
||||
|
||||
def CalculateDerivedFields(pdef : ProjectileDefinition) : Unit = {
|
||||
val (distanceMax, distanceFromAcceleration, finalVelocity) : (Float, Float, Float) = if(pdef.Acceleration == 0f) {
|
||||
(pdef.InitialVelocity * pdef.Lifespan, 0, pdef.InitialVelocity)
|
||||
}
|
||||
else {
|
||||
val distanceFromAcceleration = (pdef.AccelerationUntil * pdef.InitialVelocity) + (0.5f * pdef.Acceleration * pdef.AccelerationUntil * pdef.AccelerationUntil)
|
||||
val finalVelocity = pdef.InitialVelocity + pdef.Acceleration * pdef.AccelerationUntil
|
||||
val distanceAfterAcceleration = finalVelocity * (pdef.Lifespan - pdef.AccelerationUntil)
|
||||
(distanceFromAcceleration + distanceAfterAcceleration, distanceFromAcceleration, finalVelocity)
|
||||
}
|
||||
pdef.distanceMax = distanceMax
|
||||
pdef.distanceFromAcceleration = distanceFromAcceleration
|
||||
pdef.finalVelocity = finalVelocity
|
||||
|
||||
pdef.distanceNoDegrade = if(pdef.DegradeDelay == 0f) {
|
||||
pdef.distanceMax
|
||||
}
|
||||
else if(pdef.DegradeDelay < pdef.AccelerationUntil) {
|
||||
(pdef.DegradeDelay * pdef.InitialVelocity) + (0.5f * pdef.Acceleration * pdef.DegradeDelay * pdef.DegradeDelay)
|
||||
}
|
||||
else {
|
||||
pdef.distanceFromAcceleration + pdef.finalVelocity * (pdef.DegradeDelay - pdef.AccelerationUntil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,17 +3,22 @@ package net.psforever.objects.definition
|
|||
|
||||
import net.psforever.objects.definition.converter.VehicleConverter
|
||||
import net.psforever.objects.inventory.InventoryTile
|
||||
import net.psforever.objects.vehicles.UtilityType
|
||||
import net.psforever.objects.vehicles.{DestroyedVehicle, UtilityType}
|
||||
import net.psforever.objects.vital._
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* An object definition system used to construct and retain the parameters of various vehicles.
|
||||
* @param objectId the object id the is associated with this sort of `Vehicle`
|
||||
* @param objectId the object id that is associated with this sort of `Vehicle`
|
||||
*/
|
||||
class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
||||
class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId)
|
||||
with ResistanceProfileMutators
|
||||
with DamageResistanceModel {
|
||||
private var maxHealth : Int = 100
|
||||
/** vehicle shields offered through amp station facility benefits (generally: 20% of health + 1) */
|
||||
private var maxShields : Int = 0
|
||||
/* key - seat index, value - seat object */
|
||||
private val seats : mutable.HashMap[Int, SeatDefinition] = mutable.HashMap[Int, SeatDefinition]()
|
||||
|
|
@ -33,8 +38,12 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
|||
private var serverVehicleOverrideSpeeds : (Int, Int) = (0, 0)
|
||||
private var deconTime : Option[FiniteDuration] = None
|
||||
private var maximumCapacitor : Int = 0
|
||||
private var destroyedModel : Option[DestroyedVehicle.Value] = None
|
||||
Name = "vehicle"
|
||||
Packet = VehicleDefinition.converter
|
||||
Damage = StandardVehicleDamage
|
||||
Resistance = StandardVehicleResistance
|
||||
Model = StandardResolutions.Vehicle
|
||||
|
||||
def MaxHealth : Int = maxHealth
|
||||
|
||||
|
|
@ -138,6 +147,13 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId) {
|
|||
maximumCapacitor = maxCapacitor
|
||||
MaximumCapacitor
|
||||
}
|
||||
|
||||
def DestroyedModel : Option[DestroyedVehicle.Value] = destroyedModel
|
||||
|
||||
def DestroyedModel_=(model : Option[DestroyedVehicle.Value]) : Option[DestroyedVehicle.Value] = {
|
||||
destroyedModel = model
|
||||
DestroyedModel
|
||||
}
|
||||
}
|
||||
|
||||
object VehicleDefinition {
|
||||
|
|
|
|||
|
|
@ -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.Vehicle
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import net.psforever.packet.game.objectcreate.{InventoryItemData, _}
|
||||
import net.psforever.packet.game.objectcreate.{InventoryData, InventoryItemData, ObjectClass, PlacementData, SpecificVehicleData, VehicleData, VehicleFormat}
|
||||
import net.psforever.types.DriveState
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
|
|
@ -14,31 +15,57 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
|||
|
||||
override def ConstructorData(obj : Vehicle) : Try[VehicleData] = {
|
||||
val health = 255 * obj.Health / obj.MaxHealth //TODO not precise
|
||||
Success(
|
||||
VehicleData(
|
||||
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
|
||||
obj.Faction,
|
||||
bops = false,
|
||||
destroyed = health < 3,
|
||||
unk1 = 0,
|
||||
obj.Jammered,
|
||||
unk2 = false,
|
||||
obj.Owner match {
|
||||
case Some(owner) => owner
|
||||
case None => PlanetSideGUID(0)
|
||||
},
|
||||
unk3 = false,
|
||||
health,
|
||||
unk4 = false,
|
||||
no_mount_points = false,
|
||||
obj.DeploymentState,
|
||||
unk5 = false,
|
||||
unk6 = false,
|
||||
obj.Cloaked,
|
||||
SpecificFormatData(obj),
|
||||
Some(InventoryData(MakeDriverSeat(obj) ++ MakeUtilities(obj) ++ MakeMountings(obj)))
|
||||
)(SpecificFormatModifier)
|
||||
)
|
||||
if(health > 3) { //active
|
||||
Success(
|
||||
VehicleData(
|
||||
PlacementData(obj.Position, obj.Orientation, obj.Velocity),
|
||||
obj.Faction,
|
||||
bops = false,
|
||||
destroyed = false,
|
||||
unk1 = 0,
|
||||
obj.Jammered,
|
||||
unk2 = false,
|
||||
obj.Owner match {
|
||||
case Some(owner) => owner
|
||||
case None => PlanetSideGUID(0)
|
||||
},
|
||||
unk3 = false,
|
||||
health,
|
||||
unk4 = false,
|
||||
no_mount_points = false,
|
||||
obj.DeploymentState,
|
||||
unk5 = false,
|
||||
unk6 = false,
|
||||
obj.Cloaked,
|
||||
SpecificFormatData(obj),
|
||||
Some(InventoryData(MakeDriverSeat(obj) ++ MakeUtilities(obj) ++ MakeMountings(obj)))
|
||||
)(SpecificFormatModifier)
|
||||
)
|
||||
}
|
||||
else { //destroyed
|
||||
Success(
|
||||
VehicleData(
|
||||
PlacementData(obj.Position, obj.Orientation),
|
||||
obj.Faction,
|
||||
bops = false,
|
||||
destroyed = true,
|
||||
unk1 = 0,
|
||||
jammered = false,
|
||||
unk2 = false,
|
||||
owner_guid = PlanetSideGUID(0),
|
||||
unk3 = false,
|
||||
health = 0,
|
||||
unk4 = false,
|
||||
no_mount_points = true,
|
||||
driveState = DriveState.Mobile,
|
||||
unk5 = false,
|
||||
unk6 = false,
|
||||
cloak = false,
|
||||
SpecificFormatData(obj),
|
||||
inventory = None
|
||||
)(SpecificFormatModifier)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private def MakeDriverSeat(obj : Vehicle) : List[InventoryItemData.InventoryItem] = {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
package net.psforever.objects.equipment
|
||||
|
||||
import net.psforever.objects.Tool
|
||||
import net.psforever.objects.ballistics.DamageProfile
|
||||
import net.psforever.objects.vital.damage.DamageProfile
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
|
|
|
|||
|
|
@ -96,18 +96,16 @@ class VehicleSpawnControl(pad : VehicleSpawnPad) extends VehicleSpawnControlBase
|
|||
}
|
||||
|
||||
case VehicleSpawnControl.ProcessControl.Flush =>
|
||||
if(!periodicReminder.isCancelled) {
|
||||
periodicReminder.cancel
|
||||
orders.foreach { VehicleSpawnControl.CancelOrder(_, Continent) }
|
||||
orders = Nil
|
||||
trackedOrder match {
|
||||
case Some(entry) =>
|
||||
VehicleSpawnControl.CancelOrder(entry, Continent)
|
||||
case None => ;
|
||||
}
|
||||
trackedOrder = None
|
||||
concealPlayer ! akka.actor.Kill //will cause the actor to restart, which will abort any trapped messages
|
||||
periodicReminder.cancel
|
||||
orders.foreach { VehicleSpawnControl.CancelOrder(_, Continent) }
|
||||
orders = Nil
|
||||
trackedOrder match {
|
||||
case Some(entry) =>
|
||||
VehicleSpawnControl.CancelOrder(entry, Continent)
|
||||
case None => ;
|
||||
}
|
||||
trackedOrder = None
|
||||
concealPlayer ! akka.actor.Kill //will cause the actor to restart, which will abort any trapped messages
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 net.psforever.objects.Vehicle
|
||||
import net.psforever.objects.ballistics.VehicleSource
|
||||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
|
||||
import net.psforever.objects.serverobject.deploy.DeploymentBehavior
|
||||
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
|
||||
import net.psforever.types.ExoSuitType
|
||||
|
||||
/**
|
||||
|
|
@ -59,6 +61,22 @@ class VehicleControl(vehicle : Vehicle) extends Actor
|
|||
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, seat_num))
|
||||
}
|
||||
|
||||
case Vitality.Damage(damage_func) =>
|
||||
if(vehicle.Health > 0) {
|
||||
damage_func(vehicle)
|
||||
sender ! Vitality.DamageResolution(vehicle)
|
||||
}
|
||||
|
||||
case Vehicle.ChargeShields(amount) =>
|
||||
val now : Long = System.nanoTime
|
||||
//make certain vehicle doesn't charge shields too quickly
|
||||
if(vehicle.Health > 0 && vehicle.Shields < vehicle.MaxShields &&
|
||||
!vehicle.History.exists(VehicleControl.LastShieldChargeOrDamage(now))) {
|
||||
vehicle.History(VehicleShieldCharge(VehicleSource(vehicle), amount))
|
||||
vehicle.Shields = vehicle.Shields + amount
|
||||
sender ! Vehicle.UpdateShieldsCharge(vehicle)
|
||||
}
|
||||
|
||||
case FactionAffinity.ConvertFactionAffinity(faction) =>
|
||||
val originalAffinity = vehicle.Faction
|
||||
if(originalAffinity != (vehicle.Faction = faction)) {
|
||||
|
|
@ -81,3 +99,24 @@ class VehicleControl(vehicle : Vehicle) extends Actor
|
|||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
object VehicleControl {
|
||||
import net.psforever.objects.vital.{DamageFromProjectile, VehicleShieldCharge, VitalsActivity}
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* Determine if a given activity entry would invalidate the act of charging vehicle shields this tick.
|
||||
* @param now the current time (in nanoseconds)
|
||||
* @param act a `VitalsActivity` entry to test
|
||||
* @return `true`, if the vehicle took damage in the last five seconds or
|
||||
* charged shields in the last second;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
def LastShieldChargeOrDamage(now : Long)(act : VitalsActivity) : Boolean = {
|
||||
act match {
|
||||
case DamageFromProjectile(data) => now - data.hit_time < (5 seconds).toNanos //damage delays next charge by 5s
|
||||
case vsc : VehicleShieldCharge => now - vsc.time < (1 seconds).toNanos //previous charge delays next by 1s
|
||||
case _ => false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
package net.psforever.objects.ballistics
|
||||
package net.psforever.objects.vital
|
||||
|
||||
/**
|
||||
* 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
|
||||
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.
|
||||
|
|
@ -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
|
||||
package net.psforever.packet.game
|
||||
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
|
||||
import net.psforever.types.PlanetSideEmpire
|
||||
import scodec.Codec
|
||||
|
|
@ -15,47 +16,43 @@ import scodec.codecs._
|
|||
* 3) victim information<br>
|
||||
* In the case of a player kill, the player's name will be attributed directly.
|
||||
* In the case of an absentee kill, a description of the method of death will be attributed.
|
||||
* In the case of a suicide, the player attributed is the player who was killed (message format displays only the victim).
|
||||
* The victim's name is byte-aligned with a 5-bit buffer.<br>
|
||||
* In the case of a suicide, the player attributed is the player who was killed (message format displays only the victim).<br>
|
||||
* <br>
|
||||
* The four bytes that follow each name seems to be important to the identification of the associated player.
|
||||
* The same value will be seen in every `DestroyDisplayMessage` that includes the player, with respect to whether they are listed as the "killer" or as the "victim."
|
||||
* This holds true for every entry within thie same login session, at least.
|
||||
* Blanking these values out does not change anything about the format of the event message.
|
||||
* In the case of absentee kills, for example, where there is no killer listed, this field has been zero'd (`00000000`).<br>
|
||||
* The same value will be seen in every `DestroyDisplayMessage` that includes that player,
|
||||
* with respect to whether they are listed as the "killer" or as the "victim."
|
||||
* This holds true for every entry within the same login session, at least.
|
||||
* Blanking either of these values out does not change anything about the format of the event message.
|
||||
* If the two ids match, the packet will interpreted as the "suicide" format, even if the names do not match.
|
||||
* In the case of absentee kills where there is no killer listed, this field is zero'd.<br>
|
||||
* <br>
|
||||
* The faction affiliation is different from the normal way `PlanetSideEmpire` values are recorded.
|
||||
* The higher nibble will reflect the first part of the `PlanetSideEmpire` value.
|
||||
* An extra `20` will be added if the player is in a vehicle or turret at the time.
|
||||
* When marked as being in a vehicle or turret, the player's name will be enclosed within square brackets.
|
||||
* The length of the player's name found at the start of the wide character string does not reflect whether or not there will be square brackets (fortunately).<br>
|
||||
* <br>
|
||||
* The two bytes in between the killer section and the victim section are the method of homicide or suicide.
|
||||
* The color of the resulting icon is borrowed from the attributed killer's faction affiliation if it can be determined.
|
||||
* An unidentified method defaults to a skull and crossbones icon.
|
||||
* The exact range of unique and valid icon values for this parameter is currently unknown.
|
||||
* It is also unknown what the two bytes preceding `method` specify, as changing them does nothing to the displayed message.
|
||||
* When marked as being in a vehicle or a turret, the player's name will be enclosed within square brackets.
|
||||
* The length of the player's name found at the start of the character string does not reflect
|
||||
* whether or not there will be square brackets (fortunately).
|
||||
* The color of the resulting icon is borrowed from the attributed killer's faction affiliation if it can be determined
|
||||
* and the type of icon is the same as an object id.
|
||||
* An unidentified method or a missing icon defaults to a skull and crossbones.
|
||||
* @param killer the name of the player who did the killing
|
||||
* @param killer_charId Same as CharacterInfoMessage
|
||||
* @param killer_charId same as CharacterInfoMessage
|
||||
* @param killer_empire the empire affiliation of the killer
|
||||
* @param killer_inVehicle true, if the killer was in a vehicle at the time of the kill; false, otherwise
|
||||
* @param killer_in_vehicle true, if the killer was in a vehicle at the time of the kill; false, otherwise
|
||||
* @param unk na; but does not like being set to 0
|
||||
* @param method modifies the icon in the message, related to the way the victim was killed
|
||||
* @param victim the name of the player who was killed
|
||||
* @param victim_charId Same as CharacterInfoMessage
|
||||
* @param victim_charId same as CharacterInfoMessage
|
||||
* @param victim_empire the empire affiliation of the victim
|
||||
* @param victim_inVehicle true, if the victim was in a vehicle when he was killed; false, otherwise
|
||||
* @param victim_in_vehicle true, if the victim was in a vehicle when he was killed; false, otherwise
|
||||
*/
|
||||
final case class DestroyDisplayMessage(killer : String,
|
||||
killer_charId : Long,
|
||||
killer_empire : PlanetSideEmpire.Value,
|
||||
killer_inVehicle : Boolean,
|
||||
killer_in_vehicle : Boolean,
|
||||
unk : Int,
|
||||
method : Int,
|
||||
victim : String,
|
||||
victim_charId : Long,
|
||||
victim_empire : PlanetSideEmpire.Value,
|
||||
victim_inVehicle : Boolean
|
||||
victim_in_vehicle : Boolean
|
||||
)
|
||||
extends PlanetSideGamePacket {
|
||||
type Packet = DestroyDisplayMessage
|
||||
|
|
@ -68,12 +65,12 @@ object DestroyDisplayMessage extends Marshallable[DestroyDisplayMessage] {
|
|||
("killer" | PacketHelpers.encodedWideString) ::
|
||||
("killer_charId" | ulongL(32)) ::
|
||||
("killer_empire" | PlanetSideEmpire.codec) ::
|
||||
("killer_inVehicle" | bool) ::
|
||||
("killer_in_vehicle" | bool) ::
|
||||
("unk" | uint16L) ::
|
||||
("method" | uint16L) ::
|
||||
("victim" | PacketHelpers.encodedWideStringAligned(5)) ::
|
||||
("victim_charId" | ulongL(32)) ::
|
||||
("victim_empire" | PlanetSideEmpire.codec) ::
|
||||
("victim_inVehicle" | bool)
|
||||
("victim_in_vehicle" | bool)
|
||||
).as[DestroyDisplayMessage]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -156,6 +156,10 @@ final case class PlanetsideAttributeMessage(player_guid : PlanetSideGUID,
|
|||
}
|
||||
|
||||
object PlanetsideAttributeMessage extends Marshallable[PlanetsideAttributeMessage] {
|
||||
def apply(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Int) : PlanetsideAttributeMessage = {
|
||||
PlanetsideAttributeMessage(player_guid, attribute_type, attribute_value.toLong)
|
||||
}
|
||||
|
||||
implicit val codec : Codec[PlanetsideAttributeMessage] = (
|
||||
("player_guid" | PlanetSideGUID.codec) ::
|
||||
("attribute_type" | uint8L) ::
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.avatar
|
||||
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.{PlanetSideGameObject, Player}
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.inventory.Container
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game.{CargoMountPointStatusMessage, PlanetSideGUID, PlayerStateMessageUpstream}
|
||||
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
|
||||
import net.psforever.packet.game.objectcreate.{ConstructorData, ObjectCreateMessageParent}
|
||||
import net.psforever.types.ExoSuitType
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
|
|
@ -21,9 +22,14 @@ object AvatarAction {
|
|||
final case class ChangeFireState_Start(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
|
||||
final case class ChangeFireState_Stop(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
|
||||
final case class ConcealPlayer(player_guid : PlanetSideGUID) extends Action
|
||||
final case class Damage(player_guid : PlanetSideGUID, target : Player, resolution_function : (Any)=>Unit) extends Action
|
||||
final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Action
|
||||
final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int = 121) extends Action
|
||||
final case class DropItem(player_guid : PlanetSideGUID, item : Equipment, zone : Zone) extends Action
|
||||
final case class EquipmentInHand(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
|
||||
final case class HitHint(source_guid : PlanetSideGUID, player_guid : PlanetSideGUID) extends Action
|
||||
final case class LoadPlayer(player_guid : PlanetSideGUID, object_id : Int, target_guid : PlanetSideGUID, cdata : ConstructorData, pdata : Option[ObjectCreateMessageParent]) extends Action
|
||||
final case class KilledWhileInVehicle(player_guid : PlanetSideGUID) extends Action
|
||||
final case class ObjectDelete(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID, unk : Int = 0) extends Action
|
||||
final case class ObjectHeld(player_guid : PlanetSideGUID, slot : Int) extends Action
|
||||
final case class PlanetsideAttribute(player_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action
|
||||
|
|
@ -38,5 +44,4 @@ object AvatarAction {
|
|||
|
||||
// final case class PlayerStateShift(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
// final case class DestroyDisplay(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
// final case class HitHintReturn(killer : PlanetSideGUID, victim : PlanetSideGUID) extends Action
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,11 +2,12 @@
|
|||
package services.avatar
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.ballistics.SourceEntry
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game.{ObjectCreateMessage, PlanetSideGUID, PlayerStateMessageUpstream}
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
import net.psforever.types.ExoSuitType
|
||||
import net.psforever.types.{ExoSuitType, Vector3}
|
||||
|
||||
object AvatarResponse {
|
||||
trait Response
|
||||
|
|
@ -17,8 +18,13 @@ object AvatarResponse {
|
|||
final case class ChangeFireState_Start(weapon_guid : PlanetSideGUID) extends Response
|
||||
final case class ChangeFireState_Stop(weapon_guid : PlanetSideGUID) extends Response
|
||||
final case class ConcealPlayer() extends Response
|
||||
final case class EquipmentInHand(pkt : ObjectCreateMessage) extends Response
|
||||
final case class DamageResolution(target : Player, resolution_function : (Any)=>Unit) extends Response
|
||||
final case class Destroy(victim : PlanetSideGUID, killer : PlanetSideGUID, weapon : PlanetSideGUID, pos : Vector3) extends Response
|
||||
final case class DestroyDisplay(killer : SourceEntry, victim : SourceEntry, method : Int, unk : Int) extends Response
|
||||
final case class DropItem(pkt : ObjectCreateMessage) extends Response
|
||||
final case class EquipmentInHand(pkt : ObjectCreateMessage) extends Response
|
||||
final case class HitHint(source_guid : PlanetSideGUID) extends Response
|
||||
final case class KilledWhileInVehicle() extends Response
|
||||
final case class LoadPlayer(pkt : ObjectCreateMessage) extends Response
|
||||
final case class ObjectDelete(item_guid : PlanetSideGUID, unk : Int) extends Response
|
||||
final case class ObjectHeld(slot : Int) extends Response
|
||||
|
|
@ -31,6 +37,4 @@ object AvatarResponse {
|
|||
|
||||
final case class SendResponse(msg: PlanetSideGamePacket) extends Response
|
||||
// final case class PlayerStateShift(itemID : PlanetSideGUID) extends Response
|
||||
// final case class DestroyDisplay(itemID : PlanetSideGUID) extends Response
|
||||
// final case class HitHintReturn(itemID : PlanetSideGUID) extends Response
|
||||
}
|
||||
|
|
|
|||
|
|
@ -65,6 +65,18 @@ class AvatarService extends Actor {
|
|||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.ConcealPlayer())
|
||||
)
|
||||
case AvatarAction.Damage(player_guid, target, resolution_function) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.DamageResolution(target, resolution_function))
|
||||
)
|
||||
case AvatarAction.Destroy(victim, killer, weapon, pos) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", victim, AvatarResponse.Destroy(victim, killer, weapon, pos))
|
||||
)
|
||||
case AvatarAction.DestroyDisplay(killer, victim, method, unk) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", Service.defaultPlayerGUID, AvatarResponse.DestroyDisplay(killer, victim, method, unk))
|
||||
)
|
||||
case AvatarAction.DropItem(player_guid, item, zone) =>
|
||||
val definition = item.Definition
|
||||
val objectData = DroppedItemData(
|
||||
|
|
@ -86,6 +98,14 @@ class AvatarService extends Actor {
|
|||
AvatarResponse.EquipmentInHand(ObjectCreateMessage(definition.ObjectId, item.GUID, containerData, objectData))
|
||||
)
|
||||
)
|
||||
case AvatarAction.HitHint(source_guid, player_guid) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.HitHint(source_guid))
|
||||
)
|
||||
case AvatarAction.KilledWhileInVehicle(player_guid) =>
|
||||
AvatarEvents.publish(
|
||||
AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarResponse.KilledWhileInVehicle())
|
||||
)
|
||||
case AvatarAction.LoadPlayer(player_guid, object_id, target_guid, cdata, pdata) =>
|
||||
val pkt = pdata match {
|
||||
case Some(data) =>
|
||||
|
|
@ -169,22 +189,6 @@ class AvatarService extends Actor {
|
|||
AvatarServiceReply.PlayerStateShift(killer)
|
||||
))
|
||||
}
|
||||
case AvatarService.DestroyDisplay(killer, victim) =>
|
||||
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(victim)
|
||||
if (playerOpt.isDefined) {
|
||||
val player: PlayerAvatar = playerOpt.get
|
||||
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, victim,
|
||||
AvatarServiceReply.DestroyDisplay(killer)
|
||||
))
|
||||
}
|
||||
case AvatarService.HitHintReturn(source_guid,victim_guid) =>
|
||||
val playerOpt: Option[PlayerAvatar] = PlayerMasterList.getPlayer(source_guid)
|
||||
if (playerOpt.isDefined) {
|
||||
val player: PlayerAvatar = playerOpt.get
|
||||
AvatarEvents.publish(AvatarMessage("/Avatar/" + player.continent, victim_guid,
|
||||
AvatarServiceReply.DestroyDisplay(source_guid)
|
||||
))
|
||||
}
|
||||
*/
|
||||
case msg =>
|
||||
log.warn(s"Unhandled message $msg from $sender")
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ object VehicleAction {
|
|||
final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Action
|
||||
final case class LoadVehicle(player_guid : PlanetSideGUID, vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Action
|
||||
final case class MountVehicle(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, seat : Int) extends Action
|
||||
final case class ObjectDelete(player_guid : PlanetSideGUID, weapon_guid : PlanetSideGUID) extends Action
|
||||
final case class Ownership(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID) extends Action
|
||||
final case class PlanetsideAttribute(player_guid : PlanetSideGUID, target_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Action
|
||||
final case class SeatPermissions(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Action
|
||||
final case class StowEquipment(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
|
||||
final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) extends Action
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package services.vehicle
|
||||
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.{PlanetSideGameObject, Vehicle}
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
|
|
@ -19,12 +18,14 @@ object VehicleResponse {
|
|||
final case class DetachFromRails(vehicle_guid : PlanetSideGUID, rails_guid : PlanetSideGUID, rails_pos : Vector3, rails_rot : Float) extends Response
|
||||
final case class DismountVehicle(bailType : BailType.Value , unk2 : Boolean) extends Response
|
||||
final case class EquipmentInSlot(pkt : ObjectCreateMessage) extends Response
|
||||
final case class HitHint(source_guid : PlanetSideGUID) extends Response
|
||||
final case class InventoryState(obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Response
|
||||
final case class InventoryState2(obj_guid : PlanetSideGUID, parent_guid : PlanetSideGUID, value : Int) extends Response
|
||||
final case class KickPassenger(seat_num : Int, kickedByDriver : Boolean, vehicle_guid : PlanetSideGUID) extends Response
|
||||
final case class LoadVehicle(vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Response
|
||||
final case class MountVehicle(object_guid : PlanetSideGUID, seat : Int) extends Response
|
||||
final case class Ownership(vehicle_guid : PlanetSideGUID) extends Response
|
||||
final case class PlanetsideAttribute(vehicle_guid : PlanetSideGUID, attribute_type : Int, attribute_value : Long) extends Response
|
||||
final case class ResetSpawnPad(pad_guid : PlanetSideGUID) extends Response
|
||||
final case class RevealPlayer(player_guid : PlanetSideGUID) extends Response
|
||||
final case class SeatPermissions(vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Response
|
||||
|
|
|
|||
|
|
@ -92,6 +92,10 @@ class VehicleService extends Actor {
|
|||
VehicleEvents.publish(
|
||||
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.Ownership(vehicle_guid))
|
||||
)
|
||||
case VehicleAction.PlanetsideAttribute(exclude_guid, target_guid, attribute_type, attribute_value) =>
|
||||
VehicleEvents.publish(
|
||||
VehicleServiceResponse(s"/$forChannel/Vehicle", exclude_guid, VehicleResponse.PlanetsideAttribute(target_guid, attribute_type, attribute_value))
|
||||
)
|
||||
case VehicleAction.SeatPermissions(player_guid, vehicle_guid, seat_group, permission) =>
|
||||
VehicleEvents.publish(
|
||||
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package objects
|
||||
|
||||
import net.psforever.objects.definition.converter.{ACEConverter, CharacterSelectConverter, REKConverter}
|
||||
import net.psforever.objects.definition.converter.{ACEConverter, CharacterSelectConverter, DestroyedVehicleConverter, REKConverter}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.definition._
|
||||
import net.psforever.objects.equipment.CItem.{DeployedItem, Unit}
|
||||
|
|
@ -9,6 +9,7 @@ import net.psforever.objects.equipment._
|
|||
import net.psforever.objects.inventory.InventoryTile
|
||||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.vehicles.DestroyedVehicle
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, Vector3}
|
||||
|
|
@ -368,7 +369,40 @@ class ConverterTest extends Specification {
|
|||
ams.Utilities(4)().GUID = PlanetSideGUID(417)
|
||||
|
||||
ams.Definition.Packet.ConstructorData(ams).isSuccess mustEqual true
|
||||
ok //TODO write more of this test
|
||||
}
|
||||
|
||||
"convert to packet (3)" in {
|
||||
val
|
||||
ams = Vehicle(GlobalDefinitions.ams)
|
||||
ams.GUID = PlanetSideGUID(413)
|
||||
ams.Health = 0 //destroyed vehicle
|
||||
|
||||
ams.Definition.Packet.ConstructorData(ams).isSuccess mustEqual true
|
||||
//did not initialize the utilities, but the converter did not fail
|
||||
}
|
||||
}
|
||||
|
||||
"DestroyedVehicle" should {
|
||||
"not convert a working vehicle" in {
|
||||
val ams = Vehicle(GlobalDefinitions.ams)
|
||||
ams.GUID = PlanetSideGUID(413)
|
||||
ams.Health mustEqual 3000 //not destroyed vehicle
|
||||
DestroyedVehicleConverter.converter.ConstructorData(ams).isFailure mustEqual true
|
||||
}
|
||||
|
||||
"convert to packet" in {
|
||||
val ams = Vehicle(GlobalDefinitions.ams)
|
||||
ams.GUID = PlanetSideGUID(413)
|
||||
ams.Health = 0
|
||||
DestroyedVehicleConverter.converter.ConstructorData(ams).isSuccess mustEqual true
|
||||
//did not initialize the utilities, but the converter did not fail
|
||||
}
|
||||
|
||||
"not convert into a detailed packet" in {
|
||||
val ams = Vehicle(GlobalDefinitions.ams)
|
||||
ams.GUID = PlanetSideGUID(413)
|
||||
ams.Health = 0
|
||||
DestroyedVehicleConverter.converter.DetailedConstructorData(ams).isFailure mustEqual true
|
||||
}
|
||||
}
|
||||
}
|
||||
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
|
||||
package objects
|
||||
|
||||
import net.psforever.objects.{GlobalDefinitions, LocalProjectile, Tool}
|
||||
import net.psforever.objects.ballistics.{DamageType, Projectile, ProjectileResolution, Projectiles}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.definition.ProjectileDefinition
|
||||
import net.psforever.types.Vector3
|
||||
import net.psforever.objects.serverobject.mblocker.Locker
|
||||
import net.psforever.objects.vital.DamageType
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import net.psforever.types._
|
||||
import org.specs2.mutable.Specification
|
||||
|
||||
class ProjectileTest extends Specification {
|
||||
val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
|
||||
val fury = Vehicle(GlobalDefinitions.fury)
|
||||
|
||||
"LocalProjectile" should {
|
||||
"construct" in {
|
||||
val obj = new LocalProjectile() //since they're just placeholders, they only need to construct
|
||||
obj.Definition.ObjectId mustEqual 0
|
||||
obj.Definition.Name mustEqual "projectile"
|
||||
}
|
||||
|
||||
"local projectile range" in {
|
||||
Projectile.BaseUID < Projectile.RangeUID mustEqual true
|
||||
}
|
||||
}
|
||||
|
||||
"ProjectileDefinition" should {
|
||||
|
|
@ -151,48 +161,156 @@ class ProjectileTest extends Specification {
|
|||
}
|
||||
}
|
||||
|
||||
"Projectile" should {
|
||||
"construct" in {
|
||||
val beamer_wep = Tool(GlobalDefinitions.beamer)
|
||||
val projectile = beamer_wep.Projectile
|
||||
val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
|
||||
"SourceEntry" should {
|
||||
"construct for players" in {
|
||||
SourceEntry(player) match {
|
||||
case o : PlayerSource =>
|
||||
o.Name mustEqual "TestCharacter"
|
||||
o.Faction mustEqual PlanetSideEmpire.TR
|
||||
o.Seated mustEqual false
|
||||
o.ExoSuit mustEqual ExoSuitType.Standard
|
||||
o.Health mustEqual 0
|
||||
o.Armor mustEqual 0
|
||||
o.Definition mustEqual GlobalDefinitions.avatar
|
||||
o.Position mustEqual Vector3.Zero
|
||||
o.Orientation mustEqual Vector3.Zero
|
||||
o.Velocity mustEqual None
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
obj.profile mustEqual beamer_wep.Projectile
|
||||
obj.tool_def mustEqual GlobalDefinitions.beamer
|
||||
"construct for vehicles" in {
|
||||
SourceEntry(fury) match {
|
||||
case o : VehicleSource =>
|
||||
o.Name mustEqual "Fury"
|
||||
o.Faction mustEqual PlanetSideEmpire.TR
|
||||
o.Definition mustEqual GlobalDefinitions.fury
|
||||
o.Health mustEqual 650
|
||||
o.Shields mustEqual 0
|
||||
o.Position mustEqual Vector3.Zero
|
||||
o.Orientation mustEqual Vector3.Zero
|
||||
o.Velocity mustEqual None
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"construct for generic object" in {
|
||||
val obj = Locker()
|
||||
SourceEntry(obj) match {
|
||||
case o : ObjectSource =>
|
||||
o.obj mustEqual obj
|
||||
o.Name mustEqual "Mb Locker"
|
||||
o.Faction mustEqual PlanetSideEmpire.NEUTRAL
|
||||
o.Definition mustEqual GlobalDefinitions.mb_locker
|
||||
o.Position mustEqual Vector3.Zero
|
||||
o.Orientation mustEqual Vector3.Zero
|
||||
o.Velocity mustEqual None
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
|
||||
"contain timely information" in {
|
||||
val obj = Player(Avatar("TestCharacter-alt", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute))
|
||||
obj.VehicleSeated = Some(PlanetSideGUID(1))
|
||||
obj.Position = Vector3(1.2f, 3.4f, 5.6f)
|
||||
obj.Orientation = Vector3(2.1f, 4.3f, 6.5f)
|
||||
obj.Velocity = Some(Vector3(1.1f, 2.2f, 3.3f))
|
||||
val sobj = SourceEntry(obj)
|
||||
obj.VehicleSeated = None
|
||||
obj.Position = Vector3.Zero
|
||||
obj.Orientation = Vector3.Zero
|
||||
obj.Velocity = None
|
||||
obj.ExoSuit = ExoSuitType.Agile
|
||||
|
||||
sobj match {
|
||||
case o : PlayerSource =>
|
||||
o.Name mustEqual "TestCharacter-alt"
|
||||
o.Faction mustEqual PlanetSideEmpire.TR
|
||||
o.Seated mustEqual true
|
||||
o.ExoSuit mustEqual ExoSuitType.Standard
|
||||
o.Definition mustEqual GlobalDefinitions.avatar
|
||||
o.Position mustEqual Vector3(1.2f, 3.4f, 5.6f)
|
||||
o.Orientation mustEqual Vector3(2.1f, 4.3f, 6.5f)
|
||||
o.Velocity mustEqual Some(Vector3(1.1f, 2.2f, 3.3f))
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"Projectile" should {
|
||||
val beamer_def = GlobalDefinitions.beamer
|
||||
val beamer_wep = Tool(beamer_def)
|
||||
val firemode = beamer_wep.FireMode
|
||||
val projectile = beamer_wep.Projectile
|
||||
|
||||
"construct" in {
|
||||
val obj = Projectile(beamer_wep.Projectile, beamer_wep.Definition, beamer_wep.FireMode, PlayerSource(player), beamer_def.ObjectId, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
|
||||
obj.profile mustEqual projectile
|
||||
obj.tool_def mustEqual beamer_def
|
||||
obj.fire_mode mustEqual firemode
|
||||
obj.owner match {
|
||||
case _ : PlayerSource =>
|
||||
ok
|
||||
case _ =>
|
||||
ko
|
||||
}
|
||||
obj.attribute_to mustEqual obj.tool_def.ObjectId
|
||||
obj.shot_origin mustEqual Vector3(1.2f, 3.4f, 5.6f)
|
||||
obj.shot_angle mustEqual Vector3(0.2f, 0.4f, 0.6f)
|
||||
obj.resolution mustEqual ProjectileResolution.Unresolved
|
||||
obj.fire_time <= System.nanoTime mustEqual true
|
||||
obj.hit_time mustEqual 0
|
||||
obj.isResolved mustEqual false
|
||||
}
|
||||
|
||||
"construct (different attribute)" in {
|
||||
val obj1 = Projectile(beamer_wep.Projectile, beamer_wep.Definition, beamer_wep.FireMode, player, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
|
||||
obj1.attribute_to mustEqual obj1.tool_def.ObjectId
|
||||
|
||||
val obj2 = Projectile(beamer_wep.Projectile, beamer_wep.Definition, beamer_wep.FireMode, PlayerSource(player), 65, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
|
||||
obj2.attribute_to == obj2.tool_def.ObjectId mustEqual false
|
||||
obj2.attribute_to mustEqual 65
|
||||
}
|
||||
|
||||
"resolve" in {
|
||||
val beamer_wep = Tool(GlobalDefinitions.beamer)
|
||||
val projectile = beamer_wep.Projectile
|
||||
val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
|
||||
val obj2 = obj.Resolve(ProjectileResolution.MissedShot)
|
||||
val obj = Projectile(projectile, beamer_def, firemode, PlayerSource(player), beamer_def.ObjectId, Vector3.Zero, Vector3.Zero)
|
||||
obj.isResolved mustEqual false
|
||||
obj.isMiss mustEqual false
|
||||
|
||||
obj.resolution mustEqual ProjectileResolution.Unresolved
|
||||
obj.fire_time <= System.nanoTime mustEqual true
|
||||
obj.hit_time mustEqual 0
|
||||
obj2.resolution mustEqual ProjectileResolution.MissedShot
|
||||
obj2.fire_time == obj.fire_time mustEqual true
|
||||
obj2.hit_time <= System.nanoTime mustEqual true
|
||||
obj2.fire_time <= obj2.hit_time mustEqual true
|
||||
obj.Resolve()
|
||||
obj.isResolved mustEqual true
|
||||
obj.isMiss mustEqual false
|
||||
}
|
||||
|
||||
"resolve, with coordinates" in {
|
||||
val beamer_wep = Tool(GlobalDefinitions.beamer)
|
||||
val projectile = beamer_wep.Projectile
|
||||
val obj = Projectile(projectile, beamer_wep.Definition, Vector3(1.2f, 3.4f, 5.6f), Vector3(0.2f, 0.4f, 0.6f))
|
||||
val obj2 = obj.Resolve(Vector3(7.2f, 8.4f, 9.6f), Vector3(1.2f, 1.4f, 1.6f), ProjectileResolution.Resolved)
|
||||
"missed" in {
|
||||
val obj = Projectile(projectile, beamer_def, firemode, PlayerSource(player), beamer_def.ObjectId, Vector3.Zero, Vector3.Zero)
|
||||
obj.isResolved mustEqual false
|
||||
obj.isMiss mustEqual false
|
||||
|
||||
obj.resolution mustEqual ProjectileResolution.Unresolved
|
||||
obj.current.Position mustEqual Vector3.Zero
|
||||
obj.current.Orientation mustEqual Vector3.Zero
|
||||
obj2.resolution mustEqual ProjectileResolution.Resolved
|
||||
obj2.current.Position mustEqual Vector3(7.2f, 8.4f, 9.6f)
|
||||
obj2.current.Orientation mustEqual Vector3(1.2f, 1.4f, 1.6f)
|
||||
obj.Miss()
|
||||
obj.isResolved mustEqual true
|
||||
obj.isMiss mustEqual true
|
||||
}
|
||||
}
|
||||
|
||||
"ResolvedProjectile" should {
|
||||
val beamer_wep = Tool(GlobalDefinitions.beamer)
|
||||
val p_source = PlayerSource(player)
|
||||
val player2 = Player(Avatar("TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute))
|
||||
val p2_source = PlayerSource(player2)
|
||||
val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero)
|
||||
val fury_dm = fury.DamageModel
|
||||
|
||||
"construct" in {
|
||||
val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, PlayerSource(player2), fury_dm, Vector3(1.2f, 3.4f, 5.6f), 123456L)
|
||||
obj.resolution mustEqual ProjectileResolution.Hit
|
||||
obj.projectile mustEqual projectile
|
||||
obj.target mustEqual p2_source
|
||||
obj.damage_model mustEqual fury.DamageModel
|
||||
obj.hit_pos mustEqual Vector3(1.2f, 3.4f, 5.6f)
|
||||
obj.hit_time mustEqual 123456L
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,14 +4,16 @@ package objects
|
|||
import akka.actor.Props
|
||||
import base.ActorTest
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.ballistics.{PlayerSource, Projectile, ProjectileResolution, ResolvedProjectile}
|
||||
import net.psforever.objects.definition.{SeatDefinition, VehicleDefinition}
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
import net.psforever.types.{CharacterVoice, ExoSuitType}
|
||||
import net.psforever.types._
|
||||
import org.specs2.mutable._
|
||||
|
||||
import scala.concurrent.duration.Duration
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class VehicleTest extends Specification {
|
||||
import VehicleTest._
|
||||
|
|
@ -611,6 +613,103 @@ class VehicleControlMountingOwnedUnlockedDriverSeatTest extends ActorTest {
|
|||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsChargingTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
|
||||
"charge vehicle shields" in {
|
||||
assert(vehicle.Shields == 0)
|
||||
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
|
||||
val msg = receiveOne(500 milliseconds)
|
||||
assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge])
|
||||
assert(vehicle.Shields == 15)
|
||||
assert(vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsNotChargingVehicleDeadTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
|
||||
"not charge vehicle shields if the vehicle is destroyed" in {
|
||||
assert(vehicle.Health > 0)
|
||||
vehicle.Health = 0
|
||||
assert(vehicle.Health == 0)
|
||||
assert(vehicle.Shields == 0)
|
||||
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
|
||||
expectNoMsg(1 seconds)
|
||||
assert(vehicle.Shields == 0)
|
||||
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsNotChargingVehicleShieldsFullTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
|
||||
"not charge vehicle shields if the vehicle is destroyed" in {
|
||||
assert(vehicle.Shields == 0)
|
||||
vehicle.Shields = vehicle.MaxShields
|
||||
assert(vehicle.Shields == vehicle.MaxShields)
|
||||
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
|
||||
expectNoMsg(1 seconds)
|
||||
assert(!vehicle.History.exists({p => p.isInstanceOf[VehicleShieldCharge]}))
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
|
||||
"charge vehicle shields" in {
|
||||
assert(vehicle.Shields == 0)
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
|
||||
val msg = receiveOne(200 milliseconds)
|
||||
assert(msg.isInstanceOf[Vehicle.UpdateShieldsCharge])
|
||||
assert(vehicle.Shields == 15)
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
|
||||
expectNoMsg(200 milliseconds)
|
||||
assert(vehicle.Shields == 15)
|
||||
}
|
||||
}
|
||||
|
||||
class VehicleControlShieldsNotChargingDamagedTest extends ActorTest {
|
||||
val vehicle = Vehicle(GlobalDefinitions.fury)
|
||||
vehicle.GUID = PlanetSideGUID(10)
|
||||
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-test")
|
||||
//
|
||||
val beamer_wep = Tool(GlobalDefinitions.beamer)
|
||||
val p_source = PlayerSource( Player(Avatar("TestTarget", PlanetSideEmpire.NC, CharacterGender.Female, 1, CharacterVoice.Mute)) )
|
||||
val projectile = Projectile(beamer_wep.Projectile, GlobalDefinitions.beamer, beamer_wep.FireMode, p_source, GlobalDefinitions.beamer.ObjectId, Vector3.Zero, Vector3.Zero)
|
||||
val fury_dm = Vehicle(GlobalDefinitions.fury).DamageModel
|
||||
val obj = ResolvedProjectile(ProjectileResolution.Hit, projectile, p_source, fury_dm, Vector3(1.2f, 3.4f, 5.6f), System.nanoTime)
|
||||
|
||||
"charge vehicle shields" in {
|
||||
assert(vehicle.Shields == 0)
|
||||
vehicle.Actor ! Vitality.Damage({case v : Vehicle => v.History(obj)})
|
||||
|
||||
val msg = receiveOne(200 milliseconds)
|
||||
assert(msg.isInstanceOf[Vitality.DamageResolution])
|
||||
assert(vehicle.Shields == 0)
|
||||
vehicle.Actor ! Vehicle.ChargeShields(15)
|
||||
|
||||
expectNoMsg(200 milliseconds)
|
||||
assert(vehicle.Shields == 0)
|
||||
}
|
||||
}
|
||||
|
||||
object VehicleTest {
|
||||
import net.psforever.objects.Avatar
|
||||
import net.psforever.types.{CharacterGender, PlanetSideEmpire}
|
||||
|
|
|
|||
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._
|
||||
|
||||
class StandardRemoverActorTest extends ActorTest {
|
||||
ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
|
||||
"RemoverActor" should {
|
||||
"handle a simple task" in {
|
||||
expectNoMsg(500 milliseconds)
|
||||
val probe = TestProbe()
|
||||
val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere)
|
||||
|
||||
val reply1 = probe.receiveOne(200 milliseconds)
|
||||
assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert])
|
||||
val reply2 = probe.receiveOne(200 milliseconds)
|
||||
assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert])
|
||||
probe.expectNoMsg(1 seconds) //delay
|
||||
val reply3 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||
val reply4 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||
val reply5 = probe.receiveOne(300 milliseconds)
|
||||
assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||
val reply6 = probe.receiveOne(500 milliseconds)
|
||||
assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||
val reply7 = probe.receiveOne(500 milliseconds)
|
||||
assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||
}
|
||||
}
|
||||
}
|
||||
//class StandardRemoverActorTest extends ActorTest {
|
||||
// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
//
|
||||
// "RemoverActor" should {
|
||||
// "handle a simple task" in {
|
||||
// expectNoMsg(500 milliseconds)
|
||||
// val probe = TestProbe()
|
||||
// val remover = system.actorOf(Props(classOf[RemoverActorTest.TestRemover], probe), "test-remover")
|
||||
// remover ! RemoverActor.AddTask(RemoverActorTest.TestObject, Zone.Nowhere)
|
||||
//
|
||||
// val reply1 = probe.receiveOne(200 milliseconds)
|
||||
// assert(reply1.isInstanceOf[RemoverActorTest.InclusionTestAlert])
|
||||
// val reply2 = probe.receiveOne(200 milliseconds)
|
||||
// assert(reply2.isInstanceOf[RemoverActorTest.InitialJobAlert])
|
||||
// probe.expectNoMsg(1 seconds) //delay
|
||||
// val reply3 = probe.receiveOne(300 milliseconds)
|
||||
// assert(reply3.isInstanceOf[RemoverActorTest.FirstJobAlert])
|
||||
// val reply4 = probe.receiveOne(300 milliseconds)
|
||||
// assert(reply4.isInstanceOf[RemoverActorTest.ClearanceTestAlert])
|
||||
// val reply5 = probe.receiveOne(300 milliseconds)
|
||||
// assert(reply5.isInstanceOf[RemoverActorTest.SecondJobAlert])
|
||||
// val reply6 = probe.receiveOne(500 milliseconds)
|
||||
// assert(reply6.isInstanceOf[RemoverActorTest.DeletionTaskAlert])
|
||||
// val reply7 = probe.receiveOne(500 milliseconds)
|
||||
// assert(reply7.isInstanceOf[RemoverActorTest.DeletionTaskRunAlert])
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//class DelayedRemoverActorTest extends ActorTest {
|
||||
// ServiceManager.boot ! ServiceManager.Register(RandomPool(2).props(Props[TaskResolver]), "taskResolver")
|
||||
|
|
|
|||
|
|
@ -13,8 +13,9 @@ import csr.{CSRWarp, CSRZone, Traveler}
|
|||
import net.psforever.objects.GlobalDefinitions._
|
||||
import services.ServiceManager.Lookup
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.ballistics._
|
||||
import net.psforever.objects.definition.ToolDefinition
|
||||
import net.psforever.objects.definition.converter.CorpseConverter
|
||||
import net.psforever.objects.definition.converter.{CorpseConverter, DestroyedVehicleConverter}
|
||||
import net.psforever.objects.equipment._
|
||||
import net.psforever.objects.loadouts._
|
||||
import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver}
|
||||
|
|
@ -35,7 +36,9 @@ import net.psforever.objects.serverobject.structures.{Building, StructureType, W
|
|||
import net.psforever.objects.serverobject.terminals._
|
||||
import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage
|
||||
import net.psforever.objects.serverobject.tube.SpawnTube
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.serverobject.turret.{MannedTurret, TurretUpgrade}
|
||||
import net.psforever.objects.vehicles.{AccessPermissionGroup, Cargo, Utility, VehicleLockState, _}
|
||||
import net.psforever.objects.vital._
|
||||
import net.psforever.objects.zones.{InterstellarCluster, Zone}
|
||||
import net.psforever.packet.game.objectcreate._
|
||||
import net.psforever.types._
|
||||
|
|
@ -53,8 +56,6 @@ import scala.concurrent.Future
|
|||
import scala.concurrent.duration._
|
||||
import scala.util.Success
|
||||
import akka.pattern.ask
|
||||
import net.psforever.objects.ballistics.{Projectile, ProjectileResolution}
|
||||
import net.psforever.objects.serverobject.turret.{MannedTurret, TurretUpgrade}
|
||||
|
||||
class WorldSessionActor extends Actor with MDCContextAware {
|
||||
import WorldSessionActor._
|
||||
|
|
@ -109,6 +110,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0
|
||||
|
||||
override def postStop() = {
|
||||
//TODO normally, player avatar persists a minute or so after disconnect; we are subject to the SessionReaper
|
||||
clientKeepAlive.cancel
|
||||
reviveTimer.cancel
|
||||
respawnTimer.cancel
|
||||
|
|
@ -133,20 +135,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
})
|
||||
|
||||
//TODO final character save before doing any of this
|
||||
continent.Population ! Zone.Population.Release(avatar)
|
||||
if(player.isAlive) {
|
||||
//actually being alive or manually deconstructing
|
||||
DismountVehicleOnLogOut()
|
||||
continent.Population ! Zone.Population.Release(avatar)
|
||||
player.Position = Vector3.Zero //save character before doing this
|
||||
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectDelete(player_guid, player_guid))
|
||||
player.Position = Vector3.Zero
|
||||
if(player.VehicleSeated.nonEmpty) {
|
||||
//quickly and briefly kill player to avoid disembark animation
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(player_guid, 0, 0))
|
||||
DismountVehicleOnLogOut()
|
||||
}
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid))
|
||||
taskResolver ! GUIDTask.UnregisterAvatar(player)(continent.GUID)
|
||||
//TODO normally, the actual player avatar persists a minute or so after the user disconnects
|
||||
}
|
||||
else if(continent.LivePlayers.contains(player) && !continent.Corpses.contains(player)) {
|
||||
//player disconnected while waiting for a revive
|
||||
//similar to handling ReleaseAvatarRequestMessage
|
||||
player.Release
|
||||
continent.Population ! Zone.Population.Release(avatar)
|
||||
player.VehicleSeated match {
|
||||
case None =>
|
||||
FriskCorpse(player) //TODO eliminate dead letters
|
||||
|
|
@ -189,7 +194,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case Some(vehicle : Vehicle) =>
|
||||
vehicle.Seat(vehicle.PassengerInSeat(player).get).get.Occupant = None
|
||||
if(vehicle.Seats.values.count(_.isOccupied) == 0) {
|
||||
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent), vehicle.Definition.DeconstructionTime) //start vehicle decay
|
||||
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(vehicle, continent, vehicle.Definition.DeconstructionTime)) //start vehicle decay
|
||||
}
|
||||
vehicleService ! Service.Leave(Some(s"${vehicle.Actor}"))
|
||||
|
||||
|
|
@ -392,7 +397,30 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(GenericObjectActionMessage(guid, 36))
|
||||
}
|
||||
|
||||
case msg @ AvatarResponse.DropItem(pkt) =>
|
||||
case AvatarResponse.DamageResolution(target, resolution_function) =>
|
||||
if(player.isAlive) {
|
||||
resolution_function(target)
|
||||
|
||||
val health = player.Health
|
||||
val armor = player.Armor
|
||||
val playerGUID = player.GUID
|
||||
sendResponse(PlanetsideAttributeMessage(playerGUID, 0, health))
|
||||
sendResponse(PlanetsideAttributeMessage(playerGUID, 4, armor))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 0, health))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(playerGUID, 4, armor))
|
||||
if(health == 0 && player.isAlive) {
|
||||
KillPlayer(player)
|
||||
}
|
||||
}
|
||||
|
||||
case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
|
||||
// guid = victim // killer = killer ;)
|
||||
sendResponse(DestroyMessage(victim, killer, weapon, pos))
|
||||
|
||||
case AvatarResponse.DestroyDisplay(killer, victim, method, unk) =>
|
||||
sendResponse(DestroyDisplayMessage(killer, victim, method, unk))
|
||||
|
||||
case AvatarResponse.DropItem(pkt) =>
|
||||
if(tplayer_guid != guid) {
|
||||
sendResponse(pkt)
|
||||
}
|
||||
|
|
@ -402,6 +430,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(pkt)
|
||||
}
|
||||
|
||||
case AvatarResponse.HitHint(source_guid) =>
|
||||
sendResponse(HitHint(source_guid, guid))
|
||||
|
||||
case AvatarResponse.KilledWhileInVehicle() =>
|
||||
if(player.isAlive && player.VehicleSeated.nonEmpty) {
|
||||
continent.GUID(player.VehicleSeated.get) match {
|
||||
case Some(vehicle : Vehicle) =>
|
||||
if(vehicle.Health == 0) {
|
||||
vehicle.LastShot match {
|
||||
case Some(cause) =>
|
||||
player.History(cause)
|
||||
case None => ;
|
||||
}
|
||||
KillPlayer(player)
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
|
||||
case AvatarResponse.LoadPlayer(pkt) =>
|
||||
if(tplayer_guid != guid) {
|
||||
sendResponse(pkt)
|
||||
|
|
@ -544,7 +591,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
val tplayer_guid = if(player.HasGUID) { player.GUID} else { PlanetSideGUID(0) }
|
||||
reply match {
|
||||
case VehicleResponse.Ownership(vehicle_guid) =>
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid.toLong))
|
||||
sendResponse(PlanetsideAttributeMessage(guid, 21, vehicle_guid.guid))
|
||||
|
||||
case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) =>
|
||||
sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3))
|
||||
|
|
@ -577,6 +624,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(pkt)
|
||||
}
|
||||
|
||||
case VehicleResponse.HitHint(source_guid) =>
|
||||
sendResponse(HitHint(source_guid, player.GUID))
|
||||
|
||||
case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) =>
|
||||
if(tplayer_guid != guid) {
|
||||
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
|
||||
|
|
@ -621,6 +671,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(ObjectAttachMessage(vehicle_guid, guid, seat))
|
||||
}
|
||||
|
||||
case VehicleResponse.PlanetsideAttribute(vehicle_guid, attribute_type, attribute_value) =>
|
||||
if(tplayer_guid != guid) {
|
||||
sendResponse(PlanetsideAttributeMessage(vehicle_guid, attribute_type, attribute_value))
|
||||
}
|
||||
|
||||
case VehicleResponse.ResetSpawnPad(pad_guid) =>
|
||||
sendResponse(GenericObjectActionMessage(pad_guid, 92))
|
||||
|
||||
|
|
@ -787,6 +842,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
|
||||
case Mountable.CanMount(obj : Vehicle, seat_num) =>
|
||||
val obj_guid : PlanetSideGUID = obj.GUID
|
||||
val player_guid : PlanetSideGUID = tplayer.GUID
|
||||
log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num")
|
||||
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) //clear timer
|
||||
PlayerActionsToCancel()
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, 0, obj.Health))
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, 68, 0)) //shield health
|
||||
sendResponse(PlanetsideAttributeMessage(obj_guid, 113, 0)) //capacitor
|
||||
if(seat_num == 0) { //simplistic vehicle ownership management
|
||||
obj.Owner match {
|
||||
case Some(owner_guid) =>
|
||||
|
|
@ -807,7 +869,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
//update mounted weapon belonging to seat
|
||||
weapon.AmmoSlots.foreach(slot => { //update the magazine(s) in the weapon, specifically
|
||||
val magazine = slot.Box
|
||||
sendResponse(InventoryStateMessage(magazine.GUID, weapon.GUID, magazine.Capacity.toLong))
|
||||
sendResponse(InventoryStateMessage(magazine.GUID, weapon.GUID, magazine.Capacity))
|
||||
})
|
||||
case _ => ; //no weapons to update
|
||||
}
|
||||
|
|
@ -859,6 +921,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case Terminal.TerminalMessage(tplayer, msg, order) =>
|
||||
order match {
|
||||
case Terminal.BuyExosuit(exosuit, subtype) => //refresh armor points
|
||||
tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit))
|
||||
if(tplayer.ExoSuit == exosuit) {
|
||||
if(exosuit == ExoSuitType.MAX) {
|
||||
//special MAX case - clear any special state
|
||||
|
|
@ -1005,6 +1068,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
//TODO optimizations against replacing Equipment with the exact same Equipment and potentially for recycling existing Equipment
|
||||
log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}")
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true))
|
||||
tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit))
|
||||
//ensure arm is down
|
||||
tplayer.DrawnSlot = Player.HandsDownSlot
|
||||
sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
|
||||
|
|
@ -1105,7 +1169,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
else {
|
||||
//accommodate as much of inventory as possible
|
||||
//TODO map x,y -> x,y rather than reorganize items
|
||||
val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory) //dropped items can be forgotten
|
||||
stow
|
||||
}
|
||||
|
|
@ -1121,7 +1184,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
if(!tplayer.Certifications.contains(cert)) {
|
||||
log.info(s"$tplayer is learning the $cert certification for $cost points")
|
||||
avatar.Certifications += cert
|
||||
sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 24, cert.id.toLong))
|
||||
sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 24, cert.id))
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true))
|
||||
}
|
||||
else {
|
||||
|
|
@ -1133,7 +1196,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
if(tplayer.Certifications.contains(cert)) {
|
||||
log.info(s"$tplayer is forgetting the $cert certification for $cost points")
|
||||
avatar.Certifications -= cert
|
||||
sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 25, cert.id.toLong))
|
||||
sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 25, cert.id))
|
||||
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Sell, true))
|
||||
}
|
||||
else {
|
||||
|
|
@ -1289,9 +1352,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case VehicleSpawnPad.PlayerSeatedInVehicle(vehicle, pad) =>
|
||||
val vehicle_guid = vehicle.GUID
|
||||
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on
|
||||
//sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, 10))//vehicle.Definition.MaxHealth))
|
||||
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) // Shield health
|
||||
sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) // Capacitor (EMP)
|
||||
ReloadVehicleAccessPermissions(vehicle)
|
||||
ServerVehicleLock(vehicle)
|
||||
|
||||
|
|
@ -1551,6 +1611,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list
|
||||
sendResponse(FriendsResponse(FriendAction.InitializeFriendList, 0, true, true, Nil))
|
||||
sendResponse(FriendsResponse(FriendAction.InitializeIgnoreList, 0, true, true, Nil))
|
||||
avatarService ! Service.Join(avatar.name) //will be same as player.Name
|
||||
cluster ! InterstellarCluster.GetWorld("z6")
|
||||
|
||||
case InterstellarCluster.GiveWorld(zoneId, zone) =>
|
||||
|
|
@ -1750,6 +1811,50 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case DelayedProximityUnitStop(terminal) =>
|
||||
StopUsingProximityUnit(terminal)
|
||||
|
||||
case Vitality.DamageResolution(target : Vehicle) =>
|
||||
val targetGUID = target.GUID
|
||||
val playerGUID = player.GUID
|
||||
val continentId = continent.Id
|
||||
val players = target.Seats.values.filter(seat => { seat.isOccupied && seat.Occupant.get.isAlive })
|
||||
if(target.Health > 0) {
|
||||
//alert occupants to damage source
|
||||
players.foreach(seat => {
|
||||
val tplayer = seat.Occupant.get
|
||||
avatarService ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(playerGUID, tplayer.GUID))
|
||||
})
|
||||
}
|
||||
else {
|
||||
//alert to vehicle death (hence, occupants' deaths)
|
||||
players.foreach(seat => {
|
||||
val tplayer = seat.Occupant.get
|
||||
val tplayerGUID = tplayer.GUID
|
||||
avatarService ! AvatarServiceMessage(tplayer.Name, AvatarAction.KilledWhileInVehicle(tplayerGUID))
|
||||
avatarService ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(tplayerGUID, tplayerGUID)) //dead player still sees self
|
||||
})
|
||||
//vehicle wreckage has no weapons
|
||||
target.Weapons.values
|
||||
.filter { _.Equipment.nonEmpty }
|
||||
.foreach(slot => {
|
||||
val wep = slot.Equipment.get
|
||||
avatarService ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
|
||||
})
|
||||
if(target.Definition == GlobalDefinitions.ams) {
|
||||
target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
|
||||
ClearCurrentAmsSpawnPoint()
|
||||
}
|
||||
avatarService ! AvatarServiceMessage(continentId, AvatarAction.Destroy(targetGUID, playerGUID, playerGUID, target.Position))
|
||||
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), continent))
|
||||
vehicleService ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, continent, Some(1 minute)))
|
||||
}
|
||||
vehicleService ! VehicleServiceMessage(continentId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
|
||||
vehicleService ! VehicleServiceMessage(s"${target.Actor}", VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, target.Shields))
|
||||
|
||||
case Vitality.DamageResolution(target : PlanetSideGameObject) =>
|
||||
log.warn(s"Vital target ${target.Definition.Name} damage resolution not supported using this method")
|
||||
|
||||
case Vehicle.UpdateShieldsCharge(vehicle) =>
|
||||
vehicleService ! VehicleServiceMessage(s"${vehicle.Actor}", VehicleAction.PlanetsideAttribute(PlanetSideGUID(0), vehicle.GUID, 68, vehicle.Shields))
|
||||
|
||||
case ResponseToSelf(pkt) =>
|
||||
log.info(s"Received a direct message: $pkt")
|
||||
sendResponse(pkt)
|
||||
|
|
@ -1780,7 +1885,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
//TODO begin temp player character auto-loading; remove later
|
||||
import net.psforever.objects.GlobalDefinitions._
|
||||
import net.psforever.types.CertificationType._
|
||||
avatar = Avatar("TestCharacter" + sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1)
|
||||
val avatar = Avatar("TestCharacter" + sessionId.toString, PlanetSideEmpire.VS, CharacterGender.Female, 41, CharacterVoice.Voice1)
|
||||
avatar.Certifications += StandardAssault
|
||||
avatar.Certifications += MediumAssault
|
||||
avatar.Certifications += StandardExoSuit
|
||||
|
|
@ -1814,6 +1919,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
avatar.Certifications += AdvancedEngineering
|
||||
avatar.Certifications += FortificationEngineering
|
||||
avatar.Certifications += AssaultEngineering
|
||||
this.avatar = avatar
|
||||
AwardBattleExperiencePoints(avatar, 1000000L)
|
||||
player = new Player(avatar)
|
||||
//player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C
|
||||
|
|
@ -1992,8 +2098,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
continent.Corpses.foreach {
|
||||
TurnPlayerIntoCorpse
|
||||
}
|
||||
//load active vehicles in zone
|
||||
continent.Vehicles.foreach(vehicle => {
|
||||
//load vehicles in zone
|
||||
val (wreckages, vehicles) = continent.Vehicles.partition(vehicle => { vehicle.Health == 0 && vehicle.Definition.DestroyedModel.nonEmpty })
|
||||
//active vehicles
|
||||
vehicles.foreach(vehicle => {
|
||||
val vehicle_guid = vehicle.GUID
|
||||
val vdefinition = vehicle.Definition
|
||||
sendResponse(ObjectCreateMessage(vdefinition.ObjectId, vehicle_guid, vdefinition.Packet.ConstructorData(vehicle).get))
|
||||
|
|
@ -2014,9 +2122,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
})
|
||||
ReloadVehicleAccessPermissions(vehicle)
|
||||
})
|
||||
|
||||
// Loop over vehicles again to add cargohold occupants after all vehicles have been created on the local client
|
||||
continent.Vehicles.foreach(vehicle => {
|
||||
//Loop over vehicles again to add cargohold occupants after all vehicles have been created on the local client
|
||||
vehicles.foreach(vehicle => {
|
||||
vehicle.CargoHolds.foreach({ case (cargo_num, cargo) => {
|
||||
cargo.Occupant match {
|
||||
case Some(cargo_vehicle) =>
|
||||
|
|
@ -2031,6 +2138,16 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
}})
|
||||
})
|
||||
//vehicle wreckages
|
||||
wreckages.foreach(vehicle => {
|
||||
sendResponse(
|
||||
ObjectCreateMessage(
|
||||
vehicle.Definition.DestroyedModel.get.id,
|
||||
vehicle.GUID,
|
||||
DestroyedVehicleConverter.converter.ConstructorData(vehicle).get
|
||||
)
|
||||
)
|
||||
})
|
||||
|
||||
//implant terminals
|
||||
continent.Map.TerminalToInterface.foreach({ case ((terminal_guid, interface_guid)) =>
|
||||
|
|
@ -2198,8 +2315,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
case Some(_) =>
|
||||
//TODO we do not want to delete the player if he is seated in a vehicle when releasing
|
||||
//TODO it is necessary for now until we know how to juggle ownership properly
|
||||
val player_guid = player.GUID
|
||||
sendResponse(ObjectDeleteMessage(player_guid, 0))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, player_guid, 0))
|
||||
|
|
@ -2207,8 +2322,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
context.system.scheduler.scheduleOnce(50 milliseconds, self, UnregisterCorpseOnVehicleDisembark(player))
|
||||
//sendResponse(ObjectDetachMessage(vehicle_guid, player.GUID, Vector3.Zero, 0))
|
||||
//sendResponse(PlayerStateShiftMessage(ShiftState(1, Vector3.Zero, 0)))
|
||||
}
|
||||
|
||||
case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) =>
|
||||
|
|
@ -2289,7 +2402,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
if(messagetype == ChatMessageType.CMT_SUICIDE) {
|
||||
if(player.isAlive && deadState != DeadState.Release) {
|
||||
KillPlayer(player)
|
||||
Suicide(player)
|
||||
}
|
||||
}
|
||||
if(messagetype == ChatMessageType.CMT_DESTROY) {
|
||||
|
|
@ -2726,10 +2839,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case Some(_ : LocalProjectile) =>
|
||||
FindProjectileEntry(object_guid) match {
|
||||
case Some(projectile) =>
|
||||
if(projectile.resolution != ProjectileResolution.Unresolved) {
|
||||
log.warn(s"RequestDestroy: tried to clean up missed projectile ${object_guid.guid} but it was already resolved")
|
||||
if(projectile.isResolved) {
|
||||
log.warn(s"RequestDestroy: tried to clean up projectile ${object_guid.guid} but it was already resolved")
|
||||
}
|
||||
else {
|
||||
projectile.Miss()
|
||||
}
|
||||
ResolveProjectileEntry(object_guid, ProjectileResolution.MissedShot)
|
||||
case None =>
|
||||
log.warn(s"RequestDestroy: projectile ${object_guid.guid} has never been fired")
|
||||
}
|
||||
|
|
@ -2901,7 +3016,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType))
|
||||
sendResponse(ObjectDeleteMessage(kit.GUID, 0))
|
||||
taskResolver ! GUIDTask.UnregisterEquipment(kit)(continent.GUID)
|
||||
//TODO better health/damage control workflow
|
||||
player.History(HealFromKit(PlayerSource(player), 25, kit.Definition))
|
||||
player.Health = player.Health + 25
|
||||
sendResponse(PlanetsideAttributeMessage(avatar_guid, 0, player.Health))
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(avatar_guid, 0, player.Health))
|
||||
|
|
@ -3224,16 +3339,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
else { //shooting
|
||||
tool.Discharge
|
||||
val projectileIndex = projectile_guid.guid - Projectile.BaseUID
|
||||
val ang = obj match {
|
||||
case _ : Player =>
|
||||
obj.Orientation //TODO upper body facing
|
||||
val projectilePlace = projectiles(projectileIndex)
|
||||
if(projectilePlace match {
|
||||
case Some(projectile) => !projectile.isResolved
|
||||
case None => false
|
||||
}) {
|
||||
log.warn(s"WeaponFireMessage: former projectile ${projectile_guid.guid} was not resolved properly; overwriting anyway")
|
||||
}
|
||||
val (angle, attribution) = obj match {
|
||||
case p : Player =>
|
||||
(p.Orientation, tool.Definition.ObjectId) //TODO upper body facing
|
||||
case _ : Vehicle =>
|
||||
tool.Orientation //TODO this is too simplistic
|
||||
(tool.Orientation, obj.Definition.ObjectId) //TODO this is too simplistic to find proper angle
|
||||
case _ =>
|
||||
Vector3.Zero
|
||||
(obj.Orientation, obj.Definition.ObjectId)
|
||||
}
|
||||
projectiles(projectileIndex) =
|
||||
Some(Projectile(tool.Projectile, tool.Definition, shot_origin, ang))
|
||||
Some(Projectile(tool.Projectile, tool.Definition, tool.FireMode, player, attribution, shot_origin, angle))
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
|
|
@ -3243,15 +3365,62 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
|
||||
case msg @ HitMessage(seq_time, projectile_guid, unk1, hit_info, unk2, unk3, unk4) =>
|
||||
log.info(s"Hit: $msg")
|
||||
ResolveProjectileEntry(projectile_guid, ProjectileResolution.Hit)
|
||||
(hit_info match {
|
||||
case Some(hitInfo) =>
|
||||
continent.GUID(hitInfo.hitobject_guid.get) match {
|
||||
case Some(obj : Player) =>
|
||||
Some((obj, hitInfo.shot_origin, hitInfo.hit_pos))
|
||||
case Some(obj : Vehicle) =>
|
||||
Some((obj, hitInfo.shot_origin, hitInfo.hit_pos))
|
||||
case _ =>
|
||||
None
|
||||
}
|
||||
case None => ;
|
||||
None
|
||||
}) match {
|
||||
case Some((target, shotOrigin, hitPos)) =>
|
||||
ResolveProjectileEntry(projectile_guid, ProjectileResolution.Hit, target, hitPos) match {
|
||||
case Some(projectile) =>
|
||||
HandleDealingDamage(target, projectile)
|
||||
case None => ;
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
|
||||
case msg @ SplashHitMessage(seq_time, projectile_guid, explosion_pos, direct_victim_uid, unk3, projectile_vel, unk4, targets) =>
|
||||
log.info(s"Splash: $msg")
|
||||
ResolveProjectileEntry(projectile_guid, ProjectileResolution.Splash)
|
||||
continent.GUID(direct_victim_uid) match {
|
||||
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
|
||||
ResolveProjectileEntry(projectile_guid, ProjectileResolution.Hit, target, explosion_pos) match {
|
||||
case Some(projectile) =>
|
||||
HandleDealingDamage(target, projectile)
|
||||
case None => ;
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
targets.foreach(elem => {
|
||||
continent.GUID(elem.uid) match {
|
||||
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
|
||||
ResolveProjectileEntry(projectile_guid, ProjectileResolution.Splash, target, target.Position) match {
|
||||
case Some(projectile) =>
|
||||
HandleDealingDamage(target, projectile)
|
||||
case None => ;
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
})
|
||||
|
||||
case msg @ LashMessage(seq_time, killer_guid, victim_guid, projectile_guid, pos, unk1) =>
|
||||
log.info(s"Lash: $msg")
|
||||
ResolveProjectileEntry(projectile_guid, ProjectileResolution.Lash)
|
||||
continent.GUID(victim_guid) match {
|
||||
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
|
||||
ResolveProjectileEntry(projectile_guid, ProjectileResolution.Lash, target, pos) match {
|
||||
case Some(projectile) =>
|
||||
HandleDealingDamage(target, projectile)
|
||||
case None => ;
|
||||
}
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
case msg @ AvatarFirstTimeEventMessage(avatar_guid, object_guid, unk1, event_name) =>
|
||||
log.info("AvatarFirstTimeEvent: " + msg)
|
||||
|
|
@ -3422,9 +3591,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
|
||||
case msg @ FacilityBenefitShieldChargeRequestMessage(guid) =>
|
||||
//log.info(s"ShieldChargeRequest: $msg")
|
||||
player.VehicleSeated match {
|
||||
case Some(vehicleGUID) =>
|
||||
continent.GUID(vehicleGUID) match {
|
||||
case Some(obj : Vehicle) =>
|
||||
if(obj.Health > 0) { //vehicle will try to charge even if destroyed
|
||||
obj.Actor ! Vehicle.ChargeShields(15)
|
||||
}
|
||||
case _ =>
|
||||
log.warn(s"FacilityBenefitShieldChargeRequest: can not find vehicle ${vehicleGUID.guid} in zone ${continent.Id}")
|
||||
}
|
||||
case None =>
|
||||
log.warn(s"FacilityBenefitShieldChargeRequest: player ${player.Name} is not seated in a vehicle")
|
||||
}
|
||||
|
||||
case msg @ BattleplanMessage(char_id, player_name, zonr_id, diagrams) =>
|
||||
case msg @ BattleplanMessage(char_id, player_name, zone_id, diagrams) =>
|
||||
log.info("Battleplan: "+msg)
|
||||
|
||||
case msg @ CreateShortcutMessage(player_guid, slot, unk, add, shortcut) =>
|
||||
|
|
@ -3433,8 +3614,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
case msg @ FriendsRequest(action, friend) =>
|
||||
log.info("FriendsRequest: "+msg)
|
||||
|
||||
case msg @ HitHint(source, player_guid) =>
|
||||
log.info("HitHint: "+msg)
|
||||
case msg @ HitHint(source_guid, player_guid) =>
|
||||
log.info(s"HitHint: $msg")
|
||||
continent.GUID(player_guid) match {
|
||||
case Some(obj : Player) =>
|
||||
avatarService ! AvatarServiceMessage(obj.Name, AvatarAction.HitHint(source_guid, player_guid))
|
||||
case _ => ;
|
||||
}
|
||||
|
||||
|
||||
case msg @ TargetingImplantRequest(list) =>
|
||||
log.info("TargetingImplantRequest: "+msg)
|
||||
|
|
@ -3977,7 +4164,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
val vehicle_guid = vehicle.GUID
|
||||
(0 to 3).foreach(group => {
|
||||
sendResponse(
|
||||
PlanetsideAttributeMessage(vehicle_guid, group + 10, vehicle.PermissionGroup(group).get.id.toLong)
|
||||
PlanetsideAttributeMessage(vehicle_guid, group + 10, vehicle.PermissionGroup(group).get.id)
|
||||
)
|
||||
})
|
||||
}
|
||||
|
|
@ -4869,6 +5056,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* The player has lost the will to live and must be killed.
|
||||
* @see `Vitality`<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>
|
||||
* <br>
|
||||
|
|
@ -4898,6 +5096,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
continent.GUID(tplayer.VehicleSeated.get) match {
|
||||
case Some(obj : Vehicle) =>
|
||||
TotalDriverVehicleControl(obj)
|
||||
UnAccessContents(obj)
|
||||
case _ => ;
|
||||
}
|
||||
//make player invisible (if not, the cadaver sticks out the side in a seated position)
|
||||
|
|
@ -4906,6 +5105,29 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
}
|
||||
PlayerActionsToCancel()
|
||||
CancelAllProximityUnits()
|
||||
//TODO other methods of death?
|
||||
val pentry = PlayerSource(tplayer)
|
||||
(tplayer.History.find({p => p.isInstanceOf[PlayerSuicide]}) match {
|
||||
case Some(PlayerSuicide(_)) =>
|
||||
None
|
||||
case _ =>
|
||||
tplayer.LastShot match {
|
||||
case Some(shot) =>
|
||||
if(System.nanoTime - shot.hit_time < (10 seconds).toNanos) {
|
||||
Some(shot)
|
||||
}
|
||||
else {
|
||||
None //suicide
|
||||
}
|
||||
case None =>
|
||||
None //suicide
|
||||
}
|
||||
}) match {
|
||||
case Some(shot) =>
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.DestroyDisplay(shot.projectile.owner, pentry, shot.projectile.attribute_to))
|
||||
case None =>
|
||||
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.DestroyDisplay(pentry, pentry, 0))
|
||||
}
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
reviveTimer = context.system.scheduler.scheduleOnce(respawnTimer milliseconds, cluster, Zone.Lattice.RequestSpawnPoint(Zones.SanctuaryZoneNumber(tplayer.Faction), tplayer, 7))
|
||||
|
|
@ -5192,12 +5414,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
*/
|
||||
def ProximityMedicalTerminal(unit : Terminal with ProximityUnit) : Unit = {
|
||||
val healthFull : Boolean = if(player.Health < player.MaxHealth) {
|
||||
player.History(HealFromTerm(PlayerSource(player), 10, 0, unit.Definition))
|
||||
HealAction(player)
|
||||
}
|
||||
else {
|
||||
true
|
||||
}
|
||||
val armorFull : Boolean = if(player.Armor < player.MaxArmor) {
|
||||
player.History(HealFromTerm(PlayerSource(player), 0, 10, unit.Definition))
|
||||
ArmorRepairAction(player)
|
||||
}
|
||||
else {
|
||||
|
|
@ -5216,6 +5440,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
*/
|
||||
def ProximityHealCrystal(unit : Terminal with ProximityUnit) : Unit = {
|
||||
val healthFull : Boolean = if(player.Health < player.MaxHealth) {
|
||||
player.History(HealFromTerm(PlayerSource(player), 10, 0, unit.Definition))
|
||||
HealAction(player)
|
||||
}
|
||||
else {
|
||||
|
|
@ -5364,11 +5589,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
* @param resolution the resolution status to promote the projectile
|
||||
* @return the projectile
|
||||
*/
|
||||
def ResolveProjectileEntry(projectile_guid : PlanetSideGUID, resolution : ProjectileResolution.Value) : Option[Projectile] = {
|
||||
def ResolveProjectileEntry(projectile_guid : PlanetSideGUID, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = {
|
||||
FindProjectileEntry(projectile_guid) match {
|
||||
case Some(projectile) =>
|
||||
val index = projectile_guid.guid - Projectile.BaseUID
|
||||
ResolveProjectileEntry(projectile, index, resolution)
|
||||
ResolveProjectileEntry(projectile, index, resolution, target, pos)
|
||||
case None =>
|
||||
log.warn(s"ResolveProjectile: expected projectile, but ${projectile_guid.guid} not found")
|
||||
None
|
||||
|
|
@ -5385,13 +5610,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
* @param resolution the resolution status to promote the projectile
|
||||
* @return a copy of the projectile
|
||||
*/
|
||||
def ResolveProjectileEntry(projectile : Projectile, index : Int, resolution : ProjectileResolution.Value) : Option[Projectile] = {
|
||||
if(projectiles(index).contains(projectile)) {
|
||||
projectiles(index) = Some(projectile.Resolve(ProjectileResolution.Resolved))
|
||||
Some(projectile.Resolve(resolution))
|
||||
def ResolveProjectileEntry(projectile : Projectile, index : Int, resolution : ProjectileResolution.Value, target : PlanetSideGameObject with FactionAffinity with Vitality, pos : Vector3) : Option[ResolvedProjectile] = {
|
||||
if(!projectiles(index).contains(projectile)) {
|
||||
log.error(s"expected projectile could not be found at $index; can not resolve")
|
||||
None
|
||||
}
|
||||
else if(projectile.isMiss) {
|
||||
log.error(s"expected projectile at $index was already counted as a missed shot; can not resolve any further")
|
||||
None
|
||||
}
|
||||
else {
|
||||
None
|
||||
projectile.Resolve()
|
||||
Some(ResolvedProjectile(resolution, projectile, SourceEntry(target), target.DamageModel, pos))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5423,6 +5653,65 @@ class WorldSessionActor extends Actor with MDCContextAware {
|
|||
vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, BailType.Normal, false))
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the amount of damage to be dealt to an active `target`
|
||||
* using the information reconstructed from a `Resolvedprojectile`
|
||||
* and affect the `target` in a synchronized manner.
|
||||
* The active `target` and the target of the `ResolvedProjectile` do not have be the same.
|
||||
* @see `DamageResistanceModel`<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) = {
|
||||
log.error(error)
|
||||
sendResponse(ConnectionClose())
|
||||
|
|
|
|||
Loading…
Reference in a new issue