diff --git a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala
index 2a2539a58..e96e4a9d0 100644
--- a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala
@@ -85,6 +85,10 @@ class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends
obj.MaxArmor = MaxArmor
obj.InventoryScale = InventoryScale
obj.InventoryOffset = InventoryOffset
+ obj.Subtract.Damage0 = Subtract.Damage0
+ obj.Subtract.Damage1 = Subtract.Damage1
+ obj.Subtract.Damage2 = Subtract.Damage2
+ obj.Subtract.Damage3 = Subtract.Damage3
obj.ResistanceDirectHit = ResistanceDirectHit
obj.ResistanceSplash = ResistanceSplash
obj.ResistanceAggravated = ResistanceAggravated
@@ -166,6 +170,7 @@ object ExoSuitDefinition {
MAX.InventoryOffset = 6
MAX.Holster(0, EquipmentSize.Max)
MAX.Holster(4, EquipmentSize.Melee)
+ MAX.Subtract.Damage1 = -2
MAX.ResistanceDirectHit = 6
MAX.ResistanceSplash = 35
MAX.ResistanceAggravated = 10
diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index 622a74827..48481a715 100644
--- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -555,11 +555,19 @@ object GlobalDefinitions {
val maelstrom = ToolDefinition(ObjectClass.maelstrom)
- val phoenix = ToolDefinition(ObjectClass.phoenix) //decimator
+ val phoenix = new ToolDefinition(ObjectClass.phoenix) {
+ override def NextFireModeIndex(index : Int) : Int = index
+ } //decimator
- val striker = ToolDefinition(ObjectClass.striker)
+ val striker = new ToolDefinition(ObjectClass.striker) {
+ override def NextFireModeIndex(index : Int) : Int = index
+ DefaultFireModeIndex = 1
+ }
- val hunterseeker = ToolDefinition(ObjectClass.hunterseeker) //phoenix
+ val hunterseeker = new ToolDefinition(ObjectClass.hunterseeker) {
+ override def NextFireModeIndex(index : Int) : Int = index
+ DefaultFireModeIndex = 1
+ } //phoenix
val lancer = ToolDefinition(ObjectClass.lancer)
@@ -573,7 +581,7 @@ object GlobalDefinitions {
val bolt_driver = ToolDefinition(ObjectClass.bolt_driver)
- val oicw = ToolDefinition(ObjectClass.oicw) //scorpion
+ val oicw = ToolDefinition(ObjectClass.oicw)
val flamethrower = ToolDefinition(ObjectClass.flamethrower)
@@ -718,7 +726,9 @@ object GlobalDefinitions {
val lightgunship_weapon_system = ToolDefinition(ObjectClass.lightgunship_weapon_system)
- val wasp_weapon_system = ToolDefinition(ObjectClass.wasp_weapon_system)
+ val wasp_weapon_system = new ToolDefinition(ObjectClass.wasp_weapon_system) {
+ override def NextFireModeIndex(index : Int) : Int = index
+ }
val liberator_weapon_system = ToolDefinition(ObjectClass.liberator_weapon_system)
@@ -3924,6 +3934,7 @@ object GlobalDefinitions {
nchev_falcon.Name = "nchev_falcon"
nchev_falcon.Size = EquipmentSize.Max
nchev_falcon.AmmoTypes += falcon_ammo
+ nchev_falcon.ProjectileTypes += falcon_projectile
nchev_falcon.FireModes += new FireModeDefinition
nchev_falcon.FireModes.head.AmmoTypeIndices += 0
nchev_falcon.FireModes.head.AmmoSlotIndex = 0
@@ -4763,6 +4774,7 @@ object GlobalDefinitions {
threemanheavybuggy.TrunkOffset = 30
threemanheavybuggy.AutoPilotSpeeds = (22, 8)
threemanheavybuggy.DestroyedModel = Some(DestroyedVehicle.ThreeManHeavyBuggy)
+ threemanheavybuggy.Subtract.Damage1 = 5
twomanheavybuggy.Name = "twomanheavybuggy"
twomanheavybuggy.MaxHealth = 1800
@@ -4779,6 +4791,7 @@ object GlobalDefinitions {
twomanheavybuggy.TrunkOffset = 30
twomanheavybuggy.AutoPilotSpeeds = (22, 8)
twomanheavybuggy.DestroyedModel = Some(DestroyedVehicle.TwoManHeavyBuggy)
+ twomanheavybuggy.Subtract.Damage1 = 5
twomanhoverbuggy.Name = "twomanhoverbuggy"
twomanhoverbuggy.MaxHealth = 1600
@@ -4795,6 +4808,7 @@ object GlobalDefinitions {
twomanhoverbuggy.TrunkOffset = 30
twomanhoverbuggy.AutoPilotSpeeds = (22, 10)
twomanhoverbuggy.DestroyedModel = Some(DestroyedVehicle.TwoManHoverBuggy)
+ twomanhoverbuggy.Subtract.Damage1 = 5
mediumtransport.Name = "mediumtransport"
mediumtransport.MaxHealth = 2500
@@ -4818,6 +4832,7 @@ object GlobalDefinitions {
mediumtransport.TrunkOffset = 30
mediumtransport.AutoPilotSpeeds = (18, 6)
mediumtransport.DestroyedModel = Some(DestroyedVehicle.MediumTransport)
+ mediumtransport.Subtract.Damage1 = 7
battlewagon.Name = "battlewagon"
battlewagon.MaxHealth = 2500
@@ -4868,6 +4883,7 @@ object GlobalDefinitions {
thunderer.TrunkOffset = 30
thunderer.AutoPilotSpeeds = (18, 6)
thunderer.DestroyedModel = Some(DestroyedVehicle.MediumTransport)
+ thunderer.Subtract.Damage1 = 7
aurora.Name = "aurora"
aurora.MaxHealth = 2500
@@ -4891,6 +4907,7 @@ object GlobalDefinitions {
aurora.TrunkOffset = 30
aurora.AutoPilotSpeeds = (18, 6)
aurora.DestroyedModel = Some(DestroyedVehicle.MediumTransport)
+ aurora.Subtract.Damage1 = 7
apc_tr.Name = "apc_tr"
apc_tr.MaxHealth = 6000
@@ -5043,6 +5060,7 @@ object GlobalDefinitions {
lightning.TrunkOffset = 30
lightning.AutoPilotSpeeds = (20, 8)
lightning.DestroyedModel = Some(DestroyedVehicle.Lightning)
+ lightning.Subtract.Damage1 = 7
prowler.Name = "prowler"
prowler.MaxHealth = 4800
@@ -5062,6 +5080,7 @@ object GlobalDefinitions {
prowler.TrunkOffset = 30
prowler.AutoPilotSpeeds = (14, 6)
prowler.DestroyedModel = Some(DestroyedVehicle.Prowler)
+ prowler.Subtract.Damage1 = 9
vanguard.Name = "vanguard"
vanguard.MaxHealth = 5400
@@ -5077,6 +5096,7 @@ object GlobalDefinitions {
vanguard.TrunkOffset = 30
vanguard.AutoPilotSpeeds = (16, 6)
vanguard.DestroyedModel = Some(DestroyedVehicle.Vanguard)
+ vanguard.Subtract.Damage1 = 9
magrider.Name = "magrider"
magrider.MaxHealth = 4200
@@ -5094,6 +5114,7 @@ object GlobalDefinitions {
magrider.TrunkOffset = 30
magrider.AutoPilotSpeeds = (18, 6)
magrider.DestroyedModel = Some(DestroyedVehicle.Magrider)
+ magrider.Subtract.Damage1 = 9
val utilityConverter = new UtilityVehicleConverter
ant.Name = "ant"
@@ -5110,6 +5131,7 @@ object GlobalDefinitions {
ant.MaximumCapacitor = 1500
ant.Packet = utilityConverter
ant.DestroyedModel = Some(DestroyedVehicle.Ant)
+ ant.Subtract.Damage1 = 5
ams.Name = "ams"
ams.MaxHealth = 3000
@@ -5129,6 +5151,7 @@ object GlobalDefinitions {
ams.AutoPilotSpeeds = (18, 6)
ams.Packet = utilityConverter
ams.DestroyedModel = Some(DestroyedVehicle.Ams)
+ ams.Subtract.Damage1 = 10
val variantConverter = new VariantVehicleConverter
router.Name = "router"
@@ -5147,6 +5170,7 @@ object GlobalDefinitions {
router.AutoPilotSpeeds = (16, 6)
router.Packet = variantConverter
router.DestroyedModel = Some(DestroyedVehicle.Router)
+ router.Subtract.Damage1 = 5
switchblade.Name = "switchblade"
switchblade.MaxHealth = 1750
@@ -5164,6 +5188,8 @@ object GlobalDefinitions {
switchblade.AutoPilotSpeeds = (22, 8)
switchblade.Packet = variantConverter
switchblade.DestroyedModel = Some(DestroyedVehicle.Switchblade)
+ switchblade.Subtract.Damage0 = 5
+ switchblade.Subtract.Damage1 = 5
flail.Name = "flail"
flail.MaxHealth = 2400
@@ -5180,6 +5206,7 @@ object GlobalDefinitions {
flail.AutoPilotSpeeds = (14, 6)
flail.Packet = variantConverter
flail.DestroyedModel = Some(DestroyedVehicle.Flail)
+ flail.Subtract.Damage1 = 7
mosquito.Name = "mosquito"
mosquito.MaxHealth = 665
@@ -5210,6 +5237,7 @@ object GlobalDefinitions {
lightgunship.AutoPilotSpeeds = (0, 4)
lightgunship.Packet = variantConverter
lightgunship.DestroyedModel = Some(DestroyedVehicle.LightGunship)
+ lightgunship.Subtract.Damage1 = 3
wasp.Name = "wasp"
wasp.MaxHealth = 515
@@ -5247,6 +5275,7 @@ object GlobalDefinitions {
liberator.AutoPilotSpeeds = (0, 4)
liberator.Packet = variantConverter
liberator.DestroyedModel = Some(DestroyedVehicle.Liberator)
+ liberator.Subtract.Damage1 = 5
vulture.Name = "vulture"
vulture.MaxHealth = 2500
@@ -5269,6 +5298,7 @@ object GlobalDefinitions {
vulture.AutoPilotSpeeds = (0, 4)
vulture.Packet = variantConverter
vulture.DestroyedModel = Some(DestroyedVehicle.Liberator) //add_property vulture destroyedphysics liberator_destroyed
+ vulture.Subtract.Damage1 = 5
dropship.Name = "dropship"
dropship.MaxHealth = 5000
@@ -5323,6 +5353,7 @@ object GlobalDefinitions {
dropship.AutoPilotSpeeds = (0, 4)
dropship.Packet = variantConverter
dropship.DestroyedModel = Some(DestroyedVehicle.Dropship)
+ dropship.Subtract.Damage1 = 7
galaxy_gunship.Name = "galaxy_gunship"
galaxy_gunship.MaxHealth = 6000
@@ -5354,6 +5385,7 @@ object GlobalDefinitions {
galaxy_gunship.AutoPilotSpeeds = (0, 4)
galaxy_gunship.Packet = variantConverter
galaxy_gunship.DestroyedModel = Some(DestroyedVehicle.Dropship) //the adb calls out a galaxy_gunship_destroyed but no such asset exists
+ galaxy_gunship.Subtract.Damage1 = 7
lodestar.Name = "lodestar"
lodestar.MaxHealth = 5000
@@ -5367,6 +5399,7 @@ object GlobalDefinitions {
lodestar.AutoPilotSpeeds = (0, 4)
lodestar.Packet = variantConverter
lodestar.DestroyedModel = Some(DestroyedVehicle.Lodestar)
+ lodestar.Subtract.Damage1 = 7
phantasm.Name = "phantasm"
phantasm.MaxHealth = 2500
diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala
index 8586b0a0a..f2fc55b60 100644
--- a/common/src/main/scala/net/psforever/objects/Player.scala
+++ b/common/src/main/scala/net/psforever/objects/Player.scala
@@ -281,6 +281,8 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
ChangeSpecialAbility()
}
+ def Subtract = exosuit.Subtract
+
def ResistanceDirectHit = exosuit.ResistanceDirectHit
def ResistanceSplash = exosuit.ResistanceSplash
diff --git a/common/src/main/scala/net/psforever/objects/Tool.scala b/common/src/main/scala/net/psforever/objects/Tool.scala
index 3faff4407..6702808df 100644
--- a/common/src/main/scala/net/psforever/objects/Tool.scala
+++ b/common/src/main/scala/net/psforever/objects/Tool.scala
@@ -20,7 +20,7 @@ import scala.annotation.tailrec
class Tool(private val toolDef : ToolDefinition) extends Equipment
with FireModeSwitch[FireModeDefinition] {
/** index of the current fire mode on the `ToolDefinition`'s list of fire modes */
- private var fireModeIndex : Int = 0
+ private var fireModeIndex : Int = toolDef.DefaultFireModeIndex
/** current ammunition slot being used by this fire mode */
private var ammoSlots : List[Tool.FireModeSlot] = List.empty
diff --git a/common/src/main/scala/net/psforever/objects/ballistics/ComplexDeployableSource.scala b/common/src/main/scala/net/psforever/objects/ballistics/ComplexDeployableSource.scala
index 32e86ecd1..2448b98f5 100644
--- a/common/src/main/scala/net/psforever/objects/ballistics/ComplexDeployableSource.scala
+++ b/common/src/main/scala/net/psforever/objects/ballistics/ComplexDeployableSource.scala
@@ -4,6 +4,7 @@ package net.psforever.objects.ballistics
import net.psforever.objects.TurretDeployable
import net.psforever.objects.ce.ComplexDeployable
import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition}
+import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.types.{PlanetSideEmpire, Vector3}
final case class ComplexDeployableSource(obj_def : ObjectDefinition with BaseDeployableDefinition,
@@ -22,6 +23,7 @@ final case class ComplexDeployableSource(obj_def : ObjectDefinition with BaseDep
def Position = position
def Orientation = orientation
def Velocity = None
+ def Modifiers = obj_def.asInstanceOf[ResistanceProfile]
}
object ComplexDeployableSource {
diff --git a/common/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala b/common/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala
index bf5040093..0b538f4d1 100644
--- a/common/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala
+++ b/common/src/main/scala/net/psforever/objects/ballistics/DeployableSource.scala
@@ -4,6 +4,7 @@ package net.psforever.objects.ballistics
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.Deployable
import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition}
+import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.types.{PlanetSideEmpire, Vector3}
final case class DeployableSource(obj_def : ObjectDefinition with BaseDeployableDefinition,
@@ -20,6 +21,7 @@ final case class DeployableSource(obj_def : ObjectDefinition with BaseDeployable
def Position = position
def Orientation = orientation
def Velocity = None
+ def Modifiers = obj_def.asInstanceOf[ResistanceProfile]
}
object DeployableSource {
diff --git a/common/src/main/scala/net/psforever/objects/ballistics/ObjectSource.scala b/common/src/main/scala/net/psforever/objects/ballistics/ObjectSource.scala
index e73365df0..552bab2cb 100644
--- a/common/src/main/scala/net/psforever/objects/ballistics/ObjectSource.scala
+++ b/common/src/main/scala/net/psforever/objects/ballistics/ObjectSource.scala
@@ -3,19 +3,21 @@ package net.psforever.objects.ballistics
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
+import net.psforever.objects.vital.resistance.ResistanceProfileMutators
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 {
+ velocity : Option[Vector3]) 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
+ def Modifiers = new ResistanceProfileMutators { }
}
object ObjectSource {
diff --git a/common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala b/common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala
index 495c01365..cb054d4e8 100644
--- a/common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala
+++ b/common/src/main/scala/net/psforever/objects/ballistics/PlayerSource.scala
@@ -3,6 +3,7 @@ package net.psforever.objects.ballistics
import net.psforever.objects.Player
import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, Vector3}
final case class PlayerSource(name : String,
@@ -14,7 +15,8 @@ final case class PlayerSource(name : String,
armor : Int,
position : Vector3,
orientation : Vector3,
- velocity : Option[Vector3] = None) extends SourceEntry {
+ velocity : Option[Vector3],
+ modifiers : ResistanceProfile) extends SourceEntry {
override def Name = name
override def Faction = faction
def Definition = obj_def
@@ -25,11 +27,12 @@ final case class PlayerSource(name : String,
def Position = position
def Orientation = orientation
def Velocity = velocity
+ def Modifiers = modifiers
}
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)
+ tplayer.Health, tplayer.Armor, tplayer.Position, tplayer.Orientation, tplayer.Velocity, tplayer.asInstanceOf[ResistanceProfile])
}
}
diff --git a/common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala b/common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala
index ccc32092d..bb050e9ca 100644
--- a/common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala
+++ b/common/src/main/scala/net/psforever/objects/ballistics/SourceEntry.scala
@@ -6,6 +6,7 @@ import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.{PlanetSideGameObject, Player, TurretDeployable, Vehicle}
import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.serverobject.affinity.FactionAffinity
+import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.types.{PlanetSideEmpire, Vector3}
trait SourceEntry extends WorldEntity {
@@ -16,6 +17,7 @@ trait SourceEntry extends WorldEntity {
def Position_=(pos : Vector3) = Position
def Orientation_=(pos : Vector3) = Position
def Velocity_=(pos : Option[Vector3]) = Velocity
+ def Modifiers : ResistanceProfile
}
object SourceEntry {
@@ -24,6 +26,7 @@ object SourceEntry {
def Position = Vector3.Zero
def Orientation = Vector3.Zero
def Velocity = Some(Vector3.Zero)
+ def Modifiers = null
}
def apply(target : PlanetSideGameObject with FactionAffinity) : SourceEntry = {
diff --git a/common/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala b/common/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala
index ccd7db404..df83be2fa 100644
--- a/common/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala
+++ b/common/src/main/scala/net/psforever/objects/ballistics/VehicleSource.scala
@@ -3,6 +3,7 @@ package net.psforever.objects.ballistics
import net.psforever.objects.Vehicle
import net.psforever.objects.definition.VehicleDefinition
+import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.types.{PlanetSideEmpire, Vector3}
final case class VehicleSource(obj_def : VehicleDefinition,
@@ -11,7 +12,8 @@ final case class VehicleSource(obj_def : VehicleDefinition,
shields : Int,
position : Vector3,
orientation : Vector3,
- velocity : Option[Vector3] = None) extends SourceEntry {
+ velocity : Option[Vector3],
+ modifiers : ResistanceProfile) extends SourceEntry {
override def Name = SourceEntry.NameFormat(obj_def.Name)
override def Faction = faction
def Definition : VehicleDefinition = obj_def
@@ -20,6 +22,7 @@ final case class VehicleSource(obj_def : VehicleDefinition,
def Position = position
def Orientation = orientation
def Velocity = velocity
+ def Modifiers = modifiers
}
object VehicleSource {
@@ -31,7 +34,8 @@ object VehicleSource {
obj.Shields,
obj.Position,
obj.Orientation,
- obj.Velocity
+ obj.Velocity,
+ obj.Definition.asInstanceOf[ResistanceProfile]
)
}
}
diff --git a/common/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
index 48fe1586f..d3c1b7c2b 100644
--- a/common/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/DeployableDefinition.scala
@@ -6,11 +6,13 @@ import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem}
import net.psforever.objects.definition.converter.SmallDeployableConverter
import net.psforever.objects.serverobject.PlanetSideServerObject
-import net.psforever.objects.vital.{DamageResistanceModel, NoResistanceSelection, StandardDeployableDamage}
+import net.psforever.objects.vital.resistance.ResistanceProfileMutators
+import net.psforever.objects.vital.{DamageResistanceModel, NoResistanceSelection, StandardDeployableDamage, StandardResistanceProfile}
import scala.concurrent.duration._
-trait BaseDeployableDefinition extends DamageResistanceModel {
+trait BaseDeployableDefinition extends DamageResistanceModel
+ with ResistanceProfileMutators {
private var category : DeployableCategory.Value = DeployableCategory.Boomers
private var deployTime : Long = (1 second).toMillis //ms
private var maxHealth : Int = 1
diff --git a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala
index 043ccb77b..3a36cef91 100644
--- a/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/ProjectileDefinition.scala
@@ -2,8 +2,7 @@
package net.psforever.objects.definition
import net.psforever.objects.ballistics.Projectiles
-import net.psforever.objects.vital.DamageType
-import net.psforever.objects.vital.damage.DamageProfile
+import net.psforever.objects.vital.{DamageType, StandardDamageProfile}
/**
* The definition that outlines the damage-dealing characteristics of any projectile.
@@ -11,13 +10,8 @@ import net.psforever.objects.vital.damage.DamageProfile
* @param objectId the object's identifier number
*/
class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
- with DamageProfile {
+ with StandardDamageProfile {
private val projectileType : Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException
- private var damage0 : Int = 0
- private var damage1 : Option[Int] = None
- private var damage2 : Option[Int] = None
- private var damage3 : Option[Int] = None
- private var damage4 : Option[Int] = None
private var acceleration : Int = 0
private var accelerationUntil : Float = 0f
private var damageType : DamageType.Value = DamageType.None
@@ -45,57 +39,6 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
UseDamage1Subtract
}
- def Damage0 : Int = damage0
-
- def Damage0_=(damage : Int) : Int = {
- damage0 = damage
- damage0
- }
-
- def Damage0_=(damage : Option[Int]) : Int = {
- damage0 = damage match {
- case Some(value) => value
- case None => 0 //can not be set to None
- }
- Damage0
- }
-
- def Damage1 : Int = damage1.getOrElse(Damage0)
-
- def Damage1_=(damage : Int) : Int = Damage1_=(Some(damage))
-
- def Damage1_=(damage : Option[Int]) : Int = {
- this.damage1 = damage
- Damage1
- }
-
- def Damage2 : Int = damage2.getOrElse(Damage1)
-
- def Damage2_=(damage : Int) : Int = Damage2_=(Some(damage))
-
- def Damage2_=(damage : Option[Int]) : Int = {
- this.damage2 = damage
- Damage2
- }
-
- def Damage3 : Int = damage3.getOrElse(Damage2)
-
- def Damage3_=(damage : Int) : Int = Damage3_=(Some(damage))
-
- def Damage3_=(damage : Option[Int]) : Int = {
- this.damage3 = damage
- Damage3
- }
-
- def Damage4 : Int = damage4.getOrElse(Damage3)
-
- def Damage4_=(damage : Int) : Int = Damage4_=(Some(damage))
-
- def Damage4_=(damage : Option[Int]) : Int = {
- this.damage4 = damage
- Damage4
- }
-
def Acceleration : Int = acceleration
def Acceleration_=(accel : Int) : Int = {
diff --git a/common/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala
index 571f2431b..9b583d622 100644
--- a/common/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/ToolDefinition.scala
@@ -10,6 +10,7 @@ class ToolDefinition(objectId : Int) extends EquipmentDefinition(objectId) {
private val ammoTypes : mutable.ListBuffer[AmmoBoxDefinition] = new mutable.ListBuffer[AmmoBoxDefinition]
private val projectileTypes : mutable.ListBuffer[ProjectileDefinition] = new mutable.ListBuffer[ProjectileDefinition]
private val fireModes : mutable.ListBuffer[FireModeDefinition] = new mutable.ListBuffer[FireModeDefinition]
+ private var defaultFireModeIndex : Option[Int] = None
Name = "tool"
Packet = ToolDefinition.converter
@@ -20,6 +21,15 @@ class ToolDefinition(objectId : Int) extends EquipmentDefinition(objectId) {
def FireModes : mutable.ListBuffer[FireModeDefinition] = fireModes
def NextFireModeIndex(index : Int) : Int = index + 1
+
+ def DefaultFireModeIndex : Int = defaultFireModeIndex.getOrElse(0)
+
+ def DefaultFireModeIndex_=(index : Int) : Int = DefaultFireModeIndex_=(Some(index))
+
+ def DefaultFireModeIndex_=(index : Option[Int]) : Int = {
+ defaultFireModeIndex = index
+ DefaultFireModeIndex
+ }
}
object ToolDefinition {
diff --git a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
index 7cc959dca..ccb0ea4e9 100644
--- a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
@@ -60,6 +60,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId)
}
def Seats : mutable.HashMap[Int, SeatDefinition] = seats
+
def Cargo : mutable.HashMap[Int, CargoDefinition] = cargo
def MountPoints : mutable.HashMap[Int, Int] = mountPoints
diff --git a/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala b/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala
index 72967165a..a06944934 100644
--- a/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala
+++ b/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala
@@ -226,10 +226,12 @@ class GridInventory extends Container {
else {
val collisions : mutable.Set[InventoryItem] = mutable.Set[InventoryItem]()
var curr = actualSlot
+ val fixedItems = items.toMap
+ val fixedGrid = grid.toList
for(_ <- 0 until h) {
for(col <- 0 until w) {
- if(grid(curr + col) > -1) {
- collisions += items(grid(curr + col))
+ if(fixedGrid(curr + col) > -1) {
+ collisions += fixedItems(fixedGrid(curr + col))
}
}
curr += width
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala
index 740414276..3572d9785 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSilo.scala
@@ -71,16 +71,15 @@ object ResourceSilo {
}
/**
- * Instantiate an configure a `Resource Silo` object
+ * Instantiate and configure a `Resource Silo` object
* @param id the unique id that will be assigned to this entity
* @param context a context to allow the object to properly set up `ActorSystem` functionality;
* not necessary for this object, but required by signature
- * @return the `Locker` object
+ * @return the `ResourceSilo` object
*/
def Constructor(id : Int, context : ActorContext) : ResourceSilo = {
val obj = ResourceSilo()
obj.Actor = context.actorOf(Props(classOf[ResourceSiloControl], obj), s"${obj.Definition.Name}_$id")
- obj.Actor ! "startup"
obj
}
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala
index 4045c8bd6..b5374ff60 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala
@@ -42,7 +42,7 @@ class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with Factio
sender ! ResourceSilo.ResourceSiloMessage(player, msg, resourceSilo.Use(player, msg))
case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
resourceSilo.LowNtuWarningOn = enabled
- log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to ${enabled}")
+ log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled")
avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(PlanetSideGUID(resourceSilo.Owner.asInstanceOf[Building].ModelId), 47, if(resourceSilo.LowNtuWarningOn) 1 else 0))
case ResourceSilo.UpdateChargeLevel(amount: Int) =>
@@ -55,10 +55,10 @@ class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with Factio
}
- val ntuBarLevel = scala.math.round((resourceSilo.ChargeLevel.toFloat / resourceSilo.MaximumCharge.toFloat) * 10).toInt
+ val ntuBarLevel = scala.math.round((resourceSilo.ChargeLevel.toFloat / resourceSilo.MaximumCharge.toFloat) * 10)
// Only send updated capacitor display value to all clients if it has actually changed
if(resourceSilo.CapacitorDisplay != ntuBarLevel) {
- log.trace(s"Silo ${resourceSilo.GUID} NTU bar level has changed from ${resourceSilo.CapacitorDisplay} to ${ntuBarLevel}")
+ log.trace(s"Silo ${resourceSilo.GUID} NTU bar level has changed from ${resourceSilo.CapacitorDisplay} to $ntuBarLevel")
resourceSilo.CapacitorDisplay = ntuBarLevel
resourceSilo.Owner.Actor ! Building.SendMapUpdate(all_clients = true)
avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay))
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala
index 1b0b37978..91500a703 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala
@@ -366,12 +366,12 @@ object EquipmentTerminalDefinition {
(0 until tool.MaxAmmoSlot).foreach(index => {
val slot = tool.AmmoSlots(index)
slot.AmmoTypeIndex += obj.ammo(index).ammoIndex
- slot.Box = MakeAmmoBox(ammo(index), Some(obj.ammo(index).ammo.capacity))
+ slot.Box = MakeAmmoBox(ammo(index), Some(slot.Definition.Magazine)) //Some(obj.ammo(index).ammo.capacity)
})
tool
case obj : ShorthandAmmoBox =>
- MakeAmmoBox(obj.definition, Some(obj.capacity))
+ MakeAmmoBox(obj.definition) //Some(obj.capacity)
case obj : ShorthandConstructionItem =>
MakeConstructionItem(obj.definition)
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala
index 78a592c32..bcb5fc6a5 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala
@@ -3,14 +3,19 @@ package net.psforever.objects.serverobject.turret
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.types.Vector3
+import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
class FacilityTurret(tDef : TurretDefinition) extends Amenity
- with WeaponTurret {
+ with WeaponTurret
+ with Vitality
+ with StandardResistanceProfile {
/** some turrets can be updated; they all start without updates */
private var upgradePath : TurretUpgrade.Value = TurretUpgrade.None
WeaponTurret.LoadDefinition(this)
+ override def Health_=(toHealth : Int) = super.Health_=(math.max(1, toHealth)) //TODO properly handle destroyed facility turrets
+
def MaxHealth : Int = Definition.MaxHealth
def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap
@@ -28,6 +33,8 @@ class FacilityTurret(tDef : TurretDefinition) extends Amenity
Upgrade
}
+ def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
+
def Definition : TurretDefinition = tDef
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
index 557980a1d..0e70e0f63 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
@@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.turret
import akka.actor.Actor
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.vital.Vitality
/**
* An `Actor` that handles messages being dispatched to a specific `MannedTurret`.
@@ -38,6 +39,18 @@ class FacilityTurretControl(turret : FacilityTurret) extends Actor
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(turret, seat_num))
}
+ case Vitality.Damage(damage_func) =>
+ if(turret.Health > 0) {
+ val originalHealth = turret.Health
+ damage_func(turret)
+ val health = turret.Health
+ val damageToHealth = originalHealth - health
+ val name = turret.Actor.toString
+ val slashPoint = name.lastIndexOf("/")
+ org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint+1, name.length-1)}: BEFORE=$originalHealth, AFTER=$health, CHANGE=$damageToHealth")
+ sender ! Vitality.DamageResolution(turret)
+ }
+
case _ => ;
}
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala
index 487f13207..90abe882c 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala
@@ -3,6 +3,8 @@ package net.psforever.objects.serverobject.turret
import net.psforever.objects.definition.{ObjectDefinition, ToolDefinition}
import net.psforever.objects.vehicles.Turrets
+import net.psforever.objects.vital.{DamageResistanceModel, StandardResolutions, StandardVehicleDamage, StandardVehicleResistance}
+import net.psforever.objects.vital.resistance.ResistanceProfileMutators
import scala.collection.mutable
@@ -10,7 +12,9 @@ import scala.collection.mutable
* The definition for any `MannedTurret`.
* @param objectId the object's identifier number
*/
-class TurretDefinition(private val objectId : Int) extends ObjectDefinition(objectId) {
+class TurretDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
+ with ResistanceProfileMutators
+ with DamageResistanceModel {
Turrets(objectId) //let throw NoSuchElementException
private var maxHealth : Int = 100
@@ -25,6 +29,9 @@ class TurretDefinition(private val objectId : Int) extends ObjectDefinition(obje
/** creates internal ammunition reserves that can not become depleted
* see `MannedTurret.TurretAmmoBox` for details */
private var hasReserveAmmunition : Boolean = false
+ Damage = StandardVehicleDamage
+ Resistance = StandardVehicleResistance
+ Model = StandardResolutions.FacilityTurrets
def MaxHealth : Int = maxHealth
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
index e773696e2..b5e017a81 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
@@ -63,7 +63,16 @@ class VehicleControl(vehicle : Vehicle) extends Actor
case Vitality.Damage(damage_func) =>
if(vehicle.Health > 0) {
+ val originalHealth = vehicle.Health
+ val originalShields = vehicle.Shields
damage_func(vehicle)
+ val health = vehicle.Health
+ val shields = vehicle.Shields
+ val damageToHealth = originalHealth - health
+ val damageToShields = originalShields - shields
+ val name = vehicle.Actor.toString
+ val slashPoint = name.lastIndexOf("/")
+ org.log4s.getLogger("DamageResolution").info(s"${name.substring(slashPoint+1, name.length-1)}: BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields")
sender ! Vitality.DamageResolution(vehicle)
}
diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardDamageProfile.scala b/common/src/main/scala/net/psforever/objects/vital/StandardDamageProfile.scala
new file mode 100644
index 000000000..d48662d1b
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/vital/StandardDamageProfile.scala
@@ -0,0 +1,63 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.vital
+
+import net.psforever.objects.vital.damage.DamageProfile
+
+trait StandardDamageProfile extends DamageProfile {
+ private var damage0 : Int = 0
+ private var damage1 : Option[Int] = None
+ private var damage2 : Option[Int] = None
+ private var damage3 : Option[Int] = None
+ private var damage4 : Option[Int] = None
+
+ def Damage0 : Int = damage0
+
+ def Damage0_=(damage : Int) : Int = {
+ damage0 = damage
+ damage0
+ }
+
+ def Damage0_=(damage : Option[Int]) : Int = {
+ damage0 = damage match {
+ case Some(value) => value
+ case None => 0 //can not be set to None
+ }
+ Damage0
+ }
+
+ def Damage1 : Int = damage1.getOrElse(Damage0)
+
+ def Damage1_=(damage : Int) : Int = Damage1_=(Some(damage))
+
+ def Damage1_=(damage : Option[Int]) : Int = {
+ this.damage1 = damage
+ Damage1
+ }
+
+ def Damage2 : Int = damage2.getOrElse(Damage1)
+
+ def Damage2_=(damage : Int) : Int = Damage2_=(Some(damage))
+
+ def Damage2_=(damage : Option[Int]) : Int = {
+ this.damage2 = damage
+ Damage2
+ }
+
+ def Damage3 : Int = damage3.getOrElse(Damage2)
+
+ def Damage3_=(damage : Int) : Int = Damage3_=(Some(damage))
+
+ def Damage3_=(damage : Option[Int]) : Int = {
+ this.damage3 = damage
+ Damage3
+ }
+
+ def Damage4 : Int = damage4.getOrElse(Damage3)
+
+ def Damage4_=(damage : Int) : Int = Damage4_=(Some(damage))
+
+ def Damage4_=(damage : Option[Int]) : Int = {
+ this.damage4 = damage
+ Damage4
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala b/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala
index 740fe4385..9d5ee0dcd 100644
--- a/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala
@@ -1,7 +1,6 @@
// 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._
@@ -66,21 +65,35 @@ object AircraftSplashDamage extends DamageCalculations(
DistanceFromExplosionToTarget
)
-object InfantryLashDamage extends NoDamageBase {
- override def Calculate(data : ResolvedProjectile) : Int = (InfantryHitDamage.Calculate(data) * 0.2f).toInt
-}
+object InfantrySplashDamageDirect extends DamageCalculations(
+ SplashDamageWithRadialDegrade,
+ DamageWithModifiers(DamageAgainstAircraft),
+ NoDistance
+)
-object MaxLashDamage extends NoDamageBase {
- override def Calculate(data : ResolvedProjectile) : Int = (MaxHitDamage.Calculate(data) * 0.2f).toInt
-}
+object InfantryLashDamage extends DamageCalculations(
+ LashDamage,
+ DamageWithModifiers(DamageAgainstExoSuit),
+ DistanceBetweenTargetandSource
+)
-object VehicleLashDamage extends NoDamageBase {
- override def Calculate(data : ResolvedProjectile) : Int = (VehicleHitDamage.Calculate(data) * 0.2f).toInt
-}
+object MaxLashDamage extends DamageCalculations(
+ LashDamage,
+ DamageWithModifiers(DamageAgainstMaxSuit),
+ DistanceBetweenTargetandSource
+)
-object AircraftLashDamage extends NoDamageBase {
- override def Calculate(data : ResolvedProjectile) : Int = (AircraftHitDamage.Calculate(data) * 0.2f).toInt
-}
+object VehicleLashDamage extends DamageCalculations(
+ LashDamage,
+ DamageWithModifiers(DamageAgainstVehicle),
+ DistanceBetweenTargetandSource
+)
+
+object AircraftLashDamage extends DamageCalculations(
+ LashDamage,
+ DamageWithModifiers(DamageAgainstAircraft),
+ DistanceBetweenTargetandSource
+)
object NoDamageSelection extends DamageSelection {
def Direct = None
diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala b/common/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala
index d419c02c3..ddf1de096 100644
--- a/common/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/StandardResistanceProfile.scala
@@ -2,6 +2,7 @@
package net.psforever.objects.vital
import net.psforever.objects.PlanetSideGameObject
+import net.psforever.objects.vital.damage.DamageProfile
import net.psforever.objects.vital.resistance.ResistanceProfile
/**
@@ -16,6 +17,8 @@ trait StandardResistanceProfile extends ResistanceProfile {
assert(Definition.isInstanceOf[ResistanceProfile], s"$this object definition must extend ResistanceProfile")
private val resistDef = Definition.asInstanceOf[ResistanceProfile] //cast only once
+ def Subtract : DamageProfile = resistDef.Subtract
+
def ResistanceDirectHit : Int = resistDef.ResistanceDirectHit
def ResistanceSplash : Int = resistDef.ResistanceDirectHit
diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala b/common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala
index 5aaaa6f70..00d507c57 100644
--- a/common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala
@@ -22,7 +22,7 @@ object InfantrySplashResistance extends ResistanceCalculations[PlayerSource](
object InfantryLashResistance extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget,
- ResistanceCalculations.NoResistExtractor
+ ResistanceCalculations.ExoSuitDirectExtractor
)
object InfantryAggravatedResistance extends ResistanceCalculations[PlayerSource](
diff --git a/common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala b/common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala
index 37a5d3a53..75502f7d3 100644
--- a/common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala
@@ -23,9 +23,9 @@ object VehicleResolutions extends DamageResistCalculations(
ResolutionCalculations.VehicleApplication
)
-object SimpleDeployableResolutions extends DamageResistCalculations(
+object SimpleResolutions extends DamageResistCalculations(
ResolutionCalculations.VehicleDamageAfterResist,
- ResolutionCalculations.SimpleDeployableApplication
+ ResolutionCalculations.SimpleApplication
)
object ComplexDeployableResolutions extends DamageResistCalculations(
@@ -38,6 +38,7 @@ object StandardResolutions extends ResolutionSelection {
def Max : ResolutionCalculations.Form = MaxResolutions.Calculate
def Vehicle : ResolutionCalculations.Form = VehicleResolutions.Calculate
def Aircraft : ResolutionCalculations.Form = VehicleResolutions.Calculate
- def SimpleDeployables : ResolutionCalculations.Form = SimpleDeployableResolutions.Calculate
+ def SimpleDeployables : ResolutionCalculations.Form = SimpleResolutions.Calculate
def ComplexDeployables : ResolutionCalculations.Form = ComplexDeployableResolutions.Calculate
+ def FacilityTurrets : ResolutionCalculations.Form = SimpleResolutions.Calculate
}
diff --git a/common/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala
index 7e48430a5..9fa33eb73 100644
--- a/common/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/damage/DamageCalculations.scala
@@ -27,9 +27,16 @@ abstract class DamageCalculations(damages : DamagesType,
*/
def Calculate(data : ResolvedProjectile) : Int = {
val projectile = data.projectile
+ val profile = projectile.profile
+ val modifiers = if(profile.UseDamage1Subtract) {
+ List(projectile.fire_mode.Modifiers, data.target.Modifiers.Subtract)
+ }
+ else {
+ List(projectile.fire_mode.Modifiers)
+ }
damages(
projectile,
- extractor(projectile.profile, List(projectile.fire_mode.Modifiers)),
+ extractor(profile, modifiers),
distanceFunc(data)
)
}
@@ -70,6 +77,17 @@ object DamageCalculations {
//damage calculation functions
def NoDamage(projectile : Projectile, rawDamage : Int, distance : Float) : Int = 0
+ /**
+ * Use an unmodified damage value.
+ * @param projectile information about the weapon discharge (itself);
+ * unused
+ * @param rawDamage the accumulated amount of damage
+ * @param distance how far the source was from the target;
+ * unused
+ * @return the rawDamage value
+ */
+ def SameHit(projectile : Projectile, rawDamage : Int, distance : Float) : Int = rawDamage
+
/**
* Modify the base damage based on the degrade distance of the projectile type
* and its maximum effective distance.
@@ -106,8 +124,26 @@ object DamageCalculations {
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
+ val degrade : Float = (1 - base) * ((radius - distance)/radius) + base
+ (rawDamage * degrade).toInt
+ }
+ else {
+ 0
+ }
+ }
+
+ /**
+ * Calculate a flat lash damage value.
+ * The target needs to be more than five meters away.
+ * @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 LashDamage(projectile : Projectile, rawDamage : Int, distance : Float) : Int = {
+ val profile = projectile.profile
+ if(distance > 5 || distance <= profile.DistanceMax) {
+ (rawDamage * 0.2f) toInt
}
else {
0
diff --git a/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala
index 695b9c033..1fbeec138 100644
--- a/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceProfile.scala
@@ -1,7 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vital.resistance
-import net.psforever.objects.vital.DamageType
+import net.psforever.objects.vital.damage.DamageProfile
+import net.psforever.objects.vital.{DamageType, StandardDamageProfile}
/**
* The different values for four common methods of modifying incoming damage.
@@ -9,6 +10,8 @@ import net.psforever.objects.vital.DamageType
* This is for defining pure accessor functions.
*/
trait ResistanceProfile {
+ def Subtract : DamageProfile
+
def ResistanceDirectHit : Int
def ResistanceSplash : Int
@@ -34,11 +37,25 @@ trait ResistanceProfile {
* This is for defining both accessor and mutator functions.
*/
trait ResistanceProfileMutators extends ResistanceProfile {
+ private var subtract : DamageProfile = new StandardDamageProfile {
+ //subtract numbers are always negative modifiers
+ override def Damage0_=(damage : Int) : Int = super.Damage0_=( if(damage < 1) damage else -damage)
+
+ override def Damage1_=(damage : Int) : Int = super.Damage1_=( if(damage < 1) damage else -damage)
+
+ override def Damage2_=(damage : Int) : Int = super.Damage2_=( if(damage < 1) damage else -damage)
+
+ override def Damage3_=(damage : Int) : Int = super.Damage3_=( if(damage < 1) damage else -damage)
+
+ override def Damage4_=(damage : Int) : Int = super.Damage4_=( if(damage < 1) damage else -damage)
+ }
private var resistanceDirectHit : Int = 0
private var resistanceSplash : Int = 0
private var resistanceAggravated : Int = 0
private var radiationShielding : Float = 0f
+ def Subtract : DamageProfile = subtract
+
def ResistanceDirectHit : Int = resistanceDirectHit
def ResistanceDirectHit_=(resist : Int) : Int = {
diff --git a/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
index 4524c759e..06774500c 100644
--- a/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/resolution/ResolutionCalculations.scala
@@ -4,6 +4,7 @@ package net.psforever.objects.vital.resolution
import net.psforever.objects.{Player, TurretDeployable, Vehicle}
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
import net.psforever.objects.ce.{ComplexDeployable, SimpleDeployable}
+import net.psforever.objects.serverobject.turret.FacilityTurret
import net.psforever.objects.vital.projectile.ProjectileCalculations
/**
@@ -159,12 +160,17 @@ object ResolutionCalculations {
case _ => ;
}
- def SimpleDeployableApplication(damage : Int, data : ResolvedProjectile)(target : Any) : Unit = target match {
+ def SimpleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : Unit = target match {
case ce : SimpleDeployable =>
if(ce.Health > 0) {
ce.Health -= damage
ce.History(data)
}
+ case turret : FacilityTurret =>
+ if(turret.Health > 0) {
+ turret.Health -= damage
+ turret.History(data)
+ }
case _ =>
}
diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala
index c92705579..f4a673557 100644
--- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala
@@ -13,6 +13,7 @@ import net.psforever.objects.guid.actor.UniqueNumberSystem
import net.psforever.objects.guid.selector.RandomSelector
import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.inventory.Container
+import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Amenity, Building}
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.FacilityTurret
@@ -358,6 +359,13 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
case (None, _) | (_, None) => ; //let ZoneActor's sanity check catch this error
}
})
+ //ntu management (eventually move to a generic building startup function)
+ buildings.values
+ .flatMap(_.Amenities.filter(_.Definition == GlobalDefinitions.resource_silo))
+ .collect {
+ case silo : ResourceSilo =>
+ silo.Actor ! "startup"
+ }
}
private def CreateSpawnGroups() : Unit = {
diff --git a/common/src/main/scala/net/psforever/packet/game/BindPlayerMessage.scala b/common/src/main/scala/net/psforever/packet/game/BindPlayerMessage.scala
index 0c37d4e72..f68985768 100644
--- a/common/src/main/scala/net/psforever/packet/game/BindPlayerMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/BindPlayerMessage.scala
@@ -2,33 +2,45 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
-import net.psforever.types.Vector3
+import net.psforever.types.{SpawnGroup, Vector3}
import scodec.Codec
import scodec.codecs._
+/**
+ * The purpose of the `BindPlayerMessage` packet.
+ *
+ * `Bind` and `Unbind` are generally manual actions performed by the player.
+ * `Available` is applied to automatic Advanced Mobile Spawn points and other "Bound" points at the time of redeployment.
+ * `Lost` and `Unavailable` remove the status of being bound and have slightly different connotations.
+ * Each generates a different a events chat message if logging it turned on.
+ */
+object BindStatus extends Enumeration(1) {
+ type Type = Value
+
+ val
+ Bind,
+ Unbind,
+ Lost,
+ Available,
+ Unavailable
+ = Value
+
+ implicit val codec = PacketHelpers.createEnumerationCodec(this, uint8)
+}
+
/**
* A packet dispatched to maintain a manually-set respawn location.
*
* The packet establishes the player's ability to spawn in an arbitrary location that is not a normal local option.
- * This process is called "binding."
+ * This process is called "binding one's matrix."
* In addition to player establishing the binding, the packet updates as conditions of the respawn location changes.
*
* If `logging` is turned on, packets will display different messages depending on context.
+ * The bind descriptor will be used to flavor the events chat message.
* As long as the event is marked to be logged, when the packet is received, a message is displayed in the events window.
* If the logged action is applicable, the matrixing sound effect will be played too.
* Not displaying events is occasionally warranted for aesthetics.
- * The game will always note if this is your first time binding.
- *
- * One common occurrence of this packet is during zone transport.
- * Specifically, a packet is dispatched after unloading the current zone but before beginning loading in the new zone.
- * It is preceded by all of the `ObjectDeleteMessage` packets and itself precedes the `LoadMapMessage` packet.
- *
- * Actions:
- * `1` - bound to respawn point
- * `2` - general unbound / unbinding from respawn point
- * `3` - respawn point lost
- * `4` - bound spawn point became available
- * `5` - bound spawn point became unavailable (different from 3)
+ * The game will always note if this is your first time binding regardless of the state of this flag.
*
* Bind Descriptors:
* `@amp_station`
@@ -41,22 +53,28 @@ import scodec.codecs._
* Exploration:
* Find other bind descriptors.
* @param action the purpose of the packet
- * @param bindDesc a description of the respawn binding point
- * @param unk1 na; usually set `true` if there is more data in the packet ...
+ * @param bind_desc a text description of the respawn binding point
+ * @param unk1 na;
+ * usually set `true` if there is more data in the packet ...
* @param logging true, to report on bind point change visible in the events window;
* false, to render spawn change silent;
- * a first time event notification will always show
- * @param unk2 na; if a value, it is usually 40 (hex`28`)
- * @param unk3 na
+ * some first time notifications will always display regardless of this flag
+ * @param spawn_group the kind of spawn request that will be made;
+ * affects the type of icon displayed;
+ * will coincide with the value of `unk2` in `SpawnRequestMessage` when the spawn option is selected
+ * @param zone_number the number of the zone in which to display this spawn option;
+ * if `zone_number` is not the current zone, and the action is positive,
+ * a small map of the alternate zone with selectable spawn point will become visible
* @param unk4 na
- * @param pos a position associated with the binding
+ * @param pos coordinates for any displayed deployment map icon;
+ * `x` and `y` determine the position
*/
-final case class BindPlayerMessage(action : Int,
- bindDesc : String,
+final case class BindPlayerMessage(action : BindStatus.Value,
+ bind_desc : String,
unk1 : Boolean,
logging : Boolean,
- unk2 : Int,
- unk3 : Long,
+ spawn_group : SpawnGroup.Value,
+ zone_number : Long,
unk4 : Long,
pos : Vector3)
extends PlanetSideGamePacket {
@@ -68,20 +86,18 @@ final case class BindPlayerMessage(action : Int,
object BindPlayerMessage extends Marshallable[BindPlayerMessage] {
/**
* A common variant of this packet.
- * `16028004000000000000000000000000000000`
*/
- val STANDARD = BindPlayerMessage(2, "", false, false, 2, 0, 0, Vector3(0, 0, 0))
+ val Standard = BindPlayerMessage(BindStatus.Unbind, "", false, false, SpawnGroup.BoundAMS, 0, 0, Vector3.Zero)
+
+ private val spawnGroupCodec = PacketHelpers.createEnumerationCodec(SpawnGroup, uint4)
- //TODO: there are two ignore(1) in this packet; are they in a good position?
implicit val codec : Codec[BindPlayerMessage] = (
- ("action" | uint8L) ::
- ("bindDesc" | PacketHelpers.encodedString) ::
+ ("action" | BindStatus.codec) ::
+ ("bind_desc" | PacketHelpers.encodedString) ::
("unk1" | bool) ::
("logging" | bool) ::
- ignore(1) ::
- ("unk2" | uint4L) ::
- ignore(1) ::
- ("unk3" | uint32L) ::
+ ("spawn_group" | spawnGroupCodec) ::
+ ("zone_number" | uint32L) ::
("unk4" | uint32L) ::
("pos" | Vector3.codec_pos)
).as[BindPlayerMessage]
diff --git a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala
index f73052cfd..b4278c642 100644
--- a/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/SpawnRequestMessage.scala
@@ -1,7 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.packet.game
-import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
+import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
+import net.psforever.types.SpawnGroup
import scodec.Codec
import scodec.codecs._
@@ -9,20 +10,16 @@ import scodec.codecs._
* na
* @param unk1 when defined, na;
* non-zero when selecting the sanctuary option from a non-sanctuary continent deployment map
- * @param unk2 when defined, indicates type of spawn point by destination;
- * 0 is nothing;
- * 2 is ams;
- * 6 is towers;
- * 7 is facilities
+ * @param spawn_type the type of spawn point destination
* @param unk3 na
* @param unk4 na
- * @param unk5 when defined, the continent number
+ * @param zone_number when defined, the continent number
*/
final case class SpawnRequestMessage(unk1 : Int,
- unk2 : Long,
+ spawn_type : SpawnGroup.Value,
unk3 : Int,
unk4 : Int,
- unk5 : Int)
+ zone_number : Int)
extends PlanetSideGamePacket {
type Packet = SpawnRequestMessage
def opcode = GamePacketOpcode.SpawnRequestMessage
@@ -30,11 +27,13 @@ final case class SpawnRequestMessage(unk1 : Int,
}
object SpawnRequestMessage extends Marshallable[SpawnRequestMessage] {
+ private val spawnGroupCodec = PacketHelpers.createLongEnumerationCodec(SpawnGroup, uint32L)
+
implicit val codec : Codec[SpawnRequestMessage] = (
("unk1" | uint16L) ::
- ("unk2" | uint32L) ::
+ ("spawn_type" | spawnGroupCodec) ::
("unk3" | uint16L) ::
("unk4" | uint16L) ::
- ("unk5" | uintL(10))
+ ("zone_number" | uintL(10))
).as[SpawnRequestMessage]
}
diff --git a/common/src/main/scala/net/psforever/types/SpawnGroup.scala b/common/src/main/scala/net/psforever/types/SpawnGroup.scala
new file mode 100644
index 000000000..806a209c0
--- /dev/null
+++ b/common/src/main/scala/net/psforever/types/SpawnGroup.scala
@@ -0,0 +1,39 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.types
+
+/**
+ * The spawn group.
+ *
+ * The groups `Sanctuary`, `Tower`, and ,`Facility` are typically hard-defined by the client.
+ * The groups `AMS` and the `Bound*` spawns can only be displayed on the deployment map
+ * by sending a manual `BindPlayerMessage` packet to the client,
+ * and the designated spawn group identifier is returned to the server if the spawn point that is created is selected.
+ * The sanctuary spawn is also used as a fallback for an unknown spawn point
+ * as going back to one's own sanctuary counts as a "safe spawn."
+ *
+ * The `Sanctuary` spawn is commonly accessible on a smaller map (of the sanctuary continent)
+ * off to one side of the greater deployment map.
+ * It does not generate an icon when manually set.
+ * The icons produced by the normal and the bound tower and facility groups are not detailed.
+ * The ones that are not designated as "bound" also do not display icons when manually set.
+ * The AMS spawn group icons have an overhead AMS glyph and are smaller in radius, identical otherwise.
+ */
+object SpawnGroup extends Enumeration {
+ type Type = Value
+
+ val
+ Sanctuary,
+ BoundAMS,
+ AMS,
+ Unknown3,
+ BoundTower, //unused?
+ BoundFacility,
+ Tower,
+ Facility,
+ Unknown8,
+ Unknown9,
+ Unknown10,
+ Unknown11,
+ Unknown12
+ = Value
+}
diff --git a/common/src/main/scala/services/vehicle/VehicleService.scala b/common/src/main/scala/services/vehicle/VehicleService.scala
index e727528f1..e2acb9d17 100644
--- a/common/src/main/scala/services/vehicle/VehicleService.scala
+++ b/common/src/main/scala/services/vehicle/VehicleService.scala
@@ -198,7 +198,7 @@ class VehicleService extends Actor {
import net.psforever.objects.vehicles.UtilityType
import net.psforever.objects.GlobalDefinitions
zone.Vehicles
- .filter(veh => veh.Definition == GlobalDefinitions.ams && veh.DeploymentState == DriveState.Deployed)
+ .filter(veh => veh.Health > 0 && veh.Definition == GlobalDefinitions.ams && veh.DeploymentState == DriveState.Deployed)
.flatMap(veh => veh.Utilities.values.filter(util => util.UtilType == UtilityType.ams_respawn_tube) )
.map(util => util().asInstanceOf[SpawnTube])
}
diff --git a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala
index 8bcf4fae0..f5b0707bb 100644
--- a/common/src/main/scala/services/vehicle/support/VehicleRemover.scala
+++ b/common/src/main/scala/services/vehicle/support/VehicleRemover.scala
@@ -27,8 +27,7 @@ class VehicleRemover extends RemoverActor {
val zoneId = entry.zone.Id
vehicle.Actor ! Vehicle.PrepareForDeletion
//kick out all passengers
- vehicle.Definition.MountPoints.values.foreach(mount => {
- val seat = vehicle.Seat(mount).get
+ vehicle.Seats.values.foreach(seat => {
seat.Occupant match {
case Some(tplayer) =>
seat.Occupant = None
diff --git a/common/src/test/scala/game/BindPlayerMessageTest.scala b/common/src/test/scala/game/BindPlayerMessageTest.scala
index 93f9f6f85..a179e2e05 100644
--- a/common/src/test/scala/game/BindPlayerMessageTest.scala
+++ b/common/src/test/scala/game/BindPlayerMessageTest.scala
@@ -4,27 +4,42 @@ package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
-import net.psforever.types.Vector3
+import net.psforever.types.{SpawnGroup, Vector3}
import scodec.bits._
class BindPlayerMessageTest extends Specification {
+ val string_standard = hex"16028004000000000000000000000000000000"
val string_ams = hex"16 05 8440616D73 08 28000000 00000000 00000 00000 0000"
val string_tech = hex"16 01 8b40746563685f706c616e74 d4 28000000 38000000 00064 012b1 a044"
+ val string_akkan = hex"16048440616d7388100000001400000214e171a8e33024"
+
+ "decode (standard)" in {
+ PacketCoding.DecodePacket(string_standard).require match {
+ case BindPlayerMessage(action, bindDesc, unk1, logging, unk2, unk3, unk4, pos) =>
+ action mustEqual BindStatus.Unbind
+ bindDesc mustEqual ""
+ unk1 mustEqual false
+ logging mustEqual false
+ unk2 mustEqual SpawnGroup.BoundAMS
+ unk3 mustEqual 0
+ unk4 mustEqual 0
+ pos mustEqual Vector3.Zero
+ case _ =>
+ ko
+ }
+ }
"decode (ams)" in {
PacketCoding.DecodePacket(string_ams).require match {
case BindPlayerMessage(action, bindDesc, unk1, logging, unk2, unk3, unk4, pos) =>
- action mustEqual 5
- bindDesc.length mustEqual 4
+ action mustEqual BindStatus.Unavailable
bindDesc mustEqual "@ams"
unk1 mustEqual false
logging mustEqual false
- unk2 mustEqual 4
- unk3 mustEqual 40
+ unk2 mustEqual SpawnGroup.AMS
+ unk3 mustEqual 10
unk4 mustEqual 0
- pos.x mustEqual 0f
- pos.y mustEqual 0f
- pos.z mustEqual 0f
+ pos mustEqual Vector3.Zero
case _ =>
ko
}
@@ -33,40 +48,60 @@ class BindPlayerMessageTest extends Specification {
"decode (tech)" in {
PacketCoding.DecodePacket(string_tech).require match {
case BindPlayerMessage(action, bindDesc, unk1, logging, unk2, unk3, unk4, pos) =>
- action mustEqual 1
- bindDesc.length mustEqual 11
+ action mustEqual BindStatus.Bind
bindDesc mustEqual "@tech_plant"
unk1 mustEqual true
logging mustEqual true
- unk2 mustEqual 10
- unk3 mustEqual 40
- unk4 mustEqual 56
- pos.x mustEqual 2060.0f
- pos.y mustEqual 598.0078f
- pos.z mustEqual 274.5f
+ unk2 mustEqual SpawnGroup.BoundFacility
+ unk3 mustEqual 10
+ unk4 mustEqual 14
+ pos mustEqual Vector3(4610.0f, 6292, 69.625f)
case _ =>
ko
}
}
+ "decode (akkan)" in {
+ PacketCoding.DecodePacket(string_akkan).require match {
+ case BindPlayerMessage(action, bindDesc, unk1, logging, unk2, unk3, unk4, pos) =>
+ action mustEqual BindStatus.Available
+ bindDesc mustEqual "@ams"
+ unk1 mustEqual true
+ logging mustEqual false
+ unk2 mustEqual SpawnGroup.AMS
+ unk3 mustEqual 4
+ unk4 mustEqual 5
+ pos mustEqual Vector3(2673.039f, 4423.547f, 39.1875f)
+ case _ =>
+ ko
+ }
+ }
+
+ "encode (standard)" in {
+ val msg = BindPlayerMessage.Standard
+ val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
+
+ pkt mustEqual string_standard
+ }
+
"encode (ams)" in {
- val msg = BindPlayerMessage(5, "@ams", false, false, 4, 40, 0, Vector3(0, 0, 0))
+ val msg = BindPlayerMessage(BindStatus.Unavailable, "@ams", false, false, SpawnGroup.AMS, 10, 0, Vector3.Zero)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_ams
}
"encode (tech)" in {
- val msg = BindPlayerMessage(1, "@tech_plant", true, true, 10, 40, 56, Vector3(2060.0f, 598.0078f, 274.5f))
+ val msg = BindPlayerMessage(BindStatus.Bind, "@tech_plant", true, true, SpawnGroup.BoundFacility, 10, 14, Vector3(4610.0f, 6292, 69.625f))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_tech
}
- "standard" in {
- val msg = BindPlayerMessage.STANDARD
+ "encode (akkan)" in {
+ val msg = BindPlayerMessage(BindStatus.Available, "@ams", true, false, SpawnGroup.AMS, 4, 5, Vector3(2673.039f, 4423.547f, 39.1875f))
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
- pkt mustEqual hex"16028004000000000000000000000000000000"
+ pkt mustEqual string_akkan
}
}
diff --git a/common/src/test/scala/game/SpawnRequestMessageTest.scala b/common/src/test/scala/game/SpawnRequestMessageTest.scala
index adab70f24..bc9149c6c 100644
--- a/common/src/test/scala/game/SpawnRequestMessageTest.scala
+++ b/common/src/test/scala/game/SpawnRequestMessageTest.scala
@@ -4,6 +4,7 @@ package game
import org.specs2.mutable._
import net.psforever.packet._
import net.psforever.packet.game._
+import net.psforever.types.SpawnGroup
import scodec.bits._
class SpawnRequestMessageTest extends Specification {
@@ -13,7 +14,7 @@ class SpawnRequestMessageTest extends Specification {
PacketCoding.DecodePacket(string).require match {
case SpawnRequestMessage(unk1,unk2,unk3,unk4,unk5) =>
unk1 mustEqual 0
- unk2 mustEqual 7
+ unk2 mustEqual SpawnGroup.Facility
unk3 mustEqual 0
unk4 mustEqual 0
unk5 mustEqual 2
@@ -23,7 +24,7 @@ class SpawnRequestMessageTest extends Specification {
}
"encode" in {
- val msg = SpawnRequestMessage(0, 7, 0, 0, 2)
+ val msg = SpawnRequestMessage(0, SpawnGroup.Facility, 0, 0, 2)
val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string
diff --git a/common/src/test/scala/objects/DamageModelTests.scala b/common/src/test/scala/objects/DamageModelTests.scala
index 2b4c95b7d..1230b0438 100644
--- a/common/src/test/scala/objects/DamageModelTests.scala
+++ b/common/src/test/scala/objects/DamageModelTests.scala
@@ -106,12 +106,12 @@ class DamageCalculationsTests extends Specification {
"calculate splash damage from components (near)" in {
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
- SplashDamageWithRadialDegrade(projectile, result, 0) mustEqual 264
+ SplashDamageWithRadialDegrade(projectile, result, 0) mustEqual 132
}
"calculate splash damage from components (medium)" in {
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof))
- SplashDamageWithRadialDegrade(projectile, result, 5) mustEqual 145
+ SplashDamageWithRadialDegrade(projectile, result, 5) mustEqual 13
}
"calculate splash damage from components (far)" in {
@@ -341,7 +341,7 @@ class DamageModelTests extends Specification {
val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
func(tplayer)
- tplayer.Health mustEqual 81
+ tplayer.Health mustEqual 98
tplayer.Armor mustEqual 35
}
@@ -410,7 +410,7 @@ class DamageModelTests extends Specification {
val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
func(vehicle)
- vehicle.Health mustEqual 632
+ vehicle.Health mustEqual 641
}
}
}
diff --git a/common/src/test/scala/objects/EquipmentTest.scala b/common/src/test/scala/objects/EquipmentTest.scala
index 1fc0aeeac..ee32e62d0 100644
--- a/common/src/test/scala/objects/EquipmentTest.scala
+++ b/common/src/test/scala/objects/EquipmentTest.scala
@@ -111,6 +111,7 @@ class EquipmentTest extends Specification {
obj.Size = EquipmentSize.Rifle
obj.AmmoTypes += GlobalDefinitions.shotgun_shell
obj.AmmoTypes += GlobalDefinitions.shotgun_shell_AP
+ obj.AmmoTypes += GlobalDefinitions.shotgun_shell_AP
obj.FireModes += new FireModeDefinition
obj.FireModes.head.AmmoTypeIndices += 0
obj.FireModes.head.AmmoTypeIndices += 1
@@ -163,6 +164,29 @@ class EquipmentTest extends Specification {
obj.AmmoType mustEqual Ammo.hellfire_ammo
}
+ "default fire mode (dual magazine feed)" in {
+ val obj = ToolDefinition(1076)
+ obj.AmmoTypes += GlobalDefinitions.shotgun_shell
+ obj.AmmoTypes += GlobalDefinitions.shotgun_shell_AP
+ obj.ProjectileTypes += GlobalDefinitions.shotgun_shell_projectile
+ obj.ProjectileTypes += GlobalDefinitions.shotgun_shell_AP_projectile
+ obj.FireModes += new FireModeDefinition
+ obj.FireModes.head.AmmoTypeIndices += 0
+ obj.FireModes.head.AmmoSlotIndex = 0
+ obj.FireModes += new FireModeDefinition
+ obj.FireModes(1).AmmoTypeIndices += 1
+ obj.FireModes(1).AmmoSlotIndex = 1
+
+ val tool0 = Tool(obj)
+ tool0.FireModeIndex mustEqual 0
+ tool0.ProjectileType mustEqual GlobalDefinitions.shotgun_shell_projectile.ProjectileType
+
+ obj.DefaultFireModeIndex = 1
+ val tool1 = Tool(obj)
+ tool1.FireModeIndex mustEqual 1
+ tool1.ProjectileType mustEqual GlobalDefinitions.shotgun_shell_AP_projectile.ProjectileType
+ }
+
"multiple fire modes" in {
//explanation: sample_weapon has two fire modes; each fire mode has a different ammunition type
val obj : Tool = Tool(punisher)
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index cb7a7c3cf..ecddc5476 100644
--- a/pslogin/src/main/scala/WorldSessionActor.scala
+++ b/pslogin/src/main/scala/WorldSessionActor.scala
@@ -5,7 +5,7 @@ import java.util.concurrent.atomic.AtomicInteger
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
import net.psforever.packet._
import net.psforever.packet.control._
-import net.psforever.packet.game.{BattleDiagramAction, ObjectDetachMessage, _}
+import net.psforever.packet.game._
import scodec.Attempt.{Failure, Successful}
import scodec.bits._
import org.log4s.{Logger, MDC}
@@ -68,6 +68,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
import WorldSessionActor._
private[this] val log = org.log4s.getLogger
+ private[this] val damageLog = org.log4s.getLogger("DamageResolution")
var sessionId : Long = 0
var leftRef : ActorRef = ActorRef.noSender
var rightRef : ActorRef = ActorRef.noSender
@@ -81,7 +82,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
var player : Player = null
var avatar : Avatar = null
var progressBarValue : Option[Float] = None
- var shooting : Option[PlanetSideGUID] = None
+ var shooting : Option[PlanetSideGUID] = None //ChangeFireStateMessage_Start
+ var prefire : Option[PlanetSideGUID] = None //if WeaponFireMessage precedes ChangeFireStateMessage_Start
var accessedContainer : Option[PlanetSideGameObject with Container] = None
var flying : Boolean = false
var speed : Float = 1.0f
@@ -98,6 +100,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None)
var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
var recentTeleportAttempt : Long = 0
+ var lastTerminalOrderFulfillment : Boolean = true
var amsSpawnPoint : Option[SpawnTube] = None
var clientKeepAlive : Cancellable = DefaultCancellable.obj
@@ -115,8 +118,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* @param b `true` or `false` (or `null`)
* @return 1 for `true`; 0 for `false`
*/
- implicit def boolToInt(b : Boolean) : Int = if(b) 1
- else 0
+ implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0
override def postStop() = {
//TODO normally, player avatar persists a minute or so after disconnect; we are subject to the SessionReaper
@@ -128,7 +130,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
vehicleService ! Service.Leave()
avatarService ! Service.Leave()
galaxyService ! Service.Leave()
- cluster ! Service.Leave()
LivePlayerList.Remove(sessionId)
if(player != null && player.HasGUID) {
val player_guid = player.GUID
@@ -378,7 +379,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, pad) =>
val vdef = vehicle.Definition
if(vehicle.Seats(0).isOccupied) {
- sendResponse(ObjectDetachMessage(pad.GUID, vehicle.GUID, pad.Position + Vector3(0, 0, 0.5f), pad.Orientation.z))
+ sendResponse(ObjectDetachMessage(pad.GUID, vehicle.GUID, pad.Position + Vector3.z(0.5f), pad.Orientation.z))
}
ServerVehicleOverride(vehicle, vdef.AutoPilotSpeed1, GlobalDefinitions.isFlightVehicle(vdef) : Int)
@@ -879,6 +880,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
AnnounceDestroyDeployable(target, None)
}
+ case Vitality.DamageResolution(target : FacilityTurret) =>
+ HandleFacilityTurretDamageResolution(target)
+
case Vitality.DamageResolution(target : PlanetSideGameObject) =>
log.warn(s"Vital target ${target.Definition.Name} damage resolution not supported using this method")
@@ -947,16 +951,41 @@ class WorldSessionActor extends Actor with MDCContextAware {
case AvatarResponse.DamageResolution(target, resolution_function) =>
if(player.isAlive) {
+ val originalHealth = player.Health
+ val originalArmor = player.Armor
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)
+ val damageToHealth = originalHealth - health
+ val damageToArmor = originalArmor - armor
+ damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor, AFTER=$health/$armor, CHANGE=$damageToHealth/$damageToArmor")
+ if(damageToHealth != 0 || damageToArmor != 0) {
+ 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)
+ }
+ else {
+ //first damage entry -> most recent damage source -> killing blow
+ target.History.find(p => p.isInstanceOf[DamagingActivity]) match {
+ case Some(data : DamageFromProjectile) =>
+ data.data.projectile.owner match {
+ case pSource : PlayerSource =>
+ continent.LivePlayers.find(_.Name == pSource.Name) match {
+ case Some(tplayer) =>
+ sendResponse(HitHint(tplayer.GUID, player.GUID))
+ case None => ;
+ }
+ case vSource : SourceEntry =>
+ sendResponse(DamageWithPositionMessage(damageToHealth + damageToArmor, vSource.Position))
+ case _ => ;
+ }
+ case _ => ;
+ }
+ }
}
}
@@ -978,11 +1007,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
case AvatarResponse.HitHint(source_guid) =>
- sendResponse(HitHint(source_guid, guid))
+ if(player.isAlive) {
+ sendResponse(HitHint(source_guid, guid))
+ }
case AvatarResponse.KilledWhileInVehicle() =>
if(player.isAlive && player.VehicleSeated.nonEmpty) {
- (continent.GUID(player.VehicleSeated.get) match {
+ (continent.GUID(player.VehicleSeated) match {
case Some(obj : Vehicle) =>
if(obj.Health == 0) Some(obj)
else None
@@ -990,7 +1021,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(obj.Health == 0) Some(obj)
else None
case Some(obj : FacilityTurret) =>
- if(obj.Health == 0) Some(obj)
+ if(obj.Health == 1) Some(obj) //TODO proper turret death at 0 health
else None
case _ =>
None
@@ -999,10 +1030,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
obj.LastShot match {
case Some(cause) =>
player.History(cause)
+ KillPlayer(player)
case None => ;
}
- KillPlayer(player)
- case _ => ;
+ case _ =>
+ log.warn(s"${player.Name} was seated in a vehicle and should have been killed, but was not; suicidal fallback")
+ Suicide(player)
}
}
@@ -1409,38 +1442,34 @@ class WorldSessionActor extends Actor with MDCContextAware {
*/
def HandleTerminalMessage(tplayer : Player, msg : ItemTransactionMessage, order : Terminal.Exchange) : Unit = {
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
- player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal
- player.ExoSuit = exosuit
- if(Loadout.DetermineSubtype(tplayer) != subtype) {
- //special MAX case - suit switching to a different MAX suit; we need to change the main weapon
- sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype))
- avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
- val arms = tplayer.Slot(0).Equipment.get
- val putTask = PutEquipmentInSlot(tplayer, Tool(GlobalDefinitions.MAXArms(subtype, tplayer.Faction)), 0)
- taskResolver ! DelayedObjectHeld(tplayer, 0, List(TaskResolver.GiveTask(putTask.task, putTask.subs :+ RemoveEquipmentFromSlot(tplayer, arms, 0))))
- }
- }
- //outside of the MAX condition above, we should seldom reach this point through conventional methods
- tplayer.Armor = tplayer.MaxArmor
- sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor))
- avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor))
+ case Terminal.BuyExosuit(exosuit, subtype) =>
+ //TODO check exo-suit permissions
+ val originalSuit = tplayer.ExoSuit
+ val originalSubtype = Loadout.DetermineSubtype(tplayer)
+ if(originalSuit != exosuit || originalSubtype != subtype) {
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
- }
- else {
- //load a complete new exo-suit and shuffle the inventory around
- val originalSuit = tplayer.ExoSuit
- //save inventory before it gets cleared (empty holsters)
- val dropPred = DropPredicate(tplayer)
- val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred)
- val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred)
- //change suit (clear inventory and change holster sizes; note: holsters must be empty before this point)
- tplayer.ExoSuit = exosuit
- tplayer.Armor = tplayer.MaxArmor
+ //prepare lists of valid objects
+ val beforeInventory = tplayer.Inventory.Clear()
+ val beforeHolsters = clearHolsters(tplayer.Holsters().iterator)
+ //change suit (clear inventory and change holster sizes; holsters must be empty before this point)
+ val originalArmor = tplayer.Armor
+ tplayer.ExoSuit = exosuit //changes the value of MaxArmor to reflect the new exo-suit
+ val toMaxArmor = tplayer.MaxArmor
+ if(originalSuit != exosuit || originalSubtype != subtype || originalArmor > toMaxArmor) {
+ tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit))
+ tplayer.Armor = toMaxArmor
+ sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, toMaxArmor))
+ avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, toMaxArmor))
+ }
+ else {
+ tplayer.Armor = originalArmor
+ }
+ //ensure arm is down, even if it needs to go back up
+ if(tplayer.DrawnSlot != Player.HandsDownSlot) {
+ tplayer.DrawnSlot = Player.HandsDownSlot
+ sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
+ avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, tplayer.LastDrawnSlot))
+ }
//delete everything not dropped
(beforeHolsters ++ beforeInventory).foreach({ elem =>
sendResponse(ObjectDeleteMessage(elem.obj.GUID, 0))
@@ -1451,31 +1480,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
//report change
sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype))
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
- sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor))
- avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor))
- val finalInventory = if(exosuit == ExoSuitType.MAX) {
- //MAX weapon to be placed in first pistol slot; slot to be drawn
- taskResolver ! DelayedObjectHeld(tplayer, 0, List(PutEquipmentInSlot(tplayer, Tool(GlobalDefinitions.MAXArms(subtype, tplayer.Faction)), 0)))
- //fill melee slot
- fillEmptyHolsters(List(tplayer.Slot(4)).iterator, beforeHolsters) ++ beforeInventory
+ //sterilize holsters
+ val normalHolsters = if(originalSuit == ExoSuitType.MAX) {
+ val (maxWeapons, normalWeapons) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
+ maxWeapons.foreach(entry => { taskResolver ! GUIDTask.UnregisterEquipment(entry.obj)(continent.GUID) })
+ normalWeapons
}
else {
- //remove potential MAX weapon
- tplayer.DrawnSlot = Player.HandsDownSlot
- sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
- val normalWeapons = if(originalSuit == ExoSuitType.MAX) {
- val (maxWeapons, normalWeapons) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
- maxWeapons.foreach(entry => {
- taskResolver ! GUIDTask.UnregisterEquipment(entry.obj)(continent.GUID)
- })
- avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot))
- normalWeapons
- }
- else {
- beforeHolsters
- }
- //fill holsters
- val (afterHolsters, toInventory) = normalWeapons.partition(elem => elem.obj.Size == tplayer.Slot(elem.start).Size)
+ beforeHolsters
+ }
+ //populate holsters
+ val finalInventory = if(exosuit == ExoSuitType.MAX) {
+ taskResolver ! DelayedObjectHeld(tplayer, 0, List(PutEquipmentInSlot(tplayer, Tool(GlobalDefinitions.MAXArms(subtype, tplayer.Faction)), 0)))
+ fillEmptyHolsters(List(tplayer.Slot(4)).iterator, normalHolsters) ++ beforeInventory
+ }
+ else if(originalSuit == exosuit) { //note - this will rarely be the situation
+ fillEmptyHolsters(tplayer.Holsters().iterator, normalHolsters)
+ }
+ else {
+ val (afterHolsters, toInventory) = normalHolsters.partition(elem => elem.obj.Size == tplayer.Slot(elem.start).Size)
afterHolsters.foreach({ elem => tplayer.Slot(elem.start).Equipment = elem.obj })
fillEmptyHolsters(tplayer.Holsters().iterator, toInventory ++ beforeInventory)
}
@@ -1511,7 +1534,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
case None => ;
}
//put items back into inventory
- val (stow, drop) = GridInventory.recoverInventory(finalInventory, tplayer.Inventory)
+ val (stow, drop) = if(originalSuit == exosuit) {
+ (finalInventory, Nil)
+ }
+ else {
+ GridInventory.recoverInventory(finalInventory, tplayer.Inventory)
+ }
stow.foreach(elem => {
tplayer.Inventory.Insert(elem.start, elem.obj)
val obj = elem.obj
@@ -1525,14 +1553,21 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
)
})
- //drop items on ground
+ val (finalDroppedItems, retiredItems) = drop.map(item => InventoryItem(item, -1)).partition(DropPredicate(tplayer))
+ //drop special items on ground
val pos = tplayer.Position
- val orient = Vector3(0, 0, tplayer.Orientation.z)
- ((dropHolsters ++ dropInventory).map(_.obj) ++ drop).foreach(obj => {
+ val orient = Vector3.z(tplayer.Orientation.z)
+ finalDroppedItems.foreach(entry => {
//TODO make a sound when dropping stuff
- continent.Ground ! Zone.Ground.DropItem(obj, pos, orient)
+ continent.Ground ! Zone.Ground.DropItem(entry.obj, pos, orient)
})
- sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
+ //deconstruct normal items
+ retiredItems.foreach({ entry =>
+ taskResolver ! GUIDTask.UnregisterEquipment(entry.obj)(continent.GUID)
+ })
+ }
+ else {
+ sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, false))
}
case Terminal.BuyEquipment(item) =>
@@ -1556,26 +1591,38 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) =>
- //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}")
+ //TODO check exo-suit permissions
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))
- avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, Player.HandsDownSlot))
- //load
+ //prepare lists of valid objects
+ val beforeFreeHand = tplayer.FreeHand.Equipment
val dropPred = DropPredicate(tplayer)
val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred)
val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred)
- val (_, afterHolsters) = holsters.partition(dropPred)
- //dropped items are lost
- val (_, afterInventory) = inventory.partition(dropPred)
- //dropped items are lost
- val beforeFreeHand = tplayer.FreeHand.Equipment
- //change suit (clear inventory and change holster sizes; note: holsters must be empty before this point)
+ val (_, afterHolsters) = holsters.partition(dropPred) //dropped items are forgotten
+ val (_, afterInventory) = inventory.partition(dropPred) //dropped items are forgotten
+ //change suit (clear inventory and change holster sizes; holsters must be empty before this point)
+ tplayer.FreeHand.Equipment = None //terminal and inventory will close, so prematurely dropping should be fine
+ val originalSuit = player.ExoSuit
+ val originalSubtype = Loadout.DetermineSubtype(tplayer)
+ val originalArmor = player.Armor
tplayer.ExoSuit = exosuit
- tplayer.Armor = tplayer.MaxArmor
+ val toMaxArmor = tplayer.MaxArmor
+ if(originalSuit != exosuit || originalSubtype != subtype || originalArmor > toMaxArmor) {
+ tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit))
+ tplayer.Armor = toMaxArmor
+ sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, toMaxArmor))
+ avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, toMaxArmor))
+ }
+ else {
+ tplayer.Armor = originalArmor
+ }
+ //ensure arm is down, even if it needs to go back up
+ if(tplayer.DrawnSlot != Player.HandsDownSlot) {
+ tplayer.DrawnSlot = Player.HandsDownSlot
+ sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
+ avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, tplayer.LastDrawnSlot))
+ }
//delete everything (not dropped)
beforeHolsters.foreach({ elem =>
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(tplayer.GUID, elem.obj.GUID))
@@ -1587,29 +1634,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
//report change
sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
- sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor))
- avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor))
- //re-draw equipment held in free hand
- beforeFreeHand match {
- case Some(item) =>
- tplayer.FreeHand.Equipment = beforeFreeHand
- val definition = item.Definition
- sendResponse(
- ObjectCreateDetailedMessage(
- definition.ObjectId,
- item.GUID,
- ObjectCreateMessageParent(tplayer.GUID, Player.FreeHandSlot),
- definition.Packet.DetailedConstructorData(item).get
- )
- )
- case None => ;
- }
- //draw holsters
if(exosuit == ExoSuitType.MAX) {
- tplayer.DrawnSlot = 0
- val (maxWeapons, otherWeapons) = afterHolsters.partition(entry => {
- entry.obj.Size == EquipmentSize.Max
- })
+ val (maxWeapons, otherWeapons) = afterHolsters.partition(entry => { entry.obj.Size == EquipmentSize.Max })
taskResolver ! DelayedObjectHeld(tplayer, 0, List(PutEquipmentInSlot(tplayer, maxWeapons.head.obj, 0)))
otherWeapons
}
@@ -1624,9 +1650,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
})
//drop stuff on ground
val pos = tplayer.Position
- val orient = Vector3(0, 0, tplayer.Orientation.z)
- ((dropHolsters ++ dropInventory).map(_.obj)).foreach(obj => {
- continent.Ground ! Zone.Ground.DropItem(obj, pos, orient)
+ val orient = Vector3.z(tplayer.Orientation.z)
+ ((beforeFreeHand match {
+ case Some(item) => List(InventoryItem(item, -1)) //add the item previously in free hand, if any
+ case None => Nil
+ }) ++ dropHolsters ++ dropInventory).foreach(entry => {
+ continent.Ground ! Zone.Ground.DropItem(entry.obj, pos, orient)
})
case Terminal.VehicleLoadout(definition, weapons, inventory) =>
@@ -1855,6 +1884,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.warn(s"${tplayer.Name} made a request but the terminal rejected the $order")
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false))
}
+ lastTerminalOrderFulfillment = true
}
/**
@@ -1899,7 +1929,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
case VehicleResponse.HitHint(source_guid) =>
- sendResponse(HitHint(source_guid, player.GUID))
+ if(player.isAlive) {
+ sendResponse(HitHint(source_guid, player.GUID))
+ }
case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) =>
if(tplayer_guid != guid) {
@@ -1994,24 +2026,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(msg)
case VehicleResponse.UpdateAmsSpawnPoint(list) =>
- if(player.isBackpack) {
- //dismiss old ams spawn point
- ClearCurrentAmsSpawnPoint()
- //draw new ams spawn point
- list
- .filter(tube => tube.Faction == player.Faction)
- .sortBy(tube => Vector3.DistanceSquared(tube.Position, player.Position))
- .headOption match {
- case Some(tube) =>
- sendResponse(
- BattleplanMessage(41378949, "ams", continent.Number, List(BattleDiagramAction(DiagramActionCode.StartDrawing)))
- )
- sendResponse(
- BattleplanMessage(41378949, "ams", continent.Number, List(BattleDiagramAction.drawString(tube.Position.x, tube.Position.y, 3, 0, "AMS")))
- )
- amsSpawnPoint = Some(tube)
- case None => ;
- }
+ //dismiss old ams spawn point
+ ClearCurrentAmsSpawnPoint()
+ //draw new ams spawn point
+ list
+ .filter(tube => tube.Faction == player.Faction)
+ .sortBy(tube => Vector3.DistanceSquared(tube.Position, player.Position))
+ .headOption match {
+ case Some(tube) =>
+ sendResponse(BindPlayerMessage(BindStatus.Available, "@ams", true, false, SpawnGroup.AMS, continent.Number, 5, tube.Position))
+ amsSpawnPoint = Some(tube)
+ case None => ;
}
case _ => ;
@@ -2147,6 +2172,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
val wep = slot.Equipment.get
avatarService ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
})
+ target.CargoHolds.values.foreach(hold => {
+ hold.Occupant match {
+ case Some(cargo) =>
+
+ case None => ;
+ }
+ })
target.Definition match {
case GlobalDefinitions.ams =>
target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
@@ -2199,6 +2231,48 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatarService ! AvatarServiceMessage(continentId, AvatarAction.PlanetsideAttribute(guid, 0, health))
}
+ def HandleFacilityTurretDamageResolution(target : FacilityTurret) : Unit = {
+ 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 > 1) { //TODO turret "death" at 0, as is proper
+ //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
+ })
+ //turret 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))
+// })
+// avatarService ! AvatarServiceMessage(continentId, AvatarAction.Destroy(targetGUID, playerGUID, playerGUID, player.Position))
+ target.Health = 1
+ vehicleService ! VehicleServiceMessage(continentId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.MaxHealth)) //TODO not necessary
+ if(target.Upgrade != TurretUpgrade.None) {
+ vehicleService ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.ClearSpecific(List(target), continent))
+ vehicleService ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(target, continent, TurretUpgrade.None))
+ }
+ }
+ vehicleService ! VehicleServiceMessage(continentId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
+ }
+
/**
* na
* @param tplayer na
@@ -2462,15 +2536,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
//player.Position = Vector3(4262.211f ,4067.0625f ,262.35938f) //z6, Akna.tower
//player.Orientation = Vector3(0f, 0f, 132.1875f)
// player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting
- player.Slot(0).Equipment = ConstructionItem(ace) //Tool(GlobalDefinitions.StandardPistol(player.Faction))
- player.Slot(2).Equipment = ConstructionItem(advanced_ace) //punisher //suppressor
+ player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction))
+ player.Slot(2).Equipment = Tool(suppressor)
player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction))
- player.Slot(6).Equipment = ConstructionItem(ace) //bullet_9mm
- player.Slot(9).Equipment = ConstructionItem(ace) //bullet_9mm
- player.Slot(12).Equipment = ConstructionItem(ace) //bullet_9mm
- player.Slot(33).Equipment = Tool(suppressor) //AmmoBox(bullet_9mm_AP)
- //player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction))
- //player.Slot(39).Equipment = SimpleItem(remote_electronics_kit)
+ player.Slot(6).Equipment = AmmoBox(bullet_9mm)
+ player.Slot(9).Equipment = AmmoBox(bullet_9mm)
+ player.Slot(12).Equipment = AmmoBox(bullet_9mm)
+ player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP)
+ player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction))
+ player.Slot(39).Equipment = SimpleItem(remote_electronics_kit)
player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit)
//TODO end temp player character auto-loading
self ! ListAccountCharacters
@@ -2478,97 +2552,44 @@ class WorldSessionActor extends Actor with MDCContextAware {
clientKeepAlive.cancel
clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient())
- case msg @ DismountVehicleCargoMsg(player_guid, vehicle_guid, bailed, requestedByPassenger, kicked) =>
- log.info(msg.toString)
-
- // Ignore dismount requests by passengers of the vehicle in the cargo bay for now
- // todo: allow passengers of vehicle in cargo bay to bail, but not bail the cargo vehicle itself
- if(!requestedByPassenger) {
- StartBundlingPackets()
- val vehicle = continent.GUID(vehicle_guid).get.asInstanceOf[Vehicle]
- val cargo_vehicle = continent.GUID(vehicle.MountedIn.get).get.asInstanceOf[Vehicle]
- // todo: change this to work with multiple cargo holds for potential custom vehicles in the future
- val cargo_mountpoint = cargo_vehicle.Definition.Cargo.head._1
-
- val cargoStatusMessage = CargoMountPointStatusMessage(cargo_vehicle.GUID, PlanetSideGUID(0), PlanetSideGUID(0), vehicle_guid, cargo_mountpoint, CargoStatus.InProgress, 0)
- log.info(cargoStatusMessage.toString)
- // Dismount vehicle on UI and disable "shield" effect on lodestar
- sendResponse(cargoStatusMessage)
-
-
- // Detach vehicle from cargo vehicle
- val dismount_position = if (bailed || kicked) {
- // If we're bailing drop the vehicle below the cargo vehicle
- //todo: once the server has a concept of height from the floor we should probably ensure vehicles aren't dropped below the world
- Vector3(cargo_vehicle.Position.x, cargo_vehicle.Position.y, cargo_vehicle.Position.z - 1f)
- } else if (cargo_vehicle.Definition == GlobalDefinitions.dropship) {
- // As the galaxy cargo bay is offset backwards from the center of the vehicle (unlike the lodestar) we need to set the position backwards slightly
- Vector3(cargo_vehicle.Position.x, cargo_vehicle.Position.y - 7f, cargo_vehicle.Position.z + 2f)
- } else {
- Vector3(cargo_vehicle.Position.x, cargo_vehicle.Position.y, cargo_vehicle.Position.z + 2f)
- }
-
- // Add a flag if the vehicle should mount/dismount sideways
- //todo: BFRs will likely also need this set
- val sideways = vehicle.Definition == GlobalDefinitions.router
-
- val rotation = if(sideways) {
- // dismount router "sideways" in a lodestar
- cargo_vehicle.Orientation.z - 90f
- } else {
- cargo_vehicle.Orientation.z
- }
-
- val detachMessage = ObjectDetachMessage(cargo_vehicle.GUID, vehicle_guid, dismount_position, cargo_vehicle.Orientation.x, cargo_vehicle.Orientation.y, rotation)
- log.info(detachMessage.toString)
- sendResponse(detachMessage)
-
- // Update display to show current vehicle health & shields correctly
- log.warn(s"vehicle health: ${vehicle.Health} shields: ${vehicle.Shields}")
- vehicleService ! VehicleServiceMessage(s"${vehicle.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(vehicle_guid, 0, vehicle.Health)))
- vehicleService ! VehicleServiceMessage(s"${vehicle.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(vehicle_guid, 68, vehicle.Shields)))
-
- vehicle.MountedIn = None
- cargo_vehicle.CargoHold(cargo_mountpoint).get.Occupant = None
-
- if (!bailed) {
- // Automatically drive the vehicle backwards out of the cargo bay
- if (!sideways) {
- ServerVehicleLockReverse()
- } else {
- ServerVehicleLockStrafeLeft()
- }
- } else {
- //todo: proper vehicle bailing. It works currently but when collision damage is implemented the vehicle will take damage if not in a bail state. Need to confirm how this is done with further research
- }
-
- import scala.concurrent.duration._
- import scala.concurrent.ExecutionContext.Implicits.global
- // Start a timer to check every second if the vehicle has moved far enough away to be considered dismounted, and then close the cargo door
- cargoDismountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoDismount(vehicle_guid, cargo_vehicle.GUID, cargo_mountpoint, iteration = 0))
-
- StopBundlingPackets()
-
- // Sync to other clients
- avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, cargoStatusMessage))
- avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, detachMessage))
- }
case msg @ MountVehicleCargoMsg(player_guid, vehicle_guid, cargo_vehicle_guid, unk4) =>
log.info(msg.toString)
+ (continent.GUID(vehicle_guid), continent.GUID(cargo_vehicle_guid)) match {
+ case (Some(_ : Vehicle), Some(carrier : Vehicle)) =>
+ carrier.Definition.Cargo.headOption match {
+ case Some((mountPoint, _)) => //begin the mount process - open the cargo door
+ val reply = CargoMountPointStatusMessage(cargo_vehicle_guid, PlanetSideGUID(0), vehicle_guid, PlanetSideGUID(0), mountPoint, CargoStatus.InProgress, 0)
+ log.debug(reply.toString)
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, reply))
+ sendResponse(reply)
- val cargo_vehicle = continent.GUID(cargo_vehicle_guid).get.asInstanceOf[Vehicle]
- val cargo_mountpoint = cargo_vehicle.Definition.Cargo.head._1
+ import scala.concurrent.duration._
+ import scala.concurrent.ExecutionContext.Implicits.global
+ // Start timer to check every second if the vehicle is close enough to mount, or far enough away to cancel the mounting
+ cargoMountTimer = context.system.scheduler.scheduleOnce(1 second, self, CheckCargoMounting(vehicle_guid, cargo_vehicle_guid, mountPoint, iteration = 0))
+ case None =>
+ log.warn(s"MountVehicleCargoMsg: target carrier vehicle (${carrier.Definition.Name}) does not have a cargo hold")
+ }
+ case(None, _) | (Some(_), None) =>
+ log.warn(s"MountVehicleCargoMsg: one or more of the target vehicles do not exist - $cargo_vehicle_guid or $vehicle_guid")
+ case _ => ;
+ }
- // Begin the mount process - open the cargo door
- val reply = CargoMountPointStatusMessage(cargo_vehicle_guid, PlanetSideGUID(0), vehicle_guid, PlanetSideGUID(0), cargo_mountpoint, CargoStatus.InProgress, 0)
- log.warn(reply.toString)
- avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, reply))
- sendResponse(reply)
+ case msg @ DismountVehicleCargoMsg(player_guid, vehicle_guid, bailed, requestedByPassenger, kicked) =>
+ log.info(msg.toString)
+ if(!requestedByPassenger) {
+ continent.GUID(vehicle_guid) match {
+ case Some(cargo : Vehicle) =>
+ continent.GUID(cargo.MountedIn) match {
+ case Some(ferry : Vehicle) =>
+ HandleDismountVehicleCargo(player_guid, vehicle_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked)
+ case _ =>
+ log.warn(s"DismountVehicleCargoMsg: target ${cargo.Definition.Name} does not know what treats it as cargo")
+ }
+ case _ => ;
+ }
+ }
- import scala.concurrent.duration._
- import scala.concurrent.ExecutionContext.Implicits.global
- // Start timer to check every second if the vehicle is close enough to mount, or far enough away to cancel the mounting
- cargoMountTimer = context.system.scheduler.scheduleOnce(1 second, self, CheckCargoMounting(vehicle_guid, cargo_vehicle_guid, cargo_mountpoint, iteration = 0))
case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) =>
log.info("Handling " + msg)
sendResponse(ActionResultMessage.Pass)
@@ -2843,9 +2864,29 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.FacingYawUpper = yaw_upper
player.Crouching = is_crouching
player.Jumping = is_jumping
+
if(vel.isDefined && usingMedicalTerminal.isDefined) {
StopUsingProximityUnit(continent.GUID(usingMedicalTerminal.get).get.asInstanceOf[ProximityTerminal])
}
+ accessedContainer match {
+ case Some(veh : Vehicle) =>
+ if(vel.isDefined || Vector3.DistanceSquared(player.Position, veh.Position) > 100) {
+ val guid = player.GUID
+ sendResponse(UnuseItemMessage(guid, veh.GUID))
+ sendResponse(UnuseItemMessage(guid, guid))
+ veh.AccessingTrunk = None
+ UnAccessContents(veh)
+ accessedContainer = None
+ }
+ case Some(container) => //just in case
+ if(vel.isDefined) {
+ val guid = player.GUID
+ sendResponse(UnuseItemMessage(guid, container.GUID))
+ sendResponse(UnuseItemMessage(guid, guid))
+ accessedContainer = None
+ }
+ case None => ;
+ }
val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match {
case Some(item) => item.Definition == GlobalDefinitions.bolt_driver
case None => false
@@ -2933,10 +2974,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
context.system.scheduler.scheduleOnce(50 milliseconds, self, UnregisterCorpseOnVehicleDisembark(player))
}
- case msg @ SpawnRequestMessage(u1, u2, u3, u4, u5) =>
+ case msg @ SpawnRequestMessage(u1, spawn_type, u3, u4, zone_number) =>
log.info(s"SpawnRequestMessage: $msg")
- //TODO just focus on u5 and u2 for now
- cluster ! Zone.Lattice.RequestSpawnPoint(u5.toInt, player, u2.toInt)
+ cluster ! Zone.Lattice.RequestSpawnPoint(zone_number.toInt, player, spawn_type.id.toInt)
case msg @ SetChatFilterMessage(send_channel, origin, whitelist) =>
//log.info("SetChatFilters: " + msg)
@@ -3098,11 +3138,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(shooting.isEmpty) {
FindEquipment match {
case Some(tool : Tool) =>
- if(tool.Magazine > 0) {
+ if(tool.Magazine > 0 || prefire.contains(item_guid)) {
+ prefire = None
shooting = Some(item_guid)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
}
+ else {
+ log.warn(s"ChangeFireState_Start: ${tool.Definition.Name} magazine is empty before trying to shoot bullet")
+ EmptyMagazine(item_guid, tool)
+ }
case Some(_) => //permissible, for now
+ prefire = None
shooting = Some(item_guid)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
case None =>
@@ -3112,6 +3158,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ ChangeFireStateMessage_Stop(item_guid) =>
log.info("ChangeFireState_Stop: " + msg)
+ prefire = None
val weapon : Option[Equipment] = if(shooting.contains(item_guid)) {
shooting = None
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid))
@@ -3715,7 +3762,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(owned) {
if(tdef.isInstanceOf[MatrixTerminalDefinition]) {
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks)
- sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, terminal.Position))
+ sendResponse(BindPlayerMessage(BindStatus.Bind, "", true, true, SpawnGroup.Sanctuary, 0, 0, terminal.Position))
}
else if(tdef.isInstanceOf[RepairRearmSiloDefinition]) {
FindLocalVehicle match {
@@ -3915,12 +3962,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
- case msg @ ItemTransactionMessage(terminal_guid, _, _, _, _, _) =>
+ case msg @ ItemTransactionMessage(terminal_guid, transaction_type, _, _, _, _) =>
log.info("ItemTransaction: " + msg)
continent.GUID(terminal_guid) match {
case Some(term : Terminal) =>
log.info(s"ItemTransaction: ${term.Definition.Name} found")
- term.Actor ! Terminal.Request(player, msg)
+ if(lastTerminalOrderFulfillment) {
+ lastTerminalOrderFulfillment = false
+ term.Actor ! Terminal.Request(player, msg)
+ }
case Some(obj : PlanetSideGameObject) =>
log.error(s"ItemTransaction: $obj is not a terminal")
case _ =>
@@ -3984,14 +4034,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
FindContainedWeapon match {
case (Some(obj), Some(tool : Tool)) =>
if(tool.Magazine <= 0) { //safety: enforce ammunition depletion
- tool.Magazine = 0
- sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, weapon_guid, 0))
- sendResponse(ChangeFireStateMessage_Stop(weapon_guid))
- avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, weapon_guid))
- sendResponse(WeaponDryFireMessage(weapon_guid))
- avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.WeaponDryFire(player.GUID, weapon_guid))
+ prefire = None
+ EmptyMagazine(weapon_guid, tool)
}
else { //shooting
+ prefire = shooting.orElse(Some(weapon_guid))
tool.Discharge
val projectileIndex = projectile_guid.guid - Projectile.BaseUID
val projectilePlace = projectiles(projectileIndex)
@@ -3999,7 +4046,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Some(projectile) => !projectile.isResolved
case None => false
}) {
- log.warn(s"WeaponFireMessage: former projectile ${projectile_guid.guid} was not resolved properly; overwriting anyway")
+ log.trace(s"WeaponFireMessage: overwriting unresolved projectile ${projectile_guid.guid}")
}
val (angle, attribution) = obj match {
case p : Player =>
@@ -4023,12 +4070,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
(hit_info match {
case Some(hitInfo) =>
continent.GUID(hitInfo.hitobject_guid) 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 Some(obj : PlanetSideGameObject with Deployable) =>
- Some((obj, hitInfo.shot_origin, hitInfo.hit_pos))
+ case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
+ Some((target, hitInfo.shot_origin, hitInfo.hit_pos))
case _ =>
None
}
@@ -4048,7 +4091,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"Splash: $msg")
continent.GUID(direct_victim_uid) match {
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
- ResolveProjectileEntry(projectile_guid, ProjectileResolution.Hit, target, explosion_pos) match {
+ ResolveProjectileEntry(projectile_guid, ProjectileResolution.Splash, target, target.Position) match {
case Some(projectile) =>
HandleDealingDamage(target, projectile)
case None => ;
@@ -4058,7 +4101,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
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 {
+ ResolveProjectileEntry(projectile_guid, ProjectileResolution.Splash, target, explosion_pos) match {
case Some(projectile) =>
HandleDealingDamage(target, projectile)
case None => ;
@@ -4206,13 +4249,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
vehicle.Definition.MountPoints.values.foreach(mountpoint_num => {
vehicle.Seat(mountpoint_num) match {
case Some(seat) =>
- seat.Occupant match {
- case Some(tplayer) =>
+ seat.Occupant match {
+ case Some(tplayer) =>
if(vehicle.SeatPermissionGroup(mountpoint_num).contains(group) && tplayer != player) { //can not kick self
- seat.Occupant = None
- tplayer.VehicleSeated = None
- vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, object_guid))
- }
+ seat.Occupant = None
+ tplayer.VehicleSeated = None
+ vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, object_guid))
+ }
case None => ; // No player seated
}
case None => ; // Not a seat mounting point
@@ -4273,12 +4316,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info("FriendsRequest: "+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 _ => ;
- }
+ log.trace(s"HitHint: $msg") //HitHint is manually distributed for proper operation
case msg @ TargetingImplantRequest(list) =>
log.info("TargetingImplantRequest: "+msg)
@@ -4289,7 +4327,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
case default => log.error(s"Unhandled GamePacket $pkt")
}
-
/**
* Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item.
* Remove any encountered items and add them to an output `List`.
@@ -5343,8 +5380,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
case obj : Vehicle =>
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid))
case obj : Player =>
- if(obj.isBackpack || destination.VisibleSlots.contains(dest)) { //corpse being looted, or item was in hands
+ if(obj.isBackpack || destination.VisibleSlots.contains(dest)) { //corpse being looted, or item was accessible
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item2_guid))
+ //put hand down locally
+ if(dest == player.DrawnSlot) {
+ player.DrawnSlot = Player.HandsDownSlot
+ }
}
case _ => ;
}
@@ -5794,10 +5835,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
def ClearCurrentAmsSpawnPoint() : Unit = {
amsSpawnPoint match {
- case Some(_) =>
- sendResponse(
- BattleplanMessage(41378949, "ams", continent.Number, List(BattleDiagramAction(DiagramActionCode.StopDrawing)))
- )
+ case Some(tube) =>
+ sendResponse(BindPlayerMessage(BindStatus.Unavailable, "@ams", true, false, SpawnGroup.AMS, continent.Number, 0, Vector3.Zero))
amsSpawnPoint = None
case None => ;
}
@@ -6013,6 +6052,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
def PlayerActionsToCancel() : Unit = {
progressBarUpdate.cancel
progressBarValue = None
+ lastTerminalOrderFulfillment = true
accessedContainer match {
case Some(obj : Vehicle) =>
if(obj.AccessingTrunk.contains(player.GUID)) {
@@ -6030,6 +6070,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Some(guid) =>
sendResponse(ChangeFireStateMessage_Stop(guid))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, guid))
+ prefire = None
shooting = None
case None => ;
}
@@ -6051,8 +6092,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
def AvatarCreate() : Unit = {
player.VehicleSeated = None //TODO temp, until vehicle gating; unseat player else constructor data is messed up
player.Spawn
- player.Health = 50 //TODO temp
- player.Armor = 25
+ player.Health = 100
+ player.Armor = 50
val packet = player.Definition.Packet
val dcdata = packet.DetailedConstructorData(player).get
val player_guid = player.GUID
@@ -6555,6 +6596,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
case obj : Deployable =>
//damage is synchronized on `LSA` (results returned to and distributed from this `WSA`)
localService ! Vitality.DamageOn(obj, func)
+ case obj : FacilityTurret =>
+ //damage is synchronized on the turret actor (results returned to and distributed from this `WSA`)
+ obj.Actor ! Vitality.Damage(func)
case _ => ;
}
}
@@ -7419,7 +7463,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val sguid = src.GUID
val dguid = dest.GUID
StartBundlingPackets()
- sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z, player.Velocity)))
+ sendResponse(PlayerStateShiftMessage(ShiftState(0, dest.Position, player.Orientation.z)))
UseRouterTelepadEffect(pguid, sguid, dguid)
StopBundlingPackets()
// vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(router), continent))
@@ -7473,6 +7517,100 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
}
+ /**
+ * For a certain weapon that cna load ammunition, enforce that its magazine is empty.
+ * @param weapon_guid the weapon
+ */
+ def EmptyMagazine(weapon_guid : PlanetSideGUID) : Unit = {
+ continent.GUID(weapon_guid) match {
+ case Some(tool : Tool) =>
+ EmptyMagazine(weapon_guid, tool)
+ case _ => ;
+ }
+ }
+
+ /**
+ * For a certain weapon that cna load ammunition, enforce that its magazine is empty.
+ * Punctuate that emptiness with a ceasation of weapons fire and a dry fire sound effect.
+ * @param weapon_guid the weapon (GUID)
+ * @param tool the weapon (object)
+ */
+ def EmptyMagazine(weapon_guid : PlanetSideGUID, tool : Tool) : Unit = {
+ tool.Magazine = 0
+ sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, weapon_guid, 0))
+ sendResponse(ChangeFireStateMessage_Stop(weapon_guid))
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, weapon_guid))
+ sendResponse(WeaponDryFireMessage(weapon_guid))
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.WeaponDryFire(player.GUID, weapon_guid))
+ }
+
+ /**
+ * na
+ * @param player_guid the player that ...
+ * @param cargoGUID the globally unique number for the vehicle being ferried
+ * @param cargo the vehicle being ferried
+ * @param carrierGUID the globally unique number for the vehicle doing the ferrying
+ * @param carrier the vehicle doing the ferrying
+ * @param bailed the ferried vehicle is bailing from the cargo hold
+ * @param requestedByPassenger the ferried vehicle is being politely disembarked from the cargo hold
+ * @param kicked the ferried vehicle is being kicked out of the cargo hold
+ */
+ def HandleDismountVehicleCargo(player_guid : PlanetSideGUID, cargoGUID : PlanetSideGUID, cargo : Vehicle, carrierGUID : PlanetSideGUID, carrier : Vehicle, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) : Unit = {
+ carrier.CargoHolds.find({case((_, hold)) => hold.Occupant.contains(cargo)}) match {
+ case Some((mountPoint, hold)) =>
+ StartBundlingPackets()
+ val cargoStatusMessage = CargoMountPointStatusMessage(cargoGUID, PlanetSideGUID(0), PlanetSideGUID(0), carrierGUID, mountPoint, CargoStatus.InProgress, 0)
+ log.debug(cargoStatusMessage.toString)
+ sendResponse(cargoStatusMessage) //dismount vehicle on UI and disable "shield" effect on lodestar
+ val dismount_position = if(bailed || kicked) { //if we're bailing drop the vehicle below the cargo vehicle
+ //TODO: ensure vehicles aren't dropped below the world
+ cargo.Position - Vector3.z(1)
+ }
+ else if(cargo.Definition == GlobalDefinitions.dropship) { //the galaxy cargo bay is offset backwards from the center of the vehicle
+ Vector3(cargo.Position.x, cargo.Position.y - 7f, cargo.Position.z + 2f)
+ }
+ else {
+ cargo.Position + Vector3.z(2)
+ }
+ //TODO: BFRs will likely also need this set
+ val sideways = cargo.Definition == GlobalDefinitions.router
+ val rotation = if(sideways) {
+ (cargo.Orientation.z - 90) % 360 //dismount router "sideways" in a lodestar
+ }
+ else {
+ cargo.Orientation.z
+ }
+ val detachMessage = ObjectDetachMessage(carrierGUID, cargoGUID, dismount_position, carrier.Orientation.x, carrier.Orientation.y, rotation)
+ log.debug(detachMessage.toString)
+ sendResponse(detachMessage)
+ vehicleService ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health)))
+ vehicleService ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields)))
+ cargo.MountedIn = None
+ hold.Occupant = None
+ if(!bailed) {
+ // Automatically drive the vehicle backwards out of the cargo bay
+ if(!sideways) {
+ ServerVehicleLockReverse()
+ }
+ else {
+ ServerVehicleLockStrafeLeft()
+ }
+ }
+ else {
+ //todo: proper vehicle bailing. It works currently but when collision damage is implemented the vehicle will take damage if not in a bail state. Need to confirm how this is done with further research
+ }
+ import scala.concurrent.duration._
+ import scala.concurrent.ExecutionContext.Implicits.global
+ // Start a timer to check every second if the vehicle has moved far enough away to be considered dismounted, and then close the cargo door
+ cargoDismountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoDismount(cargoGUID, carrierGUID, mountPoint, iteration = 0))
+ StopBundlingPackets()
+ //sync to other clients
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, cargoStatusMessage))
+ avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, detachMessage))
+ case None => ;
+ }
+ }
+
def failWithError(error : String) = {
log.error(error)
sendResponse(ConnectionClose())