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