diff --git a/common/src/main/scala/net/psforever/objects/Deployables.scala b/common/src/main/scala/net/psforever/objects/Deployables.scala
index 273fb88d0..a241e9782 100644
--- a/common/src/main/scala/net/psforever/objects/Deployables.scala
+++ b/common/src/main/scala/net/psforever/objects/Deployables.scala
@@ -2,10 +2,10 @@
package net.psforever.objects
import akka.actor.ActorRef
-import scala.concurrent.duration._
+import scala.concurrent.duration._
import net.psforever.objects.ce.{Deployable, DeployedItem}
-import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.vehicles.{Utility, UtilityType}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{DeployableInfo, DeploymentAction}
import net.psforever.types.PlanetSideGUID
@@ -13,6 +13,8 @@ import services.RemoverActor
import services.local.{LocalAction, LocalServiceMessage}
object Deployables {
+ private val log = org.log4s.getLogger("Deployables")
+
object Make {
def apply(item : DeployedItem.Value) : ()=>PlanetSideGameObject with Deployable = cemap(item)
@@ -58,7 +60,7 @@ object Deployables {
* @param time length of time that the deployable is allowed to exist in the game world;
* `None` indicates the normal un-owned existence time (180 seconds)
*/
- def AnnounceDestroyDeployable(target : PlanetSideServerObject with Deployable, time : Option[FiniteDuration]) : Unit = {
+ def AnnounceDestroyDeployable(target : PlanetSideGameObject with Deployable, time : Option[FiniteDuration]) : Unit = {
val zone = target.Zone
target.OwnerName match {
case Some(owner) =>
@@ -102,4 +104,23 @@ object Deployables {
})
boomers ++ deployables
}
+
+ def RemoveTelepad(vehicle: Vehicle) : Unit = {
+ val zone = vehicle.Zone
+ (vehicle.Utility(UtilityType.internal_router_telepad_deployable) match {
+ case Some(util : Utility.InternalTelepad) =>
+ val telepad = util.Telepad
+ util.Telepad = None
+ zone.GUID(telepad)
+ case _ =>
+ None
+ }) match {
+ case Some(telepad : TelepadDeployable) =>
+ log.info(s"BeforeUnload: deconstructing telepad $telepad that was linked to router $vehicle ...")
+ telepad.Active = false
+ zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.ClearSpecific(List(telepad), zone))
+ zone.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(telepad, zone, Some(0 seconds)))
+ case _ => ;
+ }
+ }
}
diff --git a/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala b/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
index 48f62514f..02c348384 100644
--- a/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/ExplosiveDeployable.scala
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 PSForever
+// Copyright (c) 2018 PSForever
package net.psforever.objects
import akka.actor.{Actor, ActorContext, Props}
@@ -8,8 +8,11 @@ import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDepl
import net.psforever.objects.definition.converter.SmallDeployableConverter
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.vital.{StandardResolutions, Vitality}
+import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideGUID, Vector3}
+import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage}
@@ -17,14 +20,6 @@ import scala.concurrent.duration._
class ExplosiveDeployable(cdef : ExplosiveDeployableDefinition) extends ComplexDeployable(cdef)
with JammableUnit {
- private var exploded : Boolean = false
-
- def Exploded : Boolean = exploded
-
- def Exploded_=(fuse : Boolean) : Boolean = {
- exploded = fuse
- Exploded
- }
override def Definition : ExplosiveDeployableDefinition = cdef
}
@@ -59,38 +54,48 @@ object ExplosiveDeployableDefinition {
}
}
-class ExplosiveDeployableControl(mine : ExplosiveDeployable) extends Actor {
- def receive : Receive = {
- case Vitality.Damage(damage_func) =>
- val originalHealth = mine.Health
- if(originalHealth > 0) {
- val cause = damage_func(mine)
- ExplosiveDeployableControl.HandleDamageResolution(mine, cause, originalHealth - mine.Health)
- }
+class ExplosiveDeployableControl(mine : ExplosiveDeployable) extends Actor
+ with Damageable {
+ def DamageableObject = mine
- case _ => ;
+ def receive : Receive = takesDamage
+ .orElse {
+ case _ => ;
+ }
+
+ protected def TakesDamage : Receive = {
+ case Vitality.Damage(applyDamageTo) =>
+ if(mine.CanDamage) {
+ val originalHealth = mine.Health
+ val cause = applyDamageTo(mine)
+ val damage = originalHealth - mine.Health
+ if(Damageable.CanDamageOrJammer(mine, damage, cause)) {
+ ExplosiveDeployableControl.DamageResolution(mine, cause, damage)
+ }
+ else {
+ mine.Health = originalHealth
+ }
+ }
}
}
object ExplosiveDeployableControl {
- def HandleDamageResolution(target : ExplosiveDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
- val zone = target.Zone
- val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
- case Some(player) => player.GUID
- case _ => PlanetSideGUID(0)
- }
+ def DamageResolution(target : ExplosiveDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
+ target.History(cause)
if(target.Health == 0) {
- HandleDestructionAwareness(target, playerGUID, cause)
+ DestructionAwareness(target, cause)
}
- else if(!target.Jammed && cause.projectile.profile.JammerProjectile) {
+ else if(!target.Jammed && Damageable.CanJammer(target, cause)) {
if(target.Jammed = {
val radius = cause.projectile.profile.DamageRadius
Vector3.DistanceSquared(cause.hit_pos, cause.target.Position) < radius * radius
}) {
if(target.Definition.DetonateOnJamming) {
- target.Zone.LocalEvents ! LocalServiceMessage(target.Zone.Id, LocalAction.Detonate(target.GUID, target))
+ val zone = target.Zone
+ zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
+ zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.Detonate(target.GUID, target))
}
- HandleDestructionAwareness(target, playerGUID, cause)
+ DestructionAwareness(target, cause)
}
}
}
@@ -98,13 +103,19 @@ object ExplosiveDeployableControl {
/**
* na
* @param target na
- * @param attribution na
- * @param lastShot na
+ * @param cause na
*/
- def HandleDestructionAwareness(target : ExplosiveDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
+ def DestructionAwareness(target : ExplosiveDeployable, cause : ResolvedProjectile) : Unit = {
val zone = target.Zone
+ val attribution = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
+ case Some(player) => player.GUID
+ case _ => PlanetSideGUID(0)
+ }
+ target.Destroyed = true
Deployables.AnnounceDestroyDeployable(target, Some(if(target.Jammed) 0 seconds else 500 milliseconds))
- zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
+ zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, Service.defaultPlayerGUID, target.Position))
+ if(target.Health == 0) {
+ zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerEffect(Service.defaultPlayerGUID, "detonate_damaged_mine", target.GUID))
+ }
}
}
-
diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index a3b2164c7..808b572d0 100644
--- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -19,7 +19,7 @@ import net.psforever.objects.serverobject.tube.SpawnTubeDefinition
import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
import net.psforever.objects.serverobject.structures.SphereOfInfluence
import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade}
-import net.psforever.objects.vehicles.{DestroyedVehicle, SeatArmorRestriction, UtilityType}
+import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, SeatArmorRestriction, UtilityType}
import net.psforever.objects.vital.{DamageType, StandardMaxDamage, StandardResolutions}
import net.psforever.types.{CertificationType, ExoSuitType, PlanetSideEmpire, Vector3}
@@ -29,6 +29,8 @@ import scala.concurrent.duration._
object GlobalDefinitions {
// Characters
val avatar = new AvatarDefinition(121)
+ avatar.MaxHealth = 100
+ avatar.Damageable = true
/*
exo-suits
*/
@@ -889,7 +891,7 @@ object GlobalDefinitions {
val sensor_shield = SensorDeployableDefinition(DeployedItem.sensor_shield)
- val tank_traps = SimpleDeployableDefinition(DeployedItem.tank_traps)
+ val tank_traps = TrapDeployableDefinition(DeployedItem.tank_traps)
val portable_manned_turret = TurretDeployableDefinition(DeployedItem.portable_manned_turret)
@@ -904,7 +906,7 @@ object GlobalDefinitions {
val router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
//this is only treated like a deployable
- val internal_router_telepad_deployable = SimpleDeployableDefinition(DeployedItem.router_telepad_deployable)
+ val internal_router_telepad_deployable = InternalTelepadDefinition() //objectId: 744
init_deployables()
/*
@@ -1626,7 +1628,7 @@ object GlobalDefinitions {
max.ResistanceDirectHit = 6
max.ResistanceSplash = 35
max.ResistanceAggravated = 10
- max.Damage = StandardMaxDamage
+ max.DamageUsing = StandardMaxDamage
max.Model = StandardResolutions.Max
}
@@ -4470,7 +4472,10 @@ object GlobalDefinitions {
nano_dispenser.FireModes.head.AmmoSlotIndex = 0
nano_dispenser.FireModes.head.Magazine = 100
nano_dispenser.FireModes.head.CustomMagazine = Ammo.upgrade_canister -> 1
+ nano_dispenser.FireModes.head.Modifiers.Damage0 = 0
nano_dispenser.FireModes.head.Modifiers.Damage1 = 20
+ nano_dispenser.FireModes.head.Modifiers.Damage2 = 0
+ nano_dispenser.FireModes.head.Modifiers.Damage3 = 0
nano_dispenser.FireModes.head.Modifiers.Damage4 = 20
nano_dispenser.Tile = InventoryTile.Tile63
@@ -5156,6 +5161,9 @@ object GlobalDefinitions {
private def init_vehicles() : Unit = {
fury.Name = "fury"
fury.MaxHealth = 650
+ fury.Damageable = true
+ fury.Repairable = true
+ fury.RepairIfDestroyed = false
fury.MaxShields = 130 + 1
fury.Seats += 0 -> new SeatDefinition()
fury.Seats(0).Bailable = true
@@ -5171,6 +5179,9 @@ object GlobalDefinitions {
quadassault.Name = "quadassault" // Basilisk
quadassault.MaxHealth = 650
+ quadassault.Damageable = true
+ quadassault.Repairable = true
+ quadassault.RepairIfDestroyed = false
quadassault.MaxShields = 130 + 1
quadassault.Seats += 0 -> new SeatDefinition()
quadassault.Seats(0).Bailable = true
@@ -5186,6 +5197,9 @@ object GlobalDefinitions {
quadstealth.Name = "quadstealth" // Wraith
quadstealth.MaxHealth = 650
+ quadstealth.Damageable = true
+ quadstealth.Repairable = true
+ quadstealth.RepairIfDestroyed = false
quadstealth.MaxShields = 130 + 1
quadstealth.CanCloak = true
quadstealth.Seats += 0 -> new SeatDefinition()
@@ -5201,6 +5215,9 @@ object GlobalDefinitions {
two_man_assault_buggy.Name = "two_man_assault_buggy" // Harasser
two_man_assault_buggy.MaxHealth = 1250
+ two_man_assault_buggy.Damageable = true
+ two_man_assault_buggy.Repairable = true
+ two_man_assault_buggy.RepairIfDestroyed = false
two_man_assault_buggy.MaxShields = 250 + 1
two_man_assault_buggy.Seats += 0 -> new SeatDefinition()
two_man_assault_buggy.Seats(0).Bailable = true
@@ -5218,6 +5235,9 @@ object GlobalDefinitions {
skyguard.Name = "skyguard"
skyguard.MaxHealth = 1000
+ skyguard.Damageable = true
+ skyguard.Repairable = true
+ skyguard.RepairIfDestroyed = false
skyguard.MaxShields = 200 + 1
skyguard.Seats += 0 -> new SeatDefinition()
skyguard.Seats(0).Bailable = true
@@ -5236,6 +5256,9 @@ object GlobalDefinitions {
threemanheavybuggy.Name = "threemanheavybuggy" // Marauder
threemanheavybuggy.MaxHealth = 1700
+ threemanheavybuggy.Damageable = true
+ threemanheavybuggy.Repairable = true
+ threemanheavybuggy.RepairIfDestroyed = false
threemanheavybuggy.MaxShields = 340 + 1
threemanheavybuggy.Seats += 0 -> new SeatDefinition()
threemanheavybuggy.Seats(0).Bailable = true
@@ -5259,6 +5282,9 @@ object GlobalDefinitions {
twomanheavybuggy.Name = "twomanheavybuggy" // Enforcer
twomanheavybuggy.MaxHealth = 1800
+ twomanheavybuggy.Damageable = true
+ twomanheavybuggy.Repairable = true
+ twomanheavybuggy.RepairIfDestroyed = false
twomanheavybuggy.MaxShields = 360 + 1
twomanheavybuggy.Seats += 0 -> new SeatDefinition()
twomanheavybuggy.Seats(0).Bailable = true
@@ -5277,6 +5303,9 @@ object GlobalDefinitions {
twomanhoverbuggy.Name = "twomanhoverbuggy" // Thresher
twomanhoverbuggy.MaxHealth = 1600
+ twomanhoverbuggy.Damageable = true
+ twomanhoverbuggy.Repairable = true
+ twomanhoverbuggy.RepairIfDestroyed = false
twomanhoverbuggy.MaxShields = 320 + 1
twomanhoverbuggy.Seats += 0 -> new SeatDefinition()
twomanhoverbuggy.Seats(0).Bailable = true
@@ -5295,6 +5324,9 @@ object GlobalDefinitions {
mediumtransport.Name = "mediumtransport" // Deliverer
mediumtransport.MaxHealth = 2500
+ mediumtransport.Damageable = true
+ mediumtransport.Repairable = true
+ mediumtransport.RepairIfDestroyed = false
mediumtransport.MaxShields = 500 + 1
mediumtransport.Seats += 0 -> new SeatDefinition()
mediumtransport.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5320,6 +5352,9 @@ object GlobalDefinitions {
battlewagon.Name = "battlewagon" // Raider
battlewagon.MaxHealth = 2500
+ battlewagon.Damageable = true
+ battlewagon.Repairable = true
+ battlewagon.RepairIfDestroyed = false
battlewagon.MaxShields = 500 + 1
battlewagon.Seats += 0 -> new SeatDefinition()
battlewagon.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5348,6 +5383,9 @@ object GlobalDefinitions {
thunderer.Name = "thunderer"
thunderer.MaxHealth = 2500
+ thunderer.Damageable = true
+ thunderer.Repairable = true
+ thunderer.RepairIfDestroyed = false
thunderer.MaxShields = 500 + 1
thunderer.Seats += 0 -> new SeatDefinition()
thunderer.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5373,6 +5411,9 @@ object GlobalDefinitions {
aurora.Name = "aurora"
aurora.MaxHealth = 2500
+ aurora.Damageable = true
+ aurora.Repairable = true
+ aurora.RepairIfDestroyed = false
aurora.MaxShields = 500 + 1
aurora.Seats += 0 -> new SeatDefinition()
aurora.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5398,6 +5439,9 @@ object GlobalDefinitions {
apc_tr.Name = "apc_tr" // Juggernaut
apc_tr.MaxHealth = 6000
+ apc_tr.Damageable = true
+ apc_tr.Repairable = true
+ apc_tr.RepairIfDestroyed = false
apc_tr.MaxShields = 1200 + 1
apc_tr.Seats += 0 -> new SeatDefinition()
apc_tr.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5445,6 +5489,9 @@ object GlobalDefinitions {
apc_nc.Name = "apc_nc" // Vindicator
apc_nc.MaxHealth = 6000
+ apc_nc.Damageable = true
+ apc_nc.Repairable = true
+ apc_nc.RepairIfDestroyed = false
apc_nc.MaxShields = 1200 + 1
apc_nc.Seats += 0 -> new SeatDefinition()
apc_nc.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5492,6 +5539,9 @@ object GlobalDefinitions {
apc_vs.Name = "apc_vs" // Leviathan
apc_vs.MaxHealth = 6000
+ apc_vs.Damageable = true
+ apc_vs.Repairable = true
+ apc_vs.RepairIfDestroyed = false
apc_vs.MaxShields = 1200 + 1
apc_vs.Seats += 0 -> new SeatDefinition()
apc_vs.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5539,6 +5589,9 @@ object GlobalDefinitions {
lightning.Name = "lightning"
lightning.MaxHealth = 2000
+ lightning.Damageable = true
+ lightning.Repairable = true
+ lightning.RepairIfDestroyed = false
lightning.MaxShields = 400 + 1
lightning.Seats += 0 -> new SeatDefinition()
lightning.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5555,6 +5608,9 @@ object GlobalDefinitions {
prowler.Name = "prowler"
prowler.MaxHealth = 4800
+ prowler.Damageable = true
+ prowler.Repairable = true
+ prowler.RepairIfDestroyed = false
prowler.MaxShields = 960 + 1
prowler.Seats += 0 -> new SeatDefinition()
prowler.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5576,6 +5632,9 @@ object GlobalDefinitions {
vanguard.Name = "vanguard"
vanguard.MaxHealth = 5400
+ vanguard.Damageable = true
+ vanguard.Repairable = true
+ vanguard.RepairIfDestroyed = false
vanguard.MaxShields = 1080 + 1
vanguard.Seats += 0 -> new SeatDefinition()
vanguard.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5593,6 +5652,9 @@ object GlobalDefinitions {
magrider.Name = "magrider"
magrider.MaxHealth = 4200
+ magrider.Damageable = true
+ magrider.Repairable = true
+ magrider.RepairIfDestroyed = false
magrider.MaxShields = 840 + 1
magrider.Seats += 0 -> new SeatDefinition()
magrider.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5613,6 +5675,9 @@ object GlobalDefinitions {
val utilityConverter = new UtilityVehicleConverter
ant.Name = "ant"
ant.MaxHealth = 2000
+ ant.Damageable = true
+ ant.Repairable = true
+ ant.RepairIfDestroyed = false
ant.MaxShields = 400 + 1
ant.Seats += 0 -> new SeatDefinition()
ant.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5630,6 +5695,9 @@ object GlobalDefinitions {
ams.Name = "ams"
ams.MaxHealth = 3000
+ ams.Damageable = true
+ ams.Repairable = true
+ ams.RepairIfDestroyed = false
ams.MaxShields = 600 + 1
ams.Seats += 0 -> new SeatDefinition()
ams.Seats(0).ArmorRestriction = SeatArmorRestriction.NoReinforcedOrMax
@@ -5652,6 +5720,9 @@ object GlobalDefinitions {
val variantConverter = new VariantVehicleConverter
router.Name = "router"
router.MaxHealth = 4000
+ router.Damageable = true
+ router.Repairable = true
+ router.RepairIfDestroyed = false
router.MaxShields = 800 + 1
router.Seats += 0 -> new SeatDefinition()
router.MountPoints += 1 -> 0
@@ -5671,6 +5742,9 @@ object GlobalDefinitions {
switchblade.Name = "switchblade"
switchblade.MaxHealth = 1750
+ switchblade.Damageable = true
+ switchblade.Repairable = true
+ switchblade.RepairIfDestroyed = false
switchblade.MaxShields = 350 + 1
switchblade.Seats += 0 -> new SeatDefinition()
switchblade.Seats(0).ControlledWeapon = 1
@@ -5691,6 +5765,9 @@ object GlobalDefinitions {
flail.Name = "flail"
flail.MaxHealth = 2400
+ flail.Damageable = true
+ flail.Repairable = true
+ flail.RepairIfDestroyed = false
flail.MaxShields = 480 + 1
flail.Seats += 0 -> new SeatDefinition()
flail.Seats(0).ControlledWeapon = 1
@@ -5709,6 +5786,9 @@ object GlobalDefinitions {
mosquito.Name = "mosquito"
mosquito.MaxHealth = 665
+ mosquito.Damageable = true
+ mosquito.Repairable = true
+ mosquito.RepairIfDestroyed = false
mosquito.MaxShields = 133 + 1
mosquito.CanFly = true
mosquito.Seats += 0 -> new SeatDefinition()
@@ -5726,6 +5806,9 @@ object GlobalDefinitions {
lightgunship.Name = "lightgunship" // Reaver
lightgunship.MaxHealth = 1000
+ lightgunship.Damageable = true
+ lightgunship.Repairable = true
+ lightgunship.RepairIfDestroyed = false
lightgunship.MaxShields = 200 + 1
lightgunship.CanFly = true
lightgunship.Seats += 0 -> new SeatDefinition()
@@ -5744,6 +5827,9 @@ object GlobalDefinitions {
wasp.Name = "wasp"
wasp.MaxHealth = 515
+ wasp.Damageable = true
+ wasp.Repairable = true
+ wasp.RepairIfDestroyed = false
wasp.MaxShields = 103 + 1
wasp.CanFly = true
wasp.Seats += 0 -> new SeatDefinition()
@@ -5761,6 +5847,9 @@ object GlobalDefinitions {
liberator.Name = "liberator"
liberator.MaxHealth = 2500
+ liberator.Damageable = true
+ liberator.Repairable = true
+ liberator.RepairIfDestroyed = false
liberator.MaxShields = 500 + 1
liberator.CanFly = true
liberator.Seats += 0 -> new SeatDefinition()
@@ -5786,6 +5875,9 @@ object GlobalDefinitions {
vulture.Name = "vulture"
vulture.MaxHealth = 2500
+ vulture.Damageable = true
+ vulture.Repairable = true
+ vulture.RepairIfDestroyed = false
vulture.MaxShields = 500 + 1
vulture.CanFly = true
vulture.Seats += 0 -> new SeatDefinition()
@@ -5811,6 +5903,10 @@ object GlobalDefinitions {
dropship.Name = "dropship" // Galaxy
dropship.MaxHealth = 5000
+ dropship.Damageable = true
+ dropship.Repairable = true
+ dropship.RepairDistance = 20
+ dropship.RepairIfDestroyed = false
dropship.MaxShields = 1000 + 1
dropship.CanFly = true
dropship.Seats += 0 -> new SeatDefinition()
@@ -5868,6 +5964,10 @@ object GlobalDefinitions {
galaxy_gunship.Name = "galaxy_gunship"
galaxy_gunship.MaxHealth = 6000
+ galaxy_gunship.Damageable = true
+ galaxy_gunship.Repairable = true
+ galaxy_gunship.RepairDistance = 20
+ galaxy_gunship.RepairIfDestroyed = false
galaxy_gunship.MaxShields = 1200 + 1
galaxy_gunship.CanFly = true
galaxy_gunship.Seats += 0 -> new SeatDefinition()
@@ -5902,6 +6002,10 @@ object GlobalDefinitions {
lodestar.Name = "lodestar"
lodestar.MaxHealth = 5000
+ lodestar.Damageable = true
+ lodestar.Repairable = true
+ lodestar.RepairDistance = 20
+ lodestar.RepairIfDestroyed = false
lodestar.MaxShields = 1000 + 1
lodestar.CanFly = true
lodestar.Seats += 0 -> new SeatDefinition()
@@ -5926,6 +6030,9 @@ object GlobalDefinitions {
phantasm.Name = "phantasm"
phantasm.MaxHealth = 2500
+ phantasm.Damageable = true
+ phantasm.Repairable = true
+ phantasm.RepairIfDestroyed = false
phantasm.MaxShields = 500 + 1
phantasm.CanCloak = true
phantasm.CanFly = true
@@ -5958,23 +6065,35 @@ object GlobalDefinitions {
boomer.Name = "boomer"
boomer.Descriptor = "Boomers"
boomer.MaxHealth = 100
+ boomer.Damageable = true
+ boomer.DamageableByFriendlyFire = false
+ boomer.Repairable = false
boomer.DeployCategory = DeployableCategory.Boomers
boomer.DeployTime = Duration.create(1000, "ms")
he_mine.Name = "he_mine"
he_mine.Descriptor = "Mines"
he_mine.MaxHealth = 100
+ he_mine.Damageable = true
+ he_mine.DamageableByFriendlyFire = false
+ he_mine.Repairable = false
he_mine.DeployTime = Duration.create(1000, "ms")
jammer_mine.Name = "jammer_mine"
jammer_mine.Descriptor = "JammerMines"
jammer_mine.MaxHealth = 100
+ jammer_mine.Damageable = true
+ jammer_mine.DamageableByFriendlyFire = false
+ jammer_mine.Repairable = false
jammer_mine.DeployTime = Duration.create(1000, "ms")
jammer_mine.DetonateOnJamming = false
spitfire_turret.Name = "spitfire_turret"
spitfire_turret.Descriptor = "Spitfires"
spitfire_turret.MaxHealth = 100
+ spitfire_turret.Damageable = true
+ spitfire_turret.Repairable = true
+ spitfire_turret.RepairIfDestroyed = false
spitfire_turret.Weapons += 1 -> new mutable.HashMap()
spitfire_turret.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
spitfire_turret.ReserveAmmunition = false
@@ -5985,6 +6104,9 @@ object GlobalDefinitions {
spitfire_cloaked.Name = "spitfire_cloaked"
spitfire_cloaked.Descriptor = "CloakingSpitfires"
spitfire_cloaked.MaxHealth = 100
+ spitfire_cloaked.Damageable = true
+ spitfire_cloaked.Repairable = true
+ spitfire_cloaked.RepairIfDestroyed = false
spitfire_cloaked.Weapons += 1 -> new mutable.HashMap()
spitfire_cloaked.Weapons(1) += TurretUpgrade.None -> spitfire_weapon
spitfire_cloaked.ReserveAmmunition = false
@@ -5995,6 +6117,9 @@ object GlobalDefinitions {
spitfire_aa.Name = "spitfire_aa"
spitfire_aa.Descriptor = "FlakSpitfires"
spitfire_aa.MaxHealth = 100
+ spitfire_aa.Damageable = true
+ spitfire_aa.Repairable = true
+ spitfire_aa.RepairIfDestroyed = false
spitfire_aa.Weapons += 1 -> new mutable.HashMap()
spitfire_aa.Weapons(1) += TurretUpgrade.None -> spitfire_aa_weapon
spitfire_aa.ReserveAmmunition = false
@@ -6005,17 +6130,25 @@ object GlobalDefinitions {
motionalarmsensor.Name = "motionalarmsensor"
motionalarmsensor.Descriptor = "MotionSensors"
motionalarmsensor.MaxHealth = 100
+ motionalarmsensor.Damageable = true
+ motionalarmsensor.Repairable = true
+ motionalarmsensor.RepairIfDestroyed = false
motionalarmsensor.DeployTime = Duration.create(1000, "ms")
sensor_shield.Name = "sensor_shield"
sensor_shield.Descriptor = "SensorShields"
sensor_shield.MaxHealth = 100
+ sensor_shield.Damageable = true
+ sensor_shield.Repairable = true
+ sensor_shield.RepairIfDestroyed = false
sensor_shield.DeployTime = Duration.create(5000, "ms")
tank_traps.Name = "tank_traps"
tank_traps.Descriptor = "TankTraps"
tank_traps.MaxHealth = 5000
- tank_traps.Packet = new TRAPConverter
+ tank_traps.Damageable = true
+ tank_traps.Repairable = true
+ tank_traps.RepairIfDestroyed = false
tank_traps.DeployCategory = DeployableCategory.TankTraps
tank_traps.DeployTime = Duration.create(6000, "ms")
tank_traps.Model = StandardResolutions.SimpleDeployables
@@ -6024,6 +6157,9 @@ object GlobalDefinitions {
portable_manned_turret.Name = "portable_manned_turret"
portable_manned_turret.Descriptor = "FieldTurrets"
portable_manned_turret.MaxHealth = 1000
+ portable_manned_turret.Damageable = true
+ portable_manned_turret.Repairable = true
+ portable_manned_turret.RepairIfDestroyed = false
portable_manned_turret.MountPoints += 1 -> 0
portable_manned_turret.MountPoints += 2 -> 0
portable_manned_turret.Weapons += 1 -> new mutable.HashMap()
@@ -6038,6 +6174,9 @@ object GlobalDefinitions {
portable_manned_turret_nc.Name = "portable_manned_turret_nc"
portable_manned_turret_nc.Descriptor = "FieldTurrets"
portable_manned_turret_nc.MaxHealth = 1000
+ portable_manned_turret_nc.Damageable = true
+ portable_manned_turret_nc.Repairable = true
+ portable_manned_turret_nc.RepairIfDestroyed = false
portable_manned_turret_nc.MountPoints += 1 -> 0
portable_manned_turret_nc.MountPoints += 2 -> 0
portable_manned_turret_nc.Weapons += 1 -> new mutable.HashMap()
@@ -6052,6 +6191,9 @@ object GlobalDefinitions {
portable_manned_turret_tr.Name = "portable_manned_turret_tr"
portable_manned_turret_tr.Descriptor = "FieldTurrets"
portable_manned_turret_tr.MaxHealth = 1000
+ portable_manned_turret_tr.Damageable = true
+ portable_manned_turret_tr.Repairable = true
+ portable_manned_turret_tr.RepairIfDestroyed = false
portable_manned_turret_tr.MountPoints += 1 -> 0
portable_manned_turret_tr.MountPoints += 2 -> 0
portable_manned_turret_tr.Weapons += 1 -> new mutable.HashMap()
@@ -6066,6 +6208,9 @@ object GlobalDefinitions {
portable_manned_turret_vs.Name = "portable_manned_turret_vs"
portable_manned_turret_vs.Descriptor = "FieldTurrets"
portable_manned_turret_vs.MaxHealth = 1000
+ portable_manned_turret_vs.Damageable = true
+ portable_manned_turret_vs.Repairable = true
+ portable_manned_turret_vs.RepairIfDestroyed = false
portable_manned_turret_vs.MountPoints += 1 -> 0
portable_manned_turret_vs.MountPoints += 2 -> 0
portable_manned_turret_vs.Weapons += 1 -> new mutable.HashMap()
@@ -6080,18 +6225,27 @@ object GlobalDefinitions {
deployable_shield_generator.Name = "deployable_shield_generator"
deployable_shield_generator.Descriptor = "ShieldGenerators"
deployable_shield_generator.MaxHealth = 1700
+ deployable_shield_generator.Damageable = true
+ deployable_shield_generator.Repairable = true
+ deployable_shield_generator.RepairIfDestroyed = false
deployable_shield_generator.DeployTime = Duration.create(6000, "ms")
deployable_shield_generator.Model = StandardResolutions.ComplexDeployables
router_telepad_deployable.Name = "router_telepad_deployable"
router_telepad_deployable.MaxHealth = 100
+ router_telepad_deployable.Damageable = true
+ router_telepad_deployable.Repairable = false
router_telepad_deployable.DeployTime = Duration.create(1, "ms")
+ router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
router_telepad_deployable.Packet = new TelepadDeployableConverter
router_telepad_deployable.Model = StandardResolutions.SimpleDeployables
internal_router_telepad_deployable.Name = "router_telepad_deployable"
internal_router_telepad_deployable.MaxHealth = 1
+ internal_router_telepad_deployable.Damageable = false
+ internal_router_telepad_deployable.Repairable = false
internal_router_telepad_deployable.DeployTime = Duration.create(1, "ms")
+ internal_router_telepad_deployable.DeployCategory = DeployableCategory.Telepads
internal_router_telepad_deployable.Packet = new InternalTelepadDeployableConverter
}
@@ -6102,14 +6256,24 @@ object GlobalDefinitions {
ams_respawn_tube.Name = "ams_respawn_tube"
ams_respawn_tube.Delay = 5
ams_respawn_tube.SpecificPointFunc = SpawnPoint.AMS
+ ams_respawn_tube.Damageable = false
+ ams_respawn_tube.Repairable = false
matrix_terminala.Name = "matrix_terminala"
+ matrix_terminala.Damageable = false
+ matrix_terminala.Repairable = false
matrix_terminalb.Name = "matrix_terminalb"
+ matrix_terminalb.Damageable = false
+ matrix_terminalb.Repairable = false
matrix_terminalc.Name = "matrix_terminalc"
+ matrix_terminalc.Damageable = false
+ matrix_terminalc.Repairable = false
spawn_terminal.Name = "spawn_terminal"
+ spawn_terminal.Damageable = false
+ spawn_terminal.Repairable = false
order_terminal.Name = "order_terminal"
order_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
@@ -6118,6 +6282,11 @@ object GlobalDefinitions {
order_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
order_terminal.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
order_terminal.SellEquipmentByDefault = true
+ order_terminal.MaxHealth = 500
+ order_terminal.Damageable = true
+ order_terminal.Repairable = true
+ order_terminal.RepairIfDestroyed = true
+ order_terminal.Subtract.Damage1 = 8
order_terminala.Name = "order_terminala"
order_terminala.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
@@ -6127,6 +6296,8 @@ object GlobalDefinitions {
order_terminala.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
order_terminala.Tab(4).asInstanceOf[OrderTerminalDefinition.InfantryLoadoutPage].Exclude = ExoSuitType.MAX
order_terminala.SellEquipmentByDefault = true
+ order_terminala.Damageable = false
+ order_terminala.Repairable = false
order_terminalb.Name = "order_terminalb"
order_terminalb.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
@@ -6136,6 +6307,8 @@ object GlobalDefinitions {
order_terminalb.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
order_terminalb.Tab(4).asInstanceOf[OrderTerminalDefinition.InfantryLoadoutPage].Exclude = ExoSuitType.MAX
order_terminalb.SellEquipmentByDefault = true
+ order_terminalb.Damageable = false
+ order_terminalb.Repairable = false
vanu_equipment_term.Name = "vanu_equipment_term"
vanu_equipment_term.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
@@ -6144,55 +6317,124 @@ object GlobalDefinitions {
vanu_equipment_term.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
vanu_equipment_term.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
vanu_equipment_term.SellEquipmentByDefault = true
+ vanu_equipment_term.Damageable = false
+ vanu_equipment_term.Repairable = false
cert_terminal.Name = "cert_terminal"
cert_terminal.Tab += 0 -> OrderTerminalDefinition.CertificationPage(CertTerminalDefinition.certs)
+ cert_terminal.MaxHealth = 500
+ cert_terminal.Damageable = true
+ cert_terminal.Repairable = true
+ cert_terminal.RepairIfDestroyed = true
+ cert_terminal.Subtract.Damage1 = 8
+
+ implant_terminal_mech.Name = "implant_terminal_mech"
+ implant_terminal_mech.MaxHealth = 1500 //TODO 1000; right now, 1000 (mech) + 500 (interface)
+ implant_terminal_mech.Damageable = true
+ implant_terminal_mech.Repairable = true
+ implant_terminal_mech.RepairIfDestroyed = true
implant_terminal_interface.Name = "implant_terminal_interface"
implant_terminal_interface.Tab += 0 -> OrderTerminalDefinition.ImplantPage(ImplantTerminalDefinition.implants)
+ implant_terminal_interface.MaxHealth = 500
+ implant_terminal_interface.Damageable = false //TODO true
+ implant_terminal_interface.Repairable = true
+ implant_terminal_interface.RepairIfDestroyed = true
ground_vehicle_terminal.Name = "ground_vehicle_terminal"
ground_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
ground_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
+ ground_vehicle_terminal.MaxHealth = 500
+ ground_vehicle_terminal.Damageable = true
+ ground_vehicle_terminal.Repairable = true
+ ground_vehicle_terminal.RepairIfDestroyed = true
+ ground_vehicle_terminal.Subtract.Damage1 = 8
air_vehicle_terminal.Name = "air_vehicle_terminal"
air_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles, VehicleTerminalDefinition.trunk)
air_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
+ air_vehicle_terminal.MaxHealth = 500
+ air_vehicle_terminal.Damageable = true
+ air_vehicle_terminal.Repairable = true
+ air_vehicle_terminal.RepairIfDestroyed = true
+ air_vehicle_terminal.Subtract.Damage1 = 8
dropship_vehicle_terminal.Name = "dropship_vehicle_terminal"
dropship_vehicle_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles ++ VehicleTerminalDefinition.flight2Vehicles, VehicleTerminalDefinition.trunk)
dropship_vehicle_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
+ dropship_vehicle_terminal.MaxHealth = 500
+ dropship_vehicle_terminal.Damageable = true
+ dropship_vehicle_terminal.Repairable = true
+ dropship_vehicle_terminal.RepairIfDestroyed = true
+ dropship_vehicle_terminal.Subtract.Damage1 = 8
vehicle_terminal_combined.Name = "vehicle_terminal_combined"
vehicle_terminal_combined.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles ++ VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
vehicle_terminal_combined.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
+ vehicle_terminal_combined.MaxHealth = 500
+ vehicle_terminal_combined.Damageable = true
+ vehicle_terminal_combined.Repairable = true
+ vehicle_terminal_combined.RepairIfDestroyed = true
+ vehicle_terminal_combined.Subtract.Damage1 = 8
vanu_air_vehicle_term.Name = "vanu_air_vehicle_term"
vanu_air_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles, VehicleTerminalDefinition.trunk)
vanu_air_vehicle_term.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
+ vanu_air_vehicle_term.MaxHealth = 500
+ vanu_air_vehicle_term.Damageable = true
+ vanu_air_vehicle_term.Repairable = true
+ vanu_air_vehicle_term.RepairIfDestroyed = true
+ vanu_air_vehicle_term.Subtract.Damage1 = 8
vanu_vehicle_term.Name = "vanu_vehicle_term"
vanu_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
vanu_vehicle_term.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
+ vanu_vehicle_term.MaxHealth = 500
+ vanu_vehicle_term.Damageable = true
+ vanu_vehicle_term.Repairable = true
+ vanu_vehicle_term.RepairIfDestroyed = true
+ vanu_vehicle_term.Subtract.Damage1 = 8
bfr_terminal.Name = "bfr_terminal"
bfr_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.bfrVehicles, VehicleTerminalDefinition.trunk)
bfr_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
+ bfr_terminal.MaxHealth = 500
+ bfr_terminal.Damageable = true
+ bfr_terminal.Repairable = true
+ bfr_terminal.RepairIfDestroyed = true
+ bfr_terminal.Subtract.Damage1 = 8
respawn_tube.Name = "respawn_tube"
respawn_tube.Delay = 10
respawn_tube.SpecificPointFunc = SpawnPoint.Tube
+ respawn_tube.MaxHealth = 1000
+ respawn_tube.Damageable = true
+ respawn_tube.DamageableByFriendlyFire = false
+ respawn_tube.Repairable = true
+ respawn_tube.RepairIfDestroyed = true
+ respawn_tube.Subtract.Damage1 = 8
respawn_tube_sanctuary.Name = "respawn_tube"
respawn_tube_sanctuary.Delay = 10
respawn_tube_sanctuary.SpecificPointFunc = SpawnPoint.Default
+ respawn_tube_sanctuary.Damageable = false //true?
+ respawn_tube_sanctuary.DamageableByFriendlyFire = false
+ respawn_tube_sanctuary.Repairable = true
respawn_tube_tower.Name = "respawn_tube_tower"
respawn_tube_tower.Delay = 10
respawn_tube_tower.SpecificPointFunc = SpawnPoint.Tube
+ respawn_tube_tower.MaxHealth = 1000
+ respawn_tube_tower.Damageable = true
+ respawn_tube_tower.DamageableByFriendlyFire = false
+ respawn_tube_tower.Repairable = true
+ respawn_tube_tower.RepairIfDestroyed = true
+ respawn_tube_tower.Subtract.Damage1 = 8
teleportpad_terminal.Name = "teleportpad_terminal"
teleportpad_terminal.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.routerTerminal)
+ teleportpad_terminal.Damageable = false
+ teleportpad_terminal.Repairable = false
medical_terminal.Name = "medical_terminal"
medical_terminal.Interval = 500
@@ -6200,6 +6442,10 @@ object GlobalDefinitions {
medical_terminal.ArmorAmount = 10
medical_terminal.UseRadius = 0.75f
medical_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical
+ medical_terminal.MaxHealth = 500
+ medical_terminal.Damageable = true
+ medical_terminal.Repairable = true
+ medical_terminal.RepairIfDestroyed = true
adv_med_terminal.Name = "adv_med_terminal"
adv_med_terminal.Interval = 500
@@ -6207,18 +6453,26 @@ object GlobalDefinitions {
adv_med_terminal.ArmorAmount = 15
adv_med_terminal.UseRadius = 0.75f
adv_med_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical
+ adv_med_terminal.MaxHealth = 750
+ adv_med_terminal.Damageable = true
+ adv_med_terminal.Repairable = true
+ adv_med_terminal.RepairIfDestroyed = true
crystals_health_a.Name = "crystals_health_a"
crystals_health_a.Interval = 500
crystals_health_a.HealAmount = 4
crystals_health_a.UseRadius = 5
crystals_health_a.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.HealthCrystal
+ crystals_health_a.Damageable = false
+ crystals_health_a.Repairable = false
crystals_health_b.Name = "crystals_health_b"
crystals_health_b.Interval = 500
crystals_health_b.HealAmount = 4
crystals_health_b.UseRadius = 5
crystals_health_b.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.HealthCrystal
+ crystals_health_b.Damageable = false
+ crystals_health_b.Repairable = false
portable_med_terminal.Name = "portable_med_terminal"
portable_med_terminal.Interval = 500
@@ -6226,53 +6480,116 @@ object GlobalDefinitions {
portable_med_terminal.ArmorAmount = 10
portable_med_terminal.UseRadius = 3
portable_med_terminal.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.Medical
+ portable_med_terminal.MaxHealth = 500
+ portable_med_terminal.Damageable = false //TODO actually true
+ portable_med_terminal.Repairable = false
pad_landing_frame.Name = "pad_landing_frame"
pad_landing_frame.Interval = 1000
pad_landing_frame.HealAmount = 60
pad_landing_frame.UseRadius = 20
pad_landing_frame.TargetValidation += EffectTarget.Category.Aircraft -> EffectTarget.Validation.PadLanding
+ pad_landing_frame.Damageable = false
+ pad_landing_frame.Repairable = false
pad_landing_tower_frame.Name = "pad_landing_tower_frame"
pad_landing_tower_frame.Interval = 1000
pad_landing_tower_frame.HealAmount = 60
pad_landing_tower_frame.UseRadius = 20
pad_landing_tower_frame.TargetValidation += EffectTarget.Category.Aircraft -> EffectTarget.Validation.PadLanding
+ pad_landing_tower_frame.Damageable = false
+ pad_landing_tower_frame.Repairable = false
repair_silo.Name = "repair_silo"
repair_silo.Interval = 1000
repair_silo.HealAmount = 60
repair_silo.UseRadius = 20
repair_silo.TargetValidation += EffectTarget.Category.Vehicle -> EffectTarget.Validation.RepairSilo
+ repair_silo.Damageable = false
+ repair_silo.Repairable = false
+
+ mb_pad_creation.Name = "mb_pad_creation"
+ mb_pad_creation.Damageable = false
+ mb_pad_creation.Repairable = false
+
+ dropship_pad_doors.Name = "dropship_pad_doors"
+ dropship_pad_doors.Damageable = false
+ dropship_pad_doors.Repairable = false
+
+ vanu_vehicle_creation_pad.Name = "vanu_vehicle_creation_pad"
+ vanu_vehicle_creation_pad.Damageable = false
+ vanu_vehicle_creation_pad.Repairable = false
+
+ mb_locker.Name = "mb_locker"
+ mb_locker.Damageable = false
+ mb_locker.Repairable = false
+
+ lock_external.Name = "lock_external"
+ lock_external.Damageable = false
+ lock_external.Repairable = false
+
+ door.Name = "door"
+ door.Damageable = false
+ door.Repairable = false
+
+ resource_silo.Name = "resource_silo"
+ resource_silo.Damageable = false
+ resource_silo.Repairable = false
+
+ capture_terminal.Name = "capture_terminal"
+ capture_terminal.Damageable = false
+ capture_terminal.Repairable = false
+
+ secondary_capture.Name = "secondary_capture"
+ secondary_capture.Damageable = false
+ secondary_capture.Repairable = false
+
+ vanu_control_console.Name = "vanu_control_console"
+ vanu_control_console.Damageable = false
+ vanu_control_console.Repairable = false
lodestar_repair_terminal.Name = "lodestar_repair_terminal"
lodestar_repair_terminal.Interval = 1000
lodestar_repair_terminal.HealAmount = 60
lodestar_repair_terminal.UseRadius = 20
lodestar_repair_terminal.TargetValidation += EffectTarget.Category.Vehicle -> EffectTarget.Validation.RepairSilo
+ lodestar_repair_terminal.Damageable = false
+ lodestar_repair_terminal.Repairable = false
multivehicle_rearm_terminal.Name = "multivehicle_rearm_terminal"
multivehicle_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
multivehicle_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
multivehicle_rearm_terminal.SellEquipmentByDefault = true //TODO ?
+ multivehicle_rearm_terminal.Damageable = false
+ multivehicle_rearm_terminal.Repairable = false
bfr_rearm_terminal.Name = "bfr_rearm_terminal"
bfr_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(Map.empty[String, ()=>Equipment]) //TODO add stock to page
bfr_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
bfr_rearm_terminal.SellEquipmentByDefault = true //TODO ?
+ bfr_rearm_terminal.Damageable = false
+ bfr_rearm_terminal.Repairable = false
air_rearm_terminal.Name = "air_rearm_terminal"
air_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
air_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
air_rearm_terminal.SellEquipmentByDefault = true //TODO ?
+ air_rearm_terminal.Damageable = false
+ air_rearm_terminal.Repairable = false
ground_rearm_terminal.Name = "ground_rearm_terminal"
ground_rearm_terminal.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
ground_rearm_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
ground_rearm_terminal.SellEquipmentByDefault = true //TODO ?
+ ground_rearm_terminal.Damageable = false
+ ground_rearm_terminal.Repairable = false
manned_turret.Name = "manned_turret"
manned_turret.MaxHealth = 3600
+ manned_turret.Damageable = true
+ manned_turret.DamageDisablesAt = 0
+ manned_turret.Repairable = true
+ manned_turret.RepairIfDestroyed = true
manned_turret.Weapons += 1 -> new mutable.HashMap()
manned_turret.Weapons(1) += TurretUpgrade.None -> phalanx_sgl_hevgatcan
manned_turret.Weapons(1) += TurretUpgrade.AVCombo -> phalanx_avcombo
@@ -6283,6 +6600,10 @@ object GlobalDefinitions {
vanu_sentry_turret.Name = "vanu_sentry_turret"
vanu_sentry_turret.MaxHealth = 1500
+ vanu_sentry_turret.Damageable = true
+ vanu_sentry_turret.DamageDisablesAt = 0
+ vanu_sentry_turret.Repairable = true
+ vanu_sentry_turret.RepairIfDestroyed = true
vanu_sentry_turret.Weapons += 1 -> new mutable.HashMap()
vanu_sentry_turret.Weapons(1) += TurretUpgrade.None -> vanu_sentry_turret_weapon
vanu_sentry_turret.MountPoints += 1 -> 0
@@ -6290,8 +6611,41 @@ object GlobalDefinitions {
vanu_sentry_turret.FactionLocked = false
vanu_sentry_turret.ReserveAmmunition = false
+ painbox.Name = "painbox"
+ painbox.Damageable = false
+ painbox.Repairable = false
+
+ painbox_continuous.Name = "painbox_continuous"
+ painbox_continuous.Damageable = false
+ painbox_continuous.Repairable = false
+
+ painbox_door_radius.Name = "painbox_door_radius"
+ painbox_door_radius.Damageable = false
+ painbox_door_radius.Repairable = false
+
+ painbox_door_radius_continuous.Name = "painbox_door_radius_continuous"
+ painbox_door_radius_continuous.Damageable = false
+ painbox_door_radius_continuous.Repairable = false
+
+ painbox_radius.Name = "painbox_radius"
+ painbox_radius.Damageable = false
+ painbox_radius.Repairable = false
+
+ painbox_radius_continuous.Name = "painbox_radius_continuous"
+ painbox_radius_continuous.Damageable = false
+ painbox_radius_continuous.Repairable = false
+
gen_control.Name = "gen_control"
+ gen_control.Damageable = false
+ gen_control.Repairable = false
generator.Name = "generator"
+ generator.MaxHealth = 4000
+ generator.Damageable = true
+ generator.DamageableByFriendlyFire = false
+ generator.Repairable = true
+ generator.RepairDistance = 13.5f
+ generator.RepairIfDestroyed = true
+ generator.Subtract.Damage1 = 9
}
}
diff --git a/common/src/main/scala/net/psforever/objects/PlanetSideGameObject.scala b/common/src/main/scala/net/psforever/objects/PlanetSideGameObject.scala
index 5b558b33e..b4943ef85 100644
--- a/common/src/main/scala/net/psforever/objects/PlanetSideGameObject.scala
+++ b/common/src/main/scala/net/psforever/objects/PlanetSideGameObject.scala
@@ -10,6 +10,7 @@ import net.psforever.types.Vector3
*/
abstract class PlanetSideGameObject extends IdentifiableEntity with WorldEntity {
private var entity : WorldEntity = new SimpleWorldEntity()
+ private var destroyed : Boolean = false
def Entity : WorldEntity = entity
@@ -35,6 +36,13 @@ abstract class PlanetSideGameObject extends IdentifiableEntity with WorldEntity
Entity.Velocity = vec
}
+ def Destroyed : Boolean = destroyed
+
+ def Destroyed_=(state : Boolean) : Boolean = {
+ destroyed = state
+ Destroyed
+ }
+
def Definition : ObjectDefinition
}
diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala
index d00227f7a..d4527d619 100644
--- a/common/src/main/scala/net/psforever/objects/Player.scala
+++ b/common/src/main/scala/net/psforever/objects/Player.scala
@@ -23,9 +23,9 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
with Container
with JammableUnit
with ZoneAware {
- private var alive : Boolean = false
+ Health = 0 //player health is artificially managed as a part of their lifecycle; start entity as dead
+ Destroyed = true //see isAlive
private var backpack : Boolean = false
- private var health : Int = 0
private var stamina : Int = 0
private var armor : Int = 0
@@ -34,7 +34,6 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
private var capacitorLastUsedMillis : Long = 0
private var capacitorLastChargedMillis : Long = 0
- private var maxHealth : Int = 100 //TODO affected by empire benefits, territory benefits, and bops
private var maxStamina : Int = 100 //does anything affect this?
private var exosuit : ExoSuitDefinition = GlobalDefinitions.Standard
@@ -84,14 +83,14 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
def LFS : Boolean = core.LFS
- def isAlive : Boolean = alive
+ def isAlive : Boolean = !Destroyed
def isBackpack : Boolean = backpack
def Spawn : Boolean = {
if(!isAlive && !isBackpack) {
- alive = true
- Health = MaxHealth
+ Destroyed = false
+ Health = Definition.DefaultHealth
Stamina = MaxStamina
Armor = MaxArmor
Capacitor = 0
@@ -101,14 +100,15 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
}
def Die : Boolean = {
- alive = false
+ Destroyed = true
Health = 0
Stamina = 0
false
}
def Revive : Boolean = {
- alive = true
+ Destroyed = false
+ Health = Definition.DefaultHealth
true
}
@@ -122,20 +122,6 @@ class Player(private val core : Avatar) extends PlanetSideServerObject
}
}
- def Health : Int = health
-
- def Health_=(assignHealth : Int) : Int = {
- health = math.min(math.max(0, assignHealth), MaxHealth)
- Health
- }
-
- def MaxHealth : Int = maxHealth
-
- def MaxHealth_=(max : Int) : Int = {
- maxHealth = math.min(math.max(0, max), 65535)
- MaxHealth
- }
-
def Stamina : Int = stamina
def Stamina_=(assignEnergy : Int) : Int = {
@@ -662,6 +648,21 @@ object Player {
}
}
+ def GetHackLevel(player : Player): Int = {
+ if(player.Certifications.contains(CertificationType.ExpertHacking) || player.Certifications.contains(CertificationType.ElectronicsExpert)) {
+ 3
+ }
+ else if(player.Certifications.contains(CertificationType.AdvancedHacking)) {
+ 2
+ }
+ else if (player.Certifications.contains(CertificationType.Hacking)) {
+ 1
+ }
+ else {
+ 0
+ }
+ }
+
def toString(obj : Player) : String = {
val guid = if(obj.HasGUID) { s" ${obj.Continent}-${obj.GUID.guid}" } else { "" }
s"${obj.core}$guid ${obj.Health}/${obj.MaxHealth} ${obj.Armor}/${obj.MaxArmor}"
diff --git a/common/src/main/scala/net/psforever/objects/Players.scala b/common/src/main/scala/net/psforever/objects/Players.scala
new file mode 100644
index 000000000..486265c73
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/Players.scala
@@ -0,0 +1,49 @@
+// Copyright (c) 2020 PSForever
+package net.psforever.objects
+
+import net.psforever.packet.game.{InventoryStateMessage, RepairMessage}
+import services.Service
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+
+object Players {
+ private val log = org.log4s.getLogger("Players")
+
+ /**
+ * Evaluate the progress of the user applying a tool to modify some other server object.
+ * This action is using the medical applicator to revive a fallen (dead but not released) ally.
+ * @param target the player being affected by the revive action
+ * @param user the player performing the revive action
+ * @param item the tool being used to revive the target player
+ * @param progress the current progress value
+ * @return `true`, if the next cycle of progress should occur;
+ * `false`, otherwise
+ */
+ def RevivingTickAction(target : Player, user : Player, item : Tool)(progress : Float) : Boolean = {
+ if(!target.isAlive && !target.isBackpack &&
+ user.isAlive && !user.isMoving &&
+ user.Slot(user.DrawnSlot).Equipment.contains(item) && item.Magazine > 0) {
+ val magazine = item.Discharge
+ val events = target.Zone.AvatarEvents
+ val uname = user.Name
+ events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine)))
+ events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, RepairMessage(target.GUID, progress.toInt)))
+ true
+ }
+ else {
+ false
+ }
+ }
+
+ /**
+ * na
+ * @see `AvatarAction.Revive`
+ * @see `AvatarResponse.Revive`
+ * @param target the player being revived
+ * @param medic the name of the player doing the reviving
+ */
+ def FinishRevivingPlayer(target : Player, medic : String)() : Unit = {
+ val name = target.Name
+ log.info(s"$medic had revived $name")
+ target.Zone.AvatarEvents ! AvatarServiceMessage(name, AvatarAction.Revive(target.GUID))
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala
index 22b35ab22..cb5106c31 100644
--- a/common/src/main/scala/net/psforever/objects/SensorDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/SensorDeployable.scala
@@ -1,4 +1,4 @@
-// Copyright (c) 2017 PSForever
+// Copyright (c) 2019 PSForever
package net.psforever.objects
import akka.actor.{Actor, ActorContext, Props}
@@ -8,12 +8,12 @@ import net.psforever.objects.definition.converter.SmallDeployableConverter
import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
import net.psforever.objects.serverobject.hackable.Hackable
-import net.psforever.objects.vital.{StandardResolutions, Vitality}
-import net.psforever.objects.zones.Zone
-import net.psforever.types.PlanetSideGUID
+import net.psforever.objects.serverobject.repair.RepairableEntity
+import net.psforever.objects.vital.StandardResolutions
+import net.psforever.types.{PlanetSideGUID, Vector3}
import services.Service
-import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
@@ -45,19 +45,25 @@ object SensorDeployableDefinition {
}
class SensorDeployableControl(sensor : SensorDeployable) extends Actor
- with JammableBehavior {
-
+ with JammableBehavior
+ with DamageableEntity
+ with RepairableEntity {
def JammableObject = sensor
+ def DamageableObject = sensor
+ def RepairableObject = sensor
- def receive : Receive = jammableBehavior.orElse {
- case Vitality.Damage(damage_func) =>
- val originalHealth = sensor.Health
- if(originalHealth > 0) {
- val cause = damage_func(sensor)
- SensorDeployableControl.HandleDamageResolution(sensor, cause, originalHealth - sensor.Health)
- }
+ def receive : Receive = jammableBehavior
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
+ .orElse {
+ case _ => ;
+ }
- case _ => ;
+ override protected def DamageLog(msg : String) : Unit = { }
+
+ override protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
+ super.DestructionAwareness(target, cause)
+ SensorDeployableControl.DestructionAwareness(sensor, PlanetSideGUID(0))
}
override def StartJammeredSound(target : Any, dur : Int) : Unit = target match {
@@ -69,7 +75,8 @@ class SensorDeployableControl(sensor : SensorDeployable) extends Actor
override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
case obj : PlanetSideServerObject with JammableUnit if !obj.Jammed =>
- sensor.Zone.LocalEvents ! LocalServiceMessage(sensor.Zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, false, 1000))
+ val zone = obj.Zone
+ zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", obj.GUID, false, 1000))
super.StartJammeredStatus(obj, dur)
case _ => ;
}
@@ -77,7 +84,8 @@ class SensorDeployableControl(sensor : SensorDeployable) extends Actor
override def CancelJammeredSound(target : Any) : Unit = {
target match {
case obj : PlanetSideServerObject if jammedSound =>
- obj.Zone.VehicleEvents ! VehicleServiceMessage(obj.Zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0))
+ val zone = obj.Zone
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, obj.GUID, 54, 0))
case _ => ;
}
super.CancelJammeredSound(target)
@@ -94,40 +102,32 @@ class SensorDeployableControl(sensor : SensorDeployable) extends Actor
}
object SensorDeployableControl {
- def HandleDamageResolution(target : SensorDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
- val zone = target.Zone
- val targetGUID = target.GUID
- val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
- case Some(player) => player.GUID
- case _ => PlanetSideGUID(0)
- }
- if(target.Health > 0) {
- //activity on map
- if(damage > 0) {
- zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
- }
- if(cause.projectile.profile.JammerProjectile) {
- target.Actor ! JammableUnit.Jammered(cause)
- }
- }
- else {
- HandleDestructionAwareness(target, playerGUID, cause)
- }
- zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(targetGUID, 0, target.Health))
- }
-
/**
* na
* @param target na
* @param attribution na
- * @param lastShot na
*/
- def HandleDestructionAwareness(target : SensorDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
- target.Actor ! JammableUnit.ClearJammeredSound()
- target.Actor ! JammableUnit.ClearJammeredStatus()
+ def DestructionAwareness(target : Damageable.Target with Deployable, attribution : PlanetSideGUID) : Unit = {
+ Deployables.AnnounceDestroyDeployable(target, Some(1 seconds))
val zone = target.Zone
- Deployables.AnnounceDestroyDeployable(target, None)
- zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", target.GUID, false, 1000))
- zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
+ zone.LocalEvents ! LocalServiceMessage(zone.Id,
+ LocalAction.TriggerEffectInfo(Service.defaultPlayerGUID, "on", target.GUID, false, 1000)
+ )
+ //position the explosion effect near the bulky area of the sensor stalk
+ val ang = target.Orientation
+ val explosionPos = {
+ val pos = target.Position
+ val yRadians = ang.y.toRadians
+ val d = Vector3.Rz(Vector3(0, 0.875f, 0), ang.z) * math.sin(yRadians).toFloat
+ Vector3(
+ pos.x + d.x,
+ pos.y + d.y,
+ pos.z + math.cos(yRadians).toFloat * 0.875f
+ )
+ }
+ zone.LocalEvents ! LocalServiceMessage(zone.Id,
+ LocalAction.TriggerEffectLocation(Service.defaultPlayerGUID, "motion_sensor_destroyed", explosionPos, ang)
+ )
+ //TODO replaced by an alternate model (charred stub)?
}
}
diff --git a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala
index ff32b4193..8e4373310 100644
--- a/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/ShieldGeneratorDeployable.scala
@@ -7,12 +7,13 @@ import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployableCatego
import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
import net.psforever.objects.definition.converter.ShieldGeneratorConverter
import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
+import net.psforever.objects.serverobject.damage.Damageable.Target
+import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.hackable.Hackable
-import net.psforever.objects.vital.Vitality
-import net.psforever.objects.zones.Zone
+import net.psforever.objects.serverobject.repair.RepairableEntity
+import net.psforever.objects.vital.resolution.ResolutionCalculations
import net.psforever.types.PlanetSideGUID
-import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.Service
import services.vehicle.{VehicleAction, VehicleServiceMessage}
@@ -34,32 +35,75 @@ class ShieldGeneratorDefinition extends ComplexDeployableDefinition(240) {
}
class ShieldGeneratorControl(gen : ShieldGeneratorDeployable) extends Actor
- with JammableBehavior {
-
+ with JammableBehavior
+ with DamageableEntity
+ with RepairableEntity {
def JammableObject = gen
+ def DamageableObject = gen
+ def RepairableObject = gen
+ private var handleDamageToShields : Boolean = false
def receive : Receive = jammableBehavior
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
.orElse {
- case Vitality.Damage(damage_func) => //note: damage status is reported as vehicle events, not local events
- if(gen.Health > 0) {
- val originalHealth = gen.Health
- val cause = damage_func(gen)
- val health = gen.Health
- val damageToHealth = originalHealth - health
- ShieldGeneratorControl.HandleDamageResolution(gen, cause, damageToHealth)
- if(damageToHealth > 0) {
- val name = gen.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")
- }
- }
-
case _ => ;
}
+
+ /**
+ * The shield generator has two upgrade paths - blocking projectiles, and providing ammunition like a terminal.
+ * Both upgrade paths are possible using the nano dispenser with an armor canister,
+ * and can only be started when the generator is undamaged.
+ * @see `PlanetSideGameObject.CanRepair`
+ * @see `RepairableEntity.CanPerformRepairs`
+ * @param player the user of the nano dispenser tool
+ * @param item the nano dispenser tool
+ */
+ override def CanBeRepairedByNanoDispenser(player : Player, item : Tool) : Unit = {
+ if(gen.CanRepair) {
+ super.CanBeRepairedByNanoDispenser(player, item)
+ }
+ else if(!gen.Destroyed) {
+ //TODO reinforced shield upgrade not implemented yet
+ //TODO ammunition supply upgrade not implemented yet
+ }
+ }
+
+ override protected def PerformDamage(target : Damageable.Target, applyDamageTo : ResolutionCalculations.Output) : Unit = {
+ val originalHealth = gen.Health
+ val originalShields = gen.Shields
+ val cause = applyDamageTo(target)
+ val health = gen.Health
+ val shields = gen.Shields
+ val damageToHealth = originalHealth - health
+ val damageToShields = originalShields - shields
+ val damage = damageToHealth + damageToShields
+ if(WillAffectTarget(target, damage, cause)) {
+ target.History(cause)
+ DamageLog(target,s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields")
+ handleDamageToShields = damageToShields > 0
+ HandleDamage(target, cause, damageToHealth)
+ }
+ else {
+ gen.Health = originalHealth
+ gen.Shields = originalShields
+ }
+ }
+
+ override protected def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Int) : Unit = {
+ super.DamageAwareness(target, cause, amount)
+ ShieldGeneratorControl.DamageAwareness(gen, cause, handleDamageToShields)
+ handleDamageToShields = false
+ }
+
+ override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
+ super.DestructionAwareness(target, cause)
+ ShieldGeneratorControl.DestructionAwareness(gen, PlanetSideGUID(0))
+ }
+
/*
while the shield generator is technically a supported jammable target, how that works is currently unknown
- electing to use a "status only, no sound" approach by overriding one with an empty function is not entirely arbitrary
- the superclass of "status" calls also sets the jammed object property
+ check the object definition for proper feature activation
*/
override def StartJammeredSound(target : Any, dur : Int) : Unit = { }
@@ -86,40 +130,23 @@ object ShieldGeneratorControl {
/**
* na
* @param target na
+ * @param cause na
+ * @param damageToShields na
*/
- def HandleDamageResolution(target : ShieldGeneratorDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
- val zone = target.Zone
- val targetGUID = target.GUID
- val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
- case Some(player) => player.GUID
- case _ => PlanetSideGUID(0)
+ def DamageAwareness(target : ShieldGeneratorDeployable, cause : ResolvedProjectile, damageToShields : Boolean) : Unit = {
+ //shields
+ if(damageToShields) {
+ val zone = target.Zone
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields))
}
- if(target.Health > 0) {
- //activity on map
- if(damage > 0) {
- zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
- }
- if(cause.projectile.profile.JammerProjectile) {
- target.Actor ! JammableUnit.Jammered(cause)
- }
- }
- else {
- HandleDestructionAwareness(target, playerGUID, cause)
- }
- zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(targetGUID, 0, target.Health))
}
/**
* na
* @param target na
* @param attribution na
- * @param lastShot na
*/
- def HandleDestructionAwareness(target : ShieldGeneratorDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
- target.Actor ! JammableUnit.ClearJammeredSound()
- target.Actor ! JammableUnit.ClearJammeredStatus()
- val zone = target.Zone
+ def DestructionAwareness(target : Damageable.Target with Deployable, attribution : PlanetSideGUID) : Unit = {
Deployables.AnnounceDestroyDeployable(target, None)
- zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
}
}
diff --git a/common/src/main/scala/net/psforever/objects/SpawnPoint.scala b/common/src/main/scala/net/psforever/objects/SpawnPoint.scala
index f50cd135d..dc86b3738 100644
--- a/common/src/main/scala/net/psforever/objects/SpawnPoint.scala
+++ b/common/src/main/scala/net/psforever/objects/SpawnPoint.scala
@@ -45,6 +45,8 @@ trait SpawnPoint {
*/
def Definition : ObjectDefinition with SpawnPointDefinition
+ def Offline : Boolean = psso.Destroyed
+
/**
* Determine a specific position and orientation in which to spawn the target.
* @return a `Tuple` of `Vector3` objects;
diff --git a/common/src/main/scala/net/psforever/objects/TrapDeployable.scala b/common/src/main/scala/net/psforever/objects/TrapDeployable.scala
index e330e9998..6998f15c4 100644
--- a/common/src/main/scala/net/psforever/objects/TrapDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/TrapDeployable.scala
@@ -1,7 +1,51 @@
-// Copyright (c) 2017 PSForever
+// Copyright (c) 2020 PSForever
package net.psforever.objects
-import net.psforever.objects.ce.SimpleDeployable
-import net.psforever.objects.definition.SimpleDeployableDefinition
+import akka.actor.{Actor, ActorContext, Props}
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.ce.{ComplexDeployable, Deployable, DeployedItem}
+import net.psforever.objects.definition.converter.TRAPConverter
+import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDeployableDefinition}
+import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
+import net.psforever.objects.serverobject.repair.RepairableEntity
+import net.psforever.objects.vital.StandardResolutions
-class TrapDeployable(cdef : SimpleDeployableDefinition) extends SimpleDeployable(cdef)
+class TrapDeployable(cdef : TrapDeployableDefinition) extends ComplexDeployable(cdef)
+
+class TrapDeployableDefinition(objectId : Int) extends ComplexDeployableDefinition(objectId) {
+ Model = StandardResolutions.SimpleDeployables
+ Packet = new TRAPConverter
+
+ override def Initialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
+ obj.Actor = context.actorOf(Props(classOf[TrapDeployableControl], obj), PlanetSideServerObject.UniqueActorName(obj))
+ }
+
+ override def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) = {
+ SimpleDeployableDefinition.SimpleUninitialize(obj, context)
+ }
+}
+
+object TrapDeployableDefinition {
+ def apply(dtype : DeployedItem.Value) : TrapDeployableDefinition = {
+ new TrapDeployableDefinition(dtype.id)
+ }
+}
+
+class TrapDeployableControl(trap : TrapDeployable) extends Actor
+ with DamageableEntity
+ with RepairableEntity {
+ def DamageableObject = trap
+ def RepairableObject = trap
+
+ def receive : Receive = takesDamage
+ .orElse(canBeRepairedByNanoDispenser)
+ .orElse {
+ case _ =>
+ }
+
+ override protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
+ super.DestructionAwareness(target, cause)
+ Deployables.AnnounceDestroyDeployable(trap, None)
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/TurretDeployable.scala b/common/src/main/scala/net/psforever/objects/TurretDeployable.scala
index 273acf40e..6f3ff3bc2 100644
--- a/common/src/main/scala/net/psforever/objects/TurretDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/TurretDeployable.scala
@@ -8,30 +8,23 @@ import net.psforever.objects.definition.{ComplexDeployableDefinition, SimpleDepl
import net.psforever.objects.definition.converter.SmallTurretConverter
import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
import net.psforever.objects.serverobject.PlanetSideServerObject
-import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
+import net.psforever.objects.serverobject.damage.Damageable.Target
+import net.psforever.objects.serverobject.damage.DamageableWeaponTurret
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.mount.MountableBehavior
+import net.psforever.objects.serverobject.repair.RepairableWeaponTurret
import net.psforever.objects.serverobject.turret.{TurretDefinition, WeaponTurret}
-import net.psforever.objects.vital.{StandardResolutions, StandardVehicleDamage, StandardVehicleResistance, Vitality}
-import net.psforever.objects.zones.Zone
-import net.psforever.types.PlanetSideGUID
-import services.Service
-import services.avatar.{AvatarAction, AvatarServiceMessage}
-import services.vehicle.{VehicleAction, VehicleServiceMessage}
+import net.psforever.objects.vital.{StandardResolutions, StandardVehicleDamage, StandardVehicleResistance}
class TurretDeployable(tdef : TurretDeployableDefinition) extends ComplexDeployable(tdef)
with WeaponTurret
with JammableUnit
with Hackable {
- WeaponTurret.LoadDefinition(this) //calls the equivalent of Health = Definition.MaxHealth
+ WeaponTurret.LoadDefinition(this)
def MountPoints : Map[Int, Int] = Definition.MountPoints.toMap
- //override to clarify inheritance conflict
- override def Health : Int = super[ComplexDeployable].Health
- //override to clarify inheritance conflict
- override def Health_=(toHealth : Int) : Int = super[ComplexDeployable].Health_=(toHealth)
-
override def Definition = tdef
}
@@ -39,8 +32,8 @@ class TurretDeployableDefinition(private val objectId : Int) extends ComplexDepl
with TurretDefinition {
Name = "turret_deployable"
Packet = new SmallTurretConverter
- Damage = StandardVehicleDamage
- Resistance = StandardVehicleResistance
+ DamageUsing = StandardVehicleDamage
+ ResistUsing = StandardVehicleResistance
Model = StandardResolutions.FacilityTurrets
//override to clarify inheritance conflict
@@ -68,113 +61,28 @@ object TurretDeployableDefinition {
class TurretControl(turret : TurretDeployable) extends Actor
with FactionAffinityBehavior.Check
with JammableMountedWeapons //note: jammable status is reported as vehicle events, not local events
- with MountableBehavior.Mount
- with MountableBehavior.Dismount {
- def MountableObject = turret //do not add type!
-
+ with MountableBehavior.TurretMount
+ with MountableBehavior.Dismount
+ with DamageableWeaponTurret
+ with RepairableWeaponTurret {
+ def MountableObject = turret
def JammableObject = turret
-
- def FactionObject : FactionAffinity = turret
+ def FactionObject = turret
+ def DamageableObject = turret
+ def RepairableObject = turret
def receive : Receive = checkBehavior
.orElse(jammableBehavior)
+ .orElse(mountBehavior)
.orElse(dismountBehavior)
- .orElse(turretMountBehavior)
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
.orElse {
- case Vitality.Damage(damage_func) => //note: damage status is reported as vehicle events, not local events
- if(turret.Health > 0) {
- val originalHealth = turret.Health
- val cause = damage_func(turret)
- val health = turret.Health
- val damageToHealth = originalHealth - health
- TurretControl.HandleDamageResolution(turret, cause, damageToHealth)
- if(damageToHealth > 0) {
- 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")
- }
- }
-
case _ => ;
}
-}
-object TurretControl {
- /**
- * na
- * @param target na
- */
- def HandleDamageResolution(target : TurretDeployable, cause : ResolvedProjectile, damage : Int) : Unit = {
- val zone = target.Zone
- val targetGUID = target.GUID
- val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
- case Some(player) => player.GUID
- case _ => PlanetSideGUID(0)
- }
- if(target.Health > 0) {
- //activity on map
- if(damage > 0) {
- zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
- //alert occupants to damage source
- HandleDamageAwareness(target, playerGUID, cause)
- }
- if(cause.projectile.profile.JammerProjectile) {
- target.Actor ! JammableUnit.Jammered(cause)
- }
- }
- else {
- //alert to turret death (hence, occupants' deaths)
- HandleDestructionAwareness(target, playerGUID, cause)
- }
- zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
- }
-
- /**
- * na
- * @param target na
- * @param attribution na
- * @param lastShot na
- */
- def HandleDamageAwareness(target : TurretDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
- val zone = target.Zone
- //alert occupants to damage source
- target.Seats.values.filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
- }).foreach(seat => {
- val tplayer = seat.Occupant.get
- zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(attribution, tplayer.GUID))
- })
- }
-
- /**
- * na
- * @param target na
- * @param attribution na
- * @param lastShot na
- */
- def HandleDestructionAwareness(target : TurretDeployable, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
- target.Actor ! JammableUnit.ClearJammeredSound()
- target.Actor ! JammableUnit.ClearJammeredStatus()
- val zone = target.Zone
- val continentId = zone.Id
- //alert to vehicle death (hence, occupants' deaths)
- target.Seats.values.filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
- }).foreach(seat => {
- val tplayer = seat.Occupant.get
- tplayer.History(lastShot)
- tplayer.Actor ! Player.Die()
- })
- //vehicle wreckage has no weapons
- target.Weapons.values
- .filter {
- _.Equipment.nonEmpty
- }
- .foreach(slot => {
- val wep = slot.Equipment.get
- zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
- })
- Deployables.AnnounceDestroyDeployable(target, None)
- zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
+ override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
+ super.DestructionAwareness(target, cause)
+ Deployables.AnnounceDestroyDeployable(turret, None)
}
}
diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala
index bd3a741ea..6ad779d7b 100644
--- a/common/src/main/scala/net/psforever/objects/Vehicle.scala
+++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala
@@ -9,6 +9,7 @@ import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.deploy.Deployment
+import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.AmenityOwner
import net.psforever.objects.vehicles._
import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
@@ -65,6 +66,7 @@ import scala.annotation.tailrec
* used in the initialization process (`loadVehicleDefinition`)
*/
class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
+ with Hackable
with FactionAffinity
with Mountable
with MountedWeapons
@@ -74,10 +76,8 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
with StandardResistanceProfile
with JammableUnit
with Container {
- private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR
- private var health : Int = 1
+ private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var shields : Int = 0
- private var isDead : Boolean = false
private var decal : Int = 0
private var trunkAccess : Option[PlanetSideGUID] = None
private var jammered : Boolean = false
@@ -144,28 +144,12 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends AmenityOwner
MountedIn
}
- def IsDead : Boolean = {
- isDead
- }
-
- def Health : Int = {
- health
- }
-
- def Health_=(assignHealth : Int) : Int = {
- if(!isDead) {
- health = math.min(math.max(0, assignHealth), MaxHealth)
+ override def Health_=(assignHealth : Int) : Int = {
+ //TODO should vehicle class enforce this?
+ if(!Destroyed) {
+ super.Health_=(assignHealth)
}
-
- if(health == 0) {
- isDead = true
- }
-
- health
- }
-
- def MaxHealth : Int = {
- Definition.MaxHealth
+ Health
}
def Shields : Int = {
@@ -644,7 +628,7 @@ object Vehicle {
def LoadDefinition(vehicle : Vehicle) : Vehicle = {
val vdef : VehicleDefinition = vehicle.Definition
//general stuff
- vehicle.Health = vdef.MaxHealth
+ vehicle.Health = vdef.DefaultHealth
//create weapons
vehicle.weapons = vdef.Weapons.map({case (num, definition) =>
val slot = EquipmentSlot(EquipmentSize.VehicleWeapon)
diff --git a/common/src/main/scala/net/psforever/objects/Vehicles.scala b/common/src/main/scala/net/psforever/objects/Vehicles.scala
index 01635b13a..e4a699aa6 100644
--- a/common/src/main/scala/net/psforever/objects/Vehicles.scala
+++ b/common/src/main/scala/net/psforever/objects/Vehicles.scala
@@ -1,20 +1,26 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects
-import net.psforever.objects.vehicles.VehicleLockState
+import net.psforever.objects.serverobject.CommonMessages
+import net.psforever.objects.vehicles.{CargoBehavior, VehicleLockState}
import net.psforever.objects.zones.Zone
-import net.psforever.types.PlanetSideGUID
+import net.psforever.packet.game.TriggeredSound
+import net.psforever.types.{DriveState, PlanetSideGUID}
+import services.RemoverActor
import services.avatar.{AvatarAction, AvatarServiceMessage}
+import services.local.{LocalAction, LocalServiceMessage}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
object Vehicles {
+ private val log = org.log4s.getLogger("Vehicles")
+
/**
* na
* @param vehicle na
- * @param tplayer na
+ * @param player na
* @return na
*/
- def Own(vehicle : Vehicle, tplayer : Player) : Option[Vehicle] = Own(vehicle, Some(tplayer))
+ def Own(vehicle : Vehicle, player : Player) : Option[Vehicle] = Own(vehicle, Some(player))
/**
* na
@@ -151,4 +157,106 @@ object Vehicles {
false
}
}
+
+ /**
+ * The orientation of a cargo vehicle as it is being loaded into and contained by a carrier vehicle.
+ * The type of carrier is not an important consideration in determining the orientation, oddly enough.
+ * @param vehicle the cargo vehicle
+ * @return the orientation as an `Integer` value;
+ * `0` for almost all cases
+ */
+ def CargoOrientation(vehicle : Vehicle) : Int = {
+ if(vehicle.Definition == GlobalDefinitions.router) {
+ 1
+ }
+ else {
+ 0
+ }
+ }
+
+ /**
+ * The process of hacking/jacking a vehicle is complete.
+ * Change the faction of the vehicle to the hacker's faction and remove all occupants.
+ * @param target The `Vehicle` object that has been hacked/jacked
+ * @param hacker the one whoi performed the hack and will inherit ownership of the target vehicle
+ * @param unk na; used by `HackMessage` as `unk5`
+ */
+ def FinishHackingVehicle(target : Vehicle, hacker : Player, unk : Long)(): Unit = {
+ log.info(s"Vehicle guid: ${target.GUID} has been jacked")
+ import scala.concurrent.duration._
+ val zone = target.Zone
+ // Forcefully dismount any cargo
+ target.CargoHolds.values.foreach(cargoHold => {
+ cargoHold.Occupant match {
+ case Some(cargo : Vehicle) => {
+ cargo.Seats(0).Occupant match {
+ case Some(cargoDriver: Player) =>
+ CargoBehavior.HandleVehicleCargoDismount(target.Zone, cargoDriver.GUID, cargo.GUID, bailed = target.Flying, requestedByPassenger = false, kicked = true )
+ case None =>
+ log.error("FinishHackingVehicle: vehicle in cargo hold missing driver")
+ CargoBehavior.HandleVehicleCargoDismount(hacker.GUID, cargo.GUID, cargo, target.GUID, target, false, false, true)
+ }
+ }
+ case None => ;
+ }
+ })
+ // Forcefully dismount all seated occupants from the vehicle
+ target.Seats.values.foreach(seat => {
+ seat.Occupant match {
+ case Some(tplayer) =>
+ seat.Occupant = None
+ tplayer.VehicleSeated = None
+ if(tplayer.HasGUID) {
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, unk2 = false, target.GUID))
+ }
+ case None => ;
+ }
+ })
+ // If the vehicle can fly and is flying deconstruct it, and well played to whomever managed to hack a plane in mid air. I'm impressed.
+ if(target.Definition.CanFly && target.Flying) {
+ // todo: Should this force the vehicle to land in the same way as when a pilot bails with passengers on board?
+ zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), zone))
+ zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, zone, Some(0 seconds)))
+ } else { // Otherwise handle ownership transfer as normal
+ // Remove ownership of our current vehicle, if we have one
+ hacker.VehicleOwned match {
+ case Some(guid : PlanetSideGUID) =>
+ zone.GUID(guid) match {
+ case Some(vehicle: Vehicle) =>
+ Vehicles.Disown(hacker, vehicle)
+ case _ => ;
+ }
+ case _ => ;
+ }
+ target.Owner match {
+ case Some(previousOwnerGuid: PlanetSideGUID) =>
+ // Remove ownership of the vehicle from the previous player
+ zone.GUID(previousOwnerGuid) match {
+ case Some(tplayer: Player) =>
+ Vehicles.Disown(tplayer, target)
+ case _ => ; // Vehicle already has no owner
+ }
+ case _ => ;
+ }
+ // Now take ownership of the jacked vehicle
+ target.Actor ! CommonMessages.Hack(hacker, target)
+ target.Faction = hacker.Faction
+ Vehicles.Own(target, hacker)
+ //todo: Send HackMessage -> HackCleared to vehicle? can be found in packet captures. Not sure if necessary.
+ // And broadcast the faction change to other clients
+ zone.AvatarEvents ! AvatarServiceMessage(hacker.Name, AvatarAction.SetEmpire(hacker.GUID, target.GUID, hacker.Faction))
+ zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.SetEmpire(hacker.GUID, target.GUID, hacker.Faction))
+ }
+ zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerSound(hacker.GUID, TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f))
+ // Clean up after specific vehicles, e.g. remove router telepads
+ // If AMS is deployed, swap it to the new faction
+ target.Definition match {
+ case GlobalDefinitions.router =>
+ log.info("FinishHackingVehicle: cleaning up after a router ...")
+ Deployables.RemoveTelepad(target)
+ case GlobalDefinitions.ams if target.DeploymentState == DriveState.Deployed =>
+ zone.VehicleEvents ! VehicleServiceMessage.AMSDeploymentChange(zone)
+ case _ => ;
+ }
+ }
}
diff --git a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
index cffa60dfd..61b752629 100644
--- a/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
+++ b/common/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
@@ -2,13 +2,17 @@
package net.psforever.objects.avatar
import akka.actor.Actor
-import net.psforever.objects.Player
-import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry}
-import net.psforever.objects.equipment.{JammableBehavior, JammableUnit}
-import net.psforever.objects.vital.{PlayerSuicide, Vitality}
+import net.psforever.objects.{GlobalDefinitions, Player, Tool}
+import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
+import net.psforever.objects.equipment.{Ammo, JammableBehavior, JammableUnit}
+import net.psforever.objects.serverobject.CommonMessages
+import net.psforever.objects.serverobject.damage.Damageable
+import net.psforever.objects.serverobject.mount.Mountable
+import net.psforever.objects.serverobject.repair.Repairable
+import net.psforever.objects.vital._
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
-import net.psforever.types.{ExoSuitType, PlanetSideGUID}
+import net.psforever.types.{ExoSuitType, Vector3}
import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage}
@@ -19,34 +23,108 @@ import scala.concurrent.duration._
* stub for future development
*/
class PlayerControl(player : Player) extends Actor
- with JammableBehavior {
+ with JammableBehavior
+ with Damageable {
def JammableObject = player
+ def DamageableObject = player
- private [this] val log = org.log4s.getLogger(player.Name)
- private [this] val damageLog = org.log4s.getLogger("DamageResolution")
+ private [this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
- def receive : Receive = jammableBehavior.orElse {
- case Player.Die() =>
- PlayerControl.HandleDestructionAwareness(player, player.GUID, None)
+ def receive : Receive = jammableBehavior
+ .orElse(takesDamage)
+ .orElse {
+ case Player.Die() =>
+ if(player.isAlive) {
+ PlayerControl.DestructionAwareness(player, None)
+ }
- case Vitality.Damage(resolution_function) =>
+ case CommonMessages.Use(user, Some(item : Tool)) if item.Definition == GlobalDefinitions.medicalapplicator && player.isAlive =>
+ //heal
+ val originalHealth = player.Health
+ val definition = player.Definition
+ if(player.MaxHealth > 0 && originalHealth < player.MaxHealth &&
+ user.Faction == player.Faction &&
+ item.Magazine > 0 &&
+ Vector3.Distance(user.Position, player.Position) < definition.RepairDistance) {
+ val zone = player.Zone
+ val events = zone.AvatarEvents
+ val uname = user.Name
+ val guid = player.GUID
+ if(!(player.isMoving || user.isMoving)) { //only allow stationary heals
+ val newHealth = player.Health = originalHealth + 10
+ val magazine = item.Discharge
+ events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)))
+ events ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(guid, 0, newHealth))
+ player.History(HealFromEquipment(PlayerSource(player), PlayerSource(user), newHealth - originalHealth, GlobalDefinitions.medicalapplicator))
+ }
+ if(player != user) {
+ //"Someone is trying to heal you"
+ events ! AvatarServiceMessage(player.Name, AvatarAction.PlanetsideAttributeToAll(guid, 55, 1))
+ //progress bar remains visible for all heal attempts
+ events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, RepairMessage(guid, player.Health * 100 / definition.MaxHealth)))
+ }
+ }
+
+ case CommonMessages.Use(user, Some(item : Tool)) if item.Definition == GlobalDefinitions.medicalapplicator && !player.isAlive =>
+ //revive
+ if(user != player && user.isAlive && !user.isMoving &&
+ !player.isBackpack &&
+ item.Magazine >= 25) {
+ sender ! CommonMessages.Use(player, Some((item, user)))
+ }
+
+ case CommonMessages.Use(user, Some(item : Tool)) if item.Definition == GlobalDefinitions.bank =>
+ val originalArmor = player.Armor
+ val definition = player.Definition
+ if(player.MaxArmor > 0 && originalArmor < player.MaxArmor &&
+ user.Faction == player.Faction &&
+ item.AmmoType == Ammo.armor_canister && item.Magazine > 0 &&
+ Vector3.Distance(user.Position, player.Position) < definition.RepairDistance) {
+ val zone = player.Zone
+ val events = zone.AvatarEvents
+ val uname = user.Name
+ val guid = player.GUID
+ if(!(player.isMoving || user.isMoving)) { //only allow stationary repairs
+ val newArmor = player.Armor = originalArmor + Repairable.Quality + RepairValue(item) + definition.RepairMod
+ val magazine = item.Discharge
+ events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)))
+ events ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(guid, 4, player.Armor))
+ player.History(RepairFromEquipment(PlayerSource(player), PlayerSource(user), newArmor - originalArmor, GlobalDefinitions.bank))
+ }
+ if(player != user) {
+ if(player.isAlive) {
+ //"Someone is trying to repair you" gets strobed twice for visibility
+ val msg = AvatarServiceMessage(player.Name, AvatarAction.PlanetsideAttributeToAll(guid, 56, 1))
+ events ! msg
+ import scala.concurrent.ExecutionContext.Implicits.global
+ context.system.scheduler.scheduleOnce(250 milliseconds, events, msg)
+ }
+ //progress bar remains visible for all repair attempts
+ events ! AvatarServiceMessage(uname, AvatarAction.SendResponse(Service.defaultPlayerGUID, RepairMessage(guid, player.Armor * 100 / player.MaxArmor)))
+ }
+ }
+
+ case _ => ;
+ }
+
+ protected def TakesDamage : Receive = {
+ case Vitality.Damage(applyDamageTo) =>
if(player.isAlive) {
val originalHealth = player.Health
val originalArmor = player.Armor
val originalCapacitor = player.Capacitor.toInt
- val cause = resolution_function(player)
+ val cause = applyDamageTo(player)
val health = player.Health
val armor = player.Armor
val capacitor = player.Capacitor.toInt
val damageToHealth = originalHealth - health
val damageToArmor = originalArmor - armor
val damageToCapacitor = originalCapacitor - capacitor
- PlayerControl.HandleDamageResolution(player, cause, damageToHealth, damageToArmor, damageToCapacitor)
- if(damageToHealth != 0 || damageToArmor != 0 || damageToCapacitor != 0) {
+ PlayerControl.HandleDamage(player, cause, damageToHealth, damageToArmor, damageToCapacitor)
+ if(damageToHealth > 0 || damageToArmor > 0 || damageToCapacitor > 0) {
damageLog.info(s"${player.Name}-infantry: BEFORE=$originalHealth/$originalArmor/$originalCapacitor, AFTER=$health/$armor/$capacitor, CHANGE=$damageToHealth/$damageToArmor/$damageToCapacitor")
}
}
- case _ => ;
}
/**
@@ -77,8 +155,12 @@ class PlayerControl(player : Player) extends Actor
override def StartJammeredStatus(target : Any, dur : Int) : Unit = target match {
case obj : Player =>
//TODO these features
- obj.Zone.AvatarEvents ! AvatarServiceMessage(obj.Zone.Id, AvatarAction.DeactivateImplantSlot(obj.GUID, 1))
- obj.Zone.AvatarEvents ! AvatarServiceMessage(obj.Zone.Id, AvatarAction.DeactivateImplantSlot(obj.GUID, 2))
+ val guid = obj.GUID
+ val zone = obj.Zone
+ val zoneId = zone.Id
+ val events = zone.AvatarEvents
+ events ! AvatarServiceMessage(zoneId, AvatarAction.DeactivateImplantSlot(guid, 1))
+ events ! AvatarServiceMessage(zoneId, AvatarAction.DeactivateImplantSlot(guid, 2))
obj.skipStaminaRegenForTurns = math.max(obj.skipStaminaRegenForTurns, 10)
super.StartJammeredStatus(target, dur)
case _ => ;
@@ -95,6 +177,13 @@ class PlayerControl(player : Player) extends Actor
super.CancelJammeredSound(obj)
case _ => ;
}
+
+ def RepairValue(item : Tool) : Int = if(player.ExoSuit != ExoSuitType.MAX) {
+ item.FireMode.Modifiers.Damage0
+ }
+ else {
+ item.FireMode.Modifiers.Damage3
+ }
}
object PlayerControl {
@@ -102,62 +191,60 @@ object PlayerControl {
* na
* @param target na
*/
- def HandleDamageResolution(target : Player, cause : ResolvedProjectile, damageToHealth : Int, damageToArmor : Int, damageToCapacitor : Int) : Unit = {
+ def HandleDamage(target : Player, cause : ResolvedProjectile, damageToHealth : Int, damageToArmor : Int, damageToCapacitor : Int) : Unit = {
val targetGUID = target.GUID
- val playerGUID = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
- case Some(player) => player.GUID
- case _ => PlanetSideGUID(0)
- }
- if(target.Health > 0) {
- //activity on map
- if(damageToHealth + damageToArmor > 0) {
- target.Zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
- //alert damage source
- HandleDamageAwareness(target, playerGUID, cause)
+ val zone = target.Zone
+ val zoneId = zone.Id
+ val events = zone.AvatarEvents
+ val health = target.Health
+ if(health > 0) {
+ if(damageToCapacitor > 0) {
+ events ! AvatarServiceMessage(target.Name, AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong))
}
- if(cause.projectile.profile.JammerProjectile) {
+ if(damageToHealth > 0 || damageToArmor > 0) {
+ target.History(cause)
+ if(damageToHealth > 0) {
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, health))
+ }
+ if(damageToArmor > 0) {
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
+ }
+ //activity on map
+ zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
+ //alert damage source
+ DamageAwareness(target, cause)
+ }
+ if(Damageable.CanJammer(target, cause)) {
target.Actor ! JammableUnit.Jammered(cause)
}
}
else {
- HandleDestructionAwareness(target, playerGUID, Some(cause))
- }
- if(damageToHealth > 0) {
- target.Zone.AvatarEvents ! AvatarServiceMessage(target.Zone.Id, AvatarAction.PlanetsideAttributeToAll(targetGUID, 0, target.Health))
- }
- if(damageToArmor > 0) {
- target.Zone.AvatarEvents ! AvatarServiceMessage(target.Zone.Id, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
- }
- if(damageToCapacitor > 0) {
- target.Zone.AvatarEvents ! AvatarServiceMessage(target.Name, AvatarAction.PlanetsideAttributeSelf(targetGUID, 7, target.Capacitor.toLong))
+ if(damageToArmor > 0) {
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 4, target.Armor))
+ }
+ DestructionAwareness(target, Some(cause))
}
}
/**
* na
* @param target na
- * @param attribution na
- * @param lastShot na
+ * @param cause na
*/
- def HandleDamageAwareness(target : Player, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
- val owner = lastShot.projectile.owner
- owner match {
- case pSource : PlayerSource =>
- target.Zone.LivePlayers.find(_.Name == pSource.Name) match {
- case Some(tplayer) =>
- target.Zone.AvatarEvents ! AvatarServiceMessage(
- target.Name,
- AvatarAction.HitHint(tplayer.GUID, target.GUID)
- )
- case None => ;
- }
- case vSource : SourceEntry =>
- target.Zone.AvatarEvents ! AvatarServiceMessage(
- target.Name,
- AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, vSource.Position))
- )
- case _ => ;
- }
+ def DamageAwareness(target : Player, cause : ResolvedProjectile) : Unit = {
+ val zone = target.Zone
+ zone.AvatarEvents ! AvatarServiceMessage(
+ target.Name,
+ cause.projectile.owner match {
+ case pSource : PlayerSource => //player damage
+ val name = pSource.Name
+ zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
+ case Some(player) => AvatarAction.HitHint(player.GUID, target.GUID)
+ case None => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
+ }
+ case source => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
+ }
+ )
}
/**
@@ -173,10 +260,9 @@ object PlayerControl {
* A maximum revive waiting timer is started.
* When this timer reaches zero, the avatar will attempt to spawn back on its faction-specific sanctuary continent.
* @param target na
- * @param attribution na
- * @param lastShot na
+ * @param cause na
*/
- def HandleDestructionAwareness(target : Player, attribution : PlanetSideGUID, lastShot : Option[ResolvedProjectile]) : Unit = {
+ def DestructionAwareness(target : Player, cause : Option[ResolvedProjectile]) : Unit = {
val player_guid = target.GUID
val pos = target.Position
val respawnTimer = 300000 //milliseconds
@@ -185,24 +271,49 @@ object PlayerControl {
val nameChannel = target.Name
val zoneChannel = zone.Id
target.Die
+ //unjam
target.Actor ! JammableUnit.ClearJammeredSound()
target.Actor ! JammableUnit.ClearJammeredStatus()
events ! AvatarServiceMessage(nameChannel, AvatarAction.Killed(player_guid)) //align client interface fields with state
- if(target.VehicleSeated.nonEmpty) {
- //make player invisible (if not, the cadaver sticks out the side in a seated position)
- events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 29, 1))
- //only the dead player should "see" their own body, so that the death camera has something to focus on
- events ! AvatarServiceMessage(zoneChannel, AvatarAction.ObjectDelete(player_guid, player_guid))
+ zone.GUID(target.VehicleSeated) match {
+ case Some(obj : Mountable) =>
+ //boot cadaver from seat
+ events ! AvatarServiceMessage(nameChannel, AvatarAction.SendResponse(Service.defaultPlayerGUID,
+ ObjectDetachMessage(obj.GUID, player_guid, target.Position, Vector3.Zero))
+ )
+ obj.PassengerInSeat(target) match {
+ case Some(index) =>
+ obj.Seats(index).Occupant = None
+ case _ => ;
+ }
+ //make player invisible
+ events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 29, 1))
+ //only the dead player should "see" their own body, so that the death camera has something to focus on
+ events ! AvatarServiceMessage(zoneChannel, AvatarAction.ObjectDelete(player_guid, player_guid))
+ case _ => ;
}
events ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 0, 0)) //health
events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 2, 0)) //stamina
- events ! AvatarServiceMessage(zoneChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 4, target.Armor)) //armor
- if(target.ExoSuit == ExoSuitType.MAX) {
+ if(target.Capacitor > 0) {
+ target.Capacitor = 0
events ! AvatarServiceMessage(nameChannel, AvatarAction.PlanetsideAttributeToAll(player_guid, 7, 0)) // capacitor
}
+ val attribute = cause match {
+ case Some(resolved) =>
+ resolved.projectile.owner match {
+ case pSource : PlayerSource =>
+ val name = pSource.Name
+ zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
+ case Some(player) => player.GUID
+ case None => player_guid
+ }
+ case _ => player_guid
+ }
+ case _ => player_guid
+ }
events ! AvatarServiceMessage(
nameChannel,
- AvatarAction.SendResponse(Service.defaultPlayerGUID, DestroyMessage(player_guid, player_guid, Service.defaultPlayerGUID, pos)) //how many players get this message?
+ AvatarAction.SendResponse(Service.defaultPlayerGUID, DestroyMessage(player_guid, attribute, Service.defaultPlayerGUID, pos)) //how many players get this message?
)
events ! AvatarServiceMessage(
nameChannel,
@@ -214,7 +325,7 @@ object PlayerControl {
case Some(PlayerSuicide(_)) =>
None
case _ =>
- lastShot.orElse { target.LastShot } match {
+ cause.orElse { target.LastShot } match {
case out @ Some(shot) =>
if(System.nanoTime - shot.hit_time < (10 seconds).toNanos) {
out
@@ -227,7 +338,6 @@ object PlayerControl {
}
}) match {
case Some(shot) =>
- zone.Activity ! Zone.HotSpot.Activity(pentry, shot.projectile.owner, shot.hit_pos)
events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(shot.projectile.owner, pentry, shot.projectile.attribute_to))
case None =>
events ! AvatarServiceMessage(zoneChannel, AvatarAction.DestroyDisplay(pentry, pentry, 0))
diff --git a/common/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala b/common/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala
index 79b24590a..63c5411de 100644
--- a/common/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala
+++ b/common/src/main/scala/net/psforever/objects/ballistics/ResolvedProjectile.scala
@@ -14,11 +14,11 @@ import net.psforever.types.Vector3
* @param target what the projectile hit
* @param damage_model the kind of damage model to which the `target` is/was subject
* @param hit_pos where the projectile hit
- * @param hit_time the sequence timing when the projectile hit the target
*/
final case class ResolvedProjectile(resolution : ProjectileResolution.Value,
projectile : Projectile,
target : SourceEntry,
damage_model : DamageResistanceModel,
- hit_pos : Vector3,
- hit_time : Long = System.nanoTime)
+ hit_pos : Vector3) {
+ val hit_time : Long = System.nanoTime
+}
diff --git a/common/src/main/scala/net/psforever/objects/ce/ComplexDeployable.scala b/common/src/main/scala/net/psforever/objects/ce/ComplexDeployable.scala
index 65665c9ea..b7eb91068 100644
--- a/common/src/main/scala/net/psforever/objects/ce/ComplexDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/ce/ComplexDeployable.scala
@@ -6,10 +6,6 @@ import net.psforever.objects.serverobject.PlanetSideServerObject
abstract class ComplexDeployable(cdef : ComplexDeployableDefinition) extends PlanetSideServerObject
with Deployable {
- Health = Definition.MaxHealth
-
- def MaxHealth : Int = Definition.MaxHealth
-
private var shields : Int = 0
def Shields : Int = shields
diff --git a/common/src/main/scala/net/psforever/objects/ce/Deployable.scala b/common/src/main/scala/net/psforever/objects/ce/Deployable.scala
index b552874e1..e77c00d8c 100644
--- a/common/src/main/scala/net/psforever/objects/ce/Deployable.scala
+++ b/common/src/main/scala/net/psforever/objects/ce/Deployable.scala
@@ -2,7 +2,7 @@
package net.psforever.objects.ce
import net.psforever.objects._
-import net.psforever.objects.definition.{BaseDeployableDefinition, ObjectDefinition}
+import net.psforever.objects.definition.DeployableDefinition
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.vital.{DamageResistanceModel, Vitality}
import net.psforever.objects.zones.ZoneAware
@@ -14,16 +14,8 @@ trait Deployable extends FactionAffinity
with OwnableByPlayer
with ZoneAware {
this : PlanetSideGameObject =>
- private var health : Int = 1
private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
- def Health : Int = health
-
- def Health_=(toHealth : Int) : Int = {
- health = math.min(math.max(0, toHealth), MaxHealth)
- Health
- }
-
def MaxHealth : Int
def Faction : PlanetSideEmpire.Value = faction
@@ -35,7 +27,7 @@ trait Deployable extends FactionAffinity
def DamageModel : DamageResistanceModel = Definition.asInstanceOf[DamageResistanceModel]
- def Definition : ObjectDefinition with BaseDeployableDefinition
+ def Definition : DeployableDefinition
}
object Deployable {
diff --git a/common/src/main/scala/net/psforever/objects/ce/SimpleDeployable.scala b/common/src/main/scala/net/psforever/objects/ce/SimpleDeployable.scala
index 76362ea90..814779da7 100644
--- a/common/src/main/scala/net/psforever/objects/ce/SimpleDeployable.scala
+++ b/common/src/main/scala/net/psforever/objects/ce/SimpleDeployable.scala
@@ -8,7 +8,5 @@ abstract class SimpleDeployable(cdef : SimpleDeployableDefinition) extends Plane
with Deployable {
Health = Definition.MaxHealth
- def MaxHealth : Int = Definition.MaxHealth
-
def Definition = cdef
}
diff --git a/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala
index 3d26b714f..164c69d55 100644
--- a/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala
@@ -3,12 +3,14 @@ package net.psforever.objects.definition
import net.psforever.objects.avatar.Avatars
import net.psforever.objects.definition.converter.AvatarConverter
+import net.psforever.objects.vital.VitalityDefinition
/**
* The definition for game objects that look like other people, and also for players.
* @param objectId the object's identifier number
*/
-class AvatarDefinition(objectId : Int) extends ObjectDefinition(objectId) {
+class AvatarDefinition(objectId : Int) extends ObjectDefinition(objectId)
+ with VitalityDefinition {
Avatars(objectId) //let throw NoSuchElementException
Packet = AvatarDefinition.converter
}
diff --git a/common/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala
index 44e1175f3..da957db9a 100644
--- a/common/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/ExoSuitDefinition.scala
@@ -26,8 +26,8 @@ class ExoSuitDefinition(private val suitType : ExoSuitType.Value) extends BasicD
protected var capacitorRechargePerSecond : Int = 0
protected var capacitorDrainPerSecond : Int = 0
Name = "exo-suit"
- Damage = StandardInfantryDamage
- Resistance = StandardInfantryResistance
+ DamageUsing = StandardInfantryDamage
+ ResistUsing = StandardInfantryResistance
Model = StandardResolutions.Infantry
def SuitType : ExoSuitType.Value = suitType
@@ -142,8 +142,8 @@ class SpecialExoSuitDefinition(private val suitType : ExoSuitType.Value) extends
obj.ResistanceDirectHit = ResistanceDirectHit
obj.ResistanceSplash = ResistanceSplash
obj.ResistanceAggravated = ResistanceAggravated
- obj.Damage = Damage
- obj.Resistance = Resistance
+ obj.DamageUsing = DamageUsing
+ obj.ResistUsing = ResistUsing
obj.Model = Model
(0 until 5).foreach(index => { obj.Holster(index, Holster(index)) })
obj
diff --git a/common/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala
index fc9e0cbb8..07b9c4304 100644
--- a/common/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/SimpleDeployableDefinition.scala
@@ -7,27 +7,16 @@ 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.resistance.ResistanceProfileMutators
-import net.psforever.objects.vital.{DamageResistanceModel, NoResistanceSelection, StandardDeployableDamage}
+import net.psforever.objects.vital.{DamageResistanceModel, NoResistanceSelection, StandardDeployableDamage, VitalityDefinition}
import scala.concurrent.duration._
-trait BaseDeployableDefinition extends DamageResistanceModel
- with ResistanceProfileMutators {
+trait BaseDeployableDefinition {
private var category : DeployableCategory.Value = DeployableCategory.Boomers
private var deployTime : Long = (1 second).toMillis //ms
- private var maxHealth : Int = 1
- Damage = StandardDeployableDamage
- Resistance = NoResistanceSelection
def Item : DeployedItem.Value
- def MaxHealth : Int = maxHealth
-
- def MaxHealth_=(toHealth : Int) : Int = {
- maxHealth = toHealth
- MaxHealth
- }
-
def DeployCategory : DeployableCategory.Value = category
def DeployCategory_=(cat : DeployableCategory.Value) : DeployableCategory.Value = {
@@ -53,20 +42,23 @@ trait BaseDeployableDefinition extends DamageResistanceModel
def Uninitialize(obj : PlanetSideServerObject with Deployable, context : ActorContext) : Unit = { }
}
-class SimpleDeployableDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
+abstract class DeployableDefinition(objectId : Int) extends ObjectDefinition(objectId)
+ with DamageResistanceModel
+ with ResistanceProfileMutators
+ with VitalityDefinition
with BaseDeployableDefinition {
private val item = DeployedItem(objectId) //let throw NoSuchElementException
+ DamageUsing = StandardDeployableDamage
+ ResistUsing = NoResistanceSelection
+
+ def Item : DeployedItem.Value = item
+}
+
+class SimpleDeployableDefinition(objectId : Int) extends DeployableDefinition(objectId) {
Packet = new SmallDeployableConverter
-
- def Item : DeployedItem.Value = item
}
-abstract class ComplexDeployableDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
- with BaseDeployableDefinition {
- private val item = DeployedItem(objectId) //let throw NoSuchElementException
-
- def Item : DeployedItem.Value = item
-}
+abstract class ComplexDeployableDefinition(objectId : Int) extends DeployableDefinition(objectId)
object SimpleDeployableDefinition {
def apply(item : DeployedItem.Value) : SimpleDeployableDefinition =
@@ -75,7 +67,7 @@ object SimpleDeployableDefinition {
def SimpleUninitialize(obj : PlanetSideGameObject, context : ActorContext) : Unit = { }
def SimpleUninitialize(obj : PlanetSideServerObject, context : ActorContext) : Unit = {
- obj.Actor ! akka.actor.PoisonPill
+ context.stop(obj.Actor)
obj.Actor = ActorRef.noSender
}
}
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 bd5a13437..8fb5f0cc0 100644
--- a/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/VehicleDefinition.scala
@@ -16,9 +16,9 @@ import scala.concurrent.duration._
* @param objectId the object id that is associated with this sort of `Vehicle`
*/
class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId)
+ with VitalityDefinition
with ResistanceProfileMutators
with DamageResistanceModel {
- private var maxHealth : Int = 100
/** vehicle shields offered through amp station facility benefits (generally: 20% of health + 1) */
private var maxShields : Int = 0
/* key - seat index, value - seat object */
@@ -45,16 +45,11 @@ class VehicleDefinition(objectId : Int) extends ObjectDefinition(objectId)
private var destroyedModel : Option[DestroyedVehicle.Value] = None
Name = "vehicle"
Packet = VehicleDefinition.converter
- Damage = StandardVehicleDamage
- Resistance = StandardVehicleResistance
+ DamageUsing = StandardVehicleDamage
+ ResistUsing = StandardVehicleResistance
Model = StandardResolutions.Vehicle
-
- def MaxHealth : Int = maxHealth
-
- def MaxHealth_=(health : Int) : Int = {
- maxHealth = health
- MaxHealth
- }
+ RepairDistance = 10
+ RepairRestoresAt = 1
def MaxShields : Int = maxShields
diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
index bfb04db8d..b80024b20 100644
--- a/common/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
+++ b/common/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
@@ -17,7 +17,7 @@ class SmallDeployableConverter extends ObjectCreateConverter[PlanetSideGameObjec
CommonFieldData(
obj.Faction,
bops = false,
- alternate = false,
+ alternate = obj.Destroyed,
false,
None,
jammered = obj match {
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala b/common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala
index 4ec1a6e70..a21a69adc 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/CommonMessages.scala
@@ -1,12 +1,13 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject
-import net.psforever.objects.{PlanetSideGameObject, Player}
+import net.psforever.objects.Player
+import net.psforever.objects.serverobject.hackable.Hackable
//temporary location for these messages
object CommonMessages {
final case class Use(player : Player, data : Option[Any] = None)
final case class Unuse(player : Player, data : Option[Any] = None)
- final case class Hack(player : Player)
+ final case class Hack(player : Player, obj : PlanetSideServerObject with Hackable, data : Option[Any] = None)
final case class ClearHack()
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala b/common/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala
new file mode 100644
index 000000000..41a28d962
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala
@@ -0,0 +1,114 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.damage
+
+import akka.actor.Actor.Receive
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.equipment.JammableUnit
+import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.affinity.FactionAffinity
+import net.psforever.objects.serverobject.hackable.Hackable
+import net.psforever.objects.vital.Vitality
+
+/**
+ * The base "control" `Actor` mixin for damage-handling code.
+ * A valid entity requires health points and
+ * may have additional obstructions to adjusting those health points such as armor and shields.
+ * All of these should be affected by the damage where applicable.
+ */
+trait Damageable {
+ /**
+ * Contextual access to the object being the target of this damage.
+ * Needs declaration in lowest implementing code.
+ * @return the entity controlled by this actor
+ */
+ def DamageableObject : Damageable.Target
+
+ /** the official mixin hook; `orElse` onto the "control" `Actor` `receive` */
+ final val takesDamage : Receive = TakesDamage
+
+ /**
+ * Implementation of the mixin hook will be provided by a child class.
+ * Override this method only when directly implementing.
+ * @see `takesDamage`
+ * @see `DamageableAmenity.PerformDamage`
+ */
+ protected def TakesDamage : Receive
+}
+
+object Damageable {
+ /* the type of all entities governed by this mixin; see Repairable.Target */
+ final type Target = PlanetSideServerObject with Vitality
+ /* the master channel for logging damage resolution information
+ * the format of the channel is expected to follow:
+ * "[identifier]: BEFORE=[before1/before2/etc.] AFTER=[after1/after2/etc.] CHANGE=[change1/change2/etc.]"
+ * ... where before1 - change1 = after1, and so forth, for each field that matters
+ * the fields do not have to be labeled but the first (if not only) should always be Health
+ */
+ final val LogChannel : String = "DamageResolution"
+
+ /**
+ * Does the possibility exist that the designated target can be affected by this projectile's damage?
+ * @see `Hackable`
+ * @see `ObjectDefinition.DamageableByFriendlyFire`
+ * @param obj the entity being damaged
+ * @param damage the amount of damage
+ * @param data historical information about the damage
+ * @return `true`, if the target can be affected;
+ * `false`, otherwise
+ */
+ def CanDamage(obj : Vitality with FactionAffinity, damage : Int, data : ResolvedProjectile) : Boolean = {
+ val definition = obj.Definition
+ damage > 0 &&
+ definition.Damageable &&
+ (definition.DamageableByFriendlyFire ||
+ (data.projectile.owner.Faction != obj.Faction ||
+ (obj match {
+ case hobj : Hackable => hobj.HackedBy.nonEmpty
+ case _ => false
+ })
+ )
+ )
+ }
+
+ /**
+ * Does the possibility exist that the designated target can be affected by this projectile's jammer effect?
+ * @see `Hackable`
+ * @see `ProjectileDefinition..JammerProjectile`
+ * @param obj the entity being damaged
+ * @param data historical information about the damage
+ * @return `true`, if the target can be affected;
+ * `false`, otherwise
+ */
+ def CanJammer(obj : Vitality with FactionAffinity, data : ResolvedProjectile) : Boolean = {
+ val projectile = data.projectile
+ projectile.profile.JammerProjectile &&
+ obj.isInstanceOf[JammableUnit] &&
+ (projectile.owner.Faction != obj.Faction ||
+ (obj match {
+ case hobj : Hackable => hobj.HackedBy.nonEmpty
+ case _ => false
+ })
+ )
+ }
+
+ /**
+ * Does the possibility exist that the designated target can be affected by this projectile?
+ * @param obj the entity being damaged
+ * @param damage the amount of damage
+ * @param data historical information about the damage
+ * @return `true`, if the target can be affected;
+ * `false`, otherwise
+ */
+ def CanDamageOrJammer(obj : Vitality with FactionAffinity, damage : Int, data : ResolvedProjectile) : Boolean = {
+ CanDamage(obj, damage, data) || CanJammer(obj, data)
+ }
+
+ /**
+ * The entity has ben destroyed.
+ * @param target the entity being damaged
+ * @param cause historical information about the damage
+ */
+ def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
+ target.Destroyed = true
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableAmenity.scala b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableAmenity.scala
new file mode 100644
index 000000000..fbe580524
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableAmenity.scala
@@ -0,0 +1,42 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.damage
+
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.serverobject.structures.Amenity
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+
+/**
+ * The "control" `Actor` mixin for damage-handling code
+ * for the majority of `Damageable` `Amenity` objects installed in a facility or a field tower,
+ * with specific exceptions for the `ImplantTerminalMech` and the `Generator`.
+ */
+trait DamageableAmenity extends DamageableEntity {
+ def DamageableObject : Amenity
+
+ override protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
+ super.DestructionAwareness(target, cause)
+ DamageableAmenity.DestructionAwareness(target, cause)
+ target.ClearHistory()
+ }
+}
+
+object DamageableAmenity {
+ /**
+ * A destroyed `Amenity` target dispatches two messages to chance its model and operational states.
+ * The common manifestation is a sparking entity that will no longer report being accessible.
+ * These `PlanetSideAttributeMessage` attributes are the same as reported during zone load client configuration.
+ * @see `AvatarAction.PlanetsideAttributeToAll`
+ * @see `AvatarServiceMessage`
+ * @see `Zone.AvatarEvents`
+ * @param target the entity being destroyed
+ * @param cause historical information about the damage
+ */
+ def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
+ val zone = target.Zone
+ val zoneId = zone.Id
+ val events = zone.AvatarEvents
+ val targetGUID = target.GUID
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 50, 1))
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 51, 1))
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala
new file mode 100644
index 000000000..f4c46079e
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableEntity.scala
@@ -0,0 +1,203 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.damage
+
+import akka.actor.Actor.Receive
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.equipment.JammableUnit
+import net.psforever.objects.vital.Vitality
+import net.psforever.objects.vital.resolution.ResolutionCalculations
+import net.psforever.objects.zones.Zone
+import net.psforever.types.PlanetSideGUID
+import services.Service
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+
+/**
+ * The "control" `Actor` mixin for damage-handling code,
+ * for both expansion into other mixins and specific application on its own.
+ */
+trait DamageableEntity extends Damageable {
+ /** log specifically for damage events */
+ private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
+
+ /**
+ * Log a damage message.
+ * @param msg the message for the damage log
+ */
+ protected def DamageLog(msg : String) : Unit = {
+ damageLog.info(msg)
+ }
+ /**
+ * Log a damage message with a decorator for this target.
+ * The decorator is constructed by the `Actor` name of the entity, sliced after the last forward/slash.
+ * For example, for "foo/bar/name", the decorator is just "name".
+ * @see `PlanetSideServerObject`
+ * @param target the entity to be used for the decorator
+ * @param msg the message for the damage log
+ */
+ protected def DamageLog(target : Damageable.Target, msg : String) : Unit = {
+ val name = target.Actor.toString
+ val slashPoint = name.lastIndexOf("/")
+ DamageLog(s"${name.substring(slashPoint + 1, name.length - 1)}: $msg")
+ }
+
+ /**
+ * Catch the expected damage message and apply checks to the target.
+ * If adding custom message handling in an future child implementation,
+ * override this method and call `super.TakesDamage.orElse { ... }`.
+ * @see `Damageable.TakesDamage`
+ * @see `ResolutionCalcultions.Output`
+ * @see `Vitality.CanDamage`
+ * @see `Vitality.Damage`
+ */
+ protected def TakesDamage : Receive = {
+ case Vitality.Damage(damage_func) =>
+ val obj = DamageableObject
+ if(obj.CanDamage) {
+ PerformDamage(obj, damage_func)
+ }
+ }
+
+ /**
+ * Assess the vital statistics of the target, apply the damage, and determine if any of those statistics changed.
+ * By default, only take an interest in the change of "health".
+ * If implementing custom `DamageableAmenity` with no new message handling, choose to override this method.
+ * @see `DamageableAmenity.TakesDamage`
+ * @see `ResolutionCalculations.Output`
+ * @see `Vitality.Health`
+ * @param target the entity to be damaged
+ * @param applyDamageTo the function that applies the damage to the target in a target-tailored fashion
+ */
+ protected def PerformDamage(target : Damageable.Target, applyDamageTo : ResolutionCalculations.Output) : Unit = {
+ val originalHealth = target.Health
+ val cause = applyDamageTo(target)
+ val health = target.Health
+ val damage = originalHealth - health
+ if(WillAffectTarget(target, damage, cause)) {
+ target.History(cause)
+ DamageLog(target, s"BEFORE=$originalHealth, AFTER=$health, CHANGE=$damage")
+ HandleDamage(target, cause, damage)
+ }
+ else {
+ target.Health = originalHealth
+ }
+ }
+
+ /**
+ * Does the damage or the projectile that caused the damage offer any reason
+ * to execute the reminder of damage resolution considerations?
+ * The projectile causing additional affects, e.g., jamming, should be tested here, when applicable.
+ * Contrast with `Vitality.CanDamage`.
+ * The damage value tested against should be the total value of all meaningful vital statistics affected.
+ * @see `Damageable.CanDamageOrJammer`
+ * @see `PerformDamage`
+ * @param target the entity to be damaged
+ * @param damage the amount of damage
+ * @param cause historical information about the damage
+ * @return `true`, if damage resolution is to be evaluated;
+ * `false`, otherwise
+ */
+ protected def WillAffectTarget(target : Damageable.Target, damage : Int, cause : ResolvedProjectile) : Boolean = {
+ Damageable.CanDamageOrJammer(target, damage, cause)
+ }
+
+ /**
+ * Select between mere damage reception or target destruction.
+ * @see `VitalDefinition.DamageDestroysAt`
+ * @param target the entity being damaged
+ * @param cause historical information about the damage
+ * @param damage the amount of damage
+ */
+ protected def HandleDamage(target : Damageable.Target, cause : ResolvedProjectile, damage : Int) : Unit = {
+ if(!target.Destroyed && target.Health <= target.Definition.DamageDestroysAt) {
+ DestructionAwareness(target, cause)
+ }
+ else {
+ DamageAwareness(target, cause, damage)
+ }
+ }
+
+ /**
+ * What happens when damage is sustained but the target does not get destroyed.
+ * @param target the entity being damaged
+ * @param cause historical information about the damage
+ * @param amount the amount of damage
+ */
+ protected def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Int) : Unit = {
+ DamageableEntity.DamageAwareness(target, cause, amount)
+ }
+
+ /**
+ * What happens when the target sustains too much damage and is destroyed.
+ * @see `Damageable.DestructionAwareness`
+ * @param target the entity being destroyed
+ * @param cause historical information about the damage
+ */
+ protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
+ Damageable.DestructionAwareness(target, cause)
+ DamageableEntity.DestructionAwareness(target, cause)
+ }
+}
+
+object DamageableEntity {
+ /**
+ * A damaged target dispatches messages to:
+ * - reports its adjusted its health;
+ * - alert the activity monitor for that `Zone` about the damage; and,
+ * - provide a feedback message regarding the damage.
+ * @see `AvatarAction.PlanetsideAttributeToAll`
+ * @see `AvatarAction.SendResponse`
+ * @see `AvatarServiceMessage`
+ * @see `DamageFeedbackMessage`
+ * @see `JammableUnit.Jammered`
+ * @see `Service.defaultPlayerGUID`
+ * @see `Zone.Activity`
+ * @see `Zone.AvatarEvents`
+ * @see `Zone.HotSpot.Activity`
+ * @see `Zone.LivePlayers`
+ * @param target the entity being damaged
+ * @param cause historical information about the damage
+ */
+ def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Int) : Unit = {
+ if(Damageable.CanJammer(target, cause)) {
+ target.Actor ! JammableUnit.Jammered(cause)
+ }
+ if(amount > 0) {
+ val zone = target.Zone
+ if(!target.Destroyed) {
+ val tguid = target.GUID
+ zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health))
+ }
+ zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
+ }
+ }
+
+ /**
+ * A destroyed target dispatches messages to:
+ * - reports its adjusted its health; and,
+ * - report about its destruction.
+ * @see `AvatarAction.Destroy`
+ * @see `AvatarAction.PlanetsideAttribute`
+ * @see `AvatarServiceMessage`
+ * @see `DamageFeedbackMessage`
+ * @see `JammableUnit.ClearJammeredSound`
+ * @see `JammableUnit.ClearJammeredStatus`
+ * @see `Zone.AvatarEvents`
+ * @param target the entity being destroyed
+ * @param cause historical information about the damage
+ */
+ def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
+ //un-jam
+ target.Actor ! JammableUnit.ClearJammeredSound()
+ target.Actor ! JammableUnit.ClearJammeredStatus()
+ //
+ val zone = target.Zone
+ val zoneId = zone.Id
+ val tguid = target.GUID
+ val attribution = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
+ case Some(player) => player.GUID
+ case _ => PlanetSideGUID(0)
+ }
+ zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, target.Health))
+ zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.Destroy(tguid, attribution, Service.defaultPlayerGUID, target.Position))
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala
new file mode 100644
index 000000000..986e40f9a
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableMountable.scala
@@ -0,0 +1,72 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.damage
+
+import net.psforever.objects.Player
+import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile}
+import net.psforever.objects.serverobject.mount.Mountable
+import net.psforever.packet.game.DamageWithPositionMessage
+import services.Service
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+
+/**
+ * Functions to assist other damage-dealing code for objects that contain users.
+ */
+object DamageableMountable {
+ /**
+ * A damaged target alerts its occupants (as it is a `Mountable` object) of the source of the damage.
+ * @see `AvatarAction.HitHint`
+ * @see `AvatarAction.SendResponse`
+ * @see `AvatarServiceMessage`
+ * @see `DamageWithPositionMessage`
+ * @see `Mountable.Seats`
+ * @see `Service.defaultPlayerGUID`
+ * @see `Zone.AvatarEvents`
+ * @see `Zone.LivePlayers`
+ * @param target the entity being damaged
+ * @param cause historical information about the damage
+ */
+ def DamageAwareness(target : Damageable.Target with Mountable, cause : ResolvedProjectile) : Unit = {
+ val zone = target.Zone
+ val events = zone.AvatarEvents
+ val occupants = target.Seats.values.collect {
+ case seat if seat.isOccupied && seat.Occupant.get.isAlive =>
+ seat.Occupant.get
+ }
+ (cause.projectile.owner match {
+ case pSource : PlayerSource => //player damage
+ val name = pSource.Name
+ (zone.LivePlayers.find(_.Name == name).orElse(zone.Corpses.find(_.Name == name)) match {
+ case Some(player) => AvatarAction.HitHint(player.GUID, player.GUID)
+ case None => AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, pSource.Position))
+ }) match {
+ case AvatarAction.HitHint(_, guid) =>
+ occupants.map { tplayer => (tplayer.Name, AvatarAction.HitHint(guid, tplayer.GUID)) }
+ case msg =>
+ occupants.map { tplayer => (tplayer.Name, msg) }
+ }
+ case source => //object damage
+ val msg = AvatarAction.SendResponse(Service.defaultPlayerGUID, DamageWithPositionMessage(10, source.Position))
+ occupants.map { tplayer => (tplayer.Name, msg) }
+ }).foreach { case (channel, msg) =>
+ events ! AvatarServiceMessage(channel, msg)
+ }
+ }
+
+ /**
+ * When the target dies, so do all of its occupants.
+ * @see `Mountable.Seats`
+ * @see `Player.Die`
+ * @see `VitalsHistory.History`
+ * @param target the entity being destroyed
+ * @param cause historical information about the damage
+ */
+ def DestructionAwareness(target : Damageable.Target with Mountable, cause : ResolvedProjectile) : Unit = {
+ target.Seats.values.filter(seat => {
+ seat.isOccupied && seat.Occupant.get.isAlive
+ }).foreach(seat => {
+ val tplayer = seat.Occupant.get
+ tplayer.History(cause)
+ tplayer.Actor ! Player.Die()
+ })
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
new file mode 100644
index 000000000..dce43d36a
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableVehicle.scala
@@ -0,0 +1,187 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.damage
+
+import akka.actor.Actor.Receive
+import net.psforever.objects.{GlobalDefinitions, Vehicle}
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.serverobject.damage.Damageable.Target
+import net.psforever.objects.serverobject.deploy.Deployment
+import net.psforever.objects.vital.resolution.ResolutionCalculations
+import net.psforever.types.{DriveState, PlanetSideGUID}
+import services.{RemoverActor, Service}
+import services.local.{LocalAction, LocalServiceMessage}
+import services.vehicle.{VehicleAction, VehicleService, VehicleServiceMessage}
+
+import scala.concurrent.duration._
+
+/**
+ * The "control" `Actor` mixin for damage-handling code for `Vehicle` objects.
+ */
+trait DamageableVehicle extends DamageableEntity {
+ /** vehicles (may) have shields; they need to be handled */
+ private var handleDamageToShields : Boolean = false
+ /** whether or not the vehicle has been damaged directly, report that damage has occurred */
+ private var reportDamageToVehicle : Boolean = false
+
+ def DamageableObject : Vehicle
+
+ override protected def TakesDamage : Receive =
+ super.TakesDamage.orElse {
+ case DamageableVehicle.Damage(cause, damage) =>
+ //cargo vehicles inherit feedback from carrier
+ reportDamageToVehicle = damage > 0
+ DamageAwareness(DamageableObject, cause, amount = 0)
+
+ case DamageableVehicle.Destruction(cause) =>
+ //cargo vehicles are destroyed when carrier is destroyed
+ val obj = DamageableObject
+ obj.Health = 0
+ obj.History(cause)
+ DestructionAwareness(obj, cause)
+ }
+
+ /**
+ * Vehicles may have charged shields that absorb damage before the vehicle's own health is affected.
+ * @param target the entity to be damaged
+ * @param applyDamageTo the function that applies the damage to the target in a target-tailored fashion
+ */
+ override protected def PerformDamage(target : Damageable.Target, applyDamageTo : ResolutionCalculations.Output) : Unit = {
+ val obj = DamageableObject
+ val originalHealth = obj.Health
+ val originalShields = obj.Shields
+ val cause = applyDamageTo(obj)
+ val health = obj.Health
+ val shields = obj.Shields
+ val damageToHealth = originalHealth - health
+ val damageToShields = originalShields - shields
+ if(WillAffectTarget(target, damageToHealth + damageToShields, cause)) {
+ target.History(cause)
+ DamageLog(target, s"BEFORE=$originalHealth/$originalShields, AFTER=$health/$shields, CHANGE=$damageToHealth/$damageToShields")
+ handleDamageToShields = damageToShields > 0
+ HandleDamage(target, cause, damageToHealth + damageToShields)
+ }
+ else {
+ obj.Health = originalHealth
+ obj.Shields = originalShields
+ }
+ }
+
+ override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Int) : Unit = {
+ val obj = DamageableObject
+ val handleShields = handleDamageToShields
+ handleDamageToShields = false
+ val handleReport = reportDamageToVehicle || amount > 0
+ reportDamageToVehicle = false
+ if(Damageable.CanDamageOrJammer(target, amount, cause)) {
+ super.DamageAwareness(target, cause, amount)
+ }
+ if(handleReport) {
+ DamageableMountable.DamageAwareness(obj, cause)
+ }
+ DamageableVehicle.DamageAwareness(obj, cause, amount, handleShields)
+ }
+
+ override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
+ super.DestructionAwareness(target, cause)
+ val obj = DamageableObject
+ DamageableMountable.DestructionAwareness(obj, cause)
+ DamageableVehicle.DestructionAwareness(obj, cause)
+ DamageableWeaponTurret.DestructionAwareness(obj, cause)
+ }
+}
+
+object DamageableVehicle {
+ /**
+ * Message for instructing the target's cargo vehicles about a damage source affecting their carrier.
+ * @param cause historical information about damage
+ */
+ private case class Damage(cause : ResolvedProjectile, amount : Int)
+ /**
+ * Message for instructing the target's cargo vehicles that their carrier is destroyed,
+ * and they should be destroyed too.
+ * @param cause historical information about damage
+ */
+ private case class Destruction(cause : ResolvedProjectile)
+
+ /**
+ * Most all vehicles and the weapons mounted to them can jam
+ * if the projectile that strikes (near) them has jammering properties.
+ * A damaged carrier alerts its cargo vehicles of the source of the damage,
+ * but it will not be affected by the same jammering effect.
+ * If this vehicle has shields that were affected by previous damage, that is also reported to the clients.
+ * @see `Service.defaultPlayerGUID`
+ * @see `Vehicle.CargoHolds`
+ * @see `VehicleAction.PlanetsideAttribute`
+ * @see `VehicleServiceMessage`
+ * @param target the entity being destroyed
+ * @param cause historical information about the damage
+ * @param damage how much damage was performed
+ * @param damageToShields dispatch a shield strength update
+ */
+ def DamageAwareness(target : Vehicle, cause : ResolvedProjectile, damage : Int, damageToShields : Boolean) : Unit = {
+ //alert cargo occupants to damage source
+ target.CargoHolds.values.foreach(hold => {
+ hold.Occupant match {
+ case Some(cargo) =>
+ cargo.Actor ! DamageableVehicle.Damage(cause, damage + (if(damageToShields) 1 else 0))
+ case None => ;
+ }
+ })
+ //shields
+ if(damageToShields) {
+ val zone = target.Zone
+ zone.VehicleEvents ! VehicleServiceMessage(s"${target.Actor}", VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, target.Shields))
+ }
+ }
+
+ /**
+ * A destroyed carrier informs its cargo vehicles that they should also be destroyed
+ * for reasons of the same cause being inherited as the source of damage.
+ * Regardless of the amount of damage they carrier takes or some other target would take,
+ * its cargo vehicles die immediately.
+ * The vehicle's shields are zero'd out if they were previously energized
+ * so that the vehicle's corpse does not act like it is still protected by vehicle shields.
+ * Finally, the vehicle is tasked for deconstruction.
+ * @see `Deployment.TryDeploymentChange`
+ * @see `DriveState.Undeploying`
+ * @see `Service.defaultPlayerGUID`
+ * @see `Vehicle.CargoHolds`
+ * @see `VehicleAction.PlanetsideAttribute`
+ * @see `RemoverActor.AddTask`
+ * @see `RemoverActor.ClearSpecific`
+ * @see `VehicleServiceMessage`
+ * @see `VehicleServiceMessage.Decon`
+ * @see `Zone.VehicleEvents`
+ * @param target the entity being destroyed
+ * @param cause historical information about the damage
+ */
+ def DestructionAwareness(target : Vehicle, cause : ResolvedProjectile) : Unit = {
+ val zone = target.Zone
+ //cargo vehicles die with us
+ target.CargoHolds.values.foreach(hold => {
+ hold.Occupant match {
+ case Some(cargo) =>
+ cargo.Actor ! DamageableVehicle.Destruction(cause)
+ case None => ;
+ }
+ })
+ //special considerations for certain vehicles
+ target.Definition match {
+ case GlobalDefinitions.ams =>
+ target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
+ case GlobalDefinitions.router =>
+ target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
+ VehicleService.BeforeUnloadVehicle(target, zone)
+ zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), target, None))
+ case _ => ;
+ }
+ //shields
+ if(target.Shields > 0) {
+ target.Shields = 0
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 68, 0))
+ }
+ zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), zone))
+ zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, zone, Some(1 minute)))
+ target.ClearHistory()
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala
new file mode 100644
index 000000000..6be219348
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/damage/DamageableWeaponTurret.scala
@@ -0,0 +1,78 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.damage
+
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.serverobject.turret.{TurretUpgrade, WeaponTurret}
+import net.psforever.objects.vehicles.MountedWeapons
+import services.Service
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+import services.vehicle.support.TurretUpgrader
+import services.vehicle.VehicleServiceMessage
+
+/**
+ * The "control" `Actor` mixin for damage-handling code for `WeaponTurret` objects.
+ */
+trait DamageableWeaponTurret extends DamageableEntity {
+ def DamageableObject : Damageable.Target with WeaponTurret
+
+ override protected def DamageAwareness(target : Damageable.Target, cause : ResolvedProjectile, amount : Int) : Unit = {
+ super.DamageAwareness(target, cause, amount)
+ if(amount > 0) {
+ DamageableMountable.DamageAwareness(DamageableObject, cause)
+ }
+ }
+
+ override protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
+ super.DestructionAwareness(target, cause)
+ val obj = DamageableObject
+ DamageableWeaponTurret.DestructionAwareness(obj, cause)
+ DamageableMountable.DestructionAwareness(obj, cause)
+ }
+}
+
+object DamageableWeaponTurret {
+ /**
+ * A destroyed target dispatches a message to conceal (delete) its weapons from users.
+ * If affected by a jammer property, the jammer propoerty will be removed.
+ * If the type of entity is a `WeaponTurret`, the weapons are converted to their "normal" upgrade state.
+ * @see `AvatarAction.DeleteObject`
+ * @see `AvatarServiceMessage`
+ * @see `MountedWeapons`
+ * @see `MountedWeapons.Weapons`
+ * @see `Service.defaultPlayerGUID`
+ * @see `TurretUpgrade.None`
+ * @see `TurretUpgrader.AddTask`
+ * @see `TurretUpgrader.ClearSpecific`
+ * @see `WeaponTurret`
+ * @see `VehicleServiceMessage.TurretUpgrade`
+ * @see `Zone.AvatarEvents`
+ * @see `Zone.VehicleEvents`
+ * @param target the entity being destroyed;
+ * note: `MountedWeapons` is a parent of `WeaponTurret`
+ * but the handling code closely associates with the former
+ * @param cause historical information about the damage
+ */
+ def DestructionAwareness(target : Damageable.Target with MountedWeapons, cause : ResolvedProjectile) : Unit = {
+ //wreckage has no (visible) mounted weapons
+ val zone = target.Zone
+ val zoneId = zone.Id
+ val avatarEvents = zone.AvatarEvents
+ target.Weapons.values
+ .filter {
+ _.Equipment.nonEmpty
+ }
+ .foreach(slot => {
+ val wep = slot.Equipment.get
+ avatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
+ })
+ target match {
+ case turret : WeaponTurret =>
+ if(turret.Upgrade != TurretUpgrade.None) {
+ val vehicleEvents = zone.VehicleEvents
+ vehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.ClearSpecific(List(turret), zone))
+ vehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(turret, zone, TurretUpgrade.None))
+ }
+ case _ =>
+ }
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala
index 127d1623b..27781b446 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/doors/Door.scala
@@ -4,7 +4,6 @@ package net.psforever.objects.serverobject.doors
import net.psforever.objects.Player
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.UseItemMessage
-import net.psforever.types.Vector3
/**
* A structure-owned server object that is a "door" that can open and can close.
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorDefinition.scala
index 6a22670cd..6864f3a1a 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/doors/DoorDefinition.scala
@@ -1,12 +1,12 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.doors
-import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.structures.AmenityDefinition
/**
* The definition for any `Door`.
* Object Id 242 is a generic door.
*/
-class DoorDefinition extends ObjectDefinition(242) {
+class DoorDefinition extends AmenityDefinition(242) {
Name = "door"
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala b/common/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala
index e7838b573..d1309502d 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/generator/Generator.scala
@@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.generator
import net.psforever.objects.serverobject.structures.Amenity
+import net.psforever.types.PlanetSideGeneratorState
/**
* The generator is a big feature of all major facilities.
@@ -14,7 +15,15 @@ import net.psforever.objects.serverobject.structures.Amenity
* @param gdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
class Generator(private val gdef : GeneratorDefinition) extends Amenity {
- //TODO should have Vitality, to indicate damaged/destroyed property
+ private var condition : PlanetSideGeneratorState.Value = PlanetSideGeneratorState.Normal
+
+ def Condition : PlanetSideGeneratorState.Value = condition
+
+ def Condition_=(state : PlanetSideGeneratorState.Value) : PlanetSideGeneratorState.Value = {
+ condition = state
+ Condition
+ }
+
def Definition : GeneratorDefinition = gdef
}
@@ -24,15 +33,6 @@ object Generator {
}
import akka.actor.ActorContext
- def Constructor(id : Int, context : ActorContext) : Generator = {
- import akka.actor.Props
- import net.psforever.objects.GlobalDefinitions
-
- val obj = Generator(GlobalDefinitions.generator)
- obj.Actor = context.actorOf(Props(classOf[GeneratorControl], obj), s"${obj.Definition.Name}_$id")
- obj
- }
-
import net.psforever.types.Vector3
def Constructor(pos : Vector3)(id : Int, context : ActorContext) : Generator = {
import akka.actor.Props
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala
index 19fceeb24..b7be5b051 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorControl.scala
@@ -2,17 +2,147 @@
package net.psforever.objects.serverobject.generator
import akka.actor.Actor
-import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.{Player, Tool}
+import net.psforever.objects.ballistics._
+import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
+import net.psforever.objects.serverobject.damage.Damageable.Target
+import net.psforever.objects.serverobject.damage.DamageableEntity
+import net.psforever.objects.serverobject.repair.{Repairable, RepairableEntity}
+import net.psforever.objects.serverobject.structures.Building
+import net.psforever.objects.vital.DamageFromExplosion
+import net.psforever.packet.game.TriggerEffectMessage
+import net.psforever.types.{PlanetSideGeneratorState, Vector3}
+import services.Service
+import services.avatar.{AvatarAction, AvatarServiceMessage}
/**
* An `Actor` that handles messages being dispatched to a specific `Generator`.
* @param gen the `Generator` object being governed
*/
class GeneratorControl(gen : Generator) extends Actor
- with FactionAffinityBehavior.Check {
- def FactionObject : FactionAffinity = gen
+ with FactionAffinityBehavior.Check
+ with DamageableEntity
+ with RepairableEntity {
+ def FactionObject = gen
+ def DamageableObject = gen
+ def RepairableObject = gen
+ var imminentExplosion : Boolean = false
- def receive : Receive = checkBehavior.orElse {
- case _ => ;
+ def receive : Receive = checkBehavior
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
+ .orElse {
+ case GeneratorControl.GeneratorExplodes() => //TODO this only works with projectiles right now!
+ val zone = gen.Zone
+ gen.Health = 0
+ super.DestructionAwareness(gen, gen.LastShot.get)
+ gen.Condition = PlanetSideGeneratorState.Destroyed
+ GeneratorControl.UpdateOwner(gen)
+ //kaboom
+ zone.AvatarEvents ! AvatarServiceMessage(
+ zone.Id, AvatarAction.SendResponse(
+ Service.defaultPlayerGUID, TriggerEffectMessage(gen.GUID, "explosion_generator", None, None)
+ )
+ )
+ imminentExplosion = false
+ //kill everyone within 14m
+ gen.Owner match {
+ case b : Building =>
+ val genDef = gen.Definition
+ b.PlayersInSOI.collect {
+ case player if player.isAlive && Vector3.DistanceSquared(player.Position, gen.Position) < 196 =>
+ player.History(DamageFromExplosion(PlayerSource(player), genDef))
+ player.Actor ! Player.Die()
+ }
+ case _ => ;
+ }
+ gen.ClearHistory()
+
+ case _ => ;
+ }
+
+ override protected def CanPerformRepairs(obj : Target, player : Player, item : Tool) : Boolean = {
+ !imminentExplosion && super.CanPerformRepairs(obj, player, item)
+ }
+
+ override protected def WillAffectTarget(target : Target, damage : Int, cause : ResolvedProjectile) : Boolean = {
+ !imminentExplosion && super.WillAffectTarget(target, damage, cause)
+ }
+
+ override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Int) : Unit = {
+ super.DamageAwareness(target, cause, amount)
+ GeneratorControl.DamageAwareness(gen, cause, amount)
+ }
+
+ override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
+ if(!target.Destroyed) {
+ target.Health = 1 //temporary
+ imminentExplosion = true
+ import scala.concurrent.duration._
+ import scala.concurrent.ExecutionContext.Implicits.global
+ context.system.scheduler.scheduleOnce(10 seconds, self, GeneratorControl.GeneratorExplodes())
+ GeneratorControl.BroadcastGeneratorEvent(gen, 16)
+ }
+ }
+
+ override def Restoration(obj : Repairable.Target) : Unit = {
+ super.Restoration(obj)
+ gen.Condition = PlanetSideGeneratorState.Normal
+ GeneratorControl.UpdateOwner(gen)
+ GeneratorControl.BroadcastGeneratorEvent(gen, 17)
+ }
+}
+
+object GeneratorControl {
+ /**
+ * na
+ */
+ private case class GeneratorExplodes()
+
+ /**
+ * na
+ * @param obj na
+ */
+ private def UpdateOwner(obj : Generator) : Unit = {
+ obj.Owner match {
+ case b : Building => b.Actor ! Building.AmenityStateChange(obj)
+ case _ => ;
+ }
+ }
+
+ /**
+ * na
+ * @param target the generator
+ * @param event the action code for the event
+ */
+ private def BroadcastGeneratorEvent(target : Generator, event : Int) : Unit = {
+ target.Owner match {
+ case b : Building =>
+ val events = target.Zone.AvatarEvents
+ val msg = AvatarAction.GenericObjectAction(Service.defaultPlayerGUID, target.Owner.GUID, event)
+ b.PlayersInSOI.foreach { player =>
+ events ! AvatarServiceMessage(player.Name, msg)
+ }
+ case _ => ;
+ }
+ }
+
+ /**
+ * If not destroyed, it will complain about being damaged.
+ * @param target the entity being damaged
+ * @param cause historical information about the damage
+ * @param amount the amount of damage
+ */
+ def DamageAwareness(target : Generator, cause : ResolvedProjectile, amount : Int) : Unit = {
+ if(!target.Destroyed) {
+ val health : Float = target.Health
+ val max : Float = target.MaxHealth
+ if(target.Condition != PlanetSideGeneratorState.Critical && health / max < 0.51f) { //becoming critical
+ target.Condition = PlanetSideGeneratorState.Critical
+ GeneratorControl.UpdateOwner(target)
+ }
+ //the generator is under attack
+ GeneratorControl.BroadcastGeneratorEvent(target, 15)
+ }
}
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorDefinition.scala
index a8e4a77fc..3e6236bdf 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/generator/GeneratorDefinition.scala
@@ -1,11 +1,11 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.generator
-import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.structures.AmenityDefinition
/**
* The definition for a `Generator` object.
*/
-class GeneratorDefinition(objectId : Int) extends ObjectDefinition(objectId) {
+class GeneratorDefinition(objectId : Int) extends AmenityDefinition(objectId) {
Name = "generator"
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/hackable/GenericHackables.scala b/common/src/main/scala/net/psforever/objects/serverobject/hackable/GenericHackables.scala
new file mode 100644
index 000000000..b47e1d6a1
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/hackable/GenericHackables.scala
@@ -0,0 +1,120 @@
+// Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.hackable
+
+import net.psforever.objects.{Player, Vehicle}
+import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
+import net.psforever.packet.game.{HackMessage, HackState}
+import net.psforever.types.PlanetSideGUID
+import services.Service
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+import services.local.{LocalAction, LocalServiceMessage}
+
+import scala.util.{Failure, Success}
+
+object GenericHackables {
+ private val log = org.log4s.getLogger("HackableBehavior")
+
+ /**
+ * na
+ * @param player the player doing the hacking
+ * @param obj the object being hacked
+ * @return the percentage amount of progress per tick
+ */
+ def GetHackSpeed(player : Player, obj: PlanetSideServerObject): Float = {
+ val playerHackLevel = Player.GetHackLevel(player)
+ val timeToHack = obj match {
+ case vehicle : Vehicle => vehicle.JackingDuration(playerHackLevel)
+ case hackable : Hackable => hackable.HackDuration(playerHackLevel)
+ case _ =>
+ log.warn(s"${player.Name} tried to hack an object that has no hack time defined - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.Id}")
+ 0
+ }
+ if(timeToHack == 0) {
+ log.warn(s"${player.Name} tried to hack an object that they don't have the correct hacking level for - ${obj.Definition.Name}#${obj.GUID} on ${obj.Zone.Id}")
+ 0f
+ }
+ else {
+ //timeToHack is in seconds; progress is measured in quarters of a second (250ms)
+ (100 / timeToHack) / 4
+ }
+ }
+
+ /**
+ * Evaluate the progress of the user applying a tool to modify some server object.
+ * This action is using the remote electronics kit to convert an enemy unit into an allied unit, primarily.
+ * The act of transforming allied units of one kind into allied units of another kind (facility turret upgrades)
+ * is also governed by this action per tick of progress.
+ * @see `HackMessage`
+ * @see `HackState`
+ * @param progressType 1 - remote electronics kit hack (various ...);
+ * 2 - nano dispenser (upgrade canister) turret upgrade
+ * @param tplayer the player performing the action
+ * @param target the object being affected
+ * @param tool_guid the tool being used to affest the object
+ * @param progress the current progress value
+ * @return `true`, if the next cycle of progress should occur;
+ * `false`, otherwise
+ */
+ def HackingTickAction(progressType : Int, tplayer : Player, target : PlanetSideServerObject, tool_guid : PlanetSideGUID)(progress : Float) : Boolean = {
+ //hack state for progress bar visibility
+ val vis = if(progress <= 0L) {
+ HackState.Start
+ }
+ else if(progress >= 100L) {
+ HackState.Finished
+ }
+ else if(target.isMoving(1f)) {
+ // If the object is moving (more than slightly to account for things like magriders rotating, or the last velocity reported being the magrider dipping down on dismount) then cancel the hack
+ HackState.Cancelled
+ }
+ else {
+ HackState.Ongoing
+ }
+ target.Zone.AvatarEvents ! AvatarServiceMessage(
+ tplayer.Name,
+ AvatarAction.SendResponse(Service.defaultPlayerGUID,
+ if(!target.HasGUID) {
+ //cancel the hack (target is gone)
+ HackMessage(progressType, target.GUID, tplayer.GUID, 0, 0L, HackState.Cancelled, 8L)
+ }
+ else if(vis == HackState.Cancelled) {
+ //cancel the hack (e.g. vehicle drove away)
+ HackMessage(progressType, target.GUID, tplayer.GUID, 0, 0L, vis, 8L)
+ }
+ else {
+ HackMessage(progressType, target.GUID, tplayer.GUID, progress.toInt, 0L, vis, 8L)
+ }
+ )
+ )
+ vis != HackState.Cancelled
+ }
+
+ /**
+ * The process of hacking an object is completed.
+ * Pass the message onto the hackable object and onto the local events system.
+ * @param target the `Hackable` object that has been hacked
+ * @param user the player that is performing this hacking task
+ * @param unk na;
+ * used by `HackMessage` as `unk5`
+ * @see `HackMessage`
+ */
+ //TODO add params here depending on which params in HackMessage are important
+ def FinishHacking(target : PlanetSideServerObject with Hackable, user : Player, unk : Long)() : Unit = {
+ import akka.pattern.ask
+ import scala.concurrent.duration._
+ log.info(s"Hacked a $target")
+ // Wait for the target actor to set the HackedBy property, otherwise LocalAction.HackTemporarily will not complete properly
+ import scala.concurrent.ExecutionContext.Implicits.global
+ val tplayer = user
+ ask(target.Actor, CommonMessages.Hack(tplayer, target))(1 second).mapTo[Boolean].onComplete {
+ case Success(_) =>
+ val zone = target.Zone
+ val zoneId = zone.Id
+ val pguid = tplayer.GUID
+ zone.LocalEvents ! LocalServiceMessage(zoneId, LocalAction.TriggerSound(pguid, target.HackSound, tplayer.Position, 30, 0.49803925f))
+ zone.LocalEvents ! LocalServiceMessage(zoneId, LocalAction.HackTemporarily(pguid, zone, target, unk, target.HackEffectDuration(Player.GetHackLevel(user))))
+ case Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}")
+ }
+ }
+}
+
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/hackable/Hackable.scala b/common/src/main/scala/net/psforever/objects/serverobject/hackable/Hackable.scala
index ace2fe5b0..cea75564c 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/hackable/Hackable.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/hackable/Hackable.scala
@@ -2,11 +2,12 @@ package net.psforever.objects.serverobject.hackable
import net.psforever.objects.Player
import net.psforever.objects.serverobject.affinity.FactionAffinity
+import net.psforever.objects.serverobject.hackable.Hackable.HackInfo
import net.psforever.packet.game.TriggeredSound
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
-trait Hackable extends FactionAffinity {
- import Hackable._
+trait Hackable {
+ _ : FactionAffinity =>
/** inportant information regarding the hack and how it was started */
private var hackedBy : Option[HackInfo] = None
def HackedBy : Option[HackInfo] = hackedBy
@@ -66,6 +67,18 @@ trait Hackable extends FactionAffinity {
hackDuration = arr
arr
}
+
+// private var hackable : Option[Boolean] = None
+// def Hackable : Boolean = hackable.getOrElse(Definition.Hackable)
+//
+// def Hackable_=(state : Boolean) : Boolean = Hackable_=(Some(state))
+//
+// def Hackable_=(state : Option[Boolean]) : Boolean = {
+// hackable = state
+// Hackable
+// }
+//
+// def Definition : HackableDefinition
}
object Hackable {
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/hackable/HackableBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/hackable/HackableBehavior.scala
index 8203a8826..9a30fd8db 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/hackable/HackableBehavior.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/hackable/HackableBehavior.scala
@@ -1,3 +1,4 @@
+// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.hackable
import akka.actor.Actor
@@ -6,19 +7,19 @@ import net.psforever.objects.serverobject.CommonMessages
object HackableBehavior {
/**
* The logic governing generic `Hackable` objects that use the `Hack` and `ClearHack` message.
- * This is a mix-in trait for combining with existing Receive` logic.
+ * This is a mix-in trait for combining with existing `Receive` logic.
* @see `Hackable`
*/
trait GenericHackable {
this : Actor =>
-
def HackableObject : Hackable
val hackableBehavior : Receive = {
- case CommonMessages.Hack(player) =>
+ case CommonMessages.Hack(player, _, _) =>
val obj = HackableObject
obj.HackedBy = player
sender ! true
+
case CommonMessages.ClearHack() =>
val obj = HackableObject
obj.HackedBy = None
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/hackable/HackableDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/hackable/HackableDefinition.scala
new file mode 100644
index 000000000..f22f65c90
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/hackable/HackableDefinition.scala
@@ -0,0 +1,21 @@
+// Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.hackable
+
+class HackableDefinition {
+ private var hackable : Boolean = false
+ private var magicNumber : Long = 0
+
+ def Hackable : Boolean = hackable
+
+ def Hackable_=(state : Boolean) : Boolean = {
+ hackable = state
+ Hackable
+ }
+
+ def MagicNumber : Long = magicNumber
+
+ def MagicNumber_=(magic : Long) : Long = {
+ magicNumber = magic
+ MagicNumber
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMech.scala b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMech.scala
index 8e9b6f673..b6fed718d 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMech.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMech.scala
@@ -2,12 +2,12 @@
package net.psforever.objects.serverobject.implantmech
import net.psforever.objects.Player
-import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.vehicles.Seat
import net.psforever.packet.game.TriggeredSound
+import net.psforever.types.Vector3
/**
* A structure-owned server object that is the visible and `Mountable` component of an implant terminal.
@@ -38,7 +38,7 @@ class ImplantTerminalMech(private val idef : ImplantTerminalMechDefinition) exte
}
}
- def Definition : ObjectDefinition = idef
+ def Definition : ImplantTerminalMechDefinition = idef
}
object ImplantTerminalMech {
@@ -53,10 +53,21 @@ object ImplantTerminalMech {
import akka.actor.ActorContext
/**
* Instantiate an configure a `ImplantTerminalMech` object
+ * @param pos the position of the entity
* @param id the unique id that will be assigned to this entity
* @param context a context to allow the object to properly set up `ActorSystem` functionality
* @return the `ImplantTerminalMech` object
*/
+ def Constructor(pos : Vector3)(id : Int, context : ActorContext) : ImplantTerminalMech = {
+ import akka.actor.Props
+ import net.psforever.objects.GlobalDefinitions
+
+ val obj = ImplantTerminalMech(GlobalDefinitions.implant_terminal_mech)
+ obj.Position = pos
+ obj.Actor = context.actorOf(Props(classOf[ImplantTerminalMechControl], obj), s"${GlobalDefinitions.implant_terminal_mech.Name}_$id")
+ obj
+ }
+ @deprecated("use implant terminal mechs that have position","destroyAndRepair")
def Constructor(id : Int, context : ActorContext) : ImplantTerminalMech = {
import akka.actor.Props
import net.psforever.objects.GlobalDefinitions
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala
index ccf02782b..4824abed6 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala
@@ -2,26 +2,73 @@
package net.psforever.objects.serverobject.implantmech
import akka.actor.Actor
-import net.psforever.objects.serverobject.mount.MountableBehavior
-import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.{GlobalDefinitions, Player, SimpleItem}
+import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
+import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
+import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
+import net.psforever.objects.serverobject.damage.Damageable.Target
+import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity, DamageableMountable}
import net.psforever.objects.serverobject.hackable.HackableBehavior
+import net.psforever.objects.serverobject.repair.RepairableEntity
+import net.psforever.objects.serverobject.structures.Building
/**
* An `Actor` that handles messages being dispatched to a specific `ImplantTerminalMech`.
* @param mech the "mech" object being governed
*/
-class ImplantTerminalMechControl(mech : ImplantTerminalMech) extends Actor with FactionAffinityBehavior.Check
- with MountableBehavior.Mount with MountableBehavior.Dismount with HackableBehavior.GenericHackable {
- def MountableObject = mech //do not add type!
+class ImplantTerminalMechControl(mech : ImplantTerminalMech) extends Actor
+ with FactionAffinityBehavior.Check
+ with MountableBehavior.Mount
+ with MountableBehavior.Dismount
+ with HackableBehavior.GenericHackable
+ with DamageableEntity
+ with RepairableEntity {
+ def MountableObject = mech
def HackableObject = mech
-
- def FactionObject : FactionAffinity = mech
+ def FactionObject = mech
+ def DamageableObject = mech
+ def RepairableObject = mech
def receive : Receive = checkBehavior
.orElse(mountBehavior)
.orElse(dismountBehavior)
.orElse(hackableBehavior)
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
.orElse {
+ case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
+ //TODO setup certifications check
+ mech.Owner match {
+ case b : Building if (b.Faction != player.Faction || b.CaptureConsoleIsHacked) && mech.HackedBy.isEmpty =>
+ sender ! CommonMessages.Hack(player, mech, Some(item))
+ case _ => ;
+ }
case _ => ;
}
+
+ override protected def MountTest(obj : PlanetSideServerObject with Mountable, seatNumber : Int, player : Player) : Boolean = {
+ val zone = obj.Zone
+ zone.Map.TerminalToInterface.get(obj.GUID.guid) match {
+ case Some(interface_guid) =>
+ (zone.GUID(interface_guid) match {
+ case Some(interface) => !interface.Destroyed
+ case None => false
+ }) &&
+ super.MountTest(obj, seatNumber, player)
+ case None =>
+ false
+ }
+ }
+
+ override protected def DamageAwareness(target : Target, cause : ResolvedProjectile, amount : Int) : Unit = {
+ super.DamageAwareness(target, cause, amount)
+ DamageableMountable.DamageAwareness(DamageableObject, cause)
+ }
+
+ override protected def DestructionAwareness(target : Damageable.Target, cause : ResolvedProjectile) : Unit = {
+ super.DestructionAwareness(target, cause)
+ DamageableMountable.DestructionAwareness(DamageableObject, cause)
+ target.ClearHistory()
+ }
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechDefinition.scala
index 554a19d31..b8fd76e51 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechDefinition.scala
@@ -1,15 +1,15 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.implantmech
-import net.psforever.objects.definition.{ObjectDefinition, SeatDefinition}
-import net.psforever.objects.vehicles.SeatArmorRestriction
+import net.psforever.objects.definition.SeatDefinition
+import net.psforever.objects.serverobject.structures.AmenityDefinition
/**
* The `Definition` for any `Terminal` that is of a type "implant_terminal_interface."
* Implant terminals are composed of two components.
* This `Definition` constructs the visible mechanical tube component that can be mounted.
*/
-class ImplantTerminalMechDefinition extends ObjectDefinition(410) {
+class ImplantTerminalMechDefinition extends AmenityDefinition(410) {
/* key - seat index, value - seat object */
private val seats : Map[Int, SeatDefinition] = Map(0 -> new SeatDefinition)
/* key - entry point index, value - seat index */
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala
index d722dc265..43785543c 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockControl.scala
@@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.locks
import akka.actor.Actor
+import net.psforever.objects.{GlobalDefinitions, SimpleItem}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.hackable.HackableBehavior
@@ -18,6 +19,17 @@ class IFFLockControl(lock : IFFLock) extends Actor with FactionAffinityBehavior.
def receive : Receive = checkBehavior
.orElse(hackableBehavior)
.orElse {
+ case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
+ if((lock.Faction != player.Faction && lock.HackedBy.isEmpty) || (lock.Faction == player.Faction && lock.HackedBy.nonEmpty)) {
+ sender ! CommonMessages.Hack(player, lock, Some(item))
+ }
+ else {
+ val log = org.log4s.getLogger
+ log.warn("IFF lock is being hacked, but don't know how to handle this state:")
+ log.warn(s"Lock - Faction=${lock.Faction}, HackedBy=${lock.HackedBy}")
+ log.warn(s"Player - Faction=${player.Faction}")
+ }
+
case _ => ; //no default message
}
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockDefinition.scala
index d8c180d89..395123447 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLockDefinition.scala
@@ -1,12 +1,12 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.locks
-import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.structures.AmenityDefinition
/**
* The definition for any `IFFLock`.
* Object Id 451 is a generic external lock.
*/
-class IFFLockDefinition extends ObjectDefinition(451) {
+class IFFLockDefinition extends AmenityDefinition(451) {
Name = "iff_lock"
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLocks.scala b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLocks.scala
new file mode 100644
index 000000000..a859ceeb2
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/locks/IFFLocks.scala
@@ -0,0 +1,17 @@
+// Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.locks
+
+import services.Service
+import services.local.{LocalAction, LocalServiceMessage}
+
+object IFFLocks {
+ /**
+ * The process of resecuring an IFF lock is finished
+ * Clear the hack state and send to clients
+ * @param lock the `IFFLock` object that has been resecured
+ */
+ def FinishResecuringIFFLock(lock: IFFLock)() : Unit = {
+ val zone = lock.Zone
+ lock.Zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.ClearTemporaryHack(Service.defaultPlayerGUID, lock))
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala
index e3a11e472..1a781c08f 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerControl.scala
@@ -2,6 +2,8 @@
package net.psforever.objects.serverobject.mblocker
import akka.actor.Actor
+import net.psforever.objects.{GlobalDefinitions, SimpleItem}
+import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.hackable.HackableBehavior
@@ -16,6 +18,11 @@ class LockerControl(locker : Locker) extends Actor with FactionAffinityBehavior.
def receive : Receive = checkBehavior
.orElse(hackableBehavior)
.orElse {
+ case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
+ //TODO setup certifications check
+ if(locker.Faction != player.Faction && locker.HackedBy.isEmpty) {
+ sender ! CommonMessages.Hack(player, locker, Some(item))
+ }
case _ => ;
}
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerDefinition.scala
index b73a9e533..d58c2ef31 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/LockerDefinition.scala
@@ -1,12 +1,12 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.mblocker
-import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.structures.AmenityDefinition
/**
* The definition for any `Locker`.
* Object Id 524.
*/
-class LockerDefinition extends ObjectDefinition(524) {
+class LockerDefinition extends AmenityDefinition(524) {
Name = "mb_locker"
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala b/common/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
index 26f3ca560..175294e57 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
@@ -2,11 +2,12 @@
package net.psforever.objects.serverobject.mount
import akka.actor.Actor
-import net.psforever.objects.{PlanetSideGameObject, Vehicle}
+import net.psforever.objects.{Player, Vehicle}
import net.psforever.objects.entity.{Identifiable, WorldEntity}
+import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.hackable.Hackable
-import net.psforever.objects.serverobject.turret.TurretDefinition
+import net.psforever.objects.serverobject.turret.WeaponTurret
import net.psforever.types.DriveState
object MountableBehavior {
@@ -17,51 +18,50 @@ object MountableBehavior {
* @see `Mountable`
*/
trait Mount {
- this : Actor =>
-
- def MountableObject : PlanetSideGameObject with Mountable with FactionAffinity
+ _ : Actor =>
+ def MountableObject : PlanetSideServerObject with Mountable with FactionAffinity
val mountBehavior : Receive = {
case Mountable.TryMount(user, seat_num) =>
val obj = MountableObject
- obj.Seat(seat_num) match {
- case Some(seat) =>
-
- var isHacked = false
- if(MountableObject.isInstanceOf[Hackable]) {
- // This is a special case for implant terminals, since they're both mountable and hackable, but not jackable.
- isHacked = MountableObject.asInstanceOf[Hackable].HackedBy.isDefined
- }
-
- if((user.Faction == obj.Faction || isHacked) && (seat.Occupant = user).contains(user)) {
- user.VehicleSeated = obj.GUID
- sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num))
- }
- else {
- sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
- }
- case None =>
- sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
+ if(MountTest(MountableObject, seat_num, user)) {
+ user.VehicleSeated = obj.GUID
+ sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num))
+ }
+ else {
+ sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
}
}
- val turretMountBehavior : Receive = {
- case Mountable.TryMount(user, seat_num) =>
- val obj = MountableObject
- val definition = obj.Definition.asInstanceOf[TurretDefinition]
- obj.Seat(seat_num) match {
- case Some(seat) =>
- if((!definition.FactionLocked || user.Faction == obj.Faction) &&
- (seat.Occupant = user).contains(user)) {
- user.VehicleSeated = obj.GUID
- sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num))
- }
- else {
- sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
- }
- case None =>
- sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num))
- }
+ protected def MountTest(obj : PlanetSideServerObject with Mountable, seatNumber : Int, player : Player) : Boolean = {
+ (player.Faction == obj.Faction ||
+ (obj match {
+ case o : Hackable => o.HackedBy.isDefined
+ case _ => false
+ })) &&
+ !obj.Destroyed &&
+ (obj.Seats.get(seatNumber) match {
+ case Some(seat) => (seat.Occupant = player).contains(player)
+ case _ => false
+ })
+ }
+ }
+
+ trait TurretMount extends Mount {
+ _ : Actor =>
+
+ override protected def MountTest(obj : PlanetSideServerObject with Mountable, seatNumber : Int, player : Player) : Boolean = {
+ obj match {
+ case wep : WeaponTurret =>
+ (!wep.Definition.FactionLocked || player.Faction == obj.Faction) &&
+ !obj.Destroyed &&
+ (obj.Seats.get(seatNumber) match {
+ case Some(seat) => (seat.Occupant = player).contains(player)
+ case _ => false
+ })
+ case _ =>
+ super.MountTest(obj, seatNumber, player)
+ }
}
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala
index af0a0d3ea..ebef3a146 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala
@@ -1,12 +1,12 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad
-import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.structures.AmenityDefinition
/**
* The definition for any `VehicleSpawnPad`.
*/
-class VehicleSpawnPadDefinition(objectId : Int) extends ObjectDefinition(objectId) {
+class VehicleSpawnPadDefinition(objectId : Int) extends AmenityDefinition(objectId) {
// Different pads require a Z offset to stop vehicles falling through the world after the pad rises from the floor, these values are found in game_objects.adb.lst
private var vehicle_creation_z_offset = 0f
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxDefinition.scala
index 06ea10263..76bb9289f 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/painbox/PainboxDefinition.scala
@@ -1,9 +1,9 @@
package net.psforever.objects.serverobject.painbox
-import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.structures.AmenityDefinition
import net.psforever.types.Vector3
-class PainboxDefinition(objectId : Int) extends ObjectDefinition(objectId) {
+class PainboxDefinition(objectId : Int) extends AmenityDefinition(objectId) {
private var alwaysOn : Boolean = true
private var radius : Float = 0f
private var damage : Int = 10
@@ -39,7 +39,7 @@ class PainboxDefinition(objectId : Int) extends ObjectDefinition(objectId) {
radius = 8.55f
sphereOffset = Vector3.Zero
case _ =>
- throw new IllegalArgumentException(s"${objectId} is not a valid painbox object id")
+ throw new IllegalArgumentException(s"$objectId is not a valid painbox object id")
}
def Radius : Float = radius
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/repair/Repairable.scala b/common/src/main/scala/net/psforever/objects/serverobject/repair/Repairable.scala
new file mode 100644
index 000000000..9c4c87477
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/repair/Repairable.scala
@@ -0,0 +1,77 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.repair
+
+import akka.actor.Actor.Receive
+import net.psforever.objects.equipment.Ammo
+import net.psforever.objects.{GlobalDefinitions, Player, Tool}
+import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
+import net.psforever.objects.vital.Vitality
+
+/**
+ * The base "control" `Actor` mixin for repair-handling code
+ * related to the nano dispenser tool loaded with an armor repair canister.
+ * Unlike the `Damageable` mixin,
+ * which should be extended to interact with all aspects of a target that impede access to its health points,
+ * shield, armor, etc., `Repairable` only affects `Vitality.Health`.
+ */
+trait Repairable {
+ /**
+ * Contextual access to the object being the target of this damage.
+ * Needs declaration in lowest implementing code.
+ * @return the entity controlled by this actor
+ */
+ def RepairableObject : Repairable.Target
+
+ /**
+ * The official mixin hook; `orElse` onto the "control" `Actor` `receive`;
+ * catch the expected repair message and apply initial checks to the item
+ * @see `Ammo`
+ * @see `CanBeRepairedByNanoDispenser`
+ * @see `CommonMessages.Use`
+ * @see `GlobalDefinitions`
+ * @see `Tool.AmmoType`
+ */
+ final val canBeRepairedByNanoDispenser : Receive = {
+ case CommonMessages.Use(player, Some(item : Tool))
+ if item.Definition == GlobalDefinitions.nano_dispenser && item.AmmoType == Ammo.armor_canister =>
+ CanBeRepairedByNanoDispenser(player, item)
+ }
+
+ /**
+ * Implementation of the mixin hook will be provided by a child class.
+ * Override this method only when directly implementing.
+ * @see `canBeRepairedByNanoDispenser`
+ */
+ def CanBeRepairedByNanoDispenser(player : Player, item : Tool) : Unit
+
+ /**
+ * The amount of repair that any specific tool provides.
+ * @see `Repairable.Quality`
+ * @param item the tool in question
+ * @return an amount to add to the repair attempt progress
+ */
+ def RepairValue(item : Tool) : Int = 0
+
+ /**
+ * The entity is no longer destroyed.
+ * @param obj the entity
+ */
+ def Restoration(obj : Repairable.Target) : Unit = {
+ Repairable.Restoration(obj)
+ }
+}
+
+object Repairable {
+ /* the type of all entities governed by this mixin; see Damageable.Target */
+ final type Target = PlanetSideServerObject with Vitality
+ /* the basic repair value; originally found on the `armor_canister` object definition */
+ final val Quality : Int = 12
+
+ /**
+ * The entity is no longer destroyed.
+ * @param target the entity
+ */
+ def Restoration(target : Repairable.Target) : Unit = {
+ target.Destroyed = false
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableAmenity.scala b/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableAmenity.scala
new file mode 100644
index 000000000..8266d0e32
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableAmenity.scala
@@ -0,0 +1,37 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.repair
+
+import net.psforever.objects.serverobject.structures.Amenity
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+
+/**
+ * The "control" `Actor` mixin for repair-handling code
+ * for the majority of `Repairable` `Amenity` objects installed in a facility or a field tower.
+ */
+trait RepairableAmenity extends RepairableEntity {
+ def RepairableObject : Amenity
+
+ override def Restoration(obj : Repairable.Target) : Unit = {
+ super.Restoration(obj)
+ RepairableAmenity.Restoration(obj)
+ }
+}
+
+object RepairableAmenity {
+ /**
+ * A resotred `Amenity` target dispatches two messages to chance its model and operational states.
+ * These `PlanetSideAttributeMessage` attributes are the same as reported during zone load client configuration.
+ * @see `AvatarAction.PlanetsideAttributeToAll`
+ * @see `AvatarServiceMessage`
+ * @see `Zone.AvatarEvents`
+ * @param target the entity being destroyed
+ */
+ def Restoration(target : Repairable.Target) : Unit = {
+ val zone = target.Zone
+ val zoneId = zone.Id
+ val events = zone.AvatarEvents
+ val targetGUID = target.GUID
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 50, 0))
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(targetGUID, 51, 0))
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableEntity.scala b/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableEntity.scala
new file mode 100644
index 000000000..1724651e8
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableEntity.scala
@@ -0,0 +1,108 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.repair
+
+import net.psforever.objects.{Player, Tool}
+import net.psforever.packet.game.{InventoryStateMessage, RepairMessage}
+import net.psforever.types.{PlanetSideEmpire, Vector3}
+import services.Service
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+
+/**
+ * The "control" `Actor` mixin for repair-handling code,
+ * for both expansion into other mixins and specific application on its own.
+ * @see `Player`
+ * @see `Tool`
+ */
+trait RepairableEntity extends Repairable {
+ /**
+ * Catch the expected repair message and
+ * apply further checks to the combination of the target, the equipment, and tis user.
+ * If the checks pass, perform the repair.
+ * @param player the user of the nano dispenser tool
+ * @param item the nano dispenser tool
+ */
+ def CanBeRepairedByNanoDispenser(player : Player, item : Tool) : Unit = {
+ val obj = RepairableObject
+ if(CanPerformRepairs(obj, player, item)) {
+ PerformRepairs(obj, player, item)
+ }
+ }
+
+ /**
+ * Test the combination of target entity, equipment user, and the equipment
+ * to determine if the repair process attempt would be permitted.
+ * It is not necessary to check that the tool and its ammunition are correct types;
+ * that test was already performed.
+ *
+ * The target entity must be repairable and have less than full health
+ * and, if it is destroyed, must have an object attribute that permits it to be repaired after being destroyed.
+ * The user must have the same faction affinity as the target entity or be neutral.
+ * The equipment must have some ammunition.
+ * The user must be alive and be within a certain distance of the target entity.
+ * @see `org.log4s.getLogger`
+ * @see `PlanetSideEmpire`
+ * @see `Vector3.Distance`
+ * @see `VitalityDefinition`
+ * @param target the entity being repaired
+ * @param player the user of the nano dispenser tool
+ * @param item the nano dispenser tool
+ * @return `true`, if the target entity can be repaired;
+ * `false`, otherwise
+ */
+ protected def CanPerformRepairs(target : Repairable.Target, player : Player, item : Tool) : Boolean = {
+ val definition = target.Definition
+ definition.Repairable && target.Health < definition.MaxHealth && (definition.RepairIfDestroyed || !target.Destroyed) &&
+ (target.Faction == player.Faction || target.Faction == PlanetSideEmpire.NEUTRAL) && item.Magazine > 0 &&
+ player.isAlive && Vector3.Distance(target.Position, player.Position) < definition.RepairDistance
+ }
+
+ /**
+ * Calculate the health points change and enact that repair action if the targets are stationary.
+ * Restore the target entity to a not destroyed state if applicable.
+ * Always show the repair progress bar window by using the appropriate packet.
+ * @see `AvatarAction.PlanetsideAttributeToAll`
+ * @see `AvatarAction.SendResponse`
+ * @see `AvatarService`
+ * @see `InventoryStateMessage`
+ * @see `PlanetSideGameObject.isMoving`
+ * @see `RepairMessage`
+ * @see `Service.defaultPlayerGUID`
+ * @see `Tool.Discharge`
+ * @see `Zone.AvatarEvents`
+ * @param target the entity being repaired
+ * @param player the user of the nano dispenser tool
+ * @param item the nano dispenser tool
+ */
+ protected def PerformRepairs(target : Repairable.Target, player : Player, item : Tool) : Unit = {
+ val definition = target.Definition
+ val zone = target.Zone
+ val events = zone.AvatarEvents
+ val name = player.Name
+ val tguid = target.GUID
+ val originalHealth = target.Health
+ val updatedHealth = if(!(player.isMoving || target.isMoving)) { //only allow stationary repairs
+ val newHealth = target.Health = originalHealth + Repairable.Quality + RepairValue(item) + definition.RepairMod
+ val zoneId = zone.Id
+ val magazine = item.Discharge
+ events ! AvatarServiceMessage(name, AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(item.AmmoSlot.Box.GUID, item.GUID, magazine.toLong)))
+ if(target.Destroyed) {
+ if(newHealth >= definition.RepairRestoresAt) {
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, newHealth))
+ Restoration(target)
+ }
+ }
+ else {
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 0, newHealth))
+ }
+ newHealth
+ }
+ else {
+ originalHealth
+ }
+ //progress bar remains visible
+ events ! AvatarServiceMessage(name, AvatarAction.SendResponse(Service.defaultPlayerGUID, RepairMessage(tguid, updatedHealth * 100 / definition.MaxHealth)))
+ }
+
+ /* random object repair modifier */
+ override def RepairValue(item : Tool) : Int = item.FireMode.Modifiers.Damage1
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableVehicle.scala b/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableVehicle.scala
new file mode 100644
index 000000000..170337844
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableVehicle.scala
@@ -0,0 +1,17 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.repair
+
+import net.psforever.objects.Tool
+
+/**
+ * The "control" `Actor` mixin for repair-handling code for `Vehicle` objects.
+ */
+trait RepairableVehicle extends RepairableEntity {
+ override def Restoration(obj : Repairable.Target) : Unit = {
+ obj.Health = 0
+ obj.Destroyed = true
+ /* no vanilla vehicles are capable of being restored from destruction */
+ /* if you wanted to properly restore a destroyed vehicle, the quickest way is an ObjectCreateMessage packet */
+ /* additionally, the vehicle deconstruction task must be cancelled */
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableWeaponTurret.scala b/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableWeaponTurret.scala
new file mode 100644
index 000000000..d50a1e874
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/repair/RepairableWeaponTurret.scala
@@ -0,0 +1,51 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.repair
+
+import net.psforever.objects.Tool
+import net.psforever.objects.serverobject.turret.WeaponTurret
+import net.psforever.objects.vehicles.MountedWeapons
+import services.Service
+import services.vehicle.{VehicleAction, VehicleServiceMessage}
+
+/**
+ * The "control" `Actor` mixin for repair-handling code for `WeaponTurret` objects.
+ */
+trait RepairableWeaponTurret extends RepairableEntity {
+ def RepairableObject : Repairable.Target with WeaponTurret
+
+ override def Restoration(target : Repairable.Target) : Unit = {
+ super.Restoration(target)
+ RepairableWeaponTurret.Restoration(RepairableObject)
+ }
+}
+
+object RepairableWeaponTurret {
+ /**
+ * A restored target dispatches messages to reconstruct the weapons that were previously mounted to the turret
+ * and may have been concealed/deleted when the target was destroyed.
+ * @see `MountedWeapons`
+ * @see `MountedWeapons.Weapons`
+ * @see `Service.defaultPlayerGUID`
+ * @see `WeaponTurret`
+ * @see `VehicleAction.EquipmentInSlot`
+ * @see `VehicleServiceMessage`
+ * @see `Zone.VehicleEvents`
+ * @param target the entity being destroyed;
+ * note: `MountedWeapons` is a parent of `WeaponTurret`
+ * but the handling code closely associates with the former
+ */
+ def Restoration(target : Repairable.Target with MountedWeapons) : Unit = {
+ val zone = target.Zone
+ val zoneId = zone.Id
+ val tguid = target.GUID
+ val events = zone.VehicleEvents
+ target.Weapons
+ .map({ case (index, slot) => (index, slot.Equipment) })
+ .collect { case (index, Some(tool : Tool)) =>
+ events ! VehicleServiceMessage(
+ zoneId,
+ VehicleAction.EquipmentInSlot(Service.defaultPlayerGUID, tguid, index, tool)
+ )
+ }
+ }
+}
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 ac73f5191..315902bf9 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
@@ -2,7 +2,7 @@
package net.psforever.objects.serverobject.resourcesilo
import akka.actor.{ActorContext, Props}
-import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
+import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.UseItemMessage
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 68287d67b..7a624187b 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
@@ -30,7 +30,14 @@ class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with Factio
def Processing : Receive = checkBehavior.orElse {
case ResourceSilo.Use(player, msg) =>
- sender ! ResourceSilo.ResourceSiloMessage(player, msg, resourceSilo.Use(player, msg))
+ if(resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) {
+ resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match {
+ case Some(vehicle) =>
+ sender ! ResourceSilo.ResourceSiloMessage(player, msg, resourceSilo.Use(player, msg))
+ case _ =>
+ }
+ }
+
case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
resourceSilo.LowNtuWarningOn = enabled
log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled")
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloDefinition.scala
index 1f5570622..9169153d9 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloDefinition.scala
@@ -1,12 +1,12 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.resourcesilo
-import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.structures.AmenityDefinition
/**
* The definition for any `Resource Silo`.
* Object Id 731.
*/
-class ResourceSiloDefinition extends ObjectDefinition(731) {
+class ResourceSiloDefinition extends AmenityDefinition(731) {
Name = "resource_silo"
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/Amenity.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/Amenity.scala
index 7a9ca07d4..a83c2fcbc 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/structures/Amenity.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/Amenity.scala
@@ -1,10 +1,12 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.structures
+import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality, VitalsActivity}
import net.psforever.objects.zones.{Zone, ZoneAware}
import net.psforever.types.{PlanetSideEmpire, Vector3}
-import net.psforever.objects.zones.{ Zone => World }
+import net.psforever.objects.zones.{Zone => World}
/**
* Amenities are elements of the game that belong to other elements of the game.
@@ -14,9 +16,13 @@ import net.psforever.objects.zones.{ Zone => World }
* This association strips away at the internalization and redirects a reference to some properties somewhere else.
* An `Amenity` object belongs to its `Owner` object;
* the `Amenity` objects look to its `Owner` object for some of its properties.
+ * @see `AmenityOwner`
* @see `FactionAffinity`
*/
-abstract class Amenity extends PlanetSideServerObject with ZoneAware {
+abstract class Amenity extends PlanetSideServerObject
+ with Vitality
+ with ZoneAware
+ with StandardResistanceProfile {
private[this] val log = org.log4s.getLogger("Amenity")
/** what other entity has authority over this amenity; usually either a building or a vehicle */
private var owner : AmenityOwner = Building.NoBuilding
@@ -71,4 +77,8 @@ abstract class Amenity extends PlanetSideServerObject with ZoneAware {
}
LocationOffset
}
+
+ def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
+
+ def Definition : AmenityDefinition
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/AmenityDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/AmenityDefinition.scala
new file mode 100644
index 000000000..43df075fe
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/AmenityDefinition.scala
@@ -0,0 +1,16 @@
+// Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.structures
+
+import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.vital.{DamageResistanceModel, StandardAmenityDamage, StandardAmenityResistance, StandardResolutions, VitalityDefinition}
+import net.psforever.objects.vital.resistance.ResistanceProfileMutators
+
+abstract class AmenityDefinition(objectId : Int) extends ObjectDefinition(objectId)
+ with ResistanceProfileMutators
+ with DamageResistanceModel
+ with VitalityDefinition {
+ Name = "amenity"
+ DamageUsing = StandardAmenityDamage
+ ResistUsing = StandardAmenityResistance
+ Model = StandardResolutions.Amenities
+}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala
index 0bf1328e5..f68dc7e18 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala
@@ -6,14 +6,15 @@ import java.util.concurrent.TimeUnit
import akka.actor.{ActorContext, ActorRef}
import net.psforever.objects.{GlobalDefinitions, Player}
import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.generator.Generator
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.painbox.Painbox
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.terminals.CaptureTerminal
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.zones.Zone
-import net.psforever.packet.game._
-import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
+import net.psforever.packet.game.{Additional1, Additional2, Additional3}
+import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, PlanetSideGeneratorState, Vector3}
import scalax.collection.{Graph, GraphEdge}
import services.Service
import services.local.{LocalAction, LocalServiceMessage}
@@ -182,17 +183,17 @@ class Building(private val name: String,
case _ =>
(false, PlanetSideEmpire.NEUTRAL, 0L)
}
- //TODO if we have a generator, get the current repair state
- val (generatorState, boostGeneratorPain) = (PlanetSideGeneratorState.Normal, false) // todo: poll pain field strength
+ //if we have no generator, assume the state is "Normal"
+ val (generatorState, boostGeneratorPain) = Amenities.find(x => x.isInstanceOf[Generator]) match {
+ case Some(obj : Generator) =>
+ (obj.Condition, false) // todo: poll pain field strength
+ case _ =>
+ (PlanetSideGeneratorState.Normal, false)
+ }
//if we have spawn tubes, determine if any of them are active
val (spawnTubesNormal, boostSpawnPain) : (Boolean, Boolean) = {
- val o = Amenities.collect({ case _ : SpawnTube => true }) ///TODO obj.Health > 0
- if(o.nonEmpty) {
- (o.foldLeft(false)(_ || _), false) //TODO poll pain field strength
- }
- else {
- (true, false)
- }
+ val o = Amenities.collect({ case tube : SpawnTube if !tube.Destroyed => tube })
+ (o.nonEmpty, false) //TODO poll pain field strength
}
val latticeBenefit : Int = {
@@ -312,6 +313,7 @@ object Building {
obj
}
+ final case class AmenityStateChange(obj : Amenity)
final case class SendMapUpdate(all_clients: Boolean)
final case class TriggerZoneMapUpdate(zone_num: Int)
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala
index 8bd88709e..7f06e1b02 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingControl.scala
@@ -3,6 +3,8 @@ package net.psforever.objects.serverobject.structures
import akka.actor.{Actor, ActorRef}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.serverobject.generator.Generator
+import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.zones.InterstellarCluster
import net.psforever.packet.game.BuildingInfoUpdateMessage
import services.ServiceManager
@@ -35,43 +37,62 @@ class BuildingControl(building : Building) extends Actor with FactionAffinityBeh
building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity())
}
sender ! FactionAffinity.AssertFactionAffinity(building, faction)
- case Building.TriggerZoneMapUpdate(zone_num: Int) =>
- if(interstellarCluster != ActorRef.noSender) interstellarCluster ! InterstellarCluster.ZoneMapUpdate(zone_num)
- case Building.SendMapUpdate(all_clients: Boolean) =>
- val zoneNumber = building.Zone.Number
- val buildingNumber = building.MapId
- log.trace(s"sending BuildingInfoUpdateMessage update - zone=$zoneNumber, building=$buildingNumber")
- val (
- ntuLevel,
- isHacked, empireHack, hackTimeRemaining, controllingEmpire,
- unk1, unk1x,
- generatorState, spawnTubesNormal, forceDomeActive,
- latticeBenefit, cavernBenefit,
- unk4, unk5, unk6,
- unk7, unk7x,
- boostSpawnPain, boostGeneratorPain
- ) = building.Info
- val msg = BuildingInfoUpdateMessage(
- zoneNumber,
- buildingNumber,
- ntuLevel,
- isHacked, empireHack, hackTimeRemaining, controllingEmpire,
- unk1, unk1x,
- generatorState, spawnTubesNormal, forceDomeActive,
- latticeBenefit, cavernBenefit,
- unk4, unk5, unk6,
- unk7, unk7x,
- boostSpawnPain, boostGeneratorPain
- )
- if(all_clients) {
- if(galaxyService != ActorRef.noSender) galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(msg))
- } else {
- // Fake a GalaxyServiceResponse response back to just the sender
- sender ! GalaxyServiceResponse("", GalaxyResponse.MapUpdate(msg))
+ case Building.AmenityStateChange(obj : SpawnTube) =>
+ if(building.Amenities.contains(obj)) {
+ SendMapUpdate(allClients = true)
}
- case default =>
- log.warn(s"BuildingControl: Unknown message $default received from ${sender().path}")
+ case Building.AmenityStateChange(obj : Generator) =>
+ if(building.Amenities.contains(obj)) {
+ SendMapUpdate(allClients = true)
+ }
+
+ case Building.TriggerZoneMapUpdate(zone_num: Int) =>
+ if(interstellarCluster != ActorRef.noSender) interstellarCluster ! InterstellarCluster.ZoneMapUpdate(zone_num)
+
+ case Building.SendMapUpdate(all_clients: Boolean) =>
+ SendMapUpdate(all_clients)
+
+ case _ =>
+ }
+
+ /**
+ * na
+ * @param allClients na
+ */
+ def SendMapUpdate(allClients : Boolean) : Unit = {
+ val zoneNumber = building.Zone.Number
+ val buildingNumber = building.MapId
+ log.trace(s"sending BuildingInfoUpdateMessage update - zone=$zoneNumber, building=$buildingNumber")
+ val (
+ ntuLevel,
+ isHacked, empireHack, hackTimeRemaining, controllingEmpire,
+ unk1, unk1x,
+ generatorState, spawnTubesNormal, forceDomeActive,
+ latticeBenefit, cavernBenefit,
+ unk4, unk5, unk6,
+ unk7, unk7x,
+ boostSpawnPain, boostGeneratorPain
+ ) = building.Info
+ val msg = BuildingInfoUpdateMessage(
+ zoneNumber,
+ buildingNumber,
+ ntuLevel,
+ isHacked, empireHack, hackTimeRemaining, controllingEmpire,
+ unk1, unk1x,
+ generatorState, spawnTubesNormal, forceDomeActive,
+ latticeBenefit, cavernBenefit,
+ unk4, unk5, unk6,
+ unk7, unk7x,
+ boostSpawnPain, boostGeneratorPain
+ )
+
+ if(allClients) {
+ if(galaxyService != ActorRef.noSender) galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(msg))
+ } else {
+ // Fake a GalaxyServiceResponse response back to just the sender
+ sender ! GalaxyServiceResponse("", GalaxyResponse.MapUpdate(msg))
+ }
}
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala
index 9d7b196c5..9fb7e5189 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala
@@ -6,8 +6,8 @@ import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.{GlobalDefinitions, SpawnPoint, SpawnPointDefinition}
import net.psforever.objects.zones.Zone
-import net.psforever.packet.game.{Additional1, Additional2, Additional3, PlanetSideGeneratorState}
-import net.psforever.types.{PlanetSideEmpire, Vector3}
+import net.psforever.packet.game.{Additional1, Additional2, Additional3}
+import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3}
import scala.collection.mutable
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/CaptureTerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/CaptureTerminalControl.scala
index 056f70cd5..249a8cabb 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/CaptureTerminalControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/CaptureTerminalControl.scala
@@ -2,6 +2,7 @@
package net.psforever.objects.serverobject.terminals
import akka.actor.Actor
+import net.psforever.objects.{GlobalDefinitions, SimpleItem}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.hackable.HackableBehavior
@@ -14,6 +15,15 @@ class CaptureTerminalControl(terminal : CaptureTerminal) extends Actor with Fact
def receive : Receive = checkBehavior
.orElse(hackableBehavior)
.orElse {
- case _ => ; //no default message
+ case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
+ val canHack = terminal.HackedBy match {
+ case Some(info) => info.hackerFaction != player.Faction
+ case _ => terminal.Faction != player.Faction
+ }
+ if(canHack) {
+ sender ! CommonMessages.Hack(player, terminal, Some(item))
+ }
+
+ case _ => ; //no default message
}
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/CaptureTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/CaptureTerminalDefinition.scala
index f65cf989e..901c72799 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/CaptureTerminalDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/CaptureTerminalDefinition.scala
@@ -1,8 +1,8 @@
package net.psforever.objects.serverobject.terminals
-import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.structures.AmenityDefinition
-class CaptureTerminalDefinition(objectId : Int) extends ObjectDefinition(objectId) {
+class CaptureTerminalDefinition(objectId : Int) extends AmenityDefinition(objectId) {
Name = objectId match {
case 158 => "capture_terminal"
case 751 => "secondary_capture"
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/CaptureTerminals.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/CaptureTerminals.scala
new file mode 100644
index 000000000..edb55e5a8
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/CaptureTerminals.scala
@@ -0,0 +1,40 @@
+// Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.terminals
+
+import net.psforever.objects.Player
+import net.psforever.objects.serverobject.CommonMessages
+import services.local.{LocalAction, LocalServiceMessage}
+
+import scala.util.{Failure, Success}
+
+object CaptureTerminals {
+ private val log = org.log4s.getLogger("CaptureTerminals")
+
+ /**
+ * The process of hacking an object is completed.
+ * Pass the message onto the hackable object and onto the local events system.
+ * @param target the `Hackable` object that has been hacked
+ * @param unk na;
+ * used by `HackMessage` as `unk5`
+ * @see `HackMessage`
+ */
+ //TODO add params here depending on which params in HackMessage are important
+ def FinishHackingCaptureConsole(target : CaptureTerminal, user : Player, unk : Long)() : Unit = {
+ import akka.pattern.ask
+ import scala.concurrent.duration._
+ log.info(s"Hacked a $target")
+ // Wait for the target actor to set the HackedBy property, otherwise LocalAction.HackTemporarily will not complete properly
+ import scala.concurrent.ExecutionContext.Implicits.global
+ val tplayer = user
+ ask(target.Actor, CommonMessages.Hack(tplayer, target))(1 second).mapTo[Boolean].onComplete {
+ case Success(_) =>
+ val zone = target.Zone
+ val zoneId = zone.Id
+ val pguid = tplayer.GUID
+ zone.LocalEvents ! LocalServiceMessage(zoneId, LocalAction.TriggerSound(pguid, target.HackSound, tplayer.Position, 30, 0.49803925f))
+ zone.LocalEvents ! LocalServiceMessage(zoneId, LocalAction.HackCaptureTerminal(pguid, zone, target, unk, 8L, tplayer.Faction == target.Faction))
+ case Failure(_) => log.warn(s"Hack message failed on target guid: ${target.GUID}")
+ }
+ }
+}
+
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala
index 385ec627a..e7e960b6b 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala
@@ -4,8 +4,11 @@ package net.psforever.objects.serverobject.terminals
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects._
import net.psforever.objects.serverobject.CommonMessages
-import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
+import net.psforever.objects.serverobject.damage.DamageableAmenity
import net.psforever.objects.serverobject.hackable.HackableBehavior
+import net.psforever.objects.serverobject.repair.RepairableAmenity
+import net.psforever.objects.serverobject.structures.Building
import scala.collection.mutable
import scala.concurrent.duration._
@@ -16,27 +19,42 @@ import scala.concurrent.duration._
* it returns the same type of messages - wrapped in a `TerminalMessage` - to the `sender`.
* @param term the proximity unit (terminal)
*/
-class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable {
+class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor
+ with FactionAffinityBehavior.Check
+ with HackableBehavior.GenericHackable
+ with DamageableAmenity
+ with RepairableAmenity {
+ def FactionObject = term
+ def HackableObject = term
+ def TerminalObject = term
+ def DamageableObject = term
+ def RepairableObject = term
+
var terminalAction : Cancellable = DefaultCancellable.obj
val callbacks : mutable.ListBuffer[ActorRef] = new mutable.ListBuffer[ActorRef]()
val log = org.log4s.getLogger
- def FactionObject : FactionAffinity = term
- def HackableObject = term
-
- def TerminalObject : Terminal with ProximityUnit = term
-
def receive : Receive = checkBehavior
- .orElse(hackableBehavior)
- .orElse {
- case CommonMessages.Use(_, Some(target : PlanetSideGameObject)) =>
- if(term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) {
- Use(target, term.Continent, sender)
- }
- case CommonMessages.Use(_, Some((target : PlanetSideGameObject, callback : ActorRef))) =>
- if(term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) {
- Use(target, term.Continent, callback)
- }
+ .orElse(hackableBehavior)
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
+ .orElse {
+ case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
+ //TODO setup certifications check
+ term.Owner match {
+ case b : Building if (b.Faction != player.Faction || b.CaptureConsoleIsHacked) && term.HackedBy.isEmpty =>
+ sender ! CommonMessages.Hack(player, term, Some(item))
+ case _ => ;
+ }
+
+ case CommonMessages.Use(_, Some(target : PlanetSideGameObject)) =>
+ if(!term.Destroyed && term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) {
+ Use(target, term.Continent, sender)
+ }
+ case CommonMessages.Use(_, Some((target : PlanetSideGameObject, callback : ActorRef))) =>
+ if(!term.Destroyed && term.Definition.asInstanceOf[ProximityDefinition].Validations.exists(p => p(target))) {
+ Use(target, term.Continent, callback)
+ }
case CommonMessages.Use(_, _) =>
log.warn(s"unexpected format for CommonMessages.Use in this context")
@@ -52,7 +70,7 @@ class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor
val validateFunc : PlanetSideGameObject=>Boolean = term.Validate(proxDef.UseRadius * proxDef.UseRadius, proxDef.Validations)
val callbackList = callbacks.toList
term.Targets.zipWithIndex.foreach({ case(target, index) =>
- if(validateFunc(target)) {
+ if(!term.Destroyed && validateFunc(target)) {
callbackList.lift(index) match {
case Some(cback) =>
cback ! ProximityUnit.Action(term, target)
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala
index c143665f5..c518b09cb 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala
@@ -17,22 +17,20 @@ import net.psforever.types.{PlanetSideGUID, Vector3}
* while `Vehicle`-owned terminals may not.
* @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields
*/
-class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
+class Terminal(tdef : TerminalDefinition) extends Amenity
+ with Hackable {
HackSound = TriggeredSound.HackTerminal
HackEffectDuration = Array(0, 30, 60, 90)
HackDuration = Array(0, 10, 5, 3)
//the following fields and related methods are neither finalized nor integrated; GOTO Request
- private var health : Int = 100 //TODO not real health value
-
- def Health : Int = health
def Damaged(dam : Int) : Unit = {
- health = Math.max(0, Health - dam)
+ Health = Math.max(0, Health - dam)
}
def Repair(rep : Int) : Unit = {
- health = Math.min(Health + rep, 100)
+ Health = Math.min(Health + rep, 100)
}
/**
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala
index 4631182cc..4afbfb252 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala
@@ -2,24 +2,43 @@
package net.psforever.objects.serverobject.terminals
import akka.actor.Actor
+import net.psforever.objects.{GlobalDefinitions, SimpleItem}
import net.psforever.objects.serverobject.CommonMessages
-import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
+import net.psforever.objects.serverobject.damage.DamageableAmenity
import net.psforever.objects.serverobject.hackable.HackableBehavior
+import net.psforever.objects.serverobject.repair.RepairableAmenity
+import net.psforever.objects.serverobject.structures.Building
/**
* An `Actor` that handles messages being dispatched to a specific `Terminal`.
* @param term the `Terminal` object being governed
*/
-class TerminalControl(term : Terminal) extends Actor with FactionAffinityBehavior.Check with HackableBehavior.GenericHackable {
- def FactionObject : FactionAffinity = term
+class TerminalControl(term : Terminal) extends Actor
+ with FactionAffinityBehavior.Check
+ with HackableBehavior.GenericHackable
+ with DamageableAmenity
+ with RepairableAmenity {
+ def FactionObject = term
def HackableObject = term
+ def DamageableObject = term
+ def RepairableObject = term
def receive : Receive = checkBehavior
- .orElse(hackableBehavior)
- .orElse {
+ .orElse(hackableBehavior)
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
+ .orElse {
case Terminal.Request(player, msg) =>
sender ! Terminal.TerminalMessage(player, msg, term.Request(player, msg))
+ case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
+ //TODO setup certifications check
+ term.Owner match {
+ case b : Building if (b.Faction != player.Faction || b.CaptureConsoleIsHacked) && term.HackedBy.isEmpty =>
+ sender ! CommonMessages.Hack(player, term, Some(item))
+ case _ => ;
+ }
case _ => ;
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala
index 5659e367b..10d017600 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala
@@ -3,12 +3,13 @@ package net.psforever.objects.serverobject.terminals
import net.psforever.objects.Player
import net.psforever.objects.definition.converter.TerminalConverter
+import net.psforever.objects.serverobject.structures.AmenityDefinition
/**
* The basic definition for any `Terminal` object.
* @param objectId the object's identifier number
*/
-abstract class TerminalDefinition(objectId : Int) extends net.psforever.objects.definition.ObjectDefinition(objectId) {
+abstract class TerminalDefinition(objectId : Int) extends AmenityDefinition(objectId) {
Name = "terminal"
Packet = new TerminalConverter
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala
index 5b47ffe4c..6e7c53458 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeControl.scala
@@ -2,16 +2,47 @@
package net.psforever.objects.serverobject.tube
import akka.actor.Actor
-import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
+import net.psforever.objects.ballistics.ResolvedProjectile
+import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
+import net.psforever.objects.serverobject.damage.Damageable.Target
+import net.psforever.objects.serverobject.damage.DamageableAmenity
+import net.psforever.objects.serverobject.repair.{Repairable, RepairableAmenity}
+import net.psforever.objects.serverobject.structures.Building
/**
* An `Actor` that handles messages being dispatched to a specific `SpawnTube`.
* @param tube the `SpawnTube` object being governed
*/
-class SpawnTubeControl(tube : SpawnTube) extends Actor with FactionAffinityBehavior.Check {
- def FactionObject : FactionAffinity = tube
+class SpawnTubeControl(tube : SpawnTube) extends Actor
+ with FactionAffinityBehavior.Check
+ with DamageableAmenity
+ with RepairableAmenity {
+ def FactionObject = tube
+ def DamageableObject = tube
+ def RepairableObject = tube
- def receive : Receive = checkBehavior.orElse { case _ =>; }
+ def receive : Receive = checkBehavior
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
+ .orElse {
+ case _ => ;
+ }
+
+ override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
+ super.DestructionAwareness(target, cause)
+ tube.Owner match {
+ case b : Building => b.Actor ! Building.AmenityStateChange(tube)
+ case _ => ;
+ }
+ }
+
+ override def Restoration(obj : Repairable.Target) : Unit = {
+ super.Restoration(obj)
+ tube.Owner match {
+ case b : Building => b.Actor ! Building.AmenityStateChange(tube)
+ case _ => ;
+ }
+ }
override def toString : String = tube.Definition.Name
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala
index a78fb8dca..d96011b99 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/tube/SpawnTubeDefinition.scala
@@ -3,15 +3,14 @@ package net.psforever.objects.serverobject.tube
import akka.actor.ActorContext
import net.psforever.objects.SpawnPointDefinition
-import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.definition.converter.SpawnTubeConverter
import net.psforever.objects.serverobject.PlanetSideServerObject
-import net.psforever.objects.serverobject.structures.Amenity
+import net.psforever.objects.serverobject.structures.{Amenity, AmenityDefinition}
/**
* The definition for any spawn point in the game world.
*/
-class SpawnTubeDefinition(object_id : Int) extends ObjectDefinition(object_id)
+class SpawnTubeDefinition(object_id : Int) extends AmenityDefinition(object_id)
with SpawnPointDefinition {
Packet = new SpawnTubeConverter
}
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 a3b44afe7..b03053051 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
@@ -4,57 +4,14 @@ package net.psforever.objects.serverobject.turret
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.types.Vector3
-import net.psforever.objects.vital.{DamageResistanceModel, StandardResistanceProfile, Vitality}
class FacilityTurret(tDef : FacilityTurretDefinition) extends Amenity
with WeaponTurret
- with JammableUnit
- with Vitality
- with StandardResistanceProfile {
- /** some turrets can be updated; they all start without updates */
- private var upgradePath : TurretUpgrade.Value = TurretUpgrade.None
- private var middleOfUpgrade : Boolean = false
-
+ with JammableUnit {
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
- def Upgrade : TurretUpgrade.Value = upgradePath
-
- def Upgrade_=(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
- middleOfUpgrade = true //blocking flag; block early
- var updated = false
- //upgrade each weapon as long as that weapon has a valid option for that upgrade
- Definition.Weapons.foreach({ case(index, upgradePaths) =>
- if(upgradePaths.contains(upgrade)) {
- updated = true
- weapons(index).Equipment.get.asInstanceOf[TurretWeapon].Upgrade = upgrade
- }
- })
- if(updated) {
- upgradePath = upgrade
- }
- else {
- middleOfUpgrade = false //reset
- }
- Upgrade
- }
-
- def ConfirmUpgrade(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
- if(middleOfUpgrade && upgradePath == upgrade) {
- middleOfUpgrade = false
- }
- upgradePath
- }
-
- def isUpgrading : Boolean = middleOfUpgrade
-
- def DamageModel = Definition.asInstanceOf[DamageResistanceModel]
-
def Definition : FacilityTurretDefinition = 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 5fedf16c8..feb2fb79a 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
@@ -1,20 +1,17 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret
-import akka.actor.{Actor, Cancellable}
-import net.psforever.objects.{DefaultCancellable, GlobalDefinitions, Player}
+import akka.actor.Actor
import net.psforever.objects.ballistics.ResolvedProjectile
-import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
-import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
-import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
-import net.psforever.objects.vital.Vitality
-import net.psforever.objects.zones.Zone
-import net.psforever.types.PlanetSideGUID
-import services.Service
+import net.psforever.objects.{GlobalDefinitions, Player}
+import net.psforever.objects.equipment.JammableMountedWeapons
+import net.psforever.objects.serverobject.mount.MountableBehavior
+import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
+import net.psforever.objects.serverobject.damage.DamageableWeaponTurret
+import net.psforever.objects.serverobject.repair.Repairable.Target
+import net.psforever.objects.serverobject.repair.RepairableWeaponTurret
import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage}
-import services.vehicle.{VehicleAction, VehicleServiceMessage}
-import services.vehicle.support.TurretUpgrader
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
@@ -30,27 +27,30 @@ import scala.concurrent.duration._
*/
class FacilityTurretControl(turret : FacilityTurret) extends Actor
with FactionAffinityBehavior.Check
+ with MountableBehavior.TurretMount
with MountableBehavior.Dismount
+ with DamageableWeaponTurret
+ with RepairableWeaponTurret
with JammableMountedWeapons {
-
+ def FactionObject = turret
+ def MountableObject = turret
+ def JammableObject = turret
+ def DamageableObject = turret
+ def RepairableObject = turret
if(turret.Definition == GlobalDefinitions.vanu_sentry_turret) {
// todo: Schedule this to start when weapon is discharged, not all the time
context.system.scheduler.schedule(3 seconds, 200 milliseconds, self, FacilityTurret.RechargeAmmo())
}
- def MountableObject = turret
-
- def JammableObject = turret
-
- def FactionObject : FactionAffinity = turret
-
def receive : Receive = checkBehavior
.orElse(jammableBehavior)
+ .orElse(mountBehavior)
.orElse(dismountBehavior)
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
.orElse {
case FacilityTurret.RechargeAmmo() =>
val weapon = turret.ControlledWeapon(1).get.asInstanceOf[net.psforever.objects.Tool]
-
// recharge when last shot fired 3s delay, +1, 200ms interval
if(weapon.Magazine < weapon.MaxMagazine && System.nanoTime() - weapon.LastDischarge > 3000000000L) {
weapon.Magazine += 1
@@ -60,117 +60,27 @@ class FacilityTurretControl(turret : FacilityTurret) extends Actor
case _ => ;
}
}
- case Mountable.TryMount(user, seat_num) =>
- turret.Seat(seat_num) match {
- case Some(seat) =>
- if((!turret.Definition.FactionLocked || user.Faction == turret.Faction) &&
- (seat.Occupant = user).contains(user)) {
- user.VehicleSeated = turret.GUID
- sender ! Mountable.MountMessages(user, Mountable.CanMount(turret, seat_num))
- }
- else {
- sender ! Mountable.MountMessages(user, Mountable.CanNotMount(turret, seat_num))
- }
- case None =>
- sender ! Mountable.MountMessages(user, Mountable.CanNotMount(turret, seat_num))
- }
-
- case Vitality.Damage(damage_func) =>
- if(turret.Health > 0) {
- val originalHealth = turret.Health
- val cause = damage_func(turret)
- val health = turret.Health
- val damageToHealth = originalHealth - health
- FacilityTurretControl.HandleDamageResolution(turret, cause, damageToHealth)
- if(damageToHealth > 0) {
- 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")
- }
- }
case _ => ;
}
-}
-object FacilityTurretControl {
- def HandleDamageResolution(target : FacilityTurret, cause : ResolvedProjectile, damage : Int) : Unit = {
- val zone = target.Zone
- val targetGUID = target.GUID
- val playerGUID = target.Zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
- case Some(player) => player.GUID
- case _ => targetGUID
- }
- val continentId = zone.Id
- if(target.Health > 1) {
- //alert occupants to damage source
- if(damage > 0) {
- zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
- //alert occupants to damage source
- HandleDamageAwareness(target, playerGUID, cause)
- }
- if(cause.projectile.profile.JammerProjectile) {
- target.Actor ! JammableUnit.Jammered(cause)
- }
- }
- else {
- //alert to vehicle death (hence, occupants' deaths)
- HandleDestructionAwareness(target, playerGUID, cause)
- }
- zone.VehicleEvents ! VehicleServiceMessage(continentId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
- }
-
- /**
- * na
- * @param target na
- * @param attribution na
- * @param lastShot na
- */
- def HandleDamageAwareness(target : FacilityTurret, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
+ override protected def DestructionAwareness(target : Target, cause : ResolvedProjectile) : Unit = {
+ super.DestructionAwareness(target, cause)
val zone = target.Zone
val zoneId = zone.Id
- //alert occupants to damage source
- target.Seats.values.filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
- }).foreach(seat => {
- val tplayer = seat.Occupant.get
- zone.AvatarEvents ! AvatarServiceMessage(zoneId, AvatarAction.HitHint(attribution, tplayer.GUID))
- })
+ val events = zone.AvatarEvents
+ val tguid = target.GUID
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 1))
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 1))
}
- /**
- * na
- * @param target na
- * @param attribution na
- * @param lastShot na
- */
- def HandleDestructionAwareness(target : FacilityTurret, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
- target.Actor ! JammableUnit.ClearJammeredSound()
- target.Actor ! JammableUnit.ClearJammeredStatus()
- val zone = target.Zone
+ override def Restoration(obj : Target) : Unit = {
+ super.Restoration(obj)
+ val zone = turret.Zone
val zoneId = zone.Id
- target.Seats.values.filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
- }).foreach(seat => {
- val tplayer = seat.Occupant.get
- tplayer.History(lastShot)
- tplayer.Actor ! Player.Die()
- })
- //turret wreckage has no weapons
- // target.Weapons.values
- // .filter {
- // _.Equipment.nonEmpty
- // }
- // .foreach(slot => {
- // val wep = slot.Equipment.get
- // zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
- // })
- // zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(targetGUID, playerGUID, playerGUID, player.Position))
- target.Health = 1 //TODO turret "death" at 0, as is proper
- zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, target.GUID, 0, target.Health)) //TODO not necessary
- if(target.Upgrade != TurretUpgrade.None) {
- zone.VehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.ClearSpecific(List(target), zone))
- zone.VehicleEvents ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(target, zone, TurretUpgrade.None))
- }
+ val events = zone.AvatarEvents
+ val tguid = turret.GUID
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 0))
+ events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 0))
}
}
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala
index 43dd4db08..f4b1d203c 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretDefinition.scala
@@ -1,17 +1,17 @@
// Copyright (c) 2019 PSForever
package net.psforever.objects.serverobject.turret
-import net.psforever.objects.definition.ObjectDefinition
+import net.psforever.objects.serverobject.structures.AmenityDefinition
import net.psforever.objects.vital.{StandardResolutions, StandardVehicleDamage, StandardVehicleResistance}
/**
* The definition for any `FacilityTurret`.
* @param objectId the object's identifier number
*/
-class FacilityTurretDefinition(private val objectId : Int) extends ObjectDefinition(objectId)
+class FacilityTurretDefinition(private val objectId : Int) extends AmenityDefinition(objectId)
with TurretDefinition {
- Damage = StandardVehicleDamage
- Resistance = StandardVehicleResistance
+ DamageUsing = StandardVehicleDamage
+ ResistUsing = StandardVehicleResistance
Model = StandardResolutions.FacilityTurrets
}
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 d9a7eae2c..64ec3dc1f 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
@@ -15,8 +15,6 @@ trait TurretDefinition extends ResistanceProfileMutators
with DamageResistanceModel {
odef : ObjectDefinition =>
Turrets(odef.ObjectId) //let throw NoSuchElementException
-
- private var maxHealth : Int = 100
/* key - entry point index, value - seat index */
private val mountPoints : mutable.HashMap[Int, Int] = mutable.HashMap()
/* key - seat number, value - hash map (below) */
@@ -29,13 +27,6 @@ trait TurretDefinition extends ResistanceProfileMutators
* see `MannedTurret.TurretAmmoBox` for details */
private var hasReserveAmmunition : Boolean = false
- def MaxHealth : Int = maxHealth
-
- def MaxHealth_=(health : Int) : Int = {
- maxHealth = health
- MaxHealth
- }
-
def MountPoints : mutable.HashMap[Int, Int] = mountPoints
def Weapons : mutable.HashMap[Int, mutable.HashMap[TurretUpgrade.Value, ToolDefinition]] = weapons
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurret.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurret.scala
index 1117a40a4..cc3161322 100644
--- a/common/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurret.scala
+++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurret.scala
@@ -1,8 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret
+import net.psforever.objects.{AmmoBox, PlanetSideGameObject, Player, Tool}
import net.psforever.objects.definition.{AmmoBoxDefinition, SeatDefinition, ToolDefinition}
-import net.psforever.objects._
import net.psforever.objects.equipment.{Equipment, EquipmentSlot}
import net.psforever.objects.inventory.{Container, GridInventory}
import net.psforever.objects.serverobject.affinity.FactionAffinity
@@ -13,10 +13,8 @@ trait WeaponTurret extends FactionAffinity
with Mountable
with MountedWeapons
with Container {
- this : PlanetSideGameObject =>
+ _ : PlanetSideGameObject =>
- private var health : Int = 1
- private var jammered : Boolean = false
/** manned turrets have just one seat; this is just standard interface */
protected val seats : Map[Int, Chair] = Map(0 -> Chair(new SeatDefinition() { ControlledWeapon = Some(1) }))
/** turrets have just one weapon; this is just standard interface */
@@ -24,21 +22,21 @@ trait WeaponTurret extends FactionAffinity
/** may or may not have inaccessible inventory space
* see `ReserveAmmunition` in the definition */
protected val inventory : GridInventory = new GridInventory() {
-
import net.psforever.types.PlanetSideGUID
-
override def Remove(index : Int) : Boolean = false
override def Remove(guid : PlanetSideGUID) : Boolean = false
}
+ /** some turrets can be updated; they all start without updates */
+ private var upgradePath : TurretUpgrade.Value = TurretUpgrade.None
+ private var middleOfUpgrade : Boolean = false
- def Health : Int = {
- health
- }
-
- def Health_=(toHealth : Int) : Int = {
- health = toHealth
- health
- }
+ /*
+ do not mind what the IDE probably comments about these method prototypes for Health and MaxHealth
+ they do not override methods in Vitality, unless overrode in any class that implements this one
+ due to the inheritance requirement above, these statements are not required to be implemented or overrode ever
+ they are purely for class visibility
+ */
+ def Health : Int
def MaxHealth : Int
@@ -81,13 +79,40 @@ trait WeaponTurret extends FactionAffinity
}
}
- def Jammered : Boolean = jammered
+ def Upgrade : TurretUpgrade.Value = upgradePath
- def Jammered_=(jamState : Boolean) : Boolean = {
- jammered = jamState
- Jammered
+ def Upgrade_=(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
+ middleOfUpgrade = true //blocking flag; block early
+ var updated = false
+ //upgrade each weapon as long as that weapon has a valid option for that upgrade
+ Definition match {
+ case definition : TurretDefinition =>
+ definition.Weapons.foreach({ case(index, upgradePaths) =>
+ if(upgradePaths.contains(upgrade)) {
+ updated = true
+ weapons(index).Equipment.get.asInstanceOf[TurretWeapon].Upgrade = upgrade
+ }
+ })
+ case _ => ;
+ }
+ if(updated) {
+ upgradePath = upgrade
+ }
+ else {
+ middleOfUpgrade = false //reset
+ }
+ Upgrade
}
+ def ConfirmUpgrade(upgrade : TurretUpgrade.Value) : TurretUpgrade.Value = {
+ if(middleOfUpgrade && upgradePath == upgrade) {
+ middleOfUpgrade = false
+ }
+ upgradePath
+ }
+
+ def isUpgrading : Boolean = middleOfUpgrade
+
def Definition : TurretDefinition
}
@@ -110,8 +135,6 @@ object WeaponTurret {
*/
def LoadDefinition(turret : WeaponTurret, tdef : TurretDefinition) : WeaponTurret = {
import net.psforever.objects.equipment.EquipmentSize.BaseTurretWeapon
- //general stuff
- turret.Health = tdef.MaxHealth
//create weapons; note the class
turret.weapons = tdef.Weapons.map({case (num, upgradePaths) =>
val slot = EquipmentSlot(BaseTurretWeapon)
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurrets.scala b/common/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurrets.scala
new file mode 100644
index 000000000..0cb89b04d
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/serverobject/turret/WeaponTurrets.scala
@@ -0,0 +1,50 @@
+// Copyright (c) 2020 PSForever
+package net.psforever.objects.serverobject.turret
+
+import net.psforever.objects.{Player, Tool}
+import net.psforever.packet.game.InventoryStateMessage
+import services.Service
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+import services.vehicle.VehicleServiceMessage
+import services.vehicle.support.TurretUpgrader
+
+object WeaponTurrets {
+ private val log = org.log4s.getLogger("WeaponTurrets")
+
+ /**
+ * The process of upgrading a turret's weapon(s) is completed.
+ * Pass the message onto the turret and onto the vehicle events system.
+ * Additionally, force-deplete the ammunition count of the nano-dispenser used to perform the upgrade.
+ * @param target the turret
+ * @param tool the nano-dispenser that was used to perform this upgrade
+ * @param upgrade the new upgrade state
+ */
+ def FinishUpgradingMannedTurret(target : FacilityTurret, user : Player, tool : Tool, upgrade : TurretUpgrade.Value)() : Unit = {
+ tool.Magazine = 0
+ target.Zone.AvatarEvents ! AvatarServiceMessage(
+ user.Name,
+ AvatarAction.SendResponse(Service.defaultPlayerGUID, InventoryStateMessage(tool.AmmoSlot.Box.GUID, tool.GUID, 0))
+ )
+ FinishUpgradingMannedTurret(target, upgrade)
+ }
+
+ /**
+ * The process of upgrading a turret's weapon(s) is completed.
+ * * Pass the message onto the turret and onto the vehicle events system.
+ * @see `FacilityTurret`
+ * @see `TurretUpgrade`
+ * @see `TurretUpgrader.AddTask`
+ * @see `TurretUpgrader.ClearSpecific`
+ * @see `VehicleServiceMessage.TurretUpgrade`
+ * @param target the facility turret being upgraded
+ * @param upgrade the upgrade being applied to the turret (usually, it's weapon system)
+ */
+ def FinishUpgradingMannedTurret(target : FacilityTurret, upgrade : TurretUpgrade.Value) : Unit = {
+ log.info(s"Converting manned wall turret weapon to $upgrade")
+ val zone = target.Zone
+ val events = zone.VehicleEvents
+ events ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.ClearSpecific(List(target), zone))
+ events ! VehicleServiceMessage.TurretUpgrade(TurretUpgrader.AddTask(target, zone, upgrade))
+ }
+}
+
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala b/common/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
new file mode 100644
index 000000000..689ffff6f
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
@@ -0,0 +1,423 @@
+// Copyright (c) 2020 PSForever
+package net.psforever.objects.vehicles
+
+import akka.actor.{Actor, Cancellable}
+import net.psforever.objects.zones.Zone
+import net.psforever.objects._
+import net.psforever.objects.vehicles.CargoBehavior.{CheckCargoDismount, CheckCargoMounting}
+import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectAttachMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
+import net.psforever.types.{CargoStatus, PlanetSideGUID, Vector3}
+import services.avatar.{AvatarAction, AvatarServiceMessage}
+import services.{RemoverActor, Service}
+import services.vehicle.{VehicleAction, VehicleServiceMessage}
+
+import scala.concurrent.duration._
+
+trait CargoBehavior {
+ _ : Actor =>
+ private var cargoMountTimer : Cancellable = DefaultCancellable.obj
+ private var cargoDismountTimer : Cancellable = DefaultCancellable.obj
+
+ /* gate-keep mounting behavior so that unit does not try to dismount as cargo, or mount different vehicle */
+ private var isMounting : Option[PlanetSideGUID] = None
+ /* gate-keep dismounting behavior so that unit does not try to mount as cargo, or dismount from different vehicle */
+ private var isDismounting : Option[PlanetSideGUID] = None
+
+ def CargoObject : Vehicle
+
+ val cargoBehavior : Receive = {
+ case CheckCargoMounting(carrier_guid, mountPoint, iteration) =>
+ val obj = CargoObject
+ if((isMounting.isEmpty || isMounting.contains(carrier_guid)) && isDismounting.isEmpty &&
+ CargoBehavior.HandleCheckCargoMounting(obj.Zone, carrier_guid, obj.GUID, obj, mountPoint, iteration)) {
+ if(iteration == 0) {
+ //open the cargo bay door
+ obj.Zone.AvatarEvents ! AvatarServiceMessage(
+ obj.Zone.Id,
+ AvatarAction.SendResponse(
+ Service.defaultPlayerGUID,
+ CargoMountPointStatusMessage(carrier_guid, PlanetSideGUID(0), obj.GUID, PlanetSideGUID(0), mountPoint, CargoStatus.InProgress, 0)
+ )
+ )
+ }
+ isMounting = Some(carrier_guid)
+ import scala.concurrent.ExecutionContext.Implicits.global
+ cargoMountTimer.cancel
+ cargoMountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoMounting(carrier_guid, mountPoint, iteration + 1))
+ }
+ else {
+ isMounting = None
+ }
+
+ case CheckCargoDismount(carrier_guid, mountPoint, iteration) =>
+ val obj = CargoObject
+ if((isDismounting.isEmpty || isDismounting.contains(carrier_guid)) && isMounting.isEmpty &&
+ CargoBehavior.HandleCheckCargoDismounting(obj.Zone, carrier_guid, obj.GUID, obj, mountPoint, iteration)) {
+ isDismounting = Some(carrier_guid)
+ import scala.concurrent.ExecutionContext.Implicits.global
+ cargoDismountTimer.cancel
+ cargoDismountTimer = context.system.scheduler.scheduleOnce(250 milliseconds, self, CheckCargoDismount(carrier_guid, mountPoint, iteration + 1))
+ }
+ else {
+ isDismounting = None
+ }
+ }
+}
+
+object CargoBehavior {
+ private val log = org.log4s.getLogger("CargoBehavior")
+
+ final case class CheckCargoMounting(carrier_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int)
+ final case class CheckCargoDismount(carrier_guid: PlanetSideGUID, cargo_mountpoint: Int, iteration: Int)
+
+ /**
+ * na
+ * @param carrierGUID the ferrying carrier vehicle
+ * @param cargoGUID the vehicle being ferried as cargo
+ * @param cargo the vehicle being ferried as cargo
+ * @param mountPoint the cargo hold to which the cargo vehicle is stowed
+ * @param iteration number of times a proper mounting for this combination has been queried
+ */
+ def HandleCheckCargoMounting(zone : Zone, carrierGUID : PlanetSideGUID, cargoGUID : PlanetSideGUID, cargo : Vehicle, mountPoint : Int, iteration : Int) : Boolean = {
+ zone.GUID(carrierGUID) match {
+ case Some(carrier : Vehicle) =>
+ HandleCheckCargoMounting(cargoGUID, cargo, carrierGUID, carrier, mountPoint, iteration)
+ case carrier if iteration > 0 =>
+ log.error(s"HandleCheckCargoMounting: participant vehicles changed in the middle of a mounting event")
+ LogCargoEventMissingVehicleError("HandleCheckCargoMounting: carrier", carrier, carrierGUID)
+ false
+ case _ =>
+ false
+ }
+ }
+
+ /**
+ * na
+ * @param cargoGUID the vehicle being ferried as cargo
+ * @param cargo the vehicle being ferried as cargo
+ * @param carrierGUID the ferrying carrier vehicle
+ * @param carrier the ferrying carrier vehicle
+ * @param mountPoint the cargo hold to which the cargo vehicle is stowed
+ * @param iteration number of times a proper mounting for this combination has been queried
+ */
+ private def HandleCheckCargoMounting(cargoGUID : PlanetSideGUID, cargo : Vehicle, carrierGUID : PlanetSideGUID, carrier : Vehicle, mountPoint : Int, iteration : Int) : Boolean = {
+ val zone = carrier.Zone
+ val distance = Vector3.DistanceSquared(cargo.Position, carrier.Position)
+ carrier.CargoHold(mountPoint) match {
+ case Some(hold) if !hold.isOccupied =>
+ log.debug(s"HandleCheckCargoMounting: mount distance between $cargoGUID and $carrierGUID - actual=$distance, target=64")
+ if(distance <= 64) {
+ //cargo vehicle is close enough to assume to be physically within the carrier's hold; mount it
+ log.info(s"HandleCheckCargoMounting: mounting cargo vehicle in carrier at distance of $distance")
+ cargo.MountedIn = carrierGUID
+ hold.Occupant = cargo
+ cargo.Velocity = None
+ zone.VehicleEvents ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health)))
+ zone.VehicleEvents ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields)))
+ val (attachMsg, mountPointMsg) = CargoMountBehaviorForAll(carrier, cargo, mountPoint)
+ log.info(s"HandleCheckCargoMounting: $attachMsg")
+ log.info(s"HandleCheckCargoMounting: $mountPointMsg")
+ false
+ }
+ else if(distance > 625 || iteration >= 40) {
+ //vehicles moved too far away or took too long to get into proper position; abort mounting
+ log.info("HandleCheckCargoMounting: cargo vehicle is too far away or didn't mount within allocated time - aborting")
+ val cargoDriverGUID = cargo.Seats(0).Occupant.get.GUID
+ zone.VehicleEvents ! VehicleServiceMessage(
+ zone.Id,
+ VehicleAction.SendResponse(
+ cargoDriverGUID,
+ CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
+ )
+ )
+ false
+ //sending packet to the cargo vehicle's client results in player locking himself in his vehicle
+ //player gets stuck as "always trying to remount the cargo hold"
+ //obviously, don't do this
+ }
+ else {
+ //cargo vehicle still not in position but there is more time to wait; reschedule check
+ true
+ }
+ case None => ;
+ log.warn(s"HandleCheckCargoMounting: carrier vehicle $carrier does not have a cargo hold #$mountPoint")
+ false
+ case _ =>
+ if(iteration == 0) {
+ log.warn(s"HandleCheckCargoMounting: carrier vehicle $carrier already possesses cargo in hold #$mountPoint; this operation was initiated incorrectly")
+ }
+ else {
+ log.error(s"HandleCheckCargoMounting: something has attached to the carrier vehicle $carrier cargo of hold #$mountPoint while a cargo dismount event was ongoing; stopped at iteration $iteration / 40")
+ }
+ false
+ }
+ }
+
+ /**
+ * na
+ * @param cargoGUID na
+ * @param carrierGUID na
+ * @param mountPoint na
+ * @param iteration na
+ */
+ def HandleCheckCargoDismounting(zone : Zone, carrierGUID : PlanetSideGUID, cargoGUID : PlanetSideGUID, cargo : Vehicle, mountPoint : Int, iteration : Int) : Boolean = {
+ zone.GUID(carrierGUID) match {
+ case Some(carrier : Vehicle) =>
+ HandleCheckCargoDismounting(cargoGUID, cargo, carrierGUID, carrier, mountPoint, iteration)
+ case carrier if iteration > 0 =>
+ log.error(s"HandleCheckCargoDismounting: participant vehicles changed in the middle of a mounting event")
+ LogCargoEventMissingVehicleError("HandleCheckCargoDismounting: carrier", carrier, carrierGUID)
+ false
+ case _ =>
+ false
+ }
+ }
+
+ /**
+ * na
+ * @param cargoGUID na
+ * @param cargo na
+ * @param carrierGUID na
+ * @param carrier na
+ * @param mountPoint na
+ * @param iteration na
+ */
+ private def HandleCheckCargoDismounting(cargoGUID : PlanetSideGUID, cargo : Vehicle, carrierGUID : PlanetSideGUID, carrier : Vehicle, mountPoint : Int, iteration : Int) : Boolean = {
+ val zone = carrier.Zone
+ carrier.CargoHold(mountPoint) match {
+ case Some(hold) if !hold.isOccupied =>
+ val distance = Vector3.DistanceSquared(cargo.Position, carrier.Position)
+ log.debug(s"HandleCheckCargoDismounting: mount distance between $cargoGUID and $carrierGUID - actual=$distance, target=225")
+ if(distance > 225) {
+ //cargo vehicle has moved far enough away; close the carrier's hold door
+ log.info(s"HandleCheckCargoDismounting: dismount of cargo vehicle from carrier complete at distance of $distance")
+ val cargoDriverGUID = cargo.Seats(0).Occupant.get.GUID
+ zone.VehicleEvents ! VehicleServiceMessage(
+ zone.Id,
+ VehicleAction.SendResponse(
+ cargoDriverGUID,
+ CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
+ )
+ )
+ false
+ //sending packet to the cargo vehicle's client results in player locking himself in his vehicle
+ //player gets stuck as "always trying to remount the cargo hold"
+ //obviously, don't do this
+ }
+ else if(iteration > 40) {
+ //cargo vehicle has spent too long not getting far enough away; restore the cargo's mount in the carrier hold
+ cargo.MountedIn = carrierGUID
+ hold.Occupant = cargo
+ CargoMountBehaviorForAll(carrier, cargo, mountPoint)
+ false
+ }
+ else {
+ //cargo vehicle did not move far away enough yet and there is more time to wait; reschedule check
+ true
+ }
+ case None =>
+ log.warn(s"HandleCheckCargoDismounting: carrier vehicle $carrier does not have a cargo hold #$mountPoint")
+ false
+ case _ =>
+ if(iteration == 0) {
+ log.warn(s"HandleCheckCargoDismounting: carrier vehicle $carrier will not discharge the cargo of hold #$mountPoint; this operation was initiated incorrectly")
+ }
+ else {
+ log.error(s"HandleCheckCargoDismounting: something has attached to the carrier vehicle $carrier cargo of hold #$mountPoint while a cargo dismount event was ongoing; stopped at iteration $iteration / 40")
+ }
+ false
+ }
+ }
+
+ /**
+ * na
+ * @param player_guid na
+ * @param cargo_guid na
+ * @param bailed na
+ * @param requestedByPassenger na
+ * @param kicked na
+ */
+ def HandleVehicleCargoDismount(zone : Zone, player_guid : PlanetSideGUID, cargo_guid : PlanetSideGUID, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) : Unit = {
+ zone.GUID(cargo_guid) match {
+ case Some(cargo : Vehicle) =>
+ zone.GUID(cargo.MountedIn) match {
+ case Some(ferry : Vehicle) =>
+ HandleVehicleCargoDismount(player_guid, cargo_guid, cargo, ferry.GUID, ferry, bailed, requestedByPassenger, kicked)
+ case _ =>
+ log.warn(s"DismountVehicleCargo: target ${cargo.Definition.Name}@$cargo_guid does not know what treats it as cargo")
+ }
+ case _ =>
+ log.warn(s"DismountVehicleCargo: target $cargo_guid either is not a vehicle in ${zone.Id} or does not exist")
+ }
+ }
+
+ /**
+ * na
+ * @param player_guid the target player
+ * @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 HandleVehicleCargoDismount(player_guid : PlanetSideGUID, cargoGUID : PlanetSideGUID, cargo : Vehicle, carrierGUID : PlanetSideGUID, carrier : Vehicle, bailed : Boolean, requestedByPassenger : Boolean, kicked : Boolean) : Unit = {
+ val zone = carrier.Zone
+ carrier.CargoHolds.find({case(_, hold) => hold.Occupant.contains(cargo)}) match {
+ case Some((mountPoint, hold)) =>
+ cargo.MountedIn = None
+ hold.Occupant = None
+ val driverOpt = cargo.Seats(0).Occupant
+ val rotation : Vector3 = if(Vehicles.CargoOrientation(cargo) == 1) { //TODO: BFRs will likely also need this set
+ //dismount router "sideways" in a lodestar
+ carrier.Orientation.xy + Vector3.z((carrier.Orientation.z - 90) % 360)
+ }
+ else {
+ carrier.Orientation
+ }
+ val cargoHoldPosition : Vector3 = if(carrier.Definition == GlobalDefinitions.dropship) {
+ //the galaxy cargo bay is offset backwards from the center of the vehicle
+ carrier.Position + Vector3.Rz(Vector3(0, 7, 0), math.toRadians(carrier.Orientation.z))
+ }
+ else {
+ //the lodestar's cargo hold is almost the center of the vehicle
+ carrier.Position
+ }
+ zone.VehicleEvents ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 0, cargo.Health)))
+ zone.VehicleEvents ! VehicleServiceMessage(s"${cargo.Actor}", VehicleAction.SendResponse(PlanetSideGUID(0), PlanetsideAttributeMessage(cargoGUID, 68, cargo.Shields)))
+ if(carrier.Flying) {
+ //the carrier vehicle is flying; eject the cargo vehicle
+ val ejectCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.InProgress, 0)
+ val detachCargoMsg = ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition - Vector3.z(1), rotation)
+ val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, ejectCargoMsg))
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, detachCargoMsg))
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, resetCargoMsg))
+ log.debug(ejectCargoMsg.toString)
+ log.debug(detachCargoMsg.toString)
+ if(driverOpt.isEmpty) {
+ //TODO cargo should drop like a rock like normal; until then, deconstruct it
+ zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(cargo), zone))
+ zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(cargo, zone, Some(0 seconds)))
+ }
+ }
+ else {
+ //the carrier vehicle is not flying; just open the door and let the cargo vehicle back out; force it out if necessary
+ val cargoStatusMessage = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), cargoGUID, PlanetSideGUID(0), mountPoint, CargoStatus.InProgress, 0)
+ val cargoDetachMessage = ObjectDetachMessage(carrierGUID, cargoGUID, cargoHoldPosition + Vector3.z(1f), rotation)
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, cargoStatusMessage))
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(Service.defaultPlayerGUID, cargoDetachMessage))
+ driverOpt match {
+ case Some(driver) =>
+ zone.VehicleEvents ! VehicleServiceMessage(s"${driver.Name}", VehicleAction.KickCargo(player_guid, cargo, cargo.Definition.AutoPilotSpeed2, 2500))
+ //check every quarter second if the vehicle has moved far enough away to be considered dismounted
+ cargo.Actor ! CheckCargoDismount(carrierGUID, mountPoint, 0)
+ case None =>
+ val resetCargoMsg = CargoMountPointStatusMessage(carrierGUID, PlanetSideGUID(0), PlanetSideGUID(0), cargoGUID, mountPoint, CargoStatus.Empty, 0)
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(PlanetSideGUID(0), resetCargoMsg)) //lazy
+ //TODO cargo should back out like normal; until then, deconstruct it
+ zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(cargo), zone))
+ zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(cargo, zone, Some(0 seconds)))
+ }
+ }
+
+ case None =>
+ log.warn(s"HandleDismountVehicleCargo: can not locate cargo $cargo in any hold of the carrier vehicle $carrier")
+ }
+ }
+
+ //logging and messaging support functions
+ /**
+ * na
+ * @param decorator custom text for these messages in the log
+ * @param target an optional the target object
+ * @param targetGUID the expected globally unique identifier of the target object
+ */
+ def LogCargoEventMissingVehicleError(decorator : String, target : Option[PlanetSideGameObject], targetGUID : PlanetSideGUID) : Unit = {
+ target match {
+ case Some(_ : Vehicle) => ;
+ case Some(_) => log.error(s"$decorator target $targetGUID no longer identifies as a vehicle")
+ case None => log.error(s"$decorator target $targetGUID has gone missing")
+ }
+ }
+
+ /**
+ * Produce an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
+ * that will set up a realized parent-child association between a ferrying vehicle and a ferried vehicle.
+ * @see `CargoMountPointStatusMessage`
+ * @see `ObjectAttachMessage`
+ * @see `Vehicles.CargoOrientation`
+ * @param carrier the ferrying vehicle
+ * @param cargo the ferried vehicle
+ * @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached;
+ * also known as a "cargo hold"
+ * @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
+ */
+ def CargoMountMessages(carrier : Vehicle, cargo : Vehicle, mountPoint : Int) : (ObjectAttachMessage, CargoMountPointStatusMessage) = {
+ CargoMountMessages(carrier.GUID, cargo.GUID, mountPoint, Vehicles.CargoOrientation(cargo))
+ }
+
+ /**
+ * Produce an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
+ * that will set up a realized parent-child association between a ferrying vehicle and a ferried vehicle.
+ * @see `CargoMountPointStatusMessage`
+ * @see `ObjectAttachMessage`
+ * @param carrierGUID the ferrying vehicle
+ * @param cargoGUID the ferried vehicle
+ * @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
+ * @param orientation the positioning of the cargo vehicle in the carrier cargo bay
+ * @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
+ */
+ def CargoMountMessages(carrierGUID : PlanetSideGUID, cargoGUID : PlanetSideGUID, mountPoint : Int, orientation : Int) : (ObjectAttachMessage, CargoMountPointStatusMessage) = {
+ (
+ ObjectAttachMessage(carrierGUID, cargoGUID, mountPoint),
+ CargoMountPointStatusMessage(carrierGUID, cargoGUID, cargoGUID, PlanetSideGUID(0), mountPoint, CargoStatus.Occupied, orientation)
+ )
+ }
+
+ /**
+ * Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to all other clients, not this one.
+ * @see `CargoMountPointStatusMessage`
+ * @see `ObjectAttachMessage`
+ * @param carrier the ferrying vehicle
+ * @param cargo the ferried vehicle
+ * @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
+ * @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
+ */
+ def CargoMountBehaviorForOthers(carrier : Vehicle, cargo : Vehicle, mountPoint : Int, exclude : PlanetSideGUID) : (ObjectAttachMessage, CargoMountPointStatusMessage) = {
+ val msgs @ (attachMessage, mountPointStatusMessage) = CargoMountMessages(carrier, cargo, mountPoint)
+ CargoMountMessagesForOthers(carrier.Zone, exclude, attachMessage, mountPointStatusMessage)
+ msgs
+ }
+
+ /**
+ * Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to all other clients, not this one.
+ * @see `CargoMountPointStatusMessage`
+ * @see `ObjectAttachMessage`
+ * @param attachMessage an `ObjectAttachMessage` packet suitable for initializing cargo operations
+ * @param mountPointStatusMessage a `CargoMountPointStatusMessage` packet suitable for initializing cargo operations
+ */
+ def CargoMountMessagesForOthers(zone : Zone, exclude : PlanetSideGUID, attachMessage : ObjectAttachMessage, mountPointStatusMessage : CargoMountPointStatusMessage) : Unit = {
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(exclude, attachMessage))
+ zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.SendResponse(exclude, mountPointStatusMessage))
+ }
+
+ /**
+ * Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet to everyone.
+ * @see `CargoMountPointStatusMessage`
+ * @see `ObjectAttachMessage`
+ * @param carrier the ferrying vehicle
+ * @param cargo the ferried vehicle
+ * @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached
+ * @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet
+ */
+ def CargoMountBehaviorForAll(carrier : Vehicle, cargo : Vehicle, mountPoint : Int) : (ObjectAttachMessage, CargoMountPointStatusMessage) = {
+ val zone = carrier.Zone
+ val zoneId = zone.Id
+ val msgs @ (attachMessage, mountPointStatusMessage) = CargoMountMessages(carrier, cargo, mountPoint)
+ zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(Service.defaultPlayerGUID, attachMessage))
+ zone.VehicleEvents ! VehicleServiceMessage(zoneId, VehicleAction.SendResponse(Service.defaultPlayerGUID, mountPointStatusMessage))
+ msgs
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala b/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala
index 875be91f2..118290533 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/Utility.scala
@@ -1,13 +1,16 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vehicles
-import akka.actor.ActorContext
-import net.psforever.objects.definition.SimpleDeployableDefinition
+import akka.actor.{ActorContext, ActorRef}
+import net.psforever.objects.definition.BaseDeployableDefinition
import net.psforever.objects._
-import net.psforever.objects.ce.TelepadLike
-import net.psforever.objects.serverobject.structures.Amenity
+import net.psforever.objects.ce.{DeployedItem, TelepadLike}
+import net.psforever.objects.definition.converter.SmallDeployableConverter
+import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.structures.{Amenity, AmenityDefinition}
import net.psforever.objects.serverobject.terminals._
import net.psforever.objects.serverobject.tube.{SpawnTube, SpawnTubeDefinition}
+import net.psforever.objects.vehicles.Utility.InternalTelepadDefinition
import net.psforever.packet.game.ItemTransactionMessage
import net.psforever.types.{PlanetSideGUID, Vector3}
@@ -193,7 +196,8 @@ object Utility {
* and allows it to serve as one of the terminal points of a Router-telepad teleportation system.
* @param ddef na
*/
- class InternalTelepad(ddef : SimpleDeployableDefinition) extends Amenity
+ class InternalTelepad(ddef : InternalTelepadDefinition) extends Amenity
+ with UtilityWorldEntity
with TelepadLike {
/** a link to the telepad that serves as the other endpoint of this teleportation system */
private var activeTelepad : Option[PlanetSideGUID] = None
@@ -215,6 +219,21 @@ object Utility {
def Definition = ddef
}
+ /**
+ * As the `InternalTelepad` object is a unique intersection of `Amenity` and `TelepadLike`
+ * that is treated like a `Deployable`,
+ * its definition must be a unique intersection of `AmenityDefinition` and `BaseDeployableDefinition`.
+ * @see `AmenityDefinition`
+ * @see `BaseDeployableDefinition`
+ * @see `DeployableDefinition`
+ */
+ class InternalTelepadDefinition extends AmenityDefinition(DeployedItem.router_telepad_deployable.id)
+ with BaseDeployableDefinition {
+ Packet = new SmallDeployableConverter
+
+ def Item : DeployedItem.Value = DeployedItem.router_telepad_deployable
+ }
+
/**
* Provide the called-out object's logic.
* @param util the type of the `Amenity` object
@@ -241,3 +260,15 @@ object Utility {
TelepadLike.Setup
}
}
+
+object InternalTelepadDefinition {
+ def apply() : InternalTelepadDefinition =
+ new InternalTelepadDefinition()
+
+ def SimpleUninitialize(obj : PlanetSideGameObject, context : ActorContext) : Unit = { }
+
+ def SimpleUninitialize(obj : PlanetSideServerObject, context : ActorContext) : Unit = {
+ context.stop(obj.Actor)
+ obj.Actor = ActorRef.noSender
+ }
+}
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 4451b8c5c..4683d3a64 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala
@@ -2,19 +2,18 @@
package net.psforever.objects.vehicles
import akka.actor.{Actor, ActorRef}
-import net.psforever.objects.{GlobalDefinitions, Player, Vehicle}
+import net.psforever.objects.{GlobalDefinitions, SimpleItem, Vehicle}
import net.psforever.objects.ballistics.{ResolvedProjectile, VehicleSource}
-import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit}
+import net.psforever.objects.equipment.JammableMountedWeapons
+import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
-import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
-import net.psforever.objects.vital.{VehicleShieldCharge, Vitality}
-import net.psforever.objects.zones.Zone
-import net.psforever.types.{DriveState, ExoSuitType, PlanetSideGUID, Vector3}
-import services.{RemoverActor, Service}
-import services.avatar.{AvatarAction, AvatarServiceMessage}
-import services.local.{LocalAction, LocalServiceMessage}
-import services.vehicle.{VehicleAction, VehicleService, VehicleServiceMessage}
+import net.psforever.objects.serverobject.damage.DamageableVehicle
+import net.psforever.objects.serverobject.deploy.DeploymentBehavior
+import net.psforever.objects.serverobject.repair.RepairableVehicle
+import net.psforever.objects.vital.VehicleShieldCharge
+import net.psforever.types.{ExoSuitType, PlanetSideGUID}
+import services.vehicle.{VehicleAction, VehicleServiceMessage}
/**
* An `Actor` that handles messages being dispatched to a specific `Vehicle`.
@@ -28,32 +27,38 @@ class VehicleControl(vehicle : Vehicle) extends Actor
with DeploymentBehavior
with MountableBehavior.Mount
with MountableBehavior.Dismount
+ with CargoBehavior
+ with DamageableVehicle
+ with RepairableVehicle
with JammableMountedWeapons {
//make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach({case (_, util) => util.Setup })
def MountableObject = vehicle
-
+ def CargoObject = vehicle
def JammableObject = vehicle
-
def FactionObject = vehicle
-
def DeploymentObject = vehicle
+ def DamageableObject = vehicle
+ def RepairableObject = vehicle
def receive : Receive = Enabled
override def postStop() : Unit = {
super.postStop()
vehicle.Utilities.values.foreach { util =>
- util().Actor ! akka.actor.PoisonPill
+ context.stop(util().Actor)
util().Actor = ActorRef.noSender
}
}
def Enabled : Receive = checkBehavior
.orElse(deployBehavior)
+ .orElse(cargoBehavior)
.orElse(jammableBehavior)
+ .orElse(takesDamage)
+ .orElse(canBeRepairedByNanoDispenser)
.orElse {
case msg : Mountable.TryMount =>
tryMountBehavior.apply(msg)
@@ -61,23 +66,6 @@ class VehicleControl(vehicle : Vehicle) extends Actor
case msg : Mountable.TryDismount =>
dismountBehavior.apply(msg)
- case Vitality.Damage(damage_func) =>
- if(vehicle.Health > 0) {
- val originalHealth = vehicle.Health
- val originalShields = vehicle.Shields
- val cause = damage_func(vehicle)
- val health = vehicle.Health
- val shields = vehicle.Shields
- val damageToHealth = originalHealth - health
- val damageToShields = originalShields - shields
- VehicleControl.HandleDamageResolution(vehicle, cause, damageToHealth + damageToShields)
- if(damageToHealth > 0 || damageToShields > 0) {
- 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")
- }
- }
-
case Vehicle.ChargeShields(amount) =>
val now : Long = System.nanoTime
//make certain vehicle doesn't charge shields too quickly
@@ -95,6 +83,12 @@ class VehicleControl(vehicle : Vehicle) extends Actor
}
sender ! FactionAffinity.AssertFactionAffinity(vehicle, faction)
+ case CommonMessages.Use(player, Some(item : SimpleItem)) if item.Definition == GlobalDefinitions.remote_electronics_kit =>
+ //TODO setup certifications check
+ if(vehicle.Faction != player.Faction) {
+ sender ! CommonMessages.Hack(player, vehicle, Some(item))
+ }
+
case Vehicle.PrepareForDeletion() =>
CancelJammeredSound(vehicle)
CancelJammeredStatus(vehicle)
@@ -139,6 +133,12 @@ class VehicleControl(vehicle : Vehicle) extends Actor
case _ =>
}
+
+ override def TryJammerEffectActivate(target : Any, cause : ResolvedProjectile) : Unit = {
+ if(vehicle.MountedIn.isEmpty) {
+ super.TryJammerEffectActivate(target, cause)
+ }
+ }
}
object VehicleControl {
@@ -160,115 +160,4 @@ object VehicleControl {
case _ => false
}
}
-
- /**
- * na
- * @param target na
- */
- def HandleDamageResolution(target : Vehicle, cause : ResolvedProjectile, damage : Int) : Unit = {
- val zone = target.Zone
- val targetGUID = target.GUID
- val playerGUID = zone.LivePlayers.find { p => cause.projectile.owner.Name.equals(p.Name) } match {
- case Some(player) => player.GUID
- case _ => PlanetSideGUID(0)
- }
- if(target.Health > 0) {
- //activity on map
- if(damage > 0) {
- zone.Activity ! Zone.HotSpot.Activity(cause.target, cause.projectile.owner, cause.hit_pos)
- //alert occupants to damage source
- HandleDamageAwareness(target, playerGUID, cause)
- }
- if(cause.projectile.profile.JammerProjectile) {
- target.Actor ! JammableUnit.Jammered(cause)
- }
- }
- else {
- //alert to vehicle death (hence, occupants' deaths)
- HandleDestructionAwareness(target, playerGUID, cause)
- }
- zone.VehicleEvents ! VehicleServiceMessage(zone.Id, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 0, target.Health))
- zone.VehicleEvents ! VehicleServiceMessage(s"${target.Actor}", VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, targetGUID, 68, target.Shields))
- }
-
- /**
- * na
- * @param target na
- * @param attribution na
- * @param lastShot na
- */
- def HandleDamageAwareness(target : Vehicle, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
- val zone = target.Zone
- //alert occupants to damage source
- target.Seats.values.filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
- }).foreach(seat => {
- val tplayer = seat.Occupant.get
- zone.AvatarEvents ! AvatarServiceMessage(tplayer.Name, AvatarAction.HitHint(attribution, tplayer.GUID))
- })
- //alert cargo occupants to damage source
- target.CargoHolds.values.foreach(hold => {
- hold.Occupant match {
- case Some(cargo) =>
- cargo.Health = 0
- cargo.Shields = 0
- cargo.History(lastShot)
- HandleDamageAwareness(cargo, attribution, lastShot)
- case None => ;
- }
- })
- }
-
- /**
- * na
- * @param target na
- * @param attribution na
- * @param lastShot na
- */
- def HandleDestructionAwareness(target : Vehicle, attribution : PlanetSideGUID, lastShot : ResolvedProjectile) : Unit = {
- target.Actor ! JammableUnit.ClearJammeredSound()
- target.Actor ! JammableUnit.ClearJammeredStatus()
- val zone = target.Zone
- val continentId = zone.Id
- //alert to vehicle death (hence, occupants' deaths)
- target.Seats.values.filter(seat => {
- seat.isOccupied && seat.Occupant.get.isAlive
- }).foreach(seat => {
- val tplayer = seat.Occupant.get
- tplayer.History(lastShot)
- tplayer.Actor ! Player.Die()
- })
- //vehicle wreckage has no weapons
- target.Weapons.values
- .filter {
- _.Equipment.nonEmpty
- }
- .foreach(slot => {
- val wep = slot.Equipment.get
- zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.ObjectDelete(Service.defaultPlayerGUID, wep.GUID))
- })
- target.CargoHolds.values.foreach(hold => {
- hold.Occupant match {
- case Some(cargo) =>
- cargo.Health = 0
- cargo.Shields = 0
- cargo.Position += Vector3.z(1)
- cargo.History(lastShot) //necessary to kill cargo vehicle occupants //TODO: collision damage
- HandleDestructionAwareness(cargo, attribution, lastShot) //might cause redundant packets
- case None => ;
- }
- })
- target.Definition match {
- case GlobalDefinitions.ams =>
- target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
- case GlobalDefinitions.router =>
- target.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
- VehicleService.BeforeUnloadVehicle(target, zone)
- zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.ToggleTeleportSystem(PlanetSideGUID(0), target, None))
- case _ => ;
- }
- zone.AvatarEvents ! AvatarServiceMessage(continentId, AvatarAction.Destroy(target.GUID, attribution, attribution, target.Position))
- zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(target), zone))
- zone.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(target, zone, Some(1 minute)))
- }
}
diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleManifest.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleManifest.scala
index 92d9b9897..791882ff3 100644
--- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleManifest.scala
+++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleManifest.scala
@@ -11,19 +11,11 @@ import net.psforever.objects.zones.Zone
* after it has been loaded to a new location or to a new zone;
* this channel name should be unique to the vehicle for at least the duration of the transition;
* the vehicle-specific channel with which all passengers are coordinated back to the original vehicle
- * @param vehicle na
- * @param origin na
- * @param driverName na
- * @param passengers na
- * @param cargo na
- */
-/**
- * The channel name for summoning passengers to the vehicle
- * after it has been loaded to a new location or to a new zone.
- * This channel name should be unique to the vehicle for at least the duration of the transition.
- * The vehicle-specific channel with which all passengers are coordinated back to the original vehicle.
- * @param vehicle the vehicle being moved (or having been moved)
- * @return the channel name
+ * @param vehicle the vehicle in transport
+ * @param origin where the vehicle originally was
+ * @param driverName the name of the driver when the transport process started
+ * @param passengers the paired names and seat indices of all passengers when the transport process started
+ * @param cargo the paired driver names and cargo hold indices of all cargo vehicles when the transport process started
*/
final case class VehicleManifest(file : String,
vehicle : Vehicle,
@@ -55,4 +47,4 @@ object VehicleManifest {
def ManifestChannelName(vehicle : Vehicle) : String = {
s"transport-vehicle-channel-${vehicle.GUID.guid}"
}
-}
\ No newline at end of file
+}
diff --git a/common/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala b/common/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala
index 7d98bc7ff..beb1134ba 100644
--- a/common/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/DamageResistanceModel.scala
@@ -26,26 +26,26 @@ import net.psforever.objects.vital.resolution.ResolutionCalculations
*/
trait DamageResistanceModel {
/** the functionality that processes damage; required */
- private var damage : DamageSelection = NoDamageSelection
+ private var damageUsing : DamageSelection = NoDamageSelection
/** the functionality that processes resistance; optional */
- private var resistance : ResistanceSelection = NoResistanceSelection
+ private var resistUsing : ResistanceSelection = NoResistanceSelection
/** the functionality that prepares for damage application actions; required */
private var model : ResolutionCalculations.Form = NoResolutions.Calculate
- def Damage : DamageSelection = damage
+ def DamageUsing : DamageSelection = damageUsing
- def Damage_=(selector : DamageSelection) : DamageSelection = {
- damage = selector
- Damage
+ def DamageUsing_=(selector : DamageSelection) : DamageSelection = {
+ damageUsing = selector
+ DamageUsing
}
- def Resistance : ResistanceSelection = resistance
+ def ResistUsing : ResistanceSelection = resistUsing
- def Resistance_=(selector : ResistanceSelection) : ResistanceSelection = {
- resistance = selector
- Resistance
+ def ResistUsing_=(selector : ResistanceSelection) : ResistanceSelection = {
+ resistUsing = selector
+ ResistUsing
}
def Model : ResolutionCalculations.Form = model
@@ -61,8 +61,8 @@ trait DamageResistanceModel {
* @return a function literal that encapsulates delayed modification instructions for certain objects
*/
def Calculate(data : ResolvedProjectile) : ResolutionCalculations.Output = {
- val dam : ProjectileCalculations.Form = Damage(data)
- val res : ProjectileCalculations.Form = Resistance(data)
+ val dam : ProjectileCalculations.Form = DamageUsing(data)
+ val res : ProjectileCalculations.Form = ResistUsing(data)
Model(dam, res, data)
}
@@ -73,8 +73,8 @@ trait DamageResistanceModel {
* @return a function literal that encapsulates delayed modification instructions for certain objects
*/
def Calculate(data : ResolvedProjectile, resolution : ProjectileResolution.Value) : ResolutionCalculations.Output = {
- val dam : ProjectileCalculations.Form = Damage(resolution)
- val res : ProjectileCalculations.Form = Resistance(resolution)
+ val dam : ProjectileCalculations.Form = DamageUsing(resolution)
+ val res : ProjectileCalculations.Form = ResistUsing(resolution)
Model(dam, res, data)
}
}
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 9d5ee0dcd..043064e49 100644
--- a/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/StandardDamages.scala
@@ -95,6 +95,18 @@ object AircraftLashDamage extends DamageCalculations(
DistanceBetweenTargetandSource
)
+object AmenityHitDamage extends DamageCalculations(
+ DirectHitDamageWithDegrade,
+ DamageWithModifiers(DamageAgainstVehicle),
+ DistanceBetweenTargetandSource
+)
+
+object AmenitySplashDamage extends DamageCalculations(
+ SplashDamageWithRadialDegrade,
+ DamageWithModifiers(DamageAgainstVehicle),
+ DistanceFromExplosionToTarget
+)
+
object NoDamageSelection extends DamageSelection {
def Direct = None
def Splash = None
@@ -130,3 +142,9 @@ object StandardDeployableDamage extends DamageSelection {
def Splash = VehicleSplashDamage.Calculate
def Lash = NoDamage.Calculate
}
+
+object StandardAmenityDamage extends DamageSelection {
+ def Direct = AmenityHitDamage.Calculate
+ def Splash = AmenitySplashDamage.Calculate
+ def Lash = NoDamage.Calculate
+}
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 2c398c203..090f1f9f4 100644
--- a/common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/StandardResistances.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vital
-import net.psforever.objects.ballistics.{PlayerSource, SourceEntry, VehicleSource}
+import net.psforever.objects.ballistics.{ObjectSource, PlayerSource, SourceEntry, VehicleSource}
import net.psforever.objects.vital.projectile.ProjectileCalculations
import net.psforever.objects.vital.resistance.{ResistanceCalculations, ResistanceSelection}
@@ -50,6 +50,16 @@ object VehicleAggravatedResistance extends ResistanceCalculations[VehicleSource]
ResistanceCalculations.VehicleAggravatedExtractor
)
+object AmenityHitResistance extends ResistanceCalculations[ObjectSource](
+ ResistanceCalculations.ValidAmenityTarget,
+ ResistanceCalculations.OtherDirectExtractor
+)
+
+object AMenitySplashResistance extends ResistanceCalculations[ObjectSource](
+ ResistanceCalculations.ValidAmenityTarget,
+ ResistanceCalculations.OtherSplashExtractor
+)
+
object NoResistanceSelection extends ResistanceSelection {
def Direct : ProjectileCalculations.Form = None
def Splash : ProjectileCalculations.Form = None
@@ -70,3 +80,10 @@ object StandardVehicleResistance extends ResistanceSelection {
def Lash : ProjectileCalculations.Form = VehicleLashResistance.Calculate
def Aggravated : ProjectileCalculations.Form = VehicleAggravatedResistance.Calculate
}
+
+object StandardAmenityResistance extends ResistanceSelection {
+ def Direct : ProjectileCalculations.Form = AmenityHitResistance.Calculate
+ def Splash : ProjectileCalculations.Form = AmenityHitResistance.Calculate
+ def Lash : ProjectileCalculations.Form = None
+ def Aggravated : ProjectileCalculations.Form = None
+}
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 75502f7d3..ade516d3b 100644
--- a/common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/StandardResolutions.scala
@@ -41,4 +41,5 @@ object StandardResolutions extends ResolutionSelection {
def SimpleDeployables : ResolutionCalculations.Form = SimpleResolutions.Calculate
def ComplexDeployables : ResolutionCalculations.Form = ComplexDeployableResolutions.Calculate
def FacilityTurrets : ResolutionCalculations.Form = SimpleResolutions.Calculate
+ def Amenities : ResolutionCalculations.Form = SimpleResolutions.Calculate
}
diff --git a/common/src/main/scala/net/psforever/objects/vital/Vitality.scala b/common/src/main/scala/net/psforever/objects/vital/Vitality.scala
index 5cf22e3a2..ac4e2815d 100644
--- a/common/src/main/scala/net/psforever/objects/vital/Vitality.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/Vitality.scala
@@ -1,101 +1,48 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.vital
-import net.psforever.objects.PlanetSideGameObject
-import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry, VehicleSource}
-import net.psforever.objects.definition.KitDefinition
-import net.psforever.objects.serverobject.painbox.Painbox
-import net.psforever.objects.serverobject.terminals.TerminalDefinition
+import net.psforever.objects.ballistics.ResolvedProjectile
import net.psforever.objects.vital.resolution.ResolutionCalculations
-import net.psforever.types.{ExoSuitType, ImplantType}
-
-abstract class VitalsActivity(target : SourceEntry) {
- def Target : SourceEntry = target
- val t : Long = System.nanoTime //???
-
- def time : Long = t
-}
-
-abstract class HealingActivity(target : SourceEntry) extends VitalsActivity(target)
-
-abstract class DamagingActivity(target : SourceEntry) extends VitalsActivity(target)
-
-final case class HealFromKit(target : PlayerSource, amount : Int, kit_def : KitDefinition) extends HealingActivity(target)
-
-final case class HealFromTerm(target : PlayerSource, health : Int, armor : Int, term_def : TerminalDefinition) extends HealingActivity(target)
-
-final case class HealFromImplant(target : PlayerSource, amount : Int, implant : ImplantType.Value) extends HealingActivity(target)
-
-final case class HealFromExoSuitChange(target : PlayerSource, exosuit : ExoSuitType.Value) extends HealingActivity(target)
-
-final case class RepairFromKit(target : PlayerSource, amount : Int, kit_def : KitDefinition) extends HealingActivity(target)
-
-final case class RepairFromTerm(target : VehicleSource, amount : Int, term_def : TerminalDefinition) extends HealingActivity(target)
-
-final case class VehicleShieldCharge(target : VehicleSource, amount : Int) extends HealingActivity(target) //TODO facility
-
-final case class DamageFromProjectile(data : ResolvedProjectile) extends DamagingActivity(data.target)
-
-final case class DamageFromPainbox(target : PlayerSource, painbox : Painbox, damage : Int) extends DamagingActivity(target)
-
-final case class PlayerSuicide(target : PlayerSource) extends DamagingActivity(target)
/**
* A vital object can be hurt or damaged or healed or repaired (HDHR).
* The amount of HDHR is controlled by the damage model of this vital object reacting to stimulus.
- * A history of the previous changes in vital statistics of the underlying object is recorded
- * in reverse chronological order.
- * The damage model is also provided.
+ * The damage model is provided.
*/
-trait Vitality {
- this : PlanetSideGameObject =>
+trait Vitality extends VitalsHistory {
+ private var health : Int = Definition.DefaultHealth
+ private var defaultHealth : Option[Int] = None
+ private var maxHealth : Option[Int] = None
- /** a reverse-order list of chronological events that have occurred to these vital statistics */
- private var vitalHistory : List[VitalsActivity] = List.empty[VitalsActivity]
+ def Health : Int = health
- def History : List[VitalsActivity] = vitalHistory
-
- /**
- * A `VitalsActivity` event must be recorded.
- * Add new entry to the front of the list (for recent activity).
- * @param action the fully-informed entry
- * @return the list of previous changes to this object's vital statistics
- */
- def History(action : VitalsActivity) : List[VitalsActivity] = {
- vitalHistory = action +: vitalHistory
- vitalHistory
+ def Health_=(assignHealth : Int) : Int = {
+ health = math.min(math.max(0, assignHealth), MaxHealth)
+ Health
}
- /**
- * Very common example of a `VitalsActivity` event involving weapon discharge.
- * @param projectile the fully-informed entry of discharge of a weapon
- * @return the list of previous changes to this object's vital statistics
- */
- def History(projectile : ResolvedProjectile) : List[VitalsActivity] = {
- vitalHistory = DamageFromProjectile(projectile) +: vitalHistory
- vitalHistory
+ def DefaultHealth : Int = defaultHealth.getOrElse(Definition.DefaultHealth)
+
+ def MaxHealth : Int = maxHealth.getOrElse(Definition.MaxHealth)
+
+ def MaxHealth_=(default : Int) : Int = MaxHealth_=(Some(default))
+
+ def MaxHealth_=(default : Option[Int]) : Int = {
+ maxHealth = default
+ MaxHealth
}
- /**
- * Find, specifically, the last instance of a weapon discharge vital statistics change.
- * @return information about the discharge
- */
- def LastShot : Option[ResolvedProjectile] = {
- vitalHistory.find({p => p.isInstanceOf[DamageFromProjectile]}) match {
- case Some(entry : DamageFromProjectile) =>
- Some(entry.data)
- case _ =>
- None
- }
+ def CanDamage : Boolean = {
+ Definition.Damageable && Health > 0
}
- def ClearHistory() : List[VitalsActivity] = {
- val out = vitalHistory
- vitalHistory = List.empty[VitalsActivity]
- out
+ def CanRepair : Boolean = {
+ Definition.Repairable && Health < MaxHealth && (Health > 0 || Definition.RepairIfDestroyed)
}
def DamageModel : DamageResistanceModel
+
+ def Definition : VitalityDefinition
}
object Vitality {
diff --git a/common/src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala b/common/src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala
new file mode 100644
index 000000000..c0c74e902
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/vital/VitalityDefinition.scala
@@ -0,0 +1,131 @@
+//Copyright (c) 2020 PSForever
+package net.psforever.objects.vital
+
+/**
+ * na
+ *
+ * The expected (but not enforced) relationship between values follows:
+ * `0 <= DamageDestroysAt <= DamageDisablesAt < RepairRestoresAt <= MaxHealth`.
+ */
+trait VitalityDefinition {
+ /** the maximum amount of health that any of the objects can be allocated;
+ * corresponds to ADB property "maxhealth" */
+ private var maxHealth : Int = 0
+ /** the amount of health that all of the objects are spawned with;
+ * defaults to `MaxHealth` if unset */
+ private var defaultHealth : Option[Int] = None
+
+ def MaxHealth : Int = maxHealth
+
+ def MaxHealth_=(max : Int) : Int = {
+ maxHealth = math.min(math.max(0, max), 65535)
+ MaxHealth
+ }
+
+ def DefaultHealth : Int = defaultHealth.getOrElse(MaxHealth)
+
+ def DefaultHealth_=(default : Int) : Int = DefaultHealth_=(Some(default))
+
+ def DefaultHealth_=(default : Option[Int]) : Int = {
+ defaultHealth = default
+ DefaultHealth
+ }
+
+ /* damageable */
+ /** whether the object type accepts damage;
+ * corresponds to ABD property "damageable" */
+ private var damageable : Boolean = false
+ /** whether the object type accepts damage from allied sources;
+ * corresponds to the opposite of ABD property "damage_immune_to_friendly_fire" */
+ private var damageableByFriendlyFire : Boolean = true
+ /** at what `Health` value the object type is considered "destroyed" */
+ private var damageDestroysAt : Int = 0
+ /** at what `Health` value the object type is considered "disabled";
+ * some object types do not have anything to disable and just transition between "not destroyed" and "destroyed" */
+ private var damageDisablesAt : Option[Int] = None
+
+ def Damageable : Boolean = damageable
+
+ def Damageable_=(state : Boolean) : Boolean = {
+ damageable = state
+ Damageable
+ }
+
+ def DamageableByFriendlyFire : Boolean = damageableByFriendlyFire
+
+ def DamageableByFriendlyFire_=(state : Boolean) : Boolean = {
+ damageableByFriendlyFire = state
+ DamageableByFriendlyFire
+ }
+
+ def DamageDisablesAt : Int = damageDisablesAt.getOrElse(MaxHealth/2)
+
+ def DamageDisablesAt_=(value : Int) : Int = DamageDisablesAt_=(Some(value))
+
+ def DamageDisablesAt_=(value : Option[Int]) : Int = {
+ damageDisablesAt = value
+ DamageDisablesAt
+ }
+
+ def DamageDestroysAt : Int = damageDestroysAt
+
+ def DamageDestroysAt_=(value : Int) : Int = {
+ damageDestroysAt = value
+ DamageDestroysAt
+ }
+
+ /* repairable */
+ /** whether the object type accepts attempts to repair it with a nano dispenser tool;
+ * corresponds to ABD property "canberepairedbynanodispenser" */
+ private var repairable : Boolean = false
+ /** how far away a target can get before repairing is no longer possible;
+ * not exact in the least */
+ private var repairDistance : Float = 5
+ /** if the object type is damaged to the condition of "destroyed",
+ * is it possible to repair it back to the condition of "not destroyed" */
+ private var repairIfDestroyed : Boolean = false
+ /** at what `Health` value the object type is considered "not destroyed";
+ * this state is synonymous with "normal" or "functional";
+ * as thus, it is opposite of both `damageDestroysAt` and `damageDisablesAt` */
+ private var repairRestoresAt : Option[Int] = None
+ /** object type specific modification value that chnages the base repair quality;
+ * treat as additive */
+ private var repairMod : Int = 0
+
+ def Repairable : Boolean = repairable
+
+ def Repairable_=(repair : Boolean) : Boolean = {
+ repairable = repair
+ Repairable
+ }
+
+ def RepairDistance : Float = repairDistance
+
+ def RepairDistance_=(distance : Float) : Float = {
+ repairDistance = math.max(0, distance)
+ RepairDistance
+ }
+
+ def RepairIfDestroyed : Boolean = repairIfDestroyed
+
+ def RepairIfDestroyed_=(repair : Boolean) : Boolean = {
+ repairIfDestroyed = repair
+ RepairIfDestroyed
+ }
+
+ def RepairRestoresAt : Int = repairRestoresAt.getOrElse(MaxHealth/2)
+
+ def RepairRestoresAt_=(restore : Int) : Int = RepairRestoresAt_=(Some(restore))
+
+ def RepairRestoresAt_=(restore : Option[Int]) : Int = {
+ repairRestoresAt = restore
+ RepairRestoresAt
+ }
+
+ def RepairMod : Int = repairMod
+
+ def RepairMod_=(mod : Int) : Int = {
+ repairMod = mod
+ RepairMod
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/vital/VitalsHistory.scala b/common/src/main/scala/net/psforever/objects/vital/VitalsHistory.scala
new file mode 100644
index 000000000..5a6e03738
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/vital/VitalsHistory.scala
@@ -0,0 +1,109 @@
+// Copyright (c) 2020 PSForever
+package net.psforever.objects.vital
+
+import net.psforever.objects.ballistics.{PlayerSource, ResolvedProjectile, SourceEntry, VehicleSource}
+import net.psforever.objects.definition.{EquipmentDefinition, KitDefinition, ObjectDefinition}
+import net.psforever.objects.serverobject.painbox.Painbox
+import net.psforever.objects.serverobject.terminals.TerminalDefinition
+import net.psforever.types.{ExoSuitType, ImplantType}
+
+abstract class VitalsActivity(target : SourceEntry) {
+ def Target : SourceEntry = target
+ val t : Long = System.nanoTime //???
+
+ def time : Long = t
+}
+
+abstract class HealingActivity(target : SourceEntry) extends VitalsActivity(target)
+
+abstract class DamagingActivity(target : SourceEntry) extends VitalsActivity(target)
+
+final case class HealFromKit(target : PlayerSource, amount : Int, kit_def : KitDefinition) extends HealingActivity(target)
+
+final case class HealFromEquipment(target : PlayerSource, user : PlayerSource, amount : Int, equipment_def : EquipmentDefinition) extends HealingActivity(target)
+
+final case class HealFromTerm(target : PlayerSource, health : Int, armor : Int, term_def : TerminalDefinition) extends HealingActivity(target)
+
+final case class HealFromImplant(target : PlayerSource, amount : Int, implant : ImplantType.Value) extends HealingActivity(target)
+
+final case class HealFromExoSuitChange(target : PlayerSource, exosuit : ExoSuitType.Value) extends HealingActivity(target)
+
+final case class RepairFromKit(target : PlayerSource, amount : Int, kit_def : KitDefinition) extends HealingActivity(target)
+
+final case class RepairFromEquipment(target : PlayerSource, user : PlayerSource, amount : Int, equipment_def : EquipmentDefinition) extends HealingActivity(target)
+
+final case class RepairFromTerm(target : VehicleSource, amount : Int, term_def : TerminalDefinition) extends HealingActivity(target)
+
+final case class VehicleShieldCharge(target : VehicleSource, amount : Int) extends HealingActivity(target) //TODO facility
+
+final case class DamageFromProjectile(data : ResolvedProjectile) extends DamagingActivity(data.target)
+
+final case class DamageFromPainbox(target : PlayerSource, painbox : Painbox, damage : Int) extends DamagingActivity(target)
+
+final case class PlayerSuicide(target : PlayerSource) extends DamagingActivity(target)
+
+final case class DamageFromExplosion(target : PlayerSource, cause : ObjectDefinition) extends DamagingActivity(target)
+
+/**
+ * A vital object can be hurt or damaged or healed or repaired (HDHR).
+ * A history of the previous changes in vital statistics of the underlying object is recorded
+ * in reverse chronological order.
+ */
+trait VitalsHistory {
+ /** a reverse-order list of chronological events that have occurred to these vital statistics */
+ private var vitalsHistory : List[VitalsActivity] = List.empty[VitalsActivity]
+
+ def History : List[VitalsActivity] = vitalsHistory
+
+ /**
+ * A `VitalsActivity` event must be recorded.
+ * Add new entry to the front of the list (for recent activity).
+ * @param action the fully-informed entry
+ * @return the list of previous changes to this object's vital statistics
+ */
+ def History(action : VitalsActivity) : List[VitalsActivity] = History(Some(action))
+
+ /**
+ * A `VitalsActivity` event must be recorded.
+ * Add new entry to the front of the list (for recent activity).
+ * @param action the fully-informed entry
+ * @return the list of previous changes to this object's vital statistics
+ */
+ def History(action : Option[VitalsActivity]) : List[VitalsActivity] = {
+ action match {
+ case Some(act) =>
+ vitalsHistory = act +: vitalsHistory
+ case None => ;
+ }
+ vitalsHistory
+ }
+
+ /**
+ * Very common example of a `VitalsActivity` event involving weapon discharge.
+ * @param projectile the fully-informed entry of discharge of a weapon
+ * @return the list of previous changes to this object's vital statistics
+ */
+ def History(projectile : ResolvedProjectile) : List[VitalsActivity] = {
+ vitalsHistory = DamageFromProjectile(projectile) +: vitalsHistory
+ vitalsHistory
+ }
+
+ /**
+ * Find, specifically, the last instance of a weapon discharge vital statistics change.
+ * @return information about the discharge
+ */
+ def LastShot : Option[ResolvedProjectile] = {
+ vitalsHistory.find({p => p.isInstanceOf[DamageFromProjectile]}) match {
+ case Some(entry : DamageFromProjectile) =>
+ Some(entry.data)
+ case _ =>
+ None
+ }
+ }
+
+ def ClearHistory() : List[VitalsActivity] = {
+ val out = vitalsHistory
+ vitalsHistory = List.empty[VitalsActivity]
+ out
+ }
+}
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 f751e2fc5..971967f4e 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
@@ -59,7 +59,7 @@ object DamageCalculations {
def DamageAgainstMaxSuit(profile : DamageProfile) : Int = profile.Damage3
- def DamageAgainstUnknown(profile : DamageProfile) : Int = profile.Damage4
+ def DamageAgainstBFR(profile : DamageProfile) : Int = profile.Damage4
//raw damage selection functions
/**
diff --git a/common/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala b/common/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala
index 95f99ffc5..a5d168364 100644
--- a/common/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/damage/DamageProfile.scala
@@ -6,23 +6,28 @@ package net.psforever.objects.vital.damage
* In the same way, the five damage modifiers that are applied to the same kind of damage.
*/
trait DamageProfile {
+ /** `damage0` is for basic infantry */
def Damage0 : Int
-
+ /** `damage0` is for basic infantry */
def Damage0_=(damage : Int) : Int
+ /** `damage1` is for armor, amenities, deployables, etc. */
def Damage1 : Int
-
+ /** `damage1` is for armor, amenities, deployables, etc. */
def Damage1_=(damage : Int) : Int
+ /** `damage2` if for aircraft */
def Damage2 : Int
-
+ /** `damage2` if for aircraft */
def Damage2_=(damage : Int) : Int
+ /** `damage3` is for mechanized infantry */
def Damage3 : Int
-
+ /** `damage3` is for mechanized infantry */
def Damage3_=(damage : Int) : Int
+ /** `damage4` is for battleframe robotics */
def Damage4 : Int
-
+ /** `damage4` is for battleframe robotics */
def Damage4_=(damage : Int) : Int
}
diff --git a/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala
index 43433f85f..5b5b78eb1 100644
--- a/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala
+++ b/common/src/main/scala/net/psforever/objects/vital/resistance/ResistanceCalculations.scala
@@ -4,6 +4,7 @@ package net.psforever.objects.vital.resistance
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.ballistics._
import net.psforever.objects.definition.ExoSuitDefinition
+import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.vital.projectile.ProjectileCalculations
import net.psforever.types.ExoSuitType
@@ -103,6 +104,20 @@ object ResistanceCalculations {
}
}
+ def ValidAmenityTarget(data : ResolvedProjectile) : Try[ObjectSource] = {
+ data.target match {
+ case target : ObjectSource =>
+ if(target.obj.isInstanceOf[Amenity]) {
+ Success(target)
+ }
+ else {
+ failure("something else")
+ }
+ case _ =>
+ failure("something else")
+ }
+ }
+
//extractors
def NoResistExtractor(target : SourceEntry) : Int = 0
@@ -122,5 +137,9 @@ object ResistanceCalculations {
def VehicleRadiationExtractor(target : VehicleSource) : Float = target.Definition.RadiationShielding
+ def OtherDirectExtractor(target : ObjectSource) : Int = target.Definition.asInstanceOf[ResistanceProfile].ResistanceDirectHit
+
+ def OtherSplashExtractor(target : ObjectSource) : Int = target.Definition.asInstanceOf[ResistanceProfile].ResistanceSplash
+
def MaximumResistance(target : SourceEntry) : Int = Integer.MAX_VALUE
}
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 bd3957921..242a20b5f 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,7 +4,11 @@ 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, Deployable}
+import net.psforever.objects.serverobject.affinity.FactionAffinity
+import net.psforever.objects.serverobject.damage.Damageable
+import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.serverobject.turret.FacilityTurret
+import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.projectile.ProjectileCalculations
/**
@@ -112,10 +116,13 @@ object ResolutionCalculations {
def SubtractWithRemainder(current : Int, damage : Int) : (Int, Int) = {
val a = Math.max(0, current - damage)
val remainingDamage = Math.abs(current - damage - a)
-
(a, remainingDamage)
}
+ private def CanDamage(obj : Vitality with FactionAffinity, damage : Int, data : ResolvedProjectile) : Boolean = {
+ obj.Health > 0 && Damageable.CanDamage(obj, damage, data)
+ }
+
/**
* The expanded `(Any)=>Unit` function for infantry.
* Apply the damage values to the capacitor (if shielded NC max), health field and personal armor field for an infantry target.
@@ -130,7 +137,6 @@ object ResolutionCalculations {
var result = (0, 0)
//TODO Personal Shield implant test should go here and modify the values a and b
if(player.isAlive && !(a == 0 && b == 0)) {
- player.History(data)
if(player.Capacitor.toInt > 0 && player.isShielded) {
// Subtract armour damage from capacitor
result = SubtractWithRemainder(player.Capacitor.toInt, b)
@@ -172,8 +178,7 @@ object ResolutionCalculations {
*/
def VehicleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
target match {
- case vehicle : Vehicle if vehicle.Health > 0 && damage > 0 =>
- vehicle.History(data)
+ case vehicle : Vehicle if CanDamage(vehicle, damage, data) =>
val shields = vehicle.Shields
if(shields > damage) {
vehicle.Shields = shields - damage
@@ -192,12 +197,12 @@ object ResolutionCalculations {
def SimpleApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
target match {
- case obj : Deployable if obj.Health > 0 =>
+ case obj : Deployable if CanDamage(obj, damage, data) =>
obj.Health -= damage
- obj.History(data)
- case turret : FacilityTurret if turret.Health > 0 =>
+ case turret : FacilityTurret if CanDamage(turret, damage, data) =>
turret.Health -= damage
- turret.History(data)
+ case amenity : Amenity if CanDamage(amenity, damage, data) =>
+ amenity.Health -= damage
case _ => ;
}
data
@@ -205,8 +210,7 @@ object ResolutionCalculations {
def ComplexDeployableApplication(damage : Int, data : ResolvedProjectile)(target : Any) : ResolvedProjectile = {
target match {
- case ce : ComplexDeployable if ce.Health > 0 && damage > 0 =>
- ce.History(data)
+ case ce : ComplexDeployable if CanDamage(ce, damage, data) =>
if(ce.Shields > 0) {
if(damage > ce.Shields) {
ce.Health -= (damage - ce.Shields)
@@ -220,8 +224,7 @@ object ResolutionCalculations {
ce.Health -= damage
}
- case ce : TurretDeployable if ce.Health > 0 && damage > 0 =>
- ce.History(data)
+ case ce : TurretDeployable if CanDamage(ce, damage, data) =>
if(ce.Shields > 0) {
if(damage > ce.Shields) {
ce.Health -= (damage - ce.Shields)
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 774a951b7..3b4db0ad6 100644
--- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala
@@ -402,6 +402,16 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
})
//after all fixed GUID's are defined ...
other.foreach(obj => guid.register(obj, "dynamic"))
+ //TODO temporary; convert all old-style ImplantTerminalMech.Constructor with the kind that provides position data
+ import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
+ import net.psforever.objects.serverobject.terminals.Terminal
+ zoneMap.TerminalToInterface.foreach { case (mech_guid, interface_guid) =>
+ (GUID(mech_guid), GUID(interface_guid)) match {
+ case (Some(mech : ImplantTerminalMech), Some(interface : Terminal)) =>
+ mech.Position = interface.Position
+ case _ => ;
+ }
+ }
}
private def MakeBuildings(implicit context : ActorContext) : PairMap[Int, Building] = {
@@ -561,20 +571,26 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
def AvatarEvents : ActorRef = avatarEvents
+ def LocalEvents : ActorRef = localEvents
+
+ def VehicleEvents : ActorRef = vehicleEvents
+
+ //mainly for testing
+ def Activity_=(bus : ActorRef) : ActorRef = {
+ projector = bus
+ Activity
+ }
+
def AvatarEvents_=(bus : ActorRef) : ActorRef = {
avatarEvents = bus
AvatarEvents
}
- def LocalEvents : ActorRef = localEvents
-
def LocalEvents_=(bus : ActorRef) : ActorRef = {
localEvents = bus
LocalEvents
}
- def VehicleEvents : ActorRef = vehicleEvents
-
def VehicleEvents_=(bus : ActorRef) : ActorRef = {
vehicleEvents = bus
VehicleEvents
diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala
index af67b4533..43b87aff6 100644
--- a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala
@@ -89,7 +89,8 @@ class ZoneActor(zone : Zone) extends Actor {
//ams
zone.Vehicles
.filter(veh =>
- veh.Definition == GlobalDefinitions.ams &&
+ veh.Definition == GlobalDefinitions.ams &&
+ !veh.Destroyed &&
veh.DeploymentState == DriveState.Deployed &&
veh.Faction == player.Faction
)
@@ -120,12 +121,12 @@ class ZoneActor(zone : Zone) extends Actor {
Set.empty[StructureType.Value]
}
zone.SpawnGroups()
- .filter({ case (building, _) =>
+ .filter({ case (building, tubes) =>
buildingTypeSet.contains(building.BuildingType) && (building match {
case wg : WarpGate =>
building.Faction == player.Faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast
case _ =>
- building.Faction == player.Faction
+ building.Faction == player.Faction && !tubes.forall(sp => sp.Offline)
})
})
.toSeq
@@ -144,7 +145,10 @@ class ZoneActor(zone : Zone) extends Actor {
sender ! Zone.Lattice.SpawnPoint(zone.Id, tube)
case Some(tubes) =>
- sender ! Zone.Lattice.SpawnPoint(zone.Id, scala.util.Random.shuffle(tubes).head)
+ val tube = scala.util.Random.shuffle(
+ tubes.filter(sp => !sp.Offline)
+ ).head
+ sender ! Zone.Lattice.SpawnPoint(zone.Id, tube)
case None =>
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group))
diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
index 400303a50..fd7e56602 100644
--- a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
@@ -126,8 +126,8 @@ class ZoneMap(private val name : String) {
def TerminalToInterface : Map[Int, Int] = linkTerminalInterface
- def TerminalToInterface(interface_guid : Int, terminal_guid : Int) : Unit = {
- linkTerminalInterface = linkTerminalInterface ++ Map(interface_guid -> terminal_guid)
+ def TerminalToInterface(terminal_guid : Int, interface_guid : Int) : Unit = {
+ linkTerminalInterface = linkTerminalInterface ++ Map(terminal_guid -> interface_guid)
}
def TurretToWeapon : Map[Int, Int] = linkTurretWeapon
diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
index efc2d01d2..4fd317574 100644
--- a/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
@@ -51,7 +51,7 @@ class ZoneVehicleActor(zone : Zone, vehicleList : ListBuffer[Vehicle]) extends A
ZoneVehicleActor.recursiveFindVehicle(vehicleList.iterator, vehicle) match {
case Some(index) =>
vehicleList.remove(index)
- vehicle.Actor ! akka.actor.PoisonPill
+ context.stop(vehicle.Actor)
vehicle.Actor = ActorRef.noSender
case None => ;
sender ! Zone.Vehicle.CanNotDespawn(zone, vehicle, "can not find")
diff --git a/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala b/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala
index 723cdc7d3..cea36fc87 100644
--- a/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/BuildingInfoUpdateMessage.scala
@@ -2,25 +2,11 @@
package net.psforever.packet.game
import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket}
-import net.psforever.types.PlanetSideEmpire
+import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState}
import scodec.{Attempt, Codec, Err}
import scodec.codecs._
import shapeless.{::, HNil}
-/**
- * An `Enumeration` `Codec` that represents that various states of a major facility's Generator.
- */
-object PlanetSideGeneratorState extends Enumeration {
- type Type = Value
- val Normal,
- Critical,
- Destroyed,
- Unk3
- = Value
-
- implicit val codec = PacketHelpers.createEnumerationCodec(this, uintL(2))
-}
-
/**
* na
* @param unk1 na
diff --git a/common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala b/common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala
index a56e3e7ed..c58ff2153 100644
--- a/common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/DamageFeedbackMessage.scala
@@ -7,6 +7,22 @@ import scodec.Codec
import scodec.codecs._
import shapeless.{::, HNil}
+/**
+ * na
+ * @param unk1 na
+ * @param unk2 if no global unique identifier (below), the alternate identification for the entity
+ * @param unk2a the global unique identifier of the entity inflicting the damage
+ * @param unk2b if no global unique identifier (above), the name of the entity inflicting the damage
+ * @param unk2c if no global unique identifier (above), the object type of the entity inflicting the damage
+ * @param unk3 if no global unique identifier (below), the alternate identification for the entity
+ * @param unk3a the global unique identifier of the entity absorbing the damage
+ * @param unk3b if no global unique identifier (above), the name of the entity absorbing the damage
+ * @param unk3c if no global unique identifier (above), the object type of the entity absorbing the damage
+ * @param unk3d na
+ * @param unk4 na
+ * @param unk5 the amount of damage
+ * @param unk6 na
+ */
final case class DamageFeedbackMessage(unk1 : Int,
unk2 : Boolean,
unk2a : Option[PlanetSideGUID],
@@ -55,12 +71,12 @@ object DamageFeedbackMessage extends Marshallable[DamageFeedbackMessage] {
bool >>:~ { u3 =>
("unk2a" | conditional(u2, PlanetSideGUID.codec)) ::
(("unk2b" | conditional(!u2 && u3, PacketHelpers.encodedWideStringAligned(6))) >>:~ { u2b =>
- ("unk2c" | conditional(!(u2 && u3), uintL(11))) ::
+ ("unk2c" | conditional(!u2 && !u3, uintL(11))) ::
(bool >>:~ { u5 =>
bool >>:~ { u6 =>
("unk3a" | conditional(u5, PlanetSideGUID.codec)) ::
("unk3b" | conditional(!u5 && u6, PacketHelpers.encodedWideStringAligned( if(u2b.nonEmpty) 3 else 1 ))) ::
- ("unk3c" | conditional(!(u5 && u6), uintL(11))) ::
+ ("unk3c" | conditional(!u5 && !u6, uintL(11))) ::
("unk3d" | conditional(!u5, uint2)) ::
("unk4" | uint(3)) ::
("unk5" | uint32L) ::
diff --git a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
index 2ee959fc9..6ee5293fe 100644
--- a/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
+++ b/common/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
@@ -9,8 +9,17 @@ import scodec.codecs._
/**
* na
* Global:
- * `50 - Common Initialization?`
- * `51 - Common Initialization?`
+ * `50 - State initialization for amenities`
+ *