Damage Fix, and Stuff (#235)

* redid splash damage radial degrade calculations; lasher damage fixed to 6/0 or 2/4 depending on armor; fixed direct hit distance for weapons that act like slash weapons; gave Falcon a valid projectile

* delayed resource silo startup to avoid crash; discovered issue with MAX armor resulting from incorrect manipulation of inventory items during suit transfer

* modifications to consider a specific modifier that exists on some vehicles and armored equipment called Subtract, a DamageProfile that reduces damage due to a set flag; manually controlling damage tick marks rather than relying only on HitHint (from client)

* Tools now have default firemodes (temporary?); ammo gotten from loadouts is always full; loadouts and exo-suits juggle armor values differently on being changed; log messages for damage to players and vehicles

* weapons with one (last) shot need support communicating that one (last) shot

* fixing tests

* correcting the codec for BindPlayerMessage

* changed wording that identifies the damage log entries

* switching exosuit no longer causes butterfingers

* extending exosuit swap armor value changes to infantry loadout changes

* ranges removed from non-vehicle containers

* enabled partial damage functionality to facility turrets

* BindPlayerMessage replacing BattleplanMessage for purposes of depicting the closest AMS spawn point; specific enums in relation to spawning in BPM and SpawnRequestMessage

* rewrote exo-suit switching and loadout switching code, paying extra-special attention for the needs of MAX's

* quick amendment for previous commit

* switching from mount point check to seat checks in VehicleRemover; started refactoring of Cargo functions before realizing how complicated the full effort would be, so these changes are merely wrapping object identification by GUID

* adding a minor server delay to terminal operations
This commit is contained in:
Fate-JH 2018-11-21 20:31:51 -05:00 committed by GitHub
parent 7cec0bb577
commit 4eca226b5b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
41 changed files with 900 additions and 453 deletions

View file

@ -85,6 +85,10 @@ class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends
obj.MaxArmor = MaxArmor obj.MaxArmor = MaxArmor
obj.InventoryScale = InventoryScale obj.InventoryScale = InventoryScale
obj.InventoryOffset = InventoryOffset obj.InventoryOffset = InventoryOffset
obj.Subtract.Damage0 = Subtract.Damage0
obj.Subtract.Damage1 = Subtract.Damage1
obj.Subtract.Damage2 = Subtract.Damage2
obj.Subtract.Damage3 = Subtract.Damage3
obj.ResistanceDirectHit = ResistanceDirectHit obj.ResistanceDirectHit = ResistanceDirectHit
obj.ResistanceSplash = ResistanceSplash obj.ResistanceSplash = ResistanceSplash
obj.ResistanceAggravated = ResistanceAggravated obj.ResistanceAggravated = ResistanceAggravated
@ -166,6 +170,7 @@ object ExoSuitDefinition {
MAX.InventoryOffset = 6 MAX.InventoryOffset = 6
MAX.Holster(0, EquipmentSize.Max) MAX.Holster(0, EquipmentSize.Max)
MAX.Holster(4, EquipmentSize.Melee) MAX.Holster(4, EquipmentSize.Melee)
MAX.Subtract.Damage1 = -2
MAX.ResistanceDirectHit = 6 MAX.ResistanceDirectHit = 6
MAX.ResistanceSplash = 35 MAX.ResistanceSplash = 35
MAX.ResistanceAggravated = 10 MAX.ResistanceAggravated = 10

View file

@ -555,11 +555,19 @@ object GlobalDefinitions {
val maelstrom = ToolDefinition(ObjectClass.maelstrom) 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) val lancer = ToolDefinition(ObjectClass.lancer)
@ -573,7 +581,7 @@ object GlobalDefinitions {
val bolt_driver = ToolDefinition(ObjectClass.bolt_driver) val bolt_driver = ToolDefinition(ObjectClass.bolt_driver)
val oicw = ToolDefinition(ObjectClass.oicw) //scorpion val oicw = ToolDefinition(ObjectClass.oicw)
val flamethrower = ToolDefinition(ObjectClass.flamethrower) val flamethrower = ToolDefinition(ObjectClass.flamethrower)
@ -718,7 +726,9 @@ object GlobalDefinitions {
val lightgunship_weapon_system = ToolDefinition(ObjectClass.lightgunship_weapon_system) 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) val liberator_weapon_system = ToolDefinition(ObjectClass.liberator_weapon_system)
@ -3924,6 +3934,7 @@ object GlobalDefinitions {
nchev_falcon.Name = "nchev_falcon" nchev_falcon.Name = "nchev_falcon"
nchev_falcon.Size = EquipmentSize.Max nchev_falcon.Size = EquipmentSize.Max
nchev_falcon.AmmoTypes += falcon_ammo nchev_falcon.AmmoTypes += falcon_ammo
nchev_falcon.ProjectileTypes += falcon_projectile
nchev_falcon.FireModes += new FireModeDefinition nchev_falcon.FireModes += new FireModeDefinition
nchev_falcon.FireModes.head.AmmoTypeIndices += 0 nchev_falcon.FireModes.head.AmmoTypeIndices += 0
nchev_falcon.FireModes.head.AmmoSlotIndex = 0 nchev_falcon.FireModes.head.AmmoSlotIndex = 0
@ -4763,6 +4774,7 @@ object GlobalDefinitions {
threemanheavybuggy.TrunkOffset = 30 threemanheavybuggy.TrunkOffset = 30
threemanheavybuggy.AutoPilotSpeeds = (22, 8) threemanheavybuggy.AutoPilotSpeeds = (22, 8)
threemanheavybuggy.DestroyedModel = Some(DestroyedVehicle.ThreeManHeavyBuggy) threemanheavybuggy.DestroyedModel = Some(DestroyedVehicle.ThreeManHeavyBuggy)
threemanheavybuggy.Subtract.Damage1 = 5
twomanheavybuggy.Name = "twomanheavybuggy" twomanheavybuggy.Name = "twomanheavybuggy"
twomanheavybuggy.MaxHealth = 1800 twomanheavybuggy.MaxHealth = 1800
@ -4779,6 +4791,7 @@ object GlobalDefinitions {
twomanheavybuggy.TrunkOffset = 30 twomanheavybuggy.TrunkOffset = 30
twomanheavybuggy.AutoPilotSpeeds = (22, 8) twomanheavybuggy.AutoPilotSpeeds = (22, 8)
twomanheavybuggy.DestroyedModel = Some(DestroyedVehicle.TwoManHeavyBuggy) twomanheavybuggy.DestroyedModel = Some(DestroyedVehicle.TwoManHeavyBuggy)
twomanheavybuggy.Subtract.Damage1 = 5
twomanhoverbuggy.Name = "twomanhoverbuggy" twomanhoverbuggy.Name = "twomanhoverbuggy"
twomanhoverbuggy.MaxHealth = 1600 twomanhoverbuggy.MaxHealth = 1600
@ -4795,6 +4808,7 @@ object GlobalDefinitions {
twomanhoverbuggy.TrunkOffset = 30 twomanhoverbuggy.TrunkOffset = 30
twomanhoverbuggy.AutoPilotSpeeds = (22, 10) twomanhoverbuggy.AutoPilotSpeeds = (22, 10)
twomanhoverbuggy.DestroyedModel = Some(DestroyedVehicle.TwoManHoverBuggy) twomanhoverbuggy.DestroyedModel = Some(DestroyedVehicle.TwoManHoverBuggy)
twomanhoverbuggy.Subtract.Damage1 = 5
mediumtransport.Name = "mediumtransport" mediumtransport.Name = "mediumtransport"
mediumtransport.MaxHealth = 2500 mediumtransport.MaxHealth = 2500
@ -4818,6 +4832,7 @@ object GlobalDefinitions {
mediumtransport.TrunkOffset = 30 mediumtransport.TrunkOffset = 30
mediumtransport.AutoPilotSpeeds = (18, 6) mediumtransport.AutoPilotSpeeds = (18, 6)
mediumtransport.DestroyedModel = Some(DestroyedVehicle.MediumTransport) mediumtransport.DestroyedModel = Some(DestroyedVehicle.MediumTransport)
mediumtransport.Subtract.Damage1 = 7
battlewagon.Name = "battlewagon" battlewagon.Name = "battlewagon"
battlewagon.MaxHealth = 2500 battlewagon.MaxHealth = 2500
@ -4868,6 +4883,7 @@ object GlobalDefinitions {
thunderer.TrunkOffset = 30 thunderer.TrunkOffset = 30
thunderer.AutoPilotSpeeds = (18, 6) thunderer.AutoPilotSpeeds = (18, 6)
thunderer.DestroyedModel = Some(DestroyedVehicle.MediumTransport) thunderer.DestroyedModel = Some(DestroyedVehicle.MediumTransport)
thunderer.Subtract.Damage1 = 7
aurora.Name = "aurora" aurora.Name = "aurora"
aurora.MaxHealth = 2500 aurora.MaxHealth = 2500
@ -4891,6 +4907,7 @@ object GlobalDefinitions {
aurora.TrunkOffset = 30 aurora.TrunkOffset = 30
aurora.AutoPilotSpeeds = (18, 6) aurora.AutoPilotSpeeds = (18, 6)
aurora.DestroyedModel = Some(DestroyedVehicle.MediumTransport) aurora.DestroyedModel = Some(DestroyedVehicle.MediumTransport)
aurora.Subtract.Damage1 = 7
apc_tr.Name = "apc_tr" apc_tr.Name = "apc_tr"
apc_tr.MaxHealth = 6000 apc_tr.MaxHealth = 6000
@ -5043,6 +5060,7 @@ object GlobalDefinitions {
lightning.TrunkOffset = 30 lightning.TrunkOffset = 30
lightning.AutoPilotSpeeds = (20, 8) lightning.AutoPilotSpeeds = (20, 8)
lightning.DestroyedModel = Some(DestroyedVehicle.Lightning) lightning.DestroyedModel = Some(DestroyedVehicle.Lightning)
lightning.Subtract.Damage1 = 7
prowler.Name = "prowler" prowler.Name = "prowler"
prowler.MaxHealth = 4800 prowler.MaxHealth = 4800
@ -5062,6 +5080,7 @@ object GlobalDefinitions {
prowler.TrunkOffset = 30 prowler.TrunkOffset = 30
prowler.AutoPilotSpeeds = (14, 6) prowler.AutoPilotSpeeds = (14, 6)
prowler.DestroyedModel = Some(DestroyedVehicle.Prowler) prowler.DestroyedModel = Some(DestroyedVehicle.Prowler)
prowler.Subtract.Damage1 = 9
vanguard.Name = "vanguard" vanguard.Name = "vanguard"
vanguard.MaxHealth = 5400 vanguard.MaxHealth = 5400
@ -5077,6 +5096,7 @@ object GlobalDefinitions {
vanguard.TrunkOffset = 30 vanguard.TrunkOffset = 30
vanguard.AutoPilotSpeeds = (16, 6) vanguard.AutoPilotSpeeds = (16, 6)
vanguard.DestroyedModel = Some(DestroyedVehicle.Vanguard) vanguard.DestroyedModel = Some(DestroyedVehicle.Vanguard)
vanguard.Subtract.Damage1 = 9
magrider.Name = "magrider" magrider.Name = "magrider"
magrider.MaxHealth = 4200 magrider.MaxHealth = 4200
@ -5094,6 +5114,7 @@ object GlobalDefinitions {
magrider.TrunkOffset = 30 magrider.TrunkOffset = 30
magrider.AutoPilotSpeeds = (18, 6) magrider.AutoPilotSpeeds = (18, 6)
magrider.DestroyedModel = Some(DestroyedVehicle.Magrider) magrider.DestroyedModel = Some(DestroyedVehicle.Magrider)
magrider.Subtract.Damage1 = 9
val utilityConverter = new UtilityVehicleConverter val utilityConverter = new UtilityVehicleConverter
ant.Name = "ant" ant.Name = "ant"
@ -5110,6 +5131,7 @@ object GlobalDefinitions {
ant.MaximumCapacitor = 1500 ant.MaximumCapacitor = 1500
ant.Packet = utilityConverter ant.Packet = utilityConverter
ant.DestroyedModel = Some(DestroyedVehicle.Ant) ant.DestroyedModel = Some(DestroyedVehicle.Ant)
ant.Subtract.Damage1 = 5
ams.Name = "ams" ams.Name = "ams"
ams.MaxHealth = 3000 ams.MaxHealth = 3000
@ -5129,6 +5151,7 @@ object GlobalDefinitions {
ams.AutoPilotSpeeds = (18, 6) ams.AutoPilotSpeeds = (18, 6)
ams.Packet = utilityConverter ams.Packet = utilityConverter
ams.DestroyedModel = Some(DestroyedVehicle.Ams) ams.DestroyedModel = Some(DestroyedVehicle.Ams)
ams.Subtract.Damage1 = 10
val variantConverter = new VariantVehicleConverter val variantConverter = new VariantVehicleConverter
router.Name = "router" router.Name = "router"
@ -5147,6 +5170,7 @@ object GlobalDefinitions {
router.AutoPilotSpeeds = (16, 6) router.AutoPilotSpeeds = (16, 6)
router.Packet = variantConverter router.Packet = variantConverter
router.DestroyedModel = Some(DestroyedVehicle.Router) router.DestroyedModel = Some(DestroyedVehicle.Router)
router.Subtract.Damage1 = 5
switchblade.Name = "switchblade" switchblade.Name = "switchblade"
switchblade.MaxHealth = 1750 switchblade.MaxHealth = 1750
@ -5164,6 +5188,8 @@ object GlobalDefinitions {
switchblade.AutoPilotSpeeds = (22, 8) switchblade.AutoPilotSpeeds = (22, 8)
switchblade.Packet = variantConverter switchblade.Packet = variantConverter
switchblade.DestroyedModel = Some(DestroyedVehicle.Switchblade) switchblade.DestroyedModel = Some(DestroyedVehicle.Switchblade)
switchblade.Subtract.Damage0 = 5
switchblade.Subtract.Damage1 = 5
flail.Name = "flail" flail.Name = "flail"
flail.MaxHealth = 2400 flail.MaxHealth = 2400
@ -5180,6 +5206,7 @@ object GlobalDefinitions {
flail.AutoPilotSpeeds = (14, 6) flail.AutoPilotSpeeds = (14, 6)
flail.Packet = variantConverter flail.Packet = variantConverter
flail.DestroyedModel = Some(DestroyedVehicle.Flail) flail.DestroyedModel = Some(DestroyedVehicle.Flail)
flail.Subtract.Damage1 = 7
mosquito.Name = "mosquito" mosquito.Name = "mosquito"
mosquito.MaxHealth = 665 mosquito.MaxHealth = 665
@ -5210,6 +5237,7 @@ object GlobalDefinitions {
lightgunship.AutoPilotSpeeds = (0, 4) lightgunship.AutoPilotSpeeds = (0, 4)
lightgunship.Packet = variantConverter lightgunship.Packet = variantConverter
lightgunship.DestroyedModel = Some(DestroyedVehicle.LightGunship) lightgunship.DestroyedModel = Some(DestroyedVehicle.LightGunship)
lightgunship.Subtract.Damage1 = 3
wasp.Name = "wasp" wasp.Name = "wasp"
wasp.MaxHealth = 515 wasp.MaxHealth = 515
@ -5247,6 +5275,7 @@ object GlobalDefinitions {
liberator.AutoPilotSpeeds = (0, 4) liberator.AutoPilotSpeeds = (0, 4)
liberator.Packet = variantConverter liberator.Packet = variantConverter
liberator.DestroyedModel = Some(DestroyedVehicle.Liberator) liberator.DestroyedModel = Some(DestroyedVehicle.Liberator)
liberator.Subtract.Damage1 = 5
vulture.Name = "vulture" vulture.Name = "vulture"
vulture.MaxHealth = 2500 vulture.MaxHealth = 2500
@ -5269,6 +5298,7 @@ object GlobalDefinitions {
vulture.AutoPilotSpeeds = (0, 4) vulture.AutoPilotSpeeds = (0, 4)
vulture.Packet = variantConverter vulture.Packet = variantConverter
vulture.DestroyedModel = Some(DestroyedVehicle.Liberator) //add_property vulture destroyedphysics liberator_destroyed vulture.DestroyedModel = Some(DestroyedVehicle.Liberator) //add_property vulture destroyedphysics liberator_destroyed
vulture.Subtract.Damage1 = 5
dropship.Name = "dropship" dropship.Name = "dropship"
dropship.MaxHealth = 5000 dropship.MaxHealth = 5000
@ -5323,6 +5353,7 @@ object GlobalDefinitions {
dropship.AutoPilotSpeeds = (0, 4) dropship.AutoPilotSpeeds = (0, 4)
dropship.Packet = variantConverter dropship.Packet = variantConverter
dropship.DestroyedModel = Some(DestroyedVehicle.Dropship) dropship.DestroyedModel = Some(DestroyedVehicle.Dropship)
dropship.Subtract.Damage1 = 7
galaxy_gunship.Name = "galaxy_gunship" galaxy_gunship.Name = "galaxy_gunship"
galaxy_gunship.MaxHealth = 6000 galaxy_gunship.MaxHealth = 6000
@ -5354,6 +5385,7 @@ object GlobalDefinitions {
galaxy_gunship.AutoPilotSpeeds = (0, 4) galaxy_gunship.AutoPilotSpeeds = (0, 4)
galaxy_gunship.Packet = variantConverter 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.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.Name = "lodestar"
lodestar.MaxHealth = 5000 lodestar.MaxHealth = 5000
@ -5367,6 +5399,7 @@ object GlobalDefinitions {
lodestar.AutoPilotSpeeds = (0, 4) lodestar.AutoPilotSpeeds = (0, 4)
lodestar.Packet = variantConverter lodestar.Packet = variantConverter
lodestar.DestroyedModel = Some(DestroyedVehicle.Lodestar) lodestar.DestroyedModel = Some(DestroyedVehicle.Lodestar)
lodestar.Subtract.Damage1 = 7
phantasm.Name = "phantasm" phantasm.Name = "phantasm"
phantasm.MaxHealth = 2500 phantasm.MaxHealth = 2500

View file

@ -281,6 +281,8 @@ class Player(private val core : Avatar) extends PlanetSideGameObject
ChangeSpecialAbility() ChangeSpecialAbility()
} }
def Subtract = exosuit.Subtract
def ResistanceDirectHit = exosuit.ResistanceDirectHit def ResistanceDirectHit = exosuit.ResistanceDirectHit
def ResistanceSplash = exosuit.ResistanceSplash def ResistanceSplash = exosuit.ResistanceSplash

View file

@ -20,7 +20,7 @@ import scala.annotation.tailrec
class Tool(private val toolDef : ToolDefinition) extends Equipment class Tool(private val toolDef : ToolDefinition) extends Equipment
with FireModeSwitch[FireModeDefinition] { with FireModeSwitch[FireModeDefinition] {
/** index of the current fire mode on the `ToolDefinition`'s list of fire modes */ /** 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 */ /** current ammunition slot being used by this fire mode */
private var ammoSlots : List[Tool.FireModeSlot] = List.empty private var ammoSlots : List[Tool.FireModeSlot] = List.empty

View file

@ -4,6 +4,7 @@ package net.psforever.objects.ballistics
import net.psforever.objects.TurretDeployable import net.psforever.objects.TurretDeployable
import net.psforever.objects.ce.ComplexDeployable import net.psforever.objects.ce.ComplexDeployable
import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition} import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition}
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.types.{PlanetSideEmpire, Vector3}
final case class ComplexDeployableSource(obj_def : ObjectDefinition with BaseDeployableDefinition, 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 Position = position
def Orientation = orientation def Orientation = orientation
def Velocity = None def Velocity = None
def Modifiers = obj_def.asInstanceOf[ResistanceProfile]
} }
object ComplexDeployableSource { object ComplexDeployableSource {

View file

@ -4,6 +4,7 @@ package net.psforever.objects.ballistics
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.Deployable import net.psforever.objects.ce.Deployable
import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition} import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition}
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.types.{PlanetSideEmpire, Vector3}
final case class DeployableSource(obj_def : ObjectDefinition with BaseDeployableDefinition, 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 Position = position
def Orientation = orientation def Orientation = orientation
def Velocity = None def Velocity = None
def Modifiers = obj_def.asInstanceOf[ResistanceProfile]
} }
object DeployableSource { object DeployableSource {

View file

@ -3,19 +3,21 @@ package net.psforever.objects.ballistics
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.resistance.ResistanceProfileMutators
import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.types.{PlanetSideEmpire, Vector3}
final case class ObjectSource(obj : PlanetSideGameObject with FactionAffinity, final case class ObjectSource(obj : PlanetSideGameObject with FactionAffinity,
faction : PlanetSideEmpire.Value, faction : PlanetSideEmpire.Value,
position : Vector3, position : Vector3,
orientation : Vector3, orientation : Vector3,
velocity : Option[Vector3] = None) extends SourceEntry { velocity : Option[Vector3]) extends SourceEntry {
override def Name = SourceEntry.NameFormat(obj.Definition.Name) override def Name = SourceEntry.NameFormat(obj.Definition.Name)
override def Faction = faction override def Faction = faction
def Definition = obj.Definition def Definition = obj.Definition
def Position = position def Position = position
def Orientation = orientation def Orientation = orientation
def Velocity = velocity def Velocity = velocity
def Modifiers = new ResistanceProfileMutators { }
} }
object ObjectSource { object ObjectSource {

View file

@ -3,6 +3,7 @@ package net.psforever.objects.ballistics
import net.psforever.objects.Player import net.psforever.objects.Player
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.types.{ExoSuitType, PlanetSideEmpire, Vector3} import net.psforever.types.{ExoSuitType, PlanetSideEmpire, Vector3}
final case class PlayerSource(name : String, final case class PlayerSource(name : String,
@ -14,7 +15,8 @@ final case class PlayerSource(name : String,
armor : Int, armor : Int,
position : Vector3, position : Vector3,
orientation : Vector3, orientation : Vector3,
velocity : Option[Vector3] = None) extends SourceEntry { velocity : Option[Vector3],
modifiers : ResistanceProfile) extends SourceEntry {
override def Name = name override def Name = name
override def Faction = faction override def Faction = faction
def Definition = obj_def def Definition = obj_def
@ -25,11 +27,12 @@ final case class PlayerSource(name : String,
def Position = position def Position = position
def Orientation = orientation def Orientation = orientation
def Velocity = velocity def Velocity = velocity
def Modifiers = modifiers
} }
object PlayerSource { object PlayerSource {
def apply(tplayer : Player) : PlayerSource = { def apply(tplayer : Player) : PlayerSource = {
PlayerSource(tplayer.Name, tplayer.Definition, tplayer.Faction, tplayer.ExoSuit, tplayer.VehicleSeated.nonEmpty, 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])
} }
} }

View file

@ -6,6 +6,7 @@ import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.{PlanetSideGameObject, Player, TurretDeployable, Vehicle} import net.psforever.objects.{PlanetSideGameObject, Player, TurretDeployable, Vehicle}
import net.psforever.objects.entity.WorldEntity import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.types.{PlanetSideEmpire, Vector3}
trait SourceEntry extends WorldEntity { trait SourceEntry extends WorldEntity {
@ -16,6 +17,7 @@ trait SourceEntry extends WorldEntity {
def Position_=(pos : Vector3) = Position def Position_=(pos : Vector3) = Position
def Orientation_=(pos : Vector3) = Position def Orientation_=(pos : Vector3) = Position
def Velocity_=(pos : Option[Vector3]) = Velocity def Velocity_=(pos : Option[Vector3]) = Velocity
def Modifiers : ResistanceProfile
} }
object SourceEntry { object SourceEntry {
@ -24,6 +26,7 @@ object SourceEntry {
def Position = Vector3.Zero def Position = Vector3.Zero
def Orientation = Vector3.Zero def Orientation = Vector3.Zero
def Velocity = Some(Vector3.Zero) def Velocity = Some(Vector3.Zero)
def Modifiers = null
} }
def apply(target : PlanetSideGameObject with FactionAffinity) : SourceEntry = { def apply(target : PlanetSideGameObject with FactionAffinity) : SourceEntry = {

View file

@ -3,6 +3,7 @@ package net.psforever.objects.ballistics
import net.psforever.objects.Vehicle import net.psforever.objects.Vehicle
import net.psforever.objects.definition.VehicleDefinition import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.vital.resistance.ResistanceProfile
import net.psforever.types.{PlanetSideEmpire, Vector3} import net.psforever.types.{PlanetSideEmpire, Vector3}
final case class VehicleSource(obj_def : VehicleDefinition, final case class VehicleSource(obj_def : VehicleDefinition,
@ -11,7 +12,8 @@ final case class VehicleSource(obj_def : VehicleDefinition,
shields : Int, shields : Int,
position : Vector3, position : Vector3,
orientation : 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 Name = SourceEntry.NameFormat(obj_def.Name)
override def Faction = faction override def Faction = faction
def Definition : VehicleDefinition = obj_def def Definition : VehicleDefinition = obj_def
@ -20,6 +22,7 @@ final case class VehicleSource(obj_def : VehicleDefinition,
def Position = position def Position = position
def Orientation = orientation def Orientation = orientation
def Velocity = velocity def Velocity = velocity
def Modifiers = modifiers
} }
object VehicleSource { object VehicleSource {
@ -31,7 +34,8 @@ object VehicleSource {
obj.Shields, obj.Shields,
obj.Position, obj.Position,
obj.Orientation, obj.Orientation,
obj.Velocity obj.Velocity,
obj.Definition.asInstanceOf[ResistanceProfile]
) )
} }
} }

View file

@ -6,11 +6,13 @@ import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem} import net.psforever.objects.ce.{Deployable, DeployableCategory, DeployedItem}
import net.psforever.objects.definition.converter.SmallDeployableConverter import net.psforever.objects.definition.converter.SmallDeployableConverter
import net.psforever.objects.serverobject.PlanetSideServerObject 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._ import scala.concurrent.duration._
trait BaseDeployableDefinition extends DamageResistanceModel { trait BaseDeployableDefinition extends DamageResistanceModel
with ResistanceProfileMutators {
private var category : DeployableCategory.Value = DeployableCategory.Boomers private var category : DeployableCategory.Value = DeployableCategory.Boomers
private var deployTime : Long = (1 second).toMillis //ms private var deployTime : Long = (1 second).toMillis //ms
private var maxHealth : Int = 1 private var maxHealth : Int = 1

View file

@ -2,8 +2,7 @@
package net.psforever.objects.definition package net.psforever.objects.definition
import net.psforever.objects.ballistics.Projectiles import net.psforever.objects.ballistics.Projectiles
import net.psforever.objects.vital.DamageType import net.psforever.objects.vital.{DamageType, StandardDamageProfile}
import net.psforever.objects.vital.damage.DamageProfile
/** /**
* The definition that outlines the damage-dealing characteristics of any projectile. * The definition that outlines the damage-dealing characteristics of any projectile.
@ -11,13 +10,8 @@ import net.psforever.objects.vital.damage.DamageProfile
* @param objectId the object's identifier number * @param objectId the object's identifier number
*/ */
class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId) class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
with DamageProfile { with StandardDamageProfile {
private val projectileType : Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException private val projectileType : Projectiles.Value = Projectiles(objectId) //let throw NoSuchElementException
private var damage0 : Int = 0
private var 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 acceleration : Int = 0
private var accelerationUntil : Float = 0f private var accelerationUntil : Float = 0f
private var damageType : DamageType.Value = DamageType.None private var damageType : DamageType.Value = DamageType.None
@ -45,57 +39,6 @@ class ProjectileDefinition(objectId : Int) extends ObjectDefinition(objectId)
UseDamage1Subtract 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 : Int = acceleration
def Acceleration_=(accel : Int) : Int = { def Acceleration_=(accel : Int) : Int = {

View file

@ -10,6 +10,7 @@ class ToolDefinition(objectId : Int) extends EquipmentDefinition(objectId) {
private val ammoTypes : mutable.ListBuffer[AmmoBoxDefinition] = new mutable.ListBuffer[AmmoBoxDefinition] private val ammoTypes : mutable.ListBuffer[AmmoBoxDefinition] = new mutable.ListBuffer[AmmoBoxDefinition]
private val projectileTypes : mutable.ListBuffer[ProjectileDefinition] = new mutable.ListBuffer[ProjectileDefinition] private val projectileTypes : mutable.ListBuffer[ProjectileDefinition] = new mutable.ListBuffer[ProjectileDefinition]
private val fireModes : mutable.ListBuffer[FireModeDefinition] = new mutable.ListBuffer[FireModeDefinition] private val fireModes : mutable.ListBuffer[FireModeDefinition] = new mutable.ListBuffer[FireModeDefinition]
private var defaultFireModeIndex : Option[Int] = None
Name = "tool" Name = "tool"
Packet = ToolDefinition.converter Packet = ToolDefinition.converter
@ -20,6 +21,15 @@ class ToolDefinition(objectId : Int) extends EquipmentDefinition(objectId) {
def FireModes : mutable.ListBuffer[FireModeDefinition] = fireModes def FireModes : mutable.ListBuffer[FireModeDefinition] = fireModes
def NextFireModeIndex(index : Int) : Int = index + 1 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 { object ToolDefinition {

View file

@ -60,6 +60,7 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId)
} }
def Seats : mutable.HashMap[Int, SeatDefinition] = seats def Seats : mutable.HashMap[Int, SeatDefinition] = seats
def Cargo : mutable.HashMap[Int, CargoDefinition] = cargo def Cargo : mutable.HashMap[Int, CargoDefinition] = cargo
def MountPoints : mutable.HashMap[Int, Int] = mountPoints def MountPoints : mutable.HashMap[Int, Int] = mountPoints

View file

@ -226,10 +226,12 @@ class GridInventory extends Container {
else { else {
val collisions : mutable.Set[InventoryItem] = mutable.Set[InventoryItem]() val collisions : mutable.Set[InventoryItem] = mutable.Set[InventoryItem]()
var curr = actualSlot var curr = actualSlot
val fixedItems = items.toMap
val fixedGrid = grid.toList
for(_ <- 0 until h) { for(_ <- 0 until h) {
for(col <- 0 until w) { for(col <- 0 until w) {
if(grid(curr + col) > -1) { if(fixedGrid(curr + col) > -1) {
collisions += items(grid(curr + col)) collisions += fixedItems(fixedGrid(curr + col))
} }
} }
curr += width curr += width

View file

@ -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 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; * @param context a context to allow the object to properly set up `ActorSystem` functionality;
* not necessary for this object, but required by signature * not necessary for this object, but required by signature
* @return the `Locker` object * @return the `ResourceSilo` object
*/ */
def Constructor(id : Int, context : ActorContext) : ResourceSilo = { def Constructor(id : Int, context : ActorContext) : ResourceSilo = {
val obj = ResourceSilo() val obj = ResourceSilo()
obj.Actor = context.actorOf(Props(classOf[ResourceSiloControl], obj), s"${obj.Definition.Name}_$id") obj.Actor = context.actorOf(Props(classOf[ResourceSiloControl], obj), s"${obj.Definition.Name}_$id")
obj.Actor ! "startup"
obj obj
} }
} }

View file

@ -42,7 +42,7 @@ class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with Factio
sender ! ResourceSilo.ResourceSiloMessage(player, msg, resourceSilo.Use(player, msg)) sender ! ResourceSilo.ResourceSiloMessage(player, msg, resourceSilo.Use(player, msg))
case ResourceSilo.LowNtuWarning(enabled: Boolean) => case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
resourceSilo.LowNtuWarningOn = enabled 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)) 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) => 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 // Only send updated capacitor display value to all clients if it has actually changed
if(resourceSilo.CapacitorDisplay != ntuBarLevel) { 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.CapacitorDisplay = ntuBarLevel
resourceSilo.Owner.Actor ! Building.SendMapUpdate(all_clients = true) resourceSilo.Owner.Actor ! Building.SendMapUpdate(all_clients = true)
avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay)) avatarService ! AvatarServiceMessage(resourceSilo.Owner.asInstanceOf[Building].Zone.Id, AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay))

View file

@ -366,12 +366,12 @@ object EquipmentTerminalDefinition {
(0 until tool.MaxAmmoSlot).foreach(index => { (0 until tool.MaxAmmoSlot).foreach(index => {
val slot = tool.AmmoSlots(index) val slot = tool.AmmoSlots(index)
slot.AmmoTypeIndex += obj.ammo(index).ammoIndex 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 tool
case obj : ShorthandAmmoBox => case obj : ShorthandAmmoBox =>
MakeAmmoBox(obj.definition, Some(obj.capacity)) MakeAmmoBox(obj.definition) //Some(obj.capacity)
case obj : ShorthandConstructionItem => case obj : ShorthandConstructionItem =>
MakeConstructionItem(obj.definition) MakeConstructionItem(obj.definition)

View file

@ -3,14 +3,19 @@ package net.psforever.objects.serverobject.turret
import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
class FacilityTurret(tDef : TurretDefinition) extends Amenity class FacilityTurret(tDef : TurretDefinition) extends Amenity
with WeaponTurret { with WeaponTurret
with Vitality
with StandardResistanceProfile {
/** some turrets can be updated; they all start without updates */ /** some turrets can be updated; they all start without updates */
private var upgradePath : TurretUpgrade.Value = TurretUpgrade.None private var upgradePath : TurretUpgrade.Value = TurretUpgrade.None
WeaponTurret.LoadDefinition(this) 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 MaxHealth : Int = Definition.MaxHealth
def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap
@ -28,6 +33,8 @@ class FacilityTurret(tDef : TurretDefinition) extends Amenity
Upgrade Upgrade
} }
def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
def Definition : TurretDefinition = tDef def Definition : TurretDefinition = tDef
} }

View file

@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.turret
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.vital.Vitality
/** /**
* An `Actor` that handles messages being dispatched to a specific `MannedTurret`.<br> * An `Actor` that handles messages being dispatched to a specific `MannedTurret`.<br>
@ -38,6 +39,18 @@ class FacilityTurretControl(turret : FacilityTurret) extends Actor
sender ! Mountable.MountMessages(user, Mountable.CanNotMount(turret, seat_num)) 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 _ => ; case _ => ;
} }
} }

View file

@ -3,6 +3,8 @@ package net.psforever.objects.serverobject.turret
import net.psforever.objects.definition.{ObjectDefinition, ToolDefinition} import net.psforever.objects.definition.{ObjectDefinition, ToolDefinition}
import net.psforever.objects.vehicles.Turrets 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 import scala.collection.mutable
@ -10,7 +12,9 @@ import scala.collection.mutable
* The definition for any `MannedTurret`. * The definition for any `MannedTurret`.
* @param objectId the object's identifier number * @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 Turrets(objectId) //let throw NoSuchElementException
private var maxHealth : Int = 100 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 /** creates internal ammunition reserves that can not become depleted
* see `MannedTurret.TurretAmmoBox` for details */ * see `MannedTurret.TurretAmmoBox` for details */
private var hasReserveAmmunition : Boolean = false private var hasReserveAmmunition : Boolean = false
Damage = StandardVehicleDamage
Resistance = StandardVehicleResistance
Model = StandardResolutions.FacilityTurrets
def MaxHealth : Int = maxHealth def MaxHealth : Int = maxHealth

View file

@ -63,7 +63,16 @@ class VehicleControl(vehicle : Vehicle) extends Actor
case Vitality.Damage(damage_func) => case Vitality.Damage(damage_func) =>
if(vehicle.Health > 0) { if(vehicle.Health > 0) {
val originalHealth = vehicle.Health
val originalShields = vehicle.Shields
damage_func(vehicle) 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) sender ! Vitality.DamageResolution(vehicle)
} }

View file

@ -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
}
}

View file

@ -1,7 +1,6 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.vital package net.psforever.objects.vital
import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.vital.damage._ import net.psforever.objects.vital.damage._
import net.psforever.objects.vital.damage.DamageCalculations._ import net.psforever.objects.vital.damage.DamageCalculations._
@ -66,21 +65,35 @@ object AircraftSplashDamage extends DamageCalculations(
DistanceFromExplosionToTarget DistanceFromExplosionToTarget
) )
object InfantryLashDamage extends NoDamageBase { object InfantrySplashDamageDirect extends DamageCalculations(
override def Calculate(data : ResolvedProjectile) : Int = (InfantryHitDamage.Calculate(data) * 0.2f).toInt SplashDamageWithRadialDegrade,
} DamageWithModifiers(DamageAgainstAircraft),
NoDistance
)
object MaxLashDamage extends NoDamageBase { object InfantryLashDamage extends DamageCalculations(
override def Calculate(data : ResolvedProjectile) : Int = (MaxHitDamage.Calculate(data) * 0.2f).toInt LashDamage,
} DamageWithModifiers(DamageAgainstExoSuit),
DistanceBetweenTargetandSource
)
object VehicleLashDamage extends NoDamageBase { object MaxLashDamage extends DamageCalculations(
override def Calculate(data : ResolvedProjectile) : Int = (VehicleHitDamage.Calculate(data) * 0.2f).toInt LashDamage,
} DamageWithModifiers(DamageAgainstMaxSuit),
DistanceBetweenTargetandSource
)
object AircraftLashDamage extends NoDamageBase { object VehicleLashDamage extends DamageCalculations(
override def Calculate(data : ResolvedProjectile) : Int = (AircraftHitDamage.Calculate(data) * 0.2f).toInt LashDamage,
} DamageWithModifiers(DamageAgainstVehicle),
DistanceBetweenTargetandSource
)
object AircraftLashDamage extends DamageCalculations(
LashDamage,
DamageWithModifiers(DamageAgainstAircraft),
DistanceBetweenTargetandSource
)
object NoDamageSelection extends DamageSelection { object NoDamageSelection extends DamageSelection {
def Direct = None def Direct = None

View file

@ -2,6 +2,7 @@
package net.psforever.objects.vital package net.psforever.objects.vital
import net.psforever.objects.PlanetSideGameObject import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.vital.damage.DamageProfile
import net.psforever.objects.vital.resistance.ResistanceProfile 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") assert(Definition.isInstanceOf[ResistanceProfile], s"$this object definition must extend ResistanceProfile")
private val resistDef = Definition.asInstanceOf[ResistanceProfile] //cast only once private val resistDef = Definition.asInstanceOf[ResistanceProfile] //cast only once
def Subtract : DamageProfile = resistDef.Subtract
def ResistanceDirectHit : Int = resistDef.ResistanceDirectHit def ResistanceDirectHit : Int = resistDef.ResistanceDirectHit
def ResistanceSplash : Int = resistDef.ResistanceDirectHit def ResistanceSplash : Int = resistDef.ResistanceDirectHit

View file

@ -22,7 +22,7 @@ object InfantrySplashResistance extends ResistanceCalculations[PlayerSource](
object InfantryLashResistance extends ResistanceCalculations[PlayerSource]( object InfantryLashResistance extends ResistanceCalculations[PlayerSource](
ResistanceCalculations.ValidInfantryTarget, ResistanceCalculations.ValidInfantryTarget,
ResistanceCalculations.NoResistExtractor ResistanceCalculations.ExoSuitDirectExtractor
) )
object InfantryAggravatedResistance extends ResistanceCalculations[PlayerSource]( object InfantryAggravatedResistance extends ResistanceCalculations[PlayerSource](

View file

@ -23,9 +23,9 @@ object VehicleResolutions extends DamageResistCalculations(
ResolutionCalculations.VehicleApplication ResolutionCalculations.VehicleApplication
) )
object SimpleDeployableResolutions extends DamageResistCalculations( object SimpleResolutions extends DamageResistCalculations(
ResolutionCalculations.VehicleDamageAfterResist, ResolutionCalculations.VehicleDamageAfterResist,
ResolutionCalculations.SimpleDeployableApplication ResolutionCalculations.SimpleApplication
) )
object ComplexDeployableResolutions extends DamageResistCalculations( object ComplexDeployableResolutions extends DamageResistCalculations(
@ -38,6 +38,7 @@ object StandardResolutions extends ResolutionSelection {
def Max : ResolutionCalculations.Form = MaxResolutions.Calculate def Max : ResolutionCalculations.Form = MaxResolutions.Calculate
def Vehicle : ResolutionCalculations.Form = VehicleResolutions.Calculate def Vehicle : ResolutionCalculations.Form = VehicleResolutions.Calculate
def Aircraft : 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 ComplexDeployables : ResolutionCalculations.Form = ComplexDeployableResolutions.Calculate
def FacilityTurrets : ResolutionCalculations.Form = SimpleResolutions.Calculate
} }

View file

@ -27,9 +27,16 @@ abstract class DamageCalculations(damages : DamagesType,
*/ */
def Calculate(data : ResolvedProjectile) : Int = { def Calculate(data : ResolvedProjectile) : Int = {
val projectile = data.projectile 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( damages(
projectile, projectile,
extractor(projectile.profile, List(projectile.fire_mode.Modifiers)), extractor(profile, modifiers),
distanceFunc(data) distanceFunc(data)
) )
} }
@ -70,6 +77,17 @@ object DamageCalculations {
//damage calculation functions //damage calculation functions
def NoDamage(projectile : Projectile, rawDamage : Int, distance : Float) : Int = 0 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 * Modify the base damage based on the degrade distance of the projectile type
* and its maximum effective distance. * and its maximum effective distance.
@ -107,7 +125,25 @@ object DamageCalculations {
if(distance <= radius) { if(distance <= radius) {
val base : Float = projectile.profile.DamageAtEdge val base : Float = projectile.profile.DamageAtEdge
val degrade : Float = (1 - base) * ((radius - distance)/radius) + base val degrade : Float = (1 - base) * ((radius - distance)/radius) + base
rawDamage + (rawDamage * degrade).toInt (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 { else {
0 0

View file

@ -1,7 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.vital.resistance 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. * 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. * This is for defining pure accessor functions.
*/ */
trait ResistanceProfile { trait ResistanceProfile {
def Subtract : DamageProfile
def ResistanceDirectHit : Int def ResistanceDirectHit : Int
def ResistanceSplash : Int def ResistanceSplash : Int
@ -34,11 +37,25 @@ trait ResistanceProfile {
* This is for defining both accessor and mutator functions. * This is for defining both accessor and mutator functions.
*/ */
trait ResistanceProfileMutators extends ResistanceProfile { 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 resistanceDirectHit : Int = 0
private var resistanceSplash : Int = 0 private var resistanceSplash : Int = 0
private var resistanceAggravated : Int = 0 private var resistanceAggravated : Int = 0
private var radiationShielding : Float = 0f private var radiationShielding : Float = 0f
def Subtract : DamageProfile = subtract
def ResistanceDirectHit : Int = resistanceDirectHit def ResistanceDirectHit : Int = resistanceDirectHit
def ResistanceDirectHit_=(resist : Int) : Int = { def ResistanceDirectHit_=(resist : Int) : Int = {

View file

@ -4,6 +4,7 @@ package net.psforever.objects.vital.resolution
import net.psforever.objects.{Player, TurretDeployable, Vehicle} import net.psforever.objects.{Player, TurretDeployable, Vehicle}
import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile} import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
import net.psforever.objects.ce.{ComplexDeployable, SimpleDeployable} import net.psforever.objects.ce.{ComplexDeployable, SimpleDeployable}
import net.psforever.objects.serverobject.turret.FacilityTurret
import net.psforever.objects.vital.projectile.ProjectileCalculations import net.psforever.objects.vital.projectile.ProjectileCalculations
/** /**
@ -159,12 +160,17 @@ object ResolutionCalculations {
case _ => ; 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 => case ce : SimpleDeployable =>
if(ce.Health > 0) { if(ce.Health > 0) {
ce.Health -= damage ce.Health -= damage
ce.History(data) ce.History(data)
} }
case turret : FacilityTurret =>
if(turret.Health > 0) {
turret.Health -= damage
turret.History(data)
}
case _ => case _ =>
} }

View file

@ -13,6 +13,7 @@ import net.psforever.objects.guid.actor.UniqueNumberSystem
import net.psforever.objects.guid.selector.RandomSelector import net.psforever.objects.guid.selector.RandomSelector
import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.inventory.Container 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.structures.{Amenity, Building}
import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.serverobject.turret.FacilityTurret 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 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 = { private def CreateSpawnGroups() : Unit = {

View file

@ -2,33 +2,45 @@
package net.psforever.packet.game package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
import net.psforever.types.Vector3 import net.psforever.types.{SpawnGroup, Vector3}
import scodec.Codec import scodec.Codec
import scodec.codecs._ import scodec.codecs._
/**
* The purpose of the `BindPlayerMessage` packet.<br>
* <br>
* `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.<br> * A packet dispatched to maintain a manually-set respawn location.<br>
* <br> * <br>
* The packet establishes the player's ability to spawn in an arbitrary location that is not a normal local option. * 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.<br> * In addition to player establishing the binding, the packet updates as conditions of the respawn location changes.<br>
* <br> * <br>
* If `logging` is turned on, packets will display different messages depending on context. * 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. * 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. * If the logged action is applicable, the matrixing sound effect will be played too.
* Not displaying events is occasionally warranted for aesthetics. * Not displaying events is occasionally warranted for aesthetics.
* The game will always note if this is your first time binding.<br> * The game will always note if this is your first time binding regardless of the state of this flag.<br>
* <br>
* 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.<br>
* <br>
* Actions:<br>
* `1` - bound to respawn point<br>
* `2` - general unbound / unbinding from respawn point<br>
* `3` - respawn point lost<br>
* `4` - bound spawn point became available<br>
* `5` - bound spawn point became unavailable (different from 3)<br>
* <br> * <br>
* Bind Descriptors:<br> * Bind Descriptors:<br>
* `&#64;amp_station`<br> * `&#64;amp_station`<br>
@ -41,22 +53,28 @@ import scodec.codecs._
* Exploration:<br> * Exploration:<br>
* Find other bind descriptors. * Find other bind descriptors.
* @param action the purpose of the packet * @param action the purpose of the packet
* @param bindDesc a description of the respawn binding point * @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 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; * @param logging true, to report on bind point change visible in the events window;
* false, to render spawn change silent; * false, to render spawn change silent;
* a first time event notification will always show * some first time notifications will always display regardless of this flag
* @param unk2 na; if a value, it is usually 40 (hex`28`) * @param spawn_group the kind of spawn request that will be made;
* @param unk3 na * 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 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, final case class BindPlayerMessage(action : BindStatus.Value,
bindDesc : String, bind_desc : String,
unk1 : Boolean, unk1 : Boolean,
logging : Boolean, logging : Boolean,
unk2 : Int, spawn_group : SpawnGroup.Value,
unk3 : Long, zone_number : Long,
unk4 : Long, unk4 : Long,
pos : Vector3) pos : Vector3)
extends PlanetSideGamePacket { extends PlanetSideGamePacket {
@ -68,20 +86,18 @@ final case class BindPlayerMessage(action : Int,
object BindPlayerMessage extends Marshallable[BindPlayerMessage] { object BindPlayerMessage extends Marshallable[BindPlayerMessage] {
/** /**
* A common variant of this packet. * 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] = ( implicit val codec : Codec[BindPlayerMessage] = (
("action" | uint8L) :: ("action" | BindStatus.codec) ::
("bindDesc" | PacketHelpers.encodedString) :: ("bind_desc" | PacketHelpers.encodedString) ::
("unk1" | bool) :: ("unk1" | bool) ::
("logging" | bool) :: ("logging" | bool) ::
ignore(1) :: ("spawn_group" | spawnGroupCodec) ::
("unk2" | uint4L) :: ("zone_number" | uint32L) ::
ignore(1) ::
("unk3" | uint32L) ::
("unk4" | uint32L) :: ("unk4" | uint32L) ::
("pos" | Vector3.codec_pos) ("pos" | Vector3.codec_pos)
).as[BindPlayerMessage] ).as[BindPlayerMessage]

View file

@ -1,7 +1,8 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.packet.game 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.Codec
import scodec.codecs._ import scodec.codecs._
@ -9,20 +10,16 @@ import scodec.codecs._
* na * na
* @param unk1 when defined, na; * @param unk1 when defined, na;
* non-zero when selecting the sanctuary option from a non-sanctuary continent deployment map * 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; * @param spawn_type the type of spawn point destination
* 0 is nothing;
* 2 is ams;
* 6 is towers;
* 7 is facilities
* @param unk3 na * @param unk3 na
* @param unk4 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, final case class SpawnRequestMessage(unk1 : Int,
unk2 : Long, spawn_type : SpawnGroup.Value,
unk3 : Int, unk3 : Int,
unk4 : Int, unk4 : Int,
unk5 : Int) zone_number : Int)
extends PlanetSideGamePacket { extends PlanetSideGamePacket {
type Packet = SpawnRequestMessage type Packet = SpawnRequestMessage
def opcode = GamePacketOpcode.SpawnRequestMessage def opcode = GamePacketOpcode.SpawnRequestMessage
@ -30,11 +27,13 @@ final case class SpawnRequestMessage(unk1 : Int,
} }
object SpawnRequestMessage extends Marshallable[SpawnRequestMessage] { object SpawnRequestMessage extends Marshallable[SpawnRequestMessage] {
private val spawnGroupCodec = PacketHelpers.createLongEnumerationCodec(SpawnGroup, uint32L)
implicit val codec : Codec[SpawnRequestMessage] = ( implicit val codec : Codec[SpawnRequestMessage] = (
("unk1" | uint16L) :: ("unk1" | uint16L) ::
("unk2" | uint32L) :: ("spawn_type" | spawnGroupCodec) ::
("unk3" | uint16L) :: ("unk3" | uint16L) ::
("unk4" | uint16L) :: ("unk4" | uint16L) ::
("unk5" | uintL(10)) ("zone_number" | uintL(10))
).as[SpawnRequestMessage] ).as[SpawnRequestMessage]
} }

View file

@ -0,0 +1,39 @@
// Copyright (c) 2017 PSForever
package net.psforever.types
/**
* The spawn group.<br>
* <br>
* 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."<br>
* <br>
* 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
}

View file

@ -198,7 +198,7 @@ class VehicleService extends Actor {
import net.psforever.objects.vehicles.UtilityType import net.psforever.objects.vehicles.UtilityType
import net.psforever.objects.GlobalDefinitions import net.psforever.objects.GlobalDefinitions
zone.Vehicles 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) ) .flatMap(veh => veh.Utilities.values.filter(util => util.UtilType == UtilityType.ams_respawn_tube) )
.map(util => util().asInstanceOf[SpawnTube]) .map(util => util().asInstanceOf[SpawnTube])
} }

View file

@ -27,8 +27,7 @@ class VehicleRemover extends RemoverActor {
val zoneId = entry.zone.Id val zoneId = entry.zone.Id
vehicle.Actor ! Vehicle.PrepareForDeletion vehicle.Actor ! Vehicle.PrepareForDeletion
//kick out all passengers //kick out all passengers
vehicle.Definition.MountPoints.values.foreach(mount => { vehicle.Seats.values.foreach(seat => {
val seat = vehicle.Seat(mount).get
seat.Occupant match { seat.Occupant match {
case Some(tplayer) => case Some(tplayer) =>
seat.Occupant = None seat.Occupant = None

View file

@ -4,27 +4,42 @@ package game
import org.specs2.mutable._ import org.specs2.mutable._
import net.psforever.packet._ import net.psforever.packet._
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types.Vector3 import net.psforever.types.{SpawnGroup, Vector3}
import scodec.bits._ import scodec.bits._
class BindPlayerMessageTest extends Specification { class BindPlayerMessageTest extends Specification {
val string_standard = hex"16028004000000000000000000000000000000"
val string_ams = hex"16 05 8440616D73 08 28000000 00000000 00000 00000 0000" 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_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 { "decode (ams)" in {
PacketCoding.DecodePacket(string_ams).require match { PacketCoding.DecodePacket(string_ams).require match {
case BindPlayerMessage(action, bindDesc, unk1, logging, unk2, unk3, unk4, pos) => case BindPlayerMessage(action, bindDesc, unk1, logging, unk2, unk3, unk4, pos) =>
action mustEqual 5 action mustEqual BindStatus.Unavailable
bindDesc.length mustEqual 4
bindDesc mustEqual "@ams" bindDesc mustEqual "@ams"
unk1 mustEqual false unk1 mustEqual false
logging mustEqual false logging mustEqual false
unk2 mustEqual 4 unk2 mustEqual SpawnGroup.AMS
unk3 mustEqual 40 unk3 mustEqual 10
unk4 mustEqual 0 unk4 mustEqual 0
pos.x mustEqual 0f pos mustEqual Vector3.Zero
pos.y mustEqual 0f
pos.z mustEqual 0f
case _ => case _ =>
ko ko
} }
@ -33,40 +48,60 @@ class BindPlayerMessageTest extends Specification {
"decode (tech)" in { "decode (tech)" in {
PacketCoding.DecodePacket(string_tech).require match { PacketCoding.DecodePacket(string_tech).require match {
case BindPlayerMessage(action, bindDesc, unk1, logging, unk2, unk3, unk4, pos) => case BindPlayerMessage(action, bindDesc, unk1, logging, unk2, unk3, unk4, pos) =>
action mustEqual 1 action mustEqual BindStatus.Bind
bindDesc.length mustEqual 11
bindDesc mustEqual "@tech_plant" bindDesc mustEqual "@tech_plant"
unk1 mustEqual true unk1 mustEqual true
logging mustEqual true logging mustEqual true
unk2 mustEqual 10 unk2 mustEqual SpawnGroup.BoundFacility
unk3 mustEqual 40 unk3 mustEqual 10
unk4 mustEqual 56 unk4 mustEqual 14
pos.x mustEqual 2060.0f pos mustEqual Vector3(4610.0f, 6292, 69.625f)
pos.y mustEqual 598.0078f
pos.z mustEqual 274.5f
case _ => case _ =>
ko 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 { "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 val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_ams pkt mustEqual string_ams
} }
"encode (tech)" in { "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 val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string_tech pkt mustEqual string_tech
} }
"standard" in { "encode (akkan)" in {
val msg = BindPlayerMessage.STANDARD 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 val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual hex"16028004000000000000000000000000000000" pkt mustEqual string_akkan
} }
} }

View file

@ -4,6 +4,7 @@ package game
import org.specs2.mutable._ import org.specs2.mutable._
import net.psforever.packet._ import net.psforever.packet._
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.types.SpawnGroup
import scodec.bits._ import scodec.bits._
class SpawnRequestMessageTest extends Specification { class SpawnRequestMessageTest extends Specification {
@ -13,7 +14,7 @@ class SpawnRequestMessageTest extends Specification {
PacketCoding.DecodePacket(string).require match { PacketCoding.DecodePacket(string).require match {
case SpawnRequestMessage(unk1,unk2,unk3,unk4,unk5) => case SpawnRequestMessage(unk1,unk2,unk3,unk4,unk5) =>
unk1 mustEqual 0 unk1 mustEqual 0
unk2 mustEqual 7 unk2 mustEqual SpawnGroup.Facility
unk3 mustEqual 0 unk3 mustEqual 0
unk4 mustEqual 0 unk4 mustEqual 0
unk5 mustEqual 2 unk5 mustEqual 2
@ -23,7 +24,7 @@ class SpawnRequestMessageTest extends Specification {
} }
"encode" in { "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 val pkt = PacketCoding.EncodePacket(msg).require.toByteVector
pkt mustEqual string pkt mustEqual string

View file

@ -106,12 +106,12 @@ class DamageCalculationsTests extends Specification {
"calculate splash damage from components (near)" in { "calculate splash damage from components (near)" in {
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof)) 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 { "calculate splash damage from components (medium)" in {
val result = DamageWithModifiers(DamageAgainstVehicle)(proj_prof, List(wep_prof)) 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 { "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) val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
func(tplayer) func(tplayer)
tplayer.Health mustEqual 81 tplayer.Health mustEqual 98
tplayer.Armor mustEqual 35 tplayer.Armor mustEqual 35
} }
@ -410,7 +410,7 @@ class DamageModelTests extends Specification {
val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash) val func : (Any)=>Unit = resprojectile.damage_model.Calculate(resprojectile, ProjectileResolution.Splash)
func(vehicle) func(vehicle)
vehicle.Health mustEqual 632 vehicle.Health mustEqual 641
} }
} }
} }

View file

@ -111,6 +111,7 @@ class EquipmentTest extends Specification {
obj.Size = EquipmentSize.Rifle obj.Size = EquipmentSize.Rifle
obj.AmmoTypes += GlobalDefinitions.shotgun_shell obj.AmmoTypes += GlobalDefinitions.shotgun_shell
obj.AmmoTypes += GlobalDefinitions.shotgun_shell_AP obj.AmmoTypes += GlobalDefinitions.shotgun_shell_AP
obj.AmmoTypes += GlobalDefinitions.shotgun_shell_AP
obj.FireModes += new FireModeDefinition obj.FireModes += new FireModeDefinition
obj.FireModes.head.AmmoTypeIndices += 0 obj.FireModes.head.AmmoTypeIndices += 0
obj.FireModes.head.AmmoTypeIndices += 1 obj.FireModes.head.AmmoTypeIndices += 1
@ -163,6 +164,29 @@ class EquipmentTest extends Specification {
obj.AmmoType mustEqual Ammo.hellfire_ammo 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 { "multiple fire modes" in {
//explanation: sample_weapon has two fire modes; each fire mode has a different ammunition type //explanation: sample_weapon has two fire modes; each fire mode has a different ammunition type
val obj : Tool = Tool(punisher) val obj : Tool = Tool(punisher)

View file

@ -5,7 +5,7 @@ import java.util.concurrent.atomic.AtomicInteger
import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware} import akka.actor.{Actor, ActorRef, Cancellable, MDCContextAware}
import net.psforever.packet._ import net.psforever.packet._
import net.psforever.packet.control._ import net.psforever.packet.control._
import net.psforever.packet.game.{BattleDiagramAction, ObjectDetachMessage, _} import net.psforever.packet.game._
import scodec.Attempt.{Failure, Successful} import scodec.Attempt.{Failure, Successful}
import scodec.bits._ import scodec.bits._
import org.log4s.{Logger, MDC} import org.log4s.{Logger, MDC}
@ -68,6 +68,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
import WorldSessionActor._ import WorldSessionActor._
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
private[this] val damageLog = org.log4s.getLogger("DamageResolution")
var sessionId : Long = 0 var sessionId : Long = 0
var leftRef : ActorRef = ActorRef.noSender var leftRef : ActorRef = ActorRef.noSender
var rightRef : ActorRef = ActorRef.noSender var rightRef : ActorRef = ActorRef.noSender
@ -81,7 +82,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
var player : Player = null var player : Player = null
var avatar : Avatar = null var avatar : Avatar = null
var progressBarValue : Option[Float] = None 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 accessedContainer : Option[PlanetSideGameObject with Container] = None
var flying : Boolean = false var flying : Boolean = false
var speed : Float = 1.0f 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) val projectiles : Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.RangeUID - Projectile.BaseUID)(None)
var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons var drawDeloyableIcon : PlanetSideGameObject with Deployable => Unit = RedrawDeployableIcons
var recentTeleportAttempt : Long = 0 var recentTeleportAttempt : Long = 0
var lastTerminalOrderFulfillment : Boolean = true
var amsSpawnPoint : Option[SpawnTube] = None var amsSpawnPoint : Option[SpawnTube] = None
var clientKeepAlive : Cancellable = DefaultCancellable.obj var clientKeepAlive : Cancellable = DefaultCancellable.obj
@ -115,8 +118,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
* @param b `true` or `false` (or `null`) * @param b `true` or `false` (or `null`)
* @return 1 for `true`; 0 for `false` * @return 1 for `true`; 0 for `false`
*/ */
implicit def boolToInt(b : Boolean) : Int = if(b) 1 implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0
else 0
override def postStop() = { override def postStop() = {
//TODO normally, player avatar persists a minute or so after disconnect; we are subject to the SessionReaper //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() vehicleService ! Service.Leave()
avatarService ! Service.Leave() avatarService ! Service.Leave()
galaxyService ! Service.Leave() galaxyService ! Service.Leave()
cluster ! Service.Leave()
LivePlayerList.Remove(sessionId) LivePlayerList.Remove(sessionId)
if(player != null && player.HasGUID) { if(player != null && player.HasGUID) {
val player_guid = player.GUID val player_guid = player.GUID
@ -378,7 +379,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, pad) => case VehicleSpawnPad.ServerVehicleOverrideStart(vehicle, pad) =>
val vdef = vehicle.Definition val vdef = vehicle.Definition
if(vehicle.Seats(0).isOccupied) { 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) ServerVehicleOverride(vehicle, vdef.AutoPilotSpeed1, GlobalDefinitions.isFlightVehicle(vdef) : Int)
@ -879,6 +880,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
AnnounceDestroyDeployable(target, None) AnnounceDestroyDeployable(target, None)
} }
case Vitality.DamageResolution(target : FacilityTurret) =>
HandleFacilityTurretDamageResolution(target)
case Vitality.DamageResolution(target : PlanetSideGameObject) => case Vitality.DamageResolution(target : PlanetSideGameObject) =>
log.warn(s"Vital target ${target.Definition.Name} damage resolution not supported using this method") log.warn(s"Vital target ${target.Definition.Name} damage resolution not supported using this method")
@ -947,9 +951,15 @@ class WorldSessionActor extends Actor with MDCContextAware {
case AvatarResponse.DamageResolution(target, resolution_function) => case AvatarResponse.DamageResolution(target, resolution_function) =>
if(player.isAlive) { if(player.isAlive) {
val originalHealth = player.Health
val originalArmor = player.Armor
resolution_function(target) resolution_function(target)
val health = player.Health val health = player.Health
val armor = player.Armor val armor = player.Armor
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 val playerGUID = player.GUID
sendResponse(PlanetsideAttributeMessage(playerGUID, 0, health)) sendResponse(PlanetsideAttributeMessage(playerGUID, 0, health))
sendResponse(PlanetsideAttributeMessage(playerGUID, 4, armor)) sendResponse(PlanetsideAttributeMessage(playerGUID, 4, armor))
@ -958,6 +968,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(health == 0 && player.isAlive) { if(health == 0 && player.isAlive) {
KillPlayer(player) 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 _ => ;
}
}
}
} }
case AvatarResponse.Destroy(victim, killer, weapon, pos) => case AvatarResponse.Destroy(victim, killer, weapon, pos) =>
@ -978,11 +1007,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
case AvatarResponse.HitHint(source_guid) => case AvatarResponse.HitHint(source_guid) =>
if(player.isAlive) {
sendResponse(HitHint(source_guid, guid)) sendResponse(HitHint(source_guid, guid))
}
case AvatarResponse.KilledWhileInVehicle() => case AvatarResponse.KilledWhileInVehicle() =>
if(player.isAlive && player.VehicleSeated.nonEmpty) { if(player.isAlive && player.VehicleSeated.nonEmpty) {
(continent.GUID(player.VehicleSeated.get) match { (continent.GUID(player.VehicleSeated) match {
case Some(obj : Vehicle) => case Some(obj : Vehicle) =>
if(obj.Health == 0) Some(obj) if(obj.Health == 0) Some(obj)
else None else None
@ -990,7 +1021,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(obj.Health == 0) Some(obj) if(obj.Health == 0) Some(obj)
else None else None
case Some(obj : FacilityTurret) => 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 else None
case _ => case _ =>
None None
@ -999,10 +1030,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
obj.LastShot match { obj.LastShot match {
case Some(cause) => case Some(cause) =>
player.History(cause) player.History(cause)
KillPlayer(player)
case None => ; 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 = { def HandleTerminalMessage(tplayer : Player, msg : ItemTransactionMessage, order : Terminal.Exchange) : Unit = {
order match { order match {
case Terminal.BuyExosuit(exosuit, subtype) => //refresh armor points case Terminal.BuyExosuit(exosuit, subtype) =>
tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit)) //TODO check exo-suit permissions
if(tplayer.ExoSuit == exosuit) { val originalSuit = tplayer.ExoSuit
if(exosuit == ExoSuitType.MAX) { val originalSubtype = Loadout.DetermineSubtype(tplayer)
//special MAX case - clear any special state if(originalSuit != exosuit || originalSubtype != subtype) {
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))
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true)) sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))
//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 { else {
//load a complete new exo-suit and shuffle the inventory around tplayer.Armor = originalArmor
val originalSuit = tplayer.ExoSuit }
//save inventory before it gets cleared (empty holsters) //ensure arm is down, even if it needs to go back up
val dropPred = DropPredicate(tplayer) if(tplayer.DrawnSlot != Player.HandsDownSlot) {
val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred) tplayer.DrawnSlot = Player.HandsDownSlot
val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred) sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
//change suit (clear inventory and change holster sizes; note: holsters must be empty before this point) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, tplayer.LastDrawnSlot))
tplayer.ExoSuit = exosuit }
tplayer.Armor = tplayer.MaxArmor
//delete everything not dropped //delete everything not dropped
(beforeHolsters ++ beforeInventory).foreach({ elem => (beforeHolsters ++ beforeInventory).foreach({ elem =>
sendResponse(ObjectDeleteMessage(elem.obj.GUID, 0)) sendResponse(ObjectDeleteMessage(elem.obj.GUID, 0))
@ -1451,31 +1480,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
//report change //report change
sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype)) sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype))
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype)) avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype))
sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor)) //sterilize holsters
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor)) val normalHolsters = if(originalSuit == ExoSuitType.MAX) {
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
}
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) val (maxWeapons, normalWeapons) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
maxWeapons.foreach(entry => { maxWeapons.foreach(entry => { taskResolver ! GUIDTask.UnregisterEquipment(entry.obj)(continent.GUID) })
taskResolver ! GUIDTask.UnregisterEquipment(entry.obj)(continent.GUID)
})
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot))
normalWeapons normalWeapons
} }
else { else {
beforeHolsters beforeHolsters
} }
//fill holsters //populate holsters
val (afterHolsters, toInventory) = normalWeapons.partition(elem => elem.obj.Size == tplayer.Slot(elem.start).Size) 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 }) afterHolsters.foreach({ elem => tplayer.Slot(elem.start).Equipment = elem.obj })
fillEmptyHolsters(tplayer.Holsters().iterator, toInventory ++ beforeInventory) fillEmptyHolsters(tplayer.Holsters().iterator, toInventory ++ beforeInventory)
} }
@ -1511,7 +1534,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
case None => ; case None => ;
} }
//put items back into inventory //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 => { stow.foreach(elem => {
tplayer.Inventory.Insert(elem.start, elem.obj) tplayer.Inventory.Insert(elem.start, elem.obj)
val obj = 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 pos = tplayer.Position
val orient = Vector3(0, 0, tplayer.Orientation.z) val orient = Vector3.z(tplayer.Orientation.z)
((dropHolsters ++ dropInventory).map(_.obj) ++ drop).foreach(obj => { finalDroppedItems.foreach(entry => {
//TODO make a sound when dropping stuff //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) => case Terminal.BuyEquipment(item) =>
@ -1556,26 +1591,38 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) => 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}") 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)) sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true))
tplayer.History(HealFromExoSuitChange(PlayerSource(tplayer), exosuit)) //prepare lists of valid objects
//ensure arm is down val beforeFreeHand = tplayer.FreeHand.Equipment
tplayer.DrawnSlot = Player.HandsDownSlot
sendResponse(ObjectHeldMessage(tplayer.GUID, Player.HandsDownSlot, true))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectHeld(tplayer.GUID, Player.HandsDownSlot))
//load
val dropPred = DropPredicate(tplayer) val dropPred = DropPredicate(tplayer)
val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred) val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred)
val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred) val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred)
val (_, afterHolsters) = holsters.partition(dropPred) val (_, afterHolsters) = holsters.partition(dropPred) //dropped items are forgotten
//dropped items are lost val (_, afterInventory) = inventory.partition(dropPred) //dropped items are forgotten
val (_, afterInventory) = inventory.partition(dropPred) //change suit (clear inventory and change holster sizes; holsters must be empty before this point)
//dropped items are lost tplayer.FreeHand.Equipment = None //terminal and inventory will close, so prematurely dropping should be fine
val beforeFreeHand = tplayer.FreeHand.Equipment val originalSuit = player.ExoSuit
//change suit (clear inventory and change holster sizes; note: holsters must be empty before this point) val originalSubtype = Loadout.DetermineSubtype(tplayer)
val originalArmor = player.Armor
tplayer.ExoSuit = exosuit 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) //delete everything (not dropped)
beforeHolsters.foreach({ elem => beforeHolsters.foreach({ elem =>
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(tplayer.GUID, elem.obj.GUID)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(tplayer.GUID, elem.obj.GUID))
@ -1587,29 +1634,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
//report change //report change
sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype)) sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(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) { 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))) taskResolver ! DelayedObjectHeld(tplayer, 0, List(PutEquipmentInSlot(tplayer, maxWeapons.head.obj, 0)))
otherWeapons otherWeapons
} }
@ -1624,9 +1650,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
}) })
//drop stuff on ground //drop stuff on ground
val pos = tplayer.Position val pos = tplayer.Position
val orient = Vector3(0, 0, tplayer.Orientation.z) val orient = Vector3.z(tplayer.Orientation.z)
((dropHolsters ++ dropInventory).map(_.obj)).foreach(obj => { ((beforeFreeHand match {
continent.Ground ! Zone.Ground.DropItem(obj, pos, orient) 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) => 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") log.warn(s"${tplayer.Name} made a request but the terminal rejected the $order")
sendResponse(ItemTransactionResultMessage(msg.terminal_guid, msg.transaction_type, false)) 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) => case VehicleResponse.HitHint(source_guid) =>
if(player.isAlive) {
sendResponse(HitHint(source_guid, player.GUID)) sendResponse(HitHint(source_guid, player.GUID))
}
case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) => case VehicleResponse.InventoryState(obj, parent_guid, start, con_data) =>
if(tplayer_guid != guid) { if(tplayer_guid != guid) {
@ -1994,7 +2026,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(msg) sendResponse(msg)
case VehicleResponse.UpdateAmsSpawnPoint(list) => case VehicleResponse.UpdateAmsSpawnPoint(list) =>
if(player.isBackpack) {
//dismiss old ams spawn point //dismiss old ams spawn point
ClearCurrentAmsSpawnPoint() ClearCurrentAmsSpawnPoint()
//draw new ams spawn point //draw new ams spawn point
@ -2003,16 +2034,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
.sortBy(tube => Vector3.DistanceSquared(tube.Position, player.Position)) .sortBy(tube => Vector3.DistanceSquared(tube.Position, player.Position))
.headOption match { .headOption match {
case Some(tube) => case Some(tube) =>
sendResponse( sendResponse(BindPlayerMessage(BindStatus.Available, "@ams", true, false, SpawnGroup.AMS, continent.Number, 5, tube.Position))
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) amsSpawnPoint = Some(tube)
case None => ; case None => ;
} }
}
case _ => ; case _ => ;
} }
@ -2147,6 +2172,13 @@ class WorldSessionActor extends Actor with MDCContextAware {
val wep = slot.Equipment.get val wep = slot.Equipment.get
avatarService ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID)) 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 { target.Definition match {
case GlobalDefinitions.ams => case GlobalDefinitions.ams =>
target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying) target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
@ -2199,6 +2231,48 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatarService ! AvatarServiceMessage(continentId, AvatarAction.PlanetsideAttribute(guid, 0, health)) 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 * na
* @param tplayer 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.Position = Vector3(4262.211f ,4067.0625f ,262.35938f) //z6, Akna.tower
//player.Orientation = Vector3(0f, 0f, 132.1875f) //player.Orientation = Vector3(0f, 0f, 132.1875f)
// player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting // 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(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction))
player.Slot(2).Equipment = ConstructionItem(advanced_ace) //punisher //suppressor player.Slot(2).Equipment = Tool(suppressor)
player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction)) player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction))
player.Slot(6).Equipment = ConstructionItem(ace) //bullet_9mm player.Slot(6).Equipment = AmmoBox(bullet_9mm)
player.Slot(9).Equipment = ConstructionItem(ace) //bullet_9mm player.Slot(9).Equipment = AmmoBox(bullet_9mm)
player.Slot(12).Equipment = ConstructionItem(ace) //bullet_9mm player.Slot(12).Equipment = AmmoBox(bullet_9mm)
player.Slot(33).Equipment = Tool(suppressor) //AmmoBox(bullet_9mm_AP) player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP)
//player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction))
//player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) player.Slot(39).Equipment = SimpleItem(remote_electronics_kit)
player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit) player.Locker.Inventory += 0 -> SimpleItem(remote_electronics_kit)
//TODO end temp player character auto-loading //TODO end temp player character auto-loading
self ! ListAccountCharacters self ! ListAccountCharacters
@ -2478,97 +2552,44 @@ class WorldSessionActor extends Actor with MDCContextAware {
clientKeepAlive.cancel clientKeepAlive.cancel
clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient()) 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) => case msg @ MountVehicleCargoMsg(player_guid, vehicle_guid, cargo_vehicle_guid, unk4) =>
log.info(msg.toString) log.info(msg.toString)
(continent.GUID(vehicle_guid), continent.GUID(cargo_vehicle_guid)) match {
val cargo_vehicle = continent.GUID(cargo_vehicle_guid).get.asInstanceOf[Vehicle] case (Some(_ : Vehicle), Some(carrier : Vehicle)) =>
val cargo_mountpoint = cargo_vehicle.Definition.Cargo.head._1 carrier.Definition.Cargo.headOption match {
case Some((mountPoint, _)) => //begin the mount process - open the cargo door
// Begin the mount process - open the cargo door val reply = CargoMountPointStatusMessage(cargo_vehicle_guid, PlanetSideGUID(0), vehicle_guid, PlanetSideGUID(0), mountPoint, CargoStatus.InProgress, 0)
val reply = CargoMountPointStatusMessage(cargo_vehicle_guid, PlanetSideGUID(0), vehicle_guid, PlanetSideGUID(0), cargo_mountpoint, CargoStatus.InProgress, 0) log.debug(reply.toString)
log.warn(reply.toString)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, reply)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.SendResponse(player.GUID, reply))
sendResponse(reply) sendResponse(reply)
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global 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 // 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)) 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 _ => ;
}
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 _ => ;
}
}
case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) => case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) =>
log.info("Handling " + msg) log.info("Handling " + msg)
sendResponse(ActionResultMessage.Pass) sendResponse(ActionResultMessage.Pass)
@ -2843,9 +2864,29 @@ class WorldSessionActor extends Actor with MDCContextAware {
player.FacingYawUpper = yaw_upper player.FacingYawUpper = yaw_upper
player.Crouching = is_crouching player.Crouching = is_crouching
player.Jumping = is_jumping player.Jumping = is_jumping
if(vel.isDefined && usingMedicalTerminal.isDefined) { if(vel.isDefined && usingMedicalTerminal.isDefined) {
StopUsingProximityUnit(continent.GUID(usingMedicalTerminal.get).get.asInstanceOf[ProximityTerminal]) 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 { val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match {
case Some(item) => item.Definition == GlobalDefinitions.bolt_driver case Some(item) => item.Definition == GlobalDefinitions.bolt_driver
case None => false case None => false
@ -2933,10 +2974,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
context.system.scheduler.scheduleOnce(50 milliseconds, self, UnregisterCorpseOnVehicleDisembark(player)) 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") log.info(s"SpawnRequestMessage: $msg")
//TODO just focus on u5 and u2 for now cluster ! Zone.Lattice.RequestSpawnPoint(zone_number.toInt, player, spawn_type.id.toInt)
cluster ! Zone.Lattice.RequestSpawnPoint(u5.toInt, player, u2.toInt)
case msg @ SetChatFilterMessage(send_channel, origin, whitelist) => case msg @ SetChatFilterMessage(send_channel, origin, whitelist) =>
//log.info("SetChatFilters: " + msg) //log.info("SetChatFilters: " + msg)
@ -3098,11 +3138,17 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(shooting.isEmpty) { if(shooting.isEmpty) {
FindEquipment match { FindEquipment match {
case Some(tool : Tool) => case Some(tool : Tool) =>
if(tool.Magazine > 0) { if(tool.Magazine > 0 || prefire.contains(item_guid)) {
prefire = None
shooting = Some(item_guid) shooting = Some(item_guid)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, 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 case Some(_) => //permissible, for now
prefire = None
shooting = Some(item_guid) shooting = Some(item_guid)
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid))
case None => case None =>
@ -3112,6 +3158,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ ChangeFireStateMessage_Stop(item_guid) => case msg @ ChangeFireStateMessage_Stop(item_guid) =>
log.info("ChangeFireState_Stop: " + msg) log.info("ChangeFireState_Stop: " + msg)
prefire = None
val weapon : Option[Equipment] = if(shooting.contains(item_guid)) { val weapon : Option[Equipment] = if(shooting.contains(item_guid)) {
shooting = None shooting = None
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid))
@ -3715,7 +3762,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(owned) { if(owned) {
if(tdef.isInstanceOf[MatrixTerminalDefinition]) { if(tdef.isInstanceOf[MatrixTerminalDefinition]) {
//TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks) //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]) { else if(tdef.isInstanceOf[RepairRearmSiloDefinition]) {
FindLocalVehicle match { 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) log.info("ItemTransaction: " + msg)
continent.GUID(terminal_guid) match { continent.GUID(terminal_guid) match {
case Some(term : Terminal) => case Some(term : Terminal) =>
log.info(s"ItemTransaction: ${term.Definition.Name} found") log.info(s"ItemTransaction: ${term.Definition.Name} found")
if(lastTerminalOrderFulfillment) {
lastTerminalOrderFulfillment = false
term.Actor ! Terminal.Request(player, msg) term.Actor ! Terminal.Request(player, msg)
}
case Some(obj : PlanetSideGameObject) => case Some(obj : PlanetSideGameObject) =>
log.error(s"ItemTransaction: $obj is not a terminal") log.error(s"ItemTransaction: $obj is not a terminal")
case _ => case _ =>
@ -3984,14 +4034,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
FindContainedWeapon match { FindContainedWeapon match {
case (Some(obj), Some(tool : Tool)) => case (Some(obj), Some(tool : Tool)) =>
if(tool.Magazine <= 0) { //safety: enforce ammunition depletion if(tool.Magazine <= 0) { //safety: enforce ammunition depletion
tool.Magazine = 0 prefire = None
sendResponse(InventoryStateMessage(tool.AmmoSlot.Box.GUID, weapon_guid, 0)) EmptyMagazine(weapon_guid, tool)
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))
} }
else { //shooting else { //shooting
prefire = shooting.orElse(Some(weapon_guid))
tool.Discharge tool.Discharge
val projectileIndex = projectile_guid.guid - Projectile.BaseUID val projectileIndex = projectile_guid.guid - Projectile.BaseUID
val projectilePlace = projectiles(projectileIndex) val projectilePlace = projectiles(projectileIndex)
@ -3999,7 +4046,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Some(projectile) => !projectile.isResolved case Some(projectile) => !projectile.isResolved
case None => false 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 { val (angle, attribution) = obj match {
case p : Player => case p : Player =>
@ -4023,12 +4070,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
(hit_info match { (hit_info match {
case Some(hitInfo) => case Some(hitInfo) =>
continent.GUID(hitInfo.hitobject_guid) match { continent.GUID(hitInfo.hitobject_guid) match {
case Some(obj : Player) => case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) =>
Some((obj, hitInfo.shot_origin, hitInfo.hit_pos)) Some((target, 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 _ => case _ =>
None None
} }
@ -4048,7 +4091,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info(s"Splash: $msg") log.info(s"Splash: $msg")
continent.GUID(direct_victim_uid) match { continent.GUID(direct_victim_uid) match {
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => 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) => case Some(projectile) =>
HandleDealingDamage(target, projectile) HandleDealingDamage(target, projectile)
case None => ; case None => ;
@ -4058,7 +4101,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
targets.foreach(elem => { targets.foreach(elem => {
continent.GUID(elem.uid) match { continent.GUID(elem.uid) match {
case Some(target : PlanetSideGameObject with FactionAffinity with Vitality) => 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) => case Some(projectile) =>
HandleDealingDamage(target, projectile) HandleDealingDamage(target, projectile)
case None => ; case None => ;
@ -4273,12 +4316,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
log.info("FriendsRequest: "+msg) log.info("FriendsRequest: "+msg)
case msg @ HitHint(source_guid, player_guid) => case msg @ HitHint(source_guid, player_guid) =>
log.info(s"HitHint: $msg") log.trace(s"HitHint: $msg") //HitHint is manually distributed for proper operation
continent.GUID(player_guid) match {
case Some(obj : Player) =>
avatarService ! AvatarServiceMessage(obj.Name, AvatarAction.HitHint(source_guid, player_guid))
case _ => ;
}
case msg @ TargetingImplantRequest(list) => case msg @ TargetingImplantRequest(list) =>
log.info("TargetingImplantRequest: "+msg) log.info("TargetingImplantRequest: "+msg)
@ -4289,7 +4327,6 @@ class WorldSessionActor extends Actor with MDCContextAware {
case default => log.error(s"Unhandled GamePacket $pkt") case default => log.error(s"Unhandled GamePacket $pkt")
} }
/** /**
* Iterate over a group of `EquipmentSlot`s, some of which may be occupied with an item. * 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`. * Remove any encountered items and add them to an output `List`.
@ -5343,8 +5380,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
case obj : Vehicle => case obj : Vehicle =>
vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid)) vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid))
case obj : Player => 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)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item2_guid))
//put hand down locally
if(dest == player.DrawnSlot) {
player.DrawnSlot = Player.HandsDownSlot
}
} }
case _ => ; case _ => ;
} }
@ -5794,10 +5835,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
def ClearCurrentAmsSpawnPoint() : Unit = { def ClearCurrentAmsSpawnPoint() : Unit = {
amsSpawnPoint match { amsSpawnPoint match {
case Some(_) => case Some(tube) =>
sendResponse( sendResponse(BindPlayerMessage(BindStatus.Unavailable, "@ams", true, false, SpawnGroup.AMS, continent.Number, 0, Vector3.Zero))
BattleplanMessage(41378949, "ams", continent.Number, List(BattleDiagramAction(DiagramActionCode.StopDrawing)))
)
amsSpawnPoint = None amsSpawnPoint = None
case None => ; case None => ;
} }
@ -6013,6 +6052,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
def PlayerActionsToCancel() : Unit = { def PlayerActionsToCancel() : Unit = {
progressBarUpdate.cancel progressBarUpdate.cancel
progressBarValue = None progressBarValue = None
lastTerminalOrderFulfillment = true
accessedContainer match { accessedContainer match {
case Some(obj : Vehicle) => case Some(obj : Vehicle) =>
if(obj.AccessingTrunk.contains(player.GUID)) { if(obj.AccessingTrunk.contains(player.GUID)) {
@ -6030,6 +6070,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case Some(guid) => case Some(guid) =>
sendResponse(ChangeFireStateMessage_Stop(guid)) sendResponse(ChangeFireStateMessage_Stop(guid))
avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, guid)) avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, guid))
prefire = None
shooting = None shooting = None
case None => ; case None => ;
} }
@ -6051,8 +6092,8 @@ class WorldSessionActor extends Actor with MDCContextAware {
def AvatarCreate() : Unit = { def AvatarCreate() : Unit = {
player.VehicleSeated = None //TODO temp, until vehicle gating; unseat player else constructor data is messed up player.VehicleSeated = None //TODO temp, until vehicle gating; unseat player else constructor data is messed up
player.Spawn player.Spawn
player.Health = 50 //TODO temp player.Health = 100
player.Armor = 25 player.Armor = 50
val packet = player.Definition.Packet val packet = player.Definition.Packet
val dcdata = packet.DetailedConstructorData(player).get val dcdata = packet.DetailedConstructorData(player).get
val player_guid = player.GUID val player_guid = player.GUID
@ -6555,6 +6596,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
case obj : Deployable => case obj : Deployable =>
//damage is synchronized on `LSA` (results returned to and distributed from this `WSA`) //damage is synchronized on `LSA` (results returned to and distributed from this `WSA`)
localService ! Vitality.DamageOn(obj, func) 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 _ => ; case _ => ;
} }
} }
@ -7419,7 +7463,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val sguid = src.GUID val sguid = src.GUID
val dguid = dest.GUID val dguid = dest.GUID
StartBundlingPackets() 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) UseRouterTelepadEffect(pguid, sguid, dguid)
StopBundlingPackets() StopBundlingPackets()
// vehicleService ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(router), continent)) // 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) = { def failWithError(error : String) = {
log.error(error) log.error(error)
sendResponse(ConnectionClose()) sendResponse(ConnectionClose())