diff --git a/server/src/main/resources/overrides/game_objects0.adb.lst b/server/src/main/resources/overrides/game_objects0.adb.lst
index be23a5ae4..5e08278e5 100644
--- a/server/src/main/resources/overrides/game_objects0.adb.lst
+++ b/server/src/main/resources/overrides/game_objects0.adb.lst
@@ -76,6 +76,7 @@ add_property pulsar equiptime 600
add_property pulsar holstertime 600
add_property punisher equiptime 600
add_property punisher holstertime 600
+add_property radiator allowed false
add_property r_shotgun equiptime 750
add_property r_shotgun holstertime 750
add_property remote_electronics_kit equiptime 500
diff --git a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala
index 05608922b..cc5e710b7 100644
--- a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala
@@ -545,9 +545,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
Some(target)
- case turret: AutomatedTurret =>
+ case turret: AutomatedTurret with OwnableByPlayer =>
turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
- HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, turret.TurretOwner, projectileTypeId))
+ HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
Some(target)
}
}
@@ -558,9 +558,9 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
case target: PlanetSideServerObject with FactionAffinity with Vitality =>
sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker")
.collect {
- case turret: AutomatedTurret if turret.Target.nonEmpty =>
+ case turret: AutomatedTurret with OwnableByPlayer if turret.Target.nonEmpty =>
//the turret must be shooting at something (else) first
- HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, turret.TurretOwner, projectileTypeId))
+ HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, projectileTypeId))
}
Some(target)
}
@@ -1268,14 +1268,17 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
private def CompileAutomatedTurretDamageData(
- turret: AutomatedTurret,
- owner: SourceEntry,
+ turret: AutomatedTurret with OwnableByPlayer,
projectileTypeId: Long
): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = {
turret.Weapons
.values
.flatMap { _.Equipment }
- .collect { case weapon: Tool => (turret, weapon, owner, weapon.Projectile) }
+ .collect {
+ case weapon: Tool =>
+ val source = Deployables.AssignBlameTo(continent, turret.OwnerName, SourceEntry(turret))
+ (turret, weapon, source, weapon.Projectile)
+ }
.find { case (_, _, _, p) => p.ObjectId == projectileTypeId }
}
diff --git a/src/main/scala/net/psforever/objects/Deployables.scala b/src/main/scala/net/psforever/objects/Deployables.scala
index 826b38761..c75057197 100644
--- a/src/main/scala/net/psforever/objects/Deployables.scala
+++ b/src/main/scala/net/psforever/objects/Deployables.scala
@@ -6,6 +6,7 @@ import net.psforever.objects.avatar.{Avatar, Certification}
import scala.concurrent.duration._
import net.psforever.objects.ce.{Deployable, DeployedItem}
+import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game._
import net.psforever.types.PlanetSideGUID
@@ -261,4 +262,48 @@ object Deployables {
}
(sample intersect testDiff equals testDiff) && (sampleIntersect intersect testIntersect equals testIntersect)
}
+
+ /**
+ * Find a player with a given name in this zone.
+ * The assumption is the player is the owner of a given deployable entity.
+ * If the player can not be found, the deployable entity can stand in as it's own owner.
+ * @param zone continent in which the player should be found;
+ * should be the same zone as the deployable, but not required
+ * @param nameOpt optional player's name
+ * @param deployableSource deployable entity
+ * @return discovered player as a reference
+ */
+ def AssignBlameTo(zone: Zone, nameOpt: Option[String], deployableSource: SourceEntry): SourceEntry = {
+ zone
+ .Players
+ .find(a => nameOpt.contains(a.name))
+ .collect { a =>
+ val name = a.name
+ Deployables.AssignBlameToFrom(name, zone.LivePlayers)
+ .orElse(Deployables.AssignBlameToFrom(name, zone.Corpses))
+ .getOrElse {
+ val player = PlayerSource(name, deployableSource.Faction, deployableSource.Position) //might report minor inconsistencies, e.g., exo-suit type
+ player.copy(unique = player.unique.copy(charId = a.id), progress = a.scorecard.CurrentLife)
+ }
+ }
+ .getOrElse(deployableSource)
+ }
+
+ /**
+ * Find a player with a given name from this list of possible players.
+ * If the player is seated, attach a shallow copy of the mounting information.
+ * @param name player name
+ * @param blameList possible players in which to find the player name
+ * @return discovered player as a reference, or `None` if not found
+ */
+ private def AssignBlameToFrom(name: String, blameList: List[Player]): Option[SourceEntry] = {
+ blameList
+ .find(_.Name.equals(name))
+ .map { player =>
+ PlayerSource
+ .mountableAndSeat(player)
+ .map { case (mount, seat) => PlayerSource.inSeat(player, mount, seat) }
+ .getOrElse { PlayerSource(player) }
+ }
+ }
}
diff --git a/src/main/scala/net/psforever/objects/MineDeployableControl.scala b/src/main/scala/net/psforever/objects/MineDeployableControl.scala
index 3b393378a..0a1e46461 100644
--- a/src/main/scala/net/psforever/objects/MineDeployableControl.scala
+++ b/src/main/scala/net/psforever/objects/MineDeployableControl.scala
@@ -5,7 +5,7 @@ import akka.actor.{ActorContext, ActorRef, Props}
import net.psforever.objects.ce.{Deployable, DeployedItem}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.affinity.FactionAffinity
-import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, SourceEntry}
+import net.psforever.objects.sourcing.{DeployableSource, SourceEntry}
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.etc.TrippedMineReason
import net.psforever.objects.vital.interaction.DamageInteraction
@@ -98,40 +98,8 @@ object MineDeployableControl {
private case class Triggered()
def trippedMineReason(mine: ExplosiveDeployable): TrippedMineReason = {
- lazy val deployableSource = DeployableSource(mine)
- val zone = mine.Zone
- val ownerName = mine.OwnerName
- val blame = zone
- .Players
- .find(a => ownerName.contains(a.name))
- .collect { a =>
- val name = a.name
- assignBlameToFrom(name, zone.LivePlayers)
- .orElse(assignBlameToFrom(name, zone.Corpses))
- .getOrElse {
- val player = PlayerSource(name, mine.Faction, mine.Position) //might report minor inconsistencies, e.g., exo-suit type
- player.copy(unique = player.unique.copy(charId = a.id), progress = a.scorecard.CurrentLife)
- }
- }
- .getOrElse(deployableSource)
+ val deployableSource = DeployableSource(mine)
+ val blame = Deployables.AssignBlameTo(mine.Zone, mine.OwnerName, deployableSource)
TrippedMineReason(deployableSource, blame)
}
-
- /**
- * Find a player with a given name from this list of possible players.
- * If the player is seated, attach a shallow copy of the mounting information.
- * @param name player name
- * @param blameList possible players in which to find the player name
- * @return discovered player as a reference, or `None` if not found
- */
- private def assignBlameToFrom(name: String, blameList: List[Player]): Option[SourceEntry] = {
- blameList
- .find(_.Name.equals(name))
- .map { player =>
- PlayerSource
- .mountableAndSeat(player)
- .map { case (mount, seat) => PlayerSource.inSeat(player, mount, seat) }
- .getOrElse { PlayerSource(player) }
- }
- }
}
diff --git a/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala b/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala
index 2a6f4d23b..29c310101 100644
--- a/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala
+++ b/src/main/scala/net/psforever/objects/avatar/DeployableToolbox.scala
@@ -22,9 +22,9 @@ import scala.collection.mutable
* As deployables are added and removed, and tracked certifications are added and removed,
* these structures are updated to reflect proper count.
* For example, the greatest number of spitfire turrets that can be placed is 15 (individual count)
- * and the greatest number of shadow turrets and cerebus turrets that can be placed is 5 each (individual counts)
+ * and the greatest number of shadow turrets and cerberus turrets that can be placed is 5 each (individual counts)
* but the maximum number of small turrets that can be placed overall is only 15 (categorical count).
- * Spitfire turrets, shadow turrets, and cerebus turrets are all included in the category of small turrets.
+ * Spitfire turrets, shadow turrets, and cerberus turrets are all included in the category of small turrets.
*/
class DeployableToolbox {
diff --git a/src/main/scala/net/psforever/objects/ce/Deployable.scala b/src/main/scala/net/psforever/objects/ce/Deployable.scala
index a77989c20..ed2114172 100644
--- a/src/main/scala/net/psforever/objects/ce/Deployable.scala
+++ b/src/main/scala/net/psforever/objects/ce/Deployable.scala
@@ -114,7 +114,7 @@ object Deployable {
DeployedItem.jammer_mine.id -> DeployableIcon.DisruptorMine,
DeployedItem.spitfire_turret.id -> DeployableIcon.SpitfireTurret,
DeployedItem.spitfire_cloaked.id -> DeployableIcon.ShadowTurret,
- DeployedItem.spitfire_aa.id -> DeployableIcon.CerebusTurret,
+ DeployedItem.spitfire_aa.id -> DeployableIcon.cerberusTurret,
DeployedItem.motionalarmsensor.id -> DeployableIcon.MotionAlarmSensor,
DeployedItem.sensor_shield.id -> DeployableIcon.SensorDisruptor,
DeployedItem.tank_traps.id -> DeployableIcon.TRAP,
diff --git a/src/main/scala/net/psforever/objects/ce/DeployedItem.scala b/src/main/scala/net/psforever/objects/ce/DeployedItem.scala
index ff6d5e1e3..c144c2be9 100644
--- a/src/main/scala/net/psforever/objects/ce/DeployedItem.scala
+++ b/src/main/scala/net/psforever/objects/ce/DeployedItem.scala
@@ -10,7 +10,7 @@ object DeployedItem extends Enumeration {
final val jammer_mine = Value(420) //disruptor mine
final val motionalarmsensor = Value(575)
final val sensor_shield = Value(752) //sensor disruptor
- final val spitfire_aa = Value(819) //cerebus turret
+ final val spitfire_aa = Value(819) //cerberus turret
final val spitfire_cloaked = Value(825) //shadow turret
final val spitfire_turret = Value(826)
final val tank_traps = Value(849) //trap
diff --git a/src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala b/src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala
index 279255dc7..e1a8a8210 100644
--- a/src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala
+++ b/src/main/scala/net/psforever/objects/guid/UniqueNumberOps.scala
@@ -50,7 +50,7 @@ class UniqueNumberOps(
private val poolActors: Map[String, ActorRef]
) {
/** The timeout used by all number pool `ask` messaging */
- private implicit val timeout = UniqueNumberOps.timeout
+ private implicit val timeout: Timeout = UniqueNumberOps.timeout
/**
* The entry point for the entity GUID registration process.
@@ -149,25 +149,26 @@ class UniqueNumberOps(
val localPool = pool
val result = ask(pool, NumberPoolActor.GetAnyNumber())(timeout)
- result.onComplete {
- case Success(NumberPoolActor.GiveNumber(number)) =>
- UniqueNumberOps.processRegisterResult(
- localPromise,
- localTarget,
- localUns,
- localPoolName,
- localPool,
- number
- )
- case Success(NumberPoolActor.NoNumber(ex)) =>
- registrationProcessRetry(localPromise, ex, localTarget, localUns, localPools, localPoolName)
- case msg =>
- UniqueNumberOps.log.warn(s"unexpected message during $localTarget's registration process - $msg")
- }
- result.recover {
- case ex: AskTimeoutException =>
- localPromise.failure(new RegisteringException(msg = s"did not register entity $localTarget in time", ex))
- }
+ result
+ .recover {
+ case ex: AskTimeoutException =>
+ localPromise.failure(new RegisteringException(msg = s"did not register entity $localTarget in time", ex))
+ }
+ .onComplete {
+ case Success(NumberPoolActor.GiveNumber(number)) =>
+ UniqueNumberOps.processRegisterResult(
+ localPromise,
+ localTarget,
+ localUns,
+ localPoolName,
+ localPool,
+ number
+ )
+ case Success(NumberPoolActor.NoNumber(ex)) =>
+ registrationProcessRetry(localPromise, ex, localTarget, localUns, localPools, localPoolName)
+ case msg =>
+ UniqueNumberOps.log.warn(s"unexpected message during $localTarget's registration process - $msg")
+ }
case None =>
//do not log
@@ -197,7 +198,7 @@ class UniqueNumberOps(
if (poolName.equals("generic")) {
promise.failure(new RegisteringException(msg = s"did not register entity $obj", exception))
} else {
- org.log4s.getLogger("UniqueNumberOps").warn(s"${exception.getLocalizedMessage()} - $poolName")
+ UniqueNumberOps.log.warn(s"${exception.getLocalizedMessage()} - $poolName")
promise.completeWith(registrationProcess(obj, guid, pools, poolName = "generic"))
}
}
@@ -302,7 +303,7 @@ class UniqueNumberOps(
object UniqueNumberOps {
private val log = org.log4s.getLogger
- private implicit val timeout = Timeout(2.seconds)
+ private implicit val timeout: Timeout = Timeout(4.seconds)
/**
* Final step of the object registration process.
@@ -431,7 +432,7 @@ class UniqueNumberSetup(
) extends Actor {
init()
- final def receive: Receive = { case _ => ; }
+ final def receive: Receive = { case _ => () }
def init(): UniqueNumberOps = {
new UniqueNumberOps(hub, poolActorConversionFunc(context, hub))
diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
index 5f172c8c0..09e0973f1 100644
--- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala
@@ -106,7 +106,7 @@ class FacilityTurretControl(turret: FacilityTurret)
override protected def tryMount(obj: PlanetSideServerObject with Mountable, seatNumber: Int, player: Player): Boolean = {
val originalAutoState = AutomaticOperation
AutomaticOperation = false //turn off
- if (JammableObject.Jammed) {
+ if (AutomaticOperationPossible && JammableObject.Jammed) {
val zone = TurretObject.Zone
AutomatedTurretBehavior.stopTracking(zone, zone.id, TurretObject.GUID) //can not recover lost jamming aggro
}
@@ -220,6 +220,15 @@ class FacilityTurretControl(turret: FacilityTurret)
!TurretObject.isUpgrading
}
+ override def AutomaticOperationPossible: Boolean = {
+ super.AutomaticOperationPossible &&
+ (turret.Owner match {
+ case b: Building if b.CaptureTerminal.isEmpty => false
+ case b: Building => !b.CaptureTerminal.exists(_.Definition == GlobalDefinitions.secondary_capture)
+ case _ => false
+ })
+ }
+
private def primaryWeaponFireModeOnly(): Unit = {
if (testToResetToDefaultFireMode) {
val zone = TurretObject.Zone
@@ -295,7 +304,7 @@ class FacilityTurretControl(turret: FacilityTurret)
override def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = {
super.TryJammerEffectActivate(target, cause)
- if (JammableObject.Jammed) {
+ if (AutomaticOperationPossible && AutomaticOperation && JammableObject.Jammed) {
AutomaticOperation = false
if (!MountableObject.Seats.values.exists(_.isOccupied) && AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDelay > 0)) {
//look in direction of cause of jamming
diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretBehavior.scala
index 1c2f64354..fad4c5c34 100644
--- a/src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretBehavior.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretBehavior.scala
@@ -80,6 +80,8 @@ trait AutomatedTurretBehavior {
Actor.emptyBehavior
}
+ def AutomaticOperationPossible: Boolean = autoStats.isDefined
+
def AutomaticOperation: Boolean = automaticOperation
/**
@@ -111,7 +113,7 @@ trait AutomatedTurretBehavior {
* @return `true`, if it would be possible for automated behavior to become operational;
* `false`, otherwise
*/
- protected def AutomaticOperationFunctionalityChecks: Boolean = { autoStats.isDefined }
+ protected def AutomaticOperationFunctionalityChecks: Boolean = AutomaticOperationPossible
/**
* The last time weapons fire from the turret was confirmed by this control agency.
diff --git a/src/main/scala/net/psforever/packet/game/DeployableObjectsInfoMessage.scala b/src/main/scala/net/psforever/packet/game/DeployableObjectsInfoMessage.scala
index 4c03ec112..916c8b0a3 100644
--- a/src/main/scala/net/psforever/packet/game/DeployableObjectsInfoMessage.scala
+++ b/src/main/scala/net/psforever/packet/game/DeployableObjectsInfoMessage.scala
@@ -26,7 +26,7 @@ object DeploymentAction extends Enumeration {
object DeployableIcon extends Enumeration {
type Type = Value
- val Boomer, HEMine, MotionAlarmSensor, SpitfireTurret, RouterTelepad, DisruptorMine, ShadowTurret, CerebusTurret,
+ val Boomer, HEMine, MotionAlarmSensor, SpitfireTurret, RouterTelepad, DisruptorMine, ShadowTurret, cerberusTurret,
TRAP, AegisShieldGenerator, FieldTurret, SensorDisruptor = Value
implicit val codec: Codec[DeployableIcon.Value] = PacketHelpers.createEnumerationCodec(this, uint4L)
diff --git a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
index d62590404..a6fb90707 100644
--- a/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
+++ b/src/main/scala/net/psforever/packet/game/PlanetsideAttributeMessage.scala
@@ -32,7 +32,7 @@ import scodec.codecs._
* `86 - max spitfire turrets`
* `87 - max motion sensors`
* `88 - max shadow turrets`
- * `89 - max cerebus turrets`
+ * `89 - max cerberus turrets`
* `90 - max Aegis shield generators`
* `91 - max TRAPs`
* `92 - max OMFTs`
@@ -43,7 +43,7 @@ import scodec.codecs._
* `97 - spitfire turrets`
* `98 - motion sensors`
* `99 - shadow turrets`
- * `100 - cerebus turrets`
+ * `100 - cerberus turrets`
* `101 - Aegis shield generators`
* `102 - TRAPSs`
* `103 - OMFTs`
diff --git a/src/test/scala/objects/DeployableTest.scala b/src/test/scala/objects/DeployableTest.scala
index 26dac7579..48702f453 100644
--- a/src/test/scala/objects/DeployableTest.scala
+++ b/src/test/scala/objects/DeployableTest.scala
@@ -138,14 +138,14 @@ class TurretDeployableTest extends Specification {
DeployedItem.portable_manned_turret_nc.id,
DeployedItem.portable_manned_turret_vs.id
).foreach(id => {
- try { new TurretDeployableDefinition(id) }
+ try { new TurretDeployableDefinition(id) { } }
catch { case _: Exception => ko }
})
ok
}
"define (invalid object)" in {
- new TurretDeployableDefinition(5) must throwA[NoSuchElementException] //wrong object id altogether
+ new TurretDeployableDefinition(objectId = 5) { } must throwA[NoSuchElementException] //wrong object id altogether
}
"construct" in {
@@ -212,7 +212,7 @@ class DeployableMake extends Specification {
}
}
- "construct a cerebus turret" in {
+ "construct a cerberus turret" in {
val func = Deployables.Make(DeployedItem.spitfire_aa)
func() match {
case obj: TurretDeployable if obj.Definition == GlobalDefinitions.spitfire_aa => ok
diff --git a/src/test/scala/objects/DeployableToolboxTest.scala b/src/test/scala/objects/DeployableToolboxTest.scala
index e608812ad..a524e909d 100644
--- a/src/test/scala/objects/DeployableToolboxTest.scala
+++ b/src/test/scala/objects/DeployableToolboxTest.scala
@@ -819,13 +819,13 @@ class DeployableToolboxTest extends Specification {
val obj = new DeployableToolbox
obj.Initialize(Set(CombatEngineering))
- val cerebus = new TurretDeployable(GlobalDefinitions.spitfire_aa) //cerebus turret
- obj.Valid(cerebus) mustEqual false
+ val cerberus = new TurretDeployable(GlobalDefinitions.spitfire_aa) //cerberus turret
+ obj.Valid(cerberus) mustEqual false
obj.CountDeployable(DeployedItem.spitfire_aa).productIterator.toList mustEqual List(0, 0)
obj.UpdateMaxCounts(Set(CombatEngineering, AdvancedEngineering))
- obj.Valid(cerebus) mustEqual true
+ obj.Valid(cerberus) mustEqual true
obj.CountDeployable(DeployedItem.spitfire_aa).productIterator.toList mustEqual List(0, 5)
}