From e5cde75e72f57fe77450e2f61c5e3af1ab5e6826 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 22 Jul 2024 20:16:59 -0400 Subject: [PATCH] radiator is turned off due to potential for server crashes; cerebus -> cerberus; turret kills name owner only when they are in the same zone; fewer chances for turrets to fire when they should not --- .../resources/overrides/game_objects0.adb.lst | 1 + .../normal/WeaponAndProjectileLogic.scala | 17 ++++--- .../net/psforever/objects/Deployables.scala | 45 ++++++++++++++++++ .../objects/MineDeployableControl.scala | 38 ++------------- .../objects/avatar/DeployableToolbox.scala | 4 +- .../net/psforever/objects/ce/Deployable.scala | 2 +- .../psforever/objects/ce/DeployedItem.scala | 2 +- .../objects/guid/UniqueNumberOps.scala | 47 ++++++++++--------- .../turret/FacilityTurretControl.scala | 13 ++++- .../turret/auto/AutomatedTurretBehavior.scala | 4 +- .../game/DeployableObjectsInfoMessage.scala | 2 +- .../game/PlanetsideAttributeMessage.scala | 4 +- src/test/scala/objects/DeployableTest.scala | 6 +-- .../scala/objects/DeployableToolboxTest.scala | 6 +-- 14 files changed, 110 insertions(+), 81 deletions(-) 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) }