From 92096a01edbf05733a3821d9d934c541abcb6c57 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 5 Feb 2024 18:43:47 -0500 Subject: [PATCH] overhaul of the auto turret target selection process; conditions for MAX detection; rewired self-reporting to address the its issue a bit more specifically; ATDispatch is no longer useless as differences between facility turrets and deployable turrets have been identified, shifting the method to implementing and overriding in subclass control agencies --- .../net/psforever/actors/zone/ZoneActor.scala | 12 +- .../psforever/objects/GlobalDefinitions.scala | 2 + .../psforever/objects/TurretDeployable.scala | 79 +++- .../objects/equipment/EffectTarget.scala | 84 ++-- .../turret/FacilityTurretControl.scala | 123 ++++-- .../auto/AffectedByAutomaticTurretFire.scala | 2 +- .../turret/auto/AutomatedTurretBehavior.scala | 374 ++++++++++-------- .../turret/auto/AutomatedTurretDispatch.scala | 116 ------ 8 files changed, 425 insertions(+), 367 deletions(-) delete mode 100644 src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretDispatch.scala diff --git a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala index 43ee1ffa4..87ae03190 100644 --- a/src/main/scala/net/psforever/actors/zone/ZoneActor.scala +++ b/src/main/scala/net/psforever/actors/zone/ZoneActor.scala @@ -13,6 +13,8 @@ import akka.actor.typed.scaladsl.adapter._ import net.psforever.actors.zone.building.MajorFacilityLogic import net.psforever.objects.avatar.scoring.Kill import net.psforever.objects.serverobject.affinity.FactionAffinity +import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAwareBehavior +import net.psforever.objects.serverobject.turret.FacilityTurret import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.vital.{InGameActivity, InGameHistory} import net.psforever.objects.zones.exp.{ExperienceCalculator, SupportExperienceCalculator} @@ -96,14 +98,18 @@ class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone) case Success(buildings) => buildings.foreach { building => zone.BuildingByMapId(building.localId) match { - case Some(_: WarpGate) => ; + case Some(_: WarpGate) => () //warp gates are controlled by game logic and are better off not restored via the database case Some(b) => if ((b.Faction = PlanetSideEmpire(building.factionId)) != PlanetSideEmpire.NEUTRAL) { b.ForceDomeActive = MajorFacilityLogic.checkForceDomeStatus(b).getOrElse(false) - b.Neighbours.getOrElse(Nil).foreach { _.Actor ! BuildingActor.AlertToFactionChange(b) } + b.Neighbours.getOrElse(Nil).foreach(_.Actor ! BuildingActor.AlertToFactionChange(b)) + b.CaptureTerminal.collect { terminal => + val msg = CaptureTerminalAwareBehavior.TerminalStatusChanged(terminal, isResecured = true) + b.Amenities.collect { case turret: FacilityTurret => turret.Actor ! msg } + } } - case None => ; + case None => () // TODO this happens during testing, need a way to not always persist during tests } } diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 6c35ba60e..b6b976b56 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -9082,6 +9082,7 @@ object GlobalDefinitions { AutoChecks( validation = List( EffectTarget.Validation.SmallRoboticsTurretValidatePlayerTarget, + EffectTarget.Validation.SmallRoboticsTurretValidateMaxTarget, EffectTarget.Validation.SmallRoboticsTurretValidateGroundVehicleTarget, EffectTarget.Validation.SmallRoboticsTurretValidateAircraftTarget ) @@ -9124,6 +9125,7 @@ object GlobalDefinitions { AutoChecks( validation = List( EffectTarget.Validation.SmallRoboticsTurretValidatePlayerTarget, + EffectTarget.Validation.SmallRoboticsTurretValidateMaxTarget, EffectTarget.Validation.SmallRoboticsTurretValidateGroundVehicleTarget, EffectTarget.Validation.SmallRoboticsTurretValidateAircraftTarget ) diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala index 852f2d4c1..82e6adba3 100644 --- a/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -9,9 +9,10 @@ import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.guid.{GUIDTask, TaskWorkflow} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior -import net.psforever.objects.serverobject.damage.Damageable.Target +import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.mount.Mountable +import net.psforever.objects.serverobject.turret.auto.AutomatedTurret.Target import net.psforever.objects.serverobject.turret.auto.{AffectedByAutomaticTurretFire, AutomatedTurret, AutomatedTurretBehavior} import net.psforever.objects.serverobject.turret.{MountableTurretControl, TurretDefinition, WeaponTurret} import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} @@ -19,6 +20,7 @@ import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.interaction.DamageResult import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} +import net.psforever.types.PlanetSideGUID import scala.concurrent.duration.FiniteDuration @@ -105,7 +107,63 @@ class TurretControl(turret: TurretDeployable) case _ => () } - protected def AutomaticOperationFunctionalityChecks: Boolean = true + protected def engageNewDetectedTarget( + target: AutomatedTurret.Target, + channel: String, + turretGuid: PlanetSideGUID, + weaponGuid: PlanetSideGUID + ): Unit = { + val zone = target.Zone + AutomatedTurretBehavior.startTracking(zone, channel, turretGuid, List(target.GUID)) + AutomatedTurretBehavior.startShooting(zone, channel, weaponGuid) + } + + protected def noLongerEngageTarget( + target: AutomatedTurret.Target, + channel: String, + turretGuid: PlanetSideGUID, + weaponGuid: PlanetSideGUID + ): Option[AutomatedTurret.Target] = { + val zone = target.Zone + AutomatedTurretBehavior.stopTracking(zone, channel, turretGuid) + AutomatedTurretBehavior.stopShooting(zone, channel, weaponGuid) + None + } + + protected def testNewDetected( + target: AutomatedTurret.Target, + channel: String, + turretGuid: PlanetSideGUID, + weaponGuid: PlanetSideGUID + ): Unit = { + val zone = target.Zone + AutomatedTurretBehavior.startTracking(zone, channel, turretGuid, List(target.GUID)) + AutomatedTurretBehavior.startShooting(zone, channel, weaponGuid) + AutomatedTurretBehavior.stopShooting(zone, channel, weaponGuid) + //AutomatedTurretBehavior.stopTracking(zone, channel, turretGuid) + } + + protected def testKnownDetected( + target: AutomatedTurret.Target, + channel: String, + turretGuid: PlanetSideGUID, + weaponGuid: PlanetSideGUID + ): Unit = { + val zone = target.Zone + //AutomatedTurretBehavior.startTracking(zone, channel, turretGuid, List(target.GUID)) + AutomatedTurretBehavior.startShooting(zone, channel, weaponGuid) + AutomatedTurretBehavior.stopShooting(zone, channel, weaponGuid) + //AutomatedTurretBehavior.stopTracking(zone, channel, turretGuid) + } + + override protected def suspendTargetTesting( + target: Target, + channel: String, + turretGuid: PlanetSideGUID, + weaponGuid: PlanetSideGUID + ): Unit = { + AutomatedTurretBehavior.stopTracking(target.Zone, channel, turretGuid) + } override protected def mountTest( obj: PlanetSideServerObject with Mountable, @@ -121,12 +179,11 @@ class TurretControl(turret: TurretDeployable) AutomaticOperation = false //look in direction of cause of jamming val zone = JammableObject.Zone - AutomatedTurretBehavior.getAttackVectorFromCause(zone, cause).foreach { - attacker => - val channel = zone.id - val guid = AutomatedTurretObject.GUID - AutomatedTurretBehavior.startTracking(attacker, channel, guid, List(attacker.GUID)) - AutomatedTurretBehavior.stopTracking(attacker, channel, guid) //TODO delay by a few milliseconds? + val channel = zone.id + val guid = AutomatedTurretObject.GUID + AutomatedTurretBehavior.getAttackVectorFromCause(zone, cause).foreach { attacker => + AutomatedTurretBehavior.startTracking(zone, channel, guid, List(attacker.GUID)) + AutomatedTurretBehavior.stopTracking(zone, channel, guid) //TODO delay by a few milliseconds? } } } @@ -137,12 +194,12 @@ class TurretControl(turret: TurretDeployable) startsJammed && AutomaticOperation_=(state = true) } - override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any): Unit = { + override protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any): Unit = { attemptRetaliation(target, cause) super.DamageAwareness(target, cause, amount) } - override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = { + override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { AutomaticOperation = false super.DestructionAwareness(target, cause) CancelJammeredSound(target) @@ -178,7 +235,7 @@ class TurretControl(turret: TurretDeployable) override def finalizeDeployable(callback: ActorRef): Unit = { super.finalizeDeployable(callback) - AutomaticOperation = AutomaticOperationFunctionalityChecks + AutomaticOperation = true } override def unregisterDeployable(obj: Deployable): Unit = { diff --git a/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala b/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala index 9c949ef58..1ba8ab45a 100644 --- a/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala +++ b/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala @@ -206,13 +206,15 @@ object EffectTarget { now - t.LastDischarge } .exists(_ < 2000L) - lazy val silentRunActive = p.avatar.implants.flatten.find(a => a.definition.implantType == ImplantType.SilentRun).exists(_.active) lazy val cloakedByInfiltrationSuit = p.ExoSuit == ExoSuitType.Infiltration && p.Cloaked + lazy val silentRunActive = p.avatar.implants.flatten.find(a => a.definition.implantType == ImplantType.SilentRun).exists(_.active) lazy val movingFast = p.isMoving(test = 17d) + lazy val isJumping = p.Jumping if (radarCloakedAms(sector, pos) || radarCloakedAegis(sector, pos)) false else if (radarCloakedSensor(sector, pos, faction)) tookDamage || usedEquipment - else if (radarEnhancedInterlink(sector, pos, faction) || radarEnhancedSensor(sector, pos, faction)) true - else tookDamage || usedEquipment || p.Jumping || (cloakedByInfiltrationSuit || !silentRunActive) && movingFast + else if (radarEnhancedInterlink(sector, pos, faction)) true + else if (radarEnhancedSensor(sector, pos, faction)) !silentRunActive || tookDamage || usedEquipment || isJumping + else tookDamage || usedEquipment || isJumping || !(cloakedByInfiltrationSuit || silentRunActive) && movingFast case _ => false } @@ -229,34 +231,10 @@ object EffectTarget { lazy val usedEquipment = p.Holsters().flatMap(_.Equipment) .collect { case t: Tool => now - t.LastDischarge } .exists(_ < 2000L) + lazy val isMoving = p.isMoving(test = 1d) if (radarCloakedAms(sector, pos) || radarCloakedAegis(sector, pos)) false else if (radarCloakedSensor(sector, pos, faction)) tookDamage || usedEquipment - else true - case _ => - false - } - - def FacilityTurretValidateMaxTarget(target: PlanetSideGameObject): Boolean = - target match { - case p: Player - if p.ExoSuit == ExoSuitType.MAX && p.VehicleSeated.isEmpty => - val pos = p.Position - val faction = p.Faction - val sector = p.Zone.blockMap.sector(p.Position, range = 51f) - !(radarCloakedAms(sector, pos) || - radarCloakedAegis(sector, pos) || - radarCloakedSensor(sector, pos, faction)) && - p.isMoving(test = 17d) - case _ => - false - } - - def AutoTurretBlankPlayerTarget(target: PlanetSideGameObject): Boolean = - target match { - case p: Player => - val pos = p.Position - val sector = p.Zone.blockMap.sector(p.Position, range = 51f) - p.VehicleSeated.nonEmpty || radarCloakedAms(sector, pos) || radarCloakedAegis(sector, pos) + else isMoving case _ => false } @@ -271,9 +249,10 @@ object EffectTarget { lazy val usedEquipment = v.Weapons.values.flatMap(_.Equipment) .collect { case t: Tool => now - t.LastDischarge } .exists(_ < 2000L) + lazy val isMoving = v.isMoving(test = 1d) !GlobalDefinitions.isFlightVehicle(vdef) && ( if (vdef == GlobalDefinitions.ams && v.DeploymentState == DriveState.Deployed) false - else !v.Cloaked && ( tookDamage || usedEquipment)) + else !v.Cloaked && (isMoving || tookDamage || usedEquipment)) case _ => false } @@ -281,7 +260,24 @@ object EffectTarget { def SmallRoboticsTurretValidateAircraftTarget(target: PlanetSideGameObject): Boolean = target match { case v: Vehicle if GlobalDefinitions.isFlightVehicle(v.Definition) => - !v.Cloaked + lazy val isMoving = v.isMoving(test = 1d) + !v.Cloaked && isMoving + case _ => + false + } + + def FacilityTurretValidateMaxTarget(target: PlanetSideGameObject): Boolean = + target match { + case p: Player + if p.ExoSuit == ExoSuitType.MAX && p.VehicleSeated.isEmpty => + val pos = p.Position + val faction = p.Faction + val sector = p.Zone.blockMap.sector(p.Position, range = 51f) + lazy val isMoving = p.isMoving(test = 1d) + !(radarCloakedAms(sector, pos) || + radarCloakedAegis(sector, pos) || + radarCloakedSensor(sector, pos, faction)) && + isMoving case _ => false } @@ -296,15 +292,15 @@ object EffectTarget { lazy val usedEquipment = v.Weapons.values.flatMap(_.Equipment) .collect { case t: Tool => now - t.LastDischarge } .exists(_ < 2000L) + lazy val isMoving = v.isMoving(test = 1d) !GlobalDefinitions.isFlightVehicle(vdef) && ( - if (vdef == GlobalDefinitions.ams && v.DeploymentState == DriveState.Deployed) false + if ((vdef == GlobalDefinitions.ams && v.DeploymentState == DriveState.Deployed) || v.Cloaked) false else if ( - v.Cloaked || - GlobalDefinitions.isAtvVehicle(vdef) || + GlobalDefinitions.isAtvVehicle(vdef) || vdef == GlobalDefinitions.two_man_assault_buggy || vdef == GlobalDefinitions.skyguard ) tookDamage || usedEquipment - else true) + else isMoving) case _ => false } @@ -312,7 +308,23 @@ object EffectTarget { def FacilityTurretValidateAircraftTarget(target: PlanetSideGameObject): Boolean = target match { case v: Vehicle if GlobalDefinitions.isFlightVehicle(v.Definition) => - v.Definition != GlobalDefinitions.mosquito || !v.Cloaked + val now = System.currentTimeMillis() + lazy val tookDamage = v.LastDamage.exists(dam => dam.adversarial.nonEmpty && now - dam.interaction.hitTime< 2000L) + lazy val usedEquipment = v.Weapons.values.flatMap(_.Equipment) + .collect { case t: Tool => now - t.LastDischarge } + .exists(_ < 2000L) + lazy val isMoving = v.isMoving(test = 1d) + (v.Definition != GlobalDefinitions.mosquito || tookDamage || usedEquipment) && !v.Cloaked && isMoving + case _ => + false + } + + def AutoTurretBlankPlayerTarget(target: PlanetSideGameObject): Boolean = + target match { + case p: Player => + val pos = p.Position + val sector = p.Zone.blockMap.sector(p.Position, range = 51f) + p.VehicleSeated.nonEmpty || radarCloakedAms(sector, pos) || radarCloakedAegis(sector, pos) case _ => false } 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 0fa6232eb..b0c7993cf 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -8,14 +8,15 @@ import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.hackable.GenericHackables import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.repair.AmenityAutoRepair -import net.psforever.objects.serverobject.structures.PoweredAmenityControl +import net.psforever.objects.serverobject.structures.{Building, PoweredAmenityControl} import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalAwareBehavior} +import net.psforever.objects.serverobject.turret.auto.AutomatedTurret.Target import net.psforever.objects.serverobject.turret.auto.{AffectedByAutomaticTurretFire, AutomatedTurret, AutomatedTurretBehavior} import net.psforever.objects.vital.interaction.DamageResult import net.psforever.packet.game.ChangeFireModeMessage import net.psforever.services.Service import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} -import net.psforever.types.{BailType, PlanetSideEmpire} +import net.psforever.types.{BailType, PlanetSideEmpire, PlanetSideGUID} /** * A control agency that handles messages being dispatched to a specific `FacilityTurret`. @@ -42,7 +43,7 @@ class FacilityTurretControl(turret: FacilityTurret) private var testToResetToDefaultFireMode: Boolean = false - AutomaticOperation = AutomaticOperationFunctionalityChecks + AutomaticOperation = true override def postStop(): Unit = { super.postStop() @@ -100,7 +101,7 @@ class FacilityTurretControl(turret: FacilityTurret) override protected def tryMount(obj: PlanetSideServerObject with Mountable, seatNumber: Int, player: Player): Boolean = { AutomaticOperation = false //turn off if (!super.tryMount(obj, seatNumber, player)) { - AutomaticOperation = AutomaticOperationFunctionalityChecks //revert? + AutomaticOperation = true //revert? false } else { true @@ -145,7 +146,7 @@ class FacilityTurretControl(turret: FacilityTurret) override def Restoration(obj: Damageable.Target): Unit = { super.Restoration(obj) - AutomaticOperation = AutomaticOperationFunctionalityChecks + AutomaticOperation = true } override def tryAutoRepair() : Boolean = { @@ -175,7 +176,7 @@ class FacilityTurretControl(turret: FacilityTurret) def powerTurnOnCallback(): Unit = { tryAutoRepair() - AutomaticOperation = AutomaticOperationFunctionalityChecks + AutomaticOperation = true } override def AutomaticOperation_=(state: Boolean): Boolean = { @@ -184,12 +185,20 @@ class FacilityTurretControl(turret: FacilityTurret) result } - protected def AutomaticOperationFunctionalityChecks: Boolean = { - AutomaticOperationFunctionalityChecksExceptMounting && !TurretObject.Seats.values.exists(_.isOccupied) + override protected def AutomaticOperationFunctionalityChecks: Boolean = { + AutomaticOperationFunctionalityChecksExceptMounting && + !TurretObject.Seats.values.exists(_.isOccupied) } - protected def AutomaticOperationFunctionalityChecksExceptMounting: Boolean = { - isPowered && + private def AutomaticOperationFunctionalityChecksExceptMounting: Boolean = { + AutomaticOperationFunctionalityChecksExceptMountingAndHacking //&& + //!TurretObject.Owner.asInstanceOf[Building].CaptureTerminalIsHacked + } + + private def AutomaticOperationFunctionalityChecksExceptMountingAndHacking: Boolean = { + super.AutomaticOperationFunctionalityChecks && + isPowered && + TurretObject.Owner.Faction != PlanetSideEmpire.NEUTRAL && !JammableObject.Jammed && TurretObject.Health > TurretObject.Definition.DamageDisablesAt } @@ -213,19 +222,58 @@ class FacilityTurretControl(turret: FacilityTurret) } override protected def trySelectNewTarget(): Option[AutomatedTurret.Target] = { - if (AutomaticOperationFunctionalityChecks) { - primaryWeaponFireModeOnly() - super.trySelectNewTarget() - } else { - None - } + primaryWeaponFireModeOnly() + super.trySelectNewTarget() } - override def engageNewDetectedTarget(target: AutomatedTurret.Target): Unit = { - if (AutomaticOperationFunctionalityChecks) { - primaryWeaponFireModeOnly() - super.engageNewDetectedTarget(target) - } + def engageNewDetectedTarget( + target: Target, + channel: String, + turretGuid: PlanetSideGUID, + weaponGuid: PlanetSideGUID + ): Unit = { + val zone = target.Zone + primaryWeaponFireModeOnly() + AutomatedTurretBehavior.startTracking(zone, channel, turretGuid, List(target.GUID)) + AutomatedTurretBehavior.startShooting(zone, channel, weaponGuid) + } + + protected def noLongerEngageTarget( + target: Target, + channel: String, + turretGuid: PlanetSideGUID, + weaponGuid: PlanetSideGUID + ): Option[Target] = { + val zone = target.Zone + AutomatedTurretBehavior.stopTracking(zone, channel, turretGuid) + AutomatedTurretBehavior.stopShooting(zone, channel, weaponGuid) + None + } + + protected def testNewDetected( + target: Target, + channel: String, + turretGuid: PlanetSideGUID, + weaponGuid: PlanetSideGUID + ): Unit = { + val zone = target.Zone + AutomatedTurretBehavior.startTracking(zone, channel, turretGuid, List(target.GUID)) + AutomatedTurretBehavior.startShooting(zone, channel, weaponGuid) + AutomatedTurretBehavior.stopShooting(zone, channel, weaponGuid) + //AutomatedTurretBehavior.stopTracking(zone, channel, turretGuid) + } + + protected def testKnownDetected( + target: Target, + channel: String, + turretGuid: PlanetSideGUID, + weaponGuid: PlanetSideGUID + ): Unit = { + val zone = target.Zone + //AutomatedTurretBehavior.startTracking(zone, channel, turretGuid, List(target.GUID)) + AutomatedTurretBehavior.startShooting(zone, channel, weaponGuid) + AutomatedTurretBehavior.stopShooting(zone, channel, weaponGuid) + //AutomatedTurretBehavior.stopTracking(zone, channel, turretGuid) } override def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = { @@ -235,12 +283,11 @@ class FacilityTurretControl(turret: FacilityTurret) AutomaticOperation = false //look in direction of cause of jamming val zone = JammableObject.Zone - AutomatedTurretBehavior.getAttackVectorFromCause(zone, cause).foreach { - attacker => - val channel = zone.id - val guid = AutomatedTurretObject.GUID - AutomatedTurretBehavior.startTracking(attacker, channel, guid, List(attacker.GUID)) - AutomatedTurretBehavior.stopTracking(attacker, channel, guid) //TODO delay by a few milliseconds? + val channel = zone.id + val guid = AutomatedTurretObject.GUID + AutomatedTurretBehavior.getAttackVectorFromCause(zone, cause).foreach { attacker => + AutomatedTurretBehavior.startTracking(zone, channel, guid, List(attacker.GUID)) + AutomatedTurretBehavior.stopTracking(zone, channel, guid) //TODO delay by a few milliseconds? } } } @@ -248,24 +295,36 @@ class FacilityTurretControl(turret: FacilityTurret) override def CancelJammeredStatus(target: Any): Unit = { val startsJammed = JammableObject.Jammed super.CancelJammeredStatus(target) - startsJammed && AutomaticOperation_=(AutomaticOperationFunctionalityChecks) + startsJammed && AutomaticOperation_=(state = true) } override protected def captureTerminalIsResecured(terminal: CaptureTerminal): Unit = { - AutomaticOperation = if (terminal.Owner.Faction == PlanetSideEmpire.NEUTRAL) { + import scala.concurrent.ExecutionContext.Implicits.global + import scala.concurrent.duration._ + val originalState = AutomaticOperation + AutomaticOperation = false + val newAutomaticOperationState = if (terminal.Owner.Faction == PlanetSideEmpire.NEUTRAL) { false - } else if (AutomaticOperation || AutomaticOperationFunctionalityChecks) { - AutomaticOperation = false - CurrentTargetLastShotReported = System.currentTimeMillis() + 5000L + } else if (AutomaticOperation || AutomaticOperationFunctionalityChecksExceptMountingAndHacking) { true } else { false } super.captureTerminalIsResecured(terminal) + if (newAutomaticOperationState != originalState) { + context.system.scheduler.scheduleOnce(3.seconds) { AutomaticOperation = newAutomaticOperationState } + } } override protected def captureTerminalIsHacked(terminal: CaptureTerminal): Unit = { + import scala.concurrent.ExecutionContext.Implicits.global + import scala.concurrent.duration._ + val originalState = AutomaticOperation AutomaticOperation = false super.captureTerminalIsHacked(terminal) + val state = AutomaticOperationFunctionalityChecksExceptMountingAndHacking + if (originalState != state && (terminal.HackedBy.isEmpty || terminal.HackedBy.exists(_.hackerFaction == terminal.Faction))) { + context.system.scheduler.scheduleOnce(3.seconds) { AutomaticOperation = state } + } } } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/auto/AffectedByAutomaticTurretFire.scala b/src/main/scala/net/psforever/objects/serverobject/turret/auto/AffectedByAutomaticTurretFire.scala index 1a6580ee3..7485c3a47 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/auto/AffectedByAutomaticTurretFire.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/auto/AffectedByAutomaticTurretFire.scala @@ -29,7 +29,7 @@ trait AffectedByAutomaticTurretFire extends Damageable { protected def performAutomatedDamage(turret: AutomatedTurret): Unit = { val target = AffectedObject - if (!target.isMoving(test = 1f)) { + if (!(target.Destroyed || target.isMoving(test = 1f))) { val tool = turret.Weapons.values.head.Equipment.collect { case t: Tool => t }.get val projectileInfo = tool.Projectile val targetPos = target.Position 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 2545c709c..bf638789a 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 @@ -5,7 +5,7 @@ import akka.actor.{Actor, Cancellable} import net.psforever.objects.avatar.scoring.EquipmentStat import net.psforever.objects.equipment.EffectTarget import net.psforever.objects.serverobject.PlanetSideServerObject -import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity} +import net.psforever.objects.serverobject.damage.DamageableEntity import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.turret.Automation import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, SourceUniqueness} @@ -13,10 +13,12 @@ import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.interaction.DamageResult import net.psforever.objects.zones.exp.ToDatabase import net.psforever.objects.zones.{InteractsWithZone, Zone} -import net.psforever.objects.{Default, PlanetSideGameObject, Player, Vehicle} +import net.psforever.objects.{Default, PlanetSideGameObject, Player} +import net.psforever.packet.game.{ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ObjectDetectedMessage} +import net.psforever.services.Service +import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.types.{PlanetSideGUID, Vector3} -import scala.annotation.unused import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ @@ -40,7 +42,7 @@ trait AutomatedTurretBehavior { private var periodicValidationTest: Cancellable = Default.Cancellable /** targets that have been the subject of test shots just recently; * emptied when switching from the test shot cycle to actually selecting a target */ - private var previouslyTestedTargets: Seq[SourceUniqueness] = Seq[SourceUniqueness]() + private var ongoingTestedTargets: Seq[Target] = Seq[Target]() /** timer managing the trailing target qualifications self test * where the source will shoot directly at some target @@ -90,19 +92,16 @@ trait AutomatedTurretBehavior { */ def AutomaticOperation_=(state: Boolean): Boolean = { val previousState = automaticOperation - if (autoStats.isDefined) { - automaticOperation = state - if (!previousState && state) { - trySelectNewTarget() - } else if (previousState && !state) { - previouslyTestedTargets = Seq() - cancelSelfReportedAutoFire() - AutomatedTurretObject.Target.foreach(noLongerEngageDetectedTarget) - } - state - } else { - previousState + val newState = state && AutomaticOperationFunctionalityChecks + automaticOperation = newState + if (!previousState && newState) { + trySelectNewTarget() + } else if (previousState && !newState) { + ongoingTestedTargets = Seq() + cancelSelfReportedAutoFire() + AutomatedTurretObject.Target.foreach(noLongerEngageDetectedTarget) } + newState } /** @@ -111,7 +110,7 @@ trait AutomatedTurretBehavior { * @return `true`, if it would be possible for automated behavior to become operational; * `false`, otherwise */ - protected def AutomaticOperationFunctionalityChecks: Boolean + protected def AutomaticOperationFunctionalityChecks: Boolean = { autoStats.isDefined } /** * The last time weapons fire from the turret was confirmed by this control agency. @@ -184,7 +183,7 @@ trait AutomatedTurretBehavior { AutomatedTurretObject.Clear() currentTargetToken = None currentTargetLocation = None - previouslyTestedTargets = Seq() + ongoingTestedTargets = Seq() } /* Normal automated turret behavior */ @@ -201,15 +200,18 @@ trait AutomatedTurretBehavior { */ private def normalConfirmShot(target: Target): Boolean = { val now = System.currentTimeMillis() - if (currentTargetToken.isEmpty) { + if ( + currentTargetToken.isEmpty && + target.Faction != AutomatedTurretObject.Faction + ) { currentTargetLastShotTime = now currentTargetLocation = Some(target.Position) - previouslyTestedTargets = Seq() + ongoingTestedTargets = Seq() cancelSelfReportedAutoFire() engageNewDetectedTarget(target) true } else if ( - currentTargetToken.contains(SourceEntry(target).unique) && + currentTargetToken.contains(SourceEntry(target).unique) && now - currentTargetLastShotTime < autoStats.map(_.cooldowns.missedShot).getOrElse(0L)) { currentTargetLastShotTime = now currentTargetLocation = Some(target.Position) @@ -228,17 +230,34 @@ trait AutomatedTurretBehavior { * perform setup of variables useful to maintain firepower against the target. * @param target something the turret can potentially shoot at */ - protected def engageNewDetectedTarget(target: Target): Unit = { + private def engageNewDetectedTarget(target: Target): Unit = { val zone = target.Zone val zoneid = zone.id currentTargetToken = Some(SourceEntry(target).unique) currentTargetLocation = Some(target.Position) currentTargetSwitchTime = System.currentTimeMillis() AutomatedTurretObject.Target = target - AutomatedTurretBehavior.startTracking(target, zoneid, AutomatedTurretObject.GUID, List(target.GUID)) - AutomatedTurretBehavior.startShooting(target, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID) + engageNewDetectedTarget( + target, + zoneid, + AutomatedTurretObject.GUID, + AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID + ) } + /** + * Point the business end of the turret's weapon at a provided target + * and begin shooting at that target. + * The turret will rotate to follow the target's movements in the game world.
+ * For implementing behavior. + * Must be implemented. + * @param target something the turret can potentially shoot at + * @param channel scope of the message + * @param turretGuid turret + * @param weaponGuid turret's weapon + */ + protected def engageNewDetectedTarget(target: Target, channel: String, turretGuid: PlanetSideGUID, weaponGuid: PlanetSideGUID): Unit + /** * If the provided target is the current target: * Stop pointing the business end of the turret's weapon at a provided target. @@ -262,27 +281,31 @@ trait AutomatedTurretBehavior { * @param target something the turret can potentially shoot at * @return something the turret was potentially shoot at */ - protected def noLongerEngageDetectedTarget(target: Target): Option[Target] = { + private def noLongerEngageDetectedTarget(target: Target): Option[Target] = { AutomatedTurretObject.Target = None currentTargetToken = None currentTargetLocation = None - noLongerEngageTestedTarget(target) + noLongerEngageTarget( + target, + target.Zone.id, + AutomatedTurretObject.GUID, + AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID + ) None } /** * Stop pointing the business end of the turret's weapon at a provided target. - * Stop shooting at the target. + * Stop shooting at the target.
+ * For implementing behavior. + * Must be implemented. * @param target something the turret can potentially shoot at - * @return something the turret was potentially shoot at + * @param channel scope of the message + * @param turretGuid turret + * @param weaponGuid turret's weapon + * @return something the turret was potentially shooting at */ - private def noLongerEngageTestedTarget(target: Target): Option[Target] = { - val zone = target.Zone - val zoneid = zone.id - AutomatedTurretBehavior.stopTracking(target, zoneid, AutomatedTurretObject.GUID) - AutomatedTurretBehavior.stopShooting(target, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID) - None - } + protected def noLongerEngageTarget(target: Target, channel: String, turretGuid: PlanetSideGUID, weaponGuid: PlanetSideGUID): Option[Target] /** * While the automated turret is operational and active, @@ -308,6 +331,7 @@ trait AutomatedTurretBehavior { val validation = autoStats.get.checks.validation val disqualifiers = autoStats.get.checks.blanking val faction = AutomatedTurretObject.Faction + //current targets val selectedTargets = AutomatedTurretObject .Targets .collect { case target @@ -318,16 +342,30 @@ trait AutomatedTurretBehavior { disqualifiers.takeWhile(func => func(target)).isEmpty => target } - val (previousTargets, newTargets) = selectedTargets.partition(target => previouslyTestedTargets.contains(SourceEntry(target).unique)) - val newTargetsFunc: Iterable[(Target, (Target, PlanetSideGUID, PlanetSideGUID) => Option[(Target, Target)])] = - newTargets.map(target => (target, processForTestingNewDetectedTarget)) - val previousTargetsFunc: Iterable[(Target, (Target, PlanetSideGUID, PlanetSideGUID) => Option[(Target, Target)])] = - previousTargets.map(target => (target, processForTestingKnownDetectedTarget)) - previouslyTestedTargets = (newTargetsFunc ++ previousTargetsFunc) + //sort targets into categories + val (previousTargets, newTargets, staleTargets) = { + val previouslyTestedTokens = ongoingTestedTargets.map(target => SourceEntry(target).unique) + val (previous_targets, new_targets) = selectedTargets.partition(target => previouslyTestedTokens.contains(SourceEntry(target).unique)) + val previousTargetTokens = previous_targets.map(target => (SourceEntry(target).unique, target)) + val stale_targets = { + for { + (token, target) <- previousTargetTokens + if !previouslyTestedTokens.contains(token) + } yield target + } + (previous_targets, new_targets, stale_targets) + } + //associate with proper functionality and perform callbacks + val newTargetsFunc: Iterable[(Target, (Target, String, PlanetSideGUID, PlanetSideGUID) => Unit)] = + newTargets.map(target => (target, testNewDetected)) + val previousTargetsFunc: Iterable[(Target, (Target, String, PlanetSideGUID, PlanetSideGUID) => Unit)] = + previousTargets.map(target => (target, testKnownDetected)) + ongoingTestedTargets = (newTargetsFunc ++ previousTargetsFunc) .toSeq .sortBy { case (target, _) => Vector3.DistanceSquared(target.Position, turretPosition) } - .flatMap { case (target, func) => func(target, turretGuid, weaponGuid) } - .map { case (target, _) => SourceEntry(target).unique } + .flatMap { case (target, func) => processForTestingTarget(target, turretGuid, weaponGuid, func) } + .map { case (target, _) => target } + staleTargets.foreach(target => processForTestingTarget(target, turretGuid, weaponGuid, suspendTargetTesting)) selectedTargets.headOption } } @@ -339,75 +377,26 @@ trait AutomatedTurretBehavior { * @param target something the turret can potentially shoot at * @param turretGuid turret * @param weaponGuid turret's weapon + * @param processFunc na * @return a tuple composed of: * something the turret can potentially shoot at * something that will report whether the test shot struck the target */ - private def processForTestingNewDetectedTarget( - target: Target, - turretGuid: PlanetSideGUID, - weaponGuid: PlanetSideGUID - ): Option[(Target, Target)] = { + private def processForTestingTarget( + target: Target, + turretGuid: PlanetSideGUID, + weaponGuid: PlanetSideGUID, + processFunc: (Target, String, PlanetSideGUID, PlanetSideGUID)=>Unit + ): Option[(Target, Target)] = { target match { case target: Player => - AutomatedTurretDispatch.Generic.testNewDetected(target, target.Name, turretGuid, weaponGuid) - Some((target, target)) - case target: Vehicle => - target.Seats.values - .flatMap(_.occupants) - .collectFirst { passenger => - AutomatedTurretDispatch.Generic.testNewDetected(passenger, passenger.Name, turretGuid, weaponGuid) - (target, passenger) - } - case _ => - None - } - } - - /** - * Dispatch packets in the direction of a client perspective - * to determine if this target can be reliably struck with a projectile from the turret's weapon. - * This resolves to a player avatar entity usually and is communicated on that player's personal name channel. - * @param target something the turret can potentially shoot at - * @param turretGuid not used - * @param weaponGuid turret's weapon - * @return a tuple composed of: - * something the turret can potentially shoot at - * something that will report whether the test shot struck the target - */ - private def processForTestingKnownDetectedTarget( - target: Target, - @unused turretGuid: PlanetSideGUID, - weaponGuid: PlanetSideGUID - ): Option[(Target, Target)] = { - processForTestingKnownDetectedTarget(target, weaponGuid) - } - - /** - * Dispatch packets in the direction of a client perspective - * to determine if this target can be reliably struck with a projectile from the turret's weapon. - * This resolves to a player avatar entity usually and is communicated on that player's personal name channel. - * @param target something the turret can potentially shoot at - * @param weaponGuid turret's weapon - * @return a tuple composed of: - * something the turret can potentially shoot at - * something that will report whether the test shot struck the target - */ - private def processForTestingKnownDetectedTarget( - target: Target, - weaponGuid: PlanetSideGUID - ): Option[(Target, Target)] = { - target match { - case target: Player => - AutomatedTurretDispatch.Generic.startShooting(target, target.Name, weaponGuid) - AutomatedTurretDispatch.Generic.stopShooting(target, target.Name, weaponGuid) + processFunc(target, target.Name, turretGuid, weaponGuid) Some((target, target)) case target: Mountable => target.Seats.values .flatMap(_.occupants) .collectFirst { passenger => - AutomatedTurretDispatch.Generic.startShooting(passenger, passenger.Name, weaponGuid) - AutomatedTurretDispatch.Generic.stopShooting(passenger, passenger.Name, weaponGuid) + processFunc(target, passenger.Name, turretGuid, weaponGuid) (target, passenger) } case _ => @@ -415,6 +404,45 @@ trait AutomatedTurretBehavior { } } + /** + * Dispatch packets in the direction of a client perspective + * to determine if this target can be reliably struck with a projectile from the turret's weapon.
+ * For implementing behavior. + * Must be implemented. + * @param target something the turret can potentially shoot at + * @param channel scope of the message + * @param turretGuid turret + * @param weaponGuid turret's weapon + */ + protected def testNewDetected(target: Target, channel: String, turretGuid: PlanetSideGUID, weaponGuid: PlanetSideGUID): Unit + + /** + * Dispatch packets in the direction of a client perspective + * to determine if this target can be reliably struck with a projectile from the turret's weapon.
+ * For implementing behavior. + * Must be implemented. + * @param target something the turret can potentially shoot at + * @param channel scope of the message + * @param turretGuid not used + * @param weaponGuid turret's weapon + */ + protected def testKnownDetected(target: Target, channel: String, turretGuid: PlanetSideGUID, weaponGuid: PlanetSideGUID): Unit + + /** + * na
+ * For overriding behavior. + * @param target something the turret can potentially shoot at + * @param channel scope of the message + * @param turretGuid not used + * @param weaponGuid turret's weapon + */ + protected def suspendTargetTesting( + target: Target, + channel: String, + turretGuid: PlanetSideGUID, + weaponGuid: PlanetSideGUID + ): Unit = { /*do nothing*/ } + /** * Cull all targets that have been detected by this turret at some point * by determining which targets are either destroyed @@ -472,8 +500,8 @@ trait AutomatedTurretBehavior { } .orElse { //no target; unless we are deactivated or have any unfinished delays, search for new target - cancelSelfReportedAutoFire() - currentTargetLocation = None + //cancelSelfReportedAutoFire() + //currentTargetLocation = None if (automaticOperation && now - currentTargetLastShotTime >= 0) { trySelectNewTarget() } @@ -519,9 +547,20 @@ trait AutomatedTurretBehavior { noLongerEngageDetectedTarget(target) currentTargetLastShotTime = now + cooldownDelay None + } + else if ({ + target match { + case mount: Mountable => !mount.Seats.values.exists(_.isOccupied) + case _ => false + } + }) { + //certain targets can go "unresponsive" even though they should still be reachable, otherwise the target is mia + trySelfReportedAutofireIfStationary() + noLongerEngageDetectedTarget(target) + currentTargetLastShotTime = now + selectDelay + None } else if (now - currentTargetLastShotTime >= cooldownDelay) { //if the target goes mia through lack of response - trySelfReportedAutofireIfStationary() //certain targets can go "unresponsive" even though they should still be reachable noLongerEngageDetectedTarget(target) currentTargetLastShotTime = now + selectDelay None @@ -585,7 +624,7 @@ trait AutomatedTurretBehavior { * @see `Default.Cancellable` */ private def cancelPeriodicTargetChecks(): Boolean = { - previouslyTestedTargets = Seq() + ongoingTestedTargets = Seq() periodicValidationTest.cancel() periodicValidationTest = Default.Cancellable true @@ -675,22 +714,37 @@ trait AutomatedTurretBehavior { currentTargetLastShotTime = System.currentTimeMillis() shotsFired += 1 target match { - case v: Damageable with Mountable - if v.Destroyed && v.Seats.values.exists(_.isOccupied) => + case v: Mountable + if v.Destroyed && !v.Seats.values.exists(_.isOccupied) => targetsDestroyed += 1 case _ => () } - if (currentTargetLocation.exists(loc => Vector3.DistanceSquared(loc, target.Position) > 1f)) { - cancelSelfReportedAutoFire() - noLongerEngageDetectedTarget(target) - processForTestingNewDetectedTarget( - target, - AutomatedTurretObject.GUID, - AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID - ) - } else { - tryPerformSelfReportedAutofire(target) - } + AutomatedTurretObject.Target + .collect { oldTarget => + if (currentTargetToken.contains(SourceEntry(oldTarget).unique)) { + //target already being handled + if (oldTarget.Destroyed || currentTargetLocation.exists(loc => Vector3.DistanceSquared(loc, oldTarget.Position) > 1f)) { + //stop (destroyed, or movement disqualification) + cancelSelfReportedAutoFire() + noLongerEngageDetectedTarget(oldTarget) + processForTestingTarget( + oldTarget, + AutomatedTurretObject.GUID, + AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID, + testNewDetected + ) + } + } else { + //stop (wrong target) + cancelSelfReportedAutoFire() + } + } + .orElse { + //start new target + engageNewDetectedTarget(target) + tryPerformSelfReportedAutofire(target) + None + } } /** @@ -707,7 +761,7 @@ trait AutomatedTurretBehavior { AutomatedTurretObject.Target .collect { case target - if currentTargetLocation.exists(loc => Vector3.DistanceSquared(loc, target.Position) > 1f) && + if currentTargetLocation.exists(loc => Vector3.DistanceSquared(loc, target.Position) <= 1f) && autoStats.exists(_.refireTime > 0.seconds) => trySelfReportedAutofireTest(target) } @@ -780,6 +834,7 @@ trait AutomatedTurretBehavior { case p: PlayerSource => val weaponId = AutomatedTurretObject.Weapons.values.head.Equipment.map(_.Definition.ObjectId).getOrElse(0) ToDatabase.reportToolDischarge(p.CharId, EquipmentStat(weaponId, shotsFired, shotsFired, targetsDestroyed, 0)) + selfReportingCleanUp() case _ => () } } @@ -797,81 +852,64 @@ object AutomatedTurretBehavior { private case object PeriodicCheck - final val commonBlanking: List[PlanetSideGameObject => Boolean] = List( + private val commonBlanking: List[PlanetSideGameObject => Boolean] = List( EffectTarget.Validation.AutoTurretBlankPlayerTarget, EffectTarget.Validation.AutoTurretBlankVehicleTarget ) + private val noTargets: List[PlanetSideGUID] = List(Service.defaultPlayerGUID) + /** - * Are we tracking a `Vehicle` entity? - * or, is it some other kind of entity? - * @param target something a turret can potentially shoot at + * Are we tracking a target entity? + * @param zone the region in which the messages will be dispatched * @param channel scope of the message * @param turretGuid turret * @param list target's globally unique identifier, in list form */ - def startTracking(target: Target, channel: String, turretGuid: PlanetSideGUID, list: List[PlanetSideGUID]): Unit = { - target match { - case v: Vehicle => AutomatedTurretDispatch.Vehicle.startTracking(v, channel, turretGuid, list) - case _ => AutomatedTurretDispatch.Generic.startTracking(target, channel, turretGuid, list) - } + def startTracking(zone: Zone, channel: String, turretGuid: PlanetSideGUID, list: List[PlanetSideGUID]): Unit = { + zone.LocalEvents ! LocalServiceMessage( + channel, + LocalAction.SendResponse(ObjectDetectedMessage(turretGuid, turretGuid, 0, list)) + ) } /** - * Are we no longer tracking a `Vehicle` entity? - * or, was it some other kind of entity? - * @param target something a turret can potentially shoot at + * Are we no longer tracking a target entity? + * @param zone the region in which the messages will be dispatched * @param channel scope of the message * @param turretGuid turret */ - def stopTracking(target: Target, channel: String, turretGuid: PlanetSideGUID): Unit = { - target match { - case v: Vehicle => AutomatedTurretDispatch.Vehicle.stopTracking(v, channel, turretGuid) - case _ => AutomatedTurretDispatch.Generic.stopTracking(target, channel, turretGuid) - } + def stopTracking(zone: Zone, channel: String, turretGuid: PlanetSideGUID): Unit = { + zone.LocalEvents ! LocalServiceMessage( + channel, + LocalAction.SendResponse(ObjectDetectedMessage(turretGuid, turretGuid, 0, noTargets)) + ) } /** - * Are we shooting at a `Vehicle` entity? - * or, is it some other kind of entity? - * @param target something a turret can potentially shoot at + * Are we shooting a weapon? + * @param zone the region in which the messages will be dispatched * @param channel scope of the message * @param weaponGuid turret's weapon */ - def startShooting(target: Target, channel: String, weaponGuid: PlanetSideGUID): Unit = { - target match { - case v: Vehicle => AutomatedTurretDispatch.Vehicle.startShooting(v, channel, weaponGuid) - case _ => AutomatedTurretDispatch.Generic.startShooting(target, channel, weaponGuid) - } + def startShooting(zone: Zone, channel: String, weaponGuid: PlanetSideGUID): Unit = { + zone.LocalEvents ! LocalServiceMessage( + channel, + LocalAction.SendResponse(ChangeFireStateMessage_Start(weaponGuid)) + ) } /** - * Are we no longer shooting at a `Vehicle` entity? - * or, was it some other kind of entity? - * @param target something a turret can potentially shoot at + * Are we no longer shooting a weapon? + * @param zone the region in which the messages will be dispatched * @param channel scope of the message * @param weaponGuid turret's weapon */ - def stopShooting(target: Target, channel: String, weaponGuid: PlanetSideGUID): Unit = { - target match { - case v: Vehicle => AutomatedTurretDispatch.Vehicle.stopShooting(v, channel, weaponGuid) - case _ => AutomatedTurretDispatch.Generic.stopShooting(target, channel, weaponGuid) - } - } - - /** - * Will we be shooting at a `Vehicle` entity? - * or, will it be some other kind of entity? - * @param target something a turret can potentially shoot at - * @param channel scope of the message - * @param turretGuid turret - * @param weaponGuid turret's weapon - */ - def testNewDetected(target: Target, channel: String, turretGuid: PlanetSideGUID, weaponGuid: PlanetSideGUID): Unit = { - target match { - case v: Vehicle => AutomatedTurretDispatch.Vehicle.testNewDetected(v, channel, turretGuid, weaponGuid) - case _ => AutomatedTurretDispatch.Generic.testNewDetected(target, channel, turretGuid, weaponGuid) - } + def stopShooting(zone: Zone, channel: String, weaponGuid: PlanetSideGUID): Unit = { + zone.LocalEvents ! LocalServiceMessage( + channel, + LocalAction.SendResponse(ChangeFireStateMessage_Stop(weaponGuid)) + ) } /** @@ -932,7 +970,7 @@ object AutomatedTurretBehavior { * `foo.compareTo(bar)`, * where "foo" is calculated using `Vector3.DistanceSquared` or the absolute value of the vertical distance, * and "bar" is `range`-squared - * @return + * @return if the actual result of the comparison matches its anticipation `result` */ def shapedDistanceCheckAgainstValue( stats: Option[Automation], diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretDispatch.scala b/src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretDispatch.scala deleted file mode 100644 index 501cd7672..000000000 --- a/src/main/scala/net/psforever/objects/serverobject/turret/auto/AutomatedTurretDispatch.scala +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright (c) 2024 PSForever -package net.psforever.objects.serverobject.turret.auto - -import akka.actor.ActorRef -import net.psforever.objects.serverobject.turret.auto.AutomatedTurret.Target -import net.psforever.packet.PlanetSideGamePacket -import net.psforever.packet.game.{ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ObjectDetectedMessage} -import net.psforever.services.Service -import net.psforever.services.local.{LocalAction, LocalServiceMessage} -import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} -import net.psforever.types.PlanetSideGUID - -/** - * Dispatch messages from an `AutomatedTurret` entity's control agency - * with respects to the kind of entity which is the target. - * The main sticking point is that the message bus destination matches the type of message envelope. - * The packet messages utilized are the same either way and are tied to the action rather than the transmission process. - * @see `ChangeFireStateMessage_Start` - * @see `ChangeFireStateMessage_Stop` - * @see `ObjectDetectedMessage` - * @see `PlanetSideGamePacket` - * @see `Zone` - */ -trait AutomatedTurretDispatch { - /** - * The event bus should be accessible from the target's knowledge of their zone. - * @param target something the turret can potentially shoot at - * @return event bus to use - */ - def getEventBus(target: Target): ActorRef - - /** - * The event bus should be accessible from the target's knowledge of their zone. - * @param channel the scope of the message transmission - * @param msg the packet to be dispatched - * @return messaging envelope to use - */ - def composeMessageEnvelope(channel: String, msg: PlanetSideGamePacket): Any - - /** - * Are we tracking an entity? - */ - def startTracking(target: Target, channel: String, turretGuid: PlanetSideGUID, list: List[PlanetSideGUID]): Unit = { - getEventBus(target) ! composeMessageEnvelope(channel, startTrackingMsg(turretGuid, list)) - } - - /** - * Are we no longer tracking an entity? - */ - def stopTracking(target: Target, channel: String, turretGuid: PlanetSideGUID): Unit = { - getEventBus(target) ! composeMessageEnvelope(channel, stopTrackingMsg(turretGuid)) - } - - /** - * Are we shooting at an entity? - */ - def startShooting(target: Target, channel: String, weaponGuid: PlanetSideGUID): Unit = { - getEventBus(target) ! composeMessageEnvelope(channel, startShootingMsg(weaponGuid)) - } - - /** - * Are we no longer shooting at an entity? - */ - def stopShooting(target: Target, channel: String, weaponGuid: PlanetSideGUID): Unit = { - getEventBus(target) ! composeMessageEnvelope(channel, stopShootingMsg(weaponGuid)) - } - - /** - * Will we be shooting at an entity? - */ - def testNewDetected(target: Target, channel: String, turretGuid: PlanetSideGUID, weaponGuid: PlanetSideGUID): Unit = { - startTracking(target, channel, turretGuid, List(target.GUID)) - startShooting(target, channel, weaponGuid) - stopShooting(target, channel, weaponGuid) - } - - private def startTrackingMsg(guid: PlanetSideGUID, list: List[PlanetSideGUID]): PlanetSideGamePacket = { - ObjectDetectedMessage(guid, guid, 0, list) - } - - private def stopTrackingMsg(turretGuid: PlanetSideGUID): PlanetSideGamePacket = { - ObjectDetectedMessage(turretGuid, turretGuid, 0, AutomatedTurretDispatch.noTargets) - } - - private def startShootingMsg(weaponGuid: PlanetSideGUID): PlanetSideGamePacket = { - ChangeFireStateMessage_Start(weaponGuid) - } - - private def stopShootingMsg(weaponGuid: PlanetSideGUID): PlanetSideGamePacket = { - ChangeFireStateMessage_Stop(weaponGuid) - } -} - -object AutomatedTurretDispatch { - private val noTargets: List[PlanetSideGUID] = List(Service.defaultPlayerGUID) - - object Generic extends AutomatedTurretDispatch { - def getEventBus(target: Target): ActorRef = { - target.Zone.LocalEvents - } - - def composeMessageEnvelope(channel: String, msg: PlanetSideGamePacket): Any = { - LocalServiceMessage(channel, LocalAction.SendResponse(msg)) - } - } - - object Vehicle extends AutomatedTurretDispatch { - def getEventBus(target: Target): ActorRef = { - target.Zone.VehicleEvents - } - - def composeMessageEnvelope(channel: String, msg: PlanetSideGamePacket): Any = { - VehicleServiceMessage(channel, VehicleAction.SendResponse(Service.defaultPlayerGUID, msg)) - } - } -}