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

This commit is contained in:
Fate-JH 2024-02-05 18:43:47 -05:00
parent c97732dfe9
commit 92096a01ed
8 changed files with 425 additions and 367 deletions

View file

@ -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
}
}

View file

@ -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
)

View file

@ -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 = {

View file

@ -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
}

View file

@ -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 }
}
}
}

View file

@ -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

View file

@ -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.<br>
* 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.<br>
* 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.<br>
* 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.<br>
* 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<br>
* 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],

View file

@ -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))
}
}
}