diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index e12457e06..217347880 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -3,7 +3,7 @@ package net.psforever.actors.session.support import akka.actor.{ActorContext, typed} import net.psforever.objects.zones.Zoning -import net.psforever.objects.serverobject.turret.AutomatedTurret +import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior} import net.psforever.objects.zones.exp.ToDatabase import scala.collection.mutable @@ -432,44 +432,52 @@ private[support] class WeaponAndProjectileOperations( } def handleAIDamage(pkt: AIDamage): Unit = { - val AIDamage(_, attackerGuid, projectileTypeId, _, _) = pkt - sessionData.validObject(attackerGuid, decorator = "AIDamage/Entity") - .collect { - case turret: AutomatedTurret with OwnableByPlayer => - val owner = sessionData.validObject(turret.OwnerGuid, decorator = "AIDamage/Owner") - .collect { case obj: PlanetSideGameObject with FactionAffinity => SourceEntry(obj) } - .getOrElse { SourceEntry.None } - turret.Weapons - .values - .flatMap { _.Equipment } - .collect { case weapon: Tool => (turret, weapon, owner, weapon.Projectile) } - .find { case (_, _, _, p) => p.ObjectId == projectileTypeId } + val AIDamage(targetGuid, attackerGuid, projectileTypeId, _, _) = pkt + (continent.GUID(player.VehicleSeated) match { + case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality) if tobj.GUID == targetGuid => Some(tobj) + case _ if player.GUID == targetGuid => Some(player) + case _ => None + }).foreach { target => + sessionData.validObject(attackerGuid, decorator = "AIDamage/Attacker") + .collect { + case turret: AutomatedTurret if turret.Target.isEmpty => + turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target) + None - } - .collect { - case Some((obj, tool, owner, projectileInfo)) => - val target = sessionData.validObject(player.VehicleSeated, decorator = "AIDamage/Entity") match { - case Some(tobj: PlanetSideGameObject with FactionAffinity with Vitality) => tobj - case _ => player - } - val angle = Vector3.Unit(target.Position - obj.Position) - val proj = new Projectile( - projectileInfo, - tool.Definition, - tool.FireMode, - None, - owner, - obj.Definition.ObjectId, - obj.Position + Vector3.z(value = 1f), - angle, - Some(angle * projectileInfo.FinalVelocity) - ) - val hitPos = target.Position + Vector3.z(value = 1f) - ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile => - addShotsLanded(resprojectile.cause.attribution, shots = 1) - sessionData.handleDealingDamage(target, resprojectile) - } - } + case turret: AutomatedTurret with OwnableByPlayer => + turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target) + val owner = continent.GUID(turret.OwnerGuid) + .collect { case obj: PlanetSideGameObject with FactionAffinity => SourceEntry(obj) } + .getOrElse { SourceEntry.None } + turret.Weapons + .values + .flatMap { _.Equipment } + .collect { case weapon: Tool => (turret, weapon, owner, weapon.Projectile) } + .find { case (_, _, _, p) => p.ObjectId == projectileTypeId } + + } + .collect { + case Some((obj, tool, owner, projectileInfo)) => + + val angle = Vector3.Unit(target.Position - obj.Position) + val proj = new Projectile( + projectileInfo, + tool.Definition, + tool.FireMode, + None, + owner, + obj.Definition.ObjectId, + obj.Position + Vector3.z(value = 1f), + angle, + Some(angle * projectileInfo.FinalVelocity) + ) + val hitPos = target.Position + Vector3.z(value = 1f) + ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile => + addShotsLanded(resprojectile.cause.attribution, shots = 1) + //sessionData.handleDealingDamage(target, resprojectile) + } + } + } } /* support code */ diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala index 923e52532..c74a83925 100644 --- a/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import akka.actor.{Actor, ActorContext, Props} +import akka.actor.{Actor, ActorContext, ActorRef, Props} import net.psforever.objects.ce.{Deployable, DeployableBehavior, DeployedItem} import net.psforever.objects.definition.DeployableDefinition import net.psforever.objects.definition.converter.SmallTurretConverter @@ -17,7 +17,8 @@ import net.psforever.objects.serverobject.repair.RepairableWeaponTurret import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior, TurretDefinition, WeaponTurret} import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.interaction.DamageResult -import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance} +import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance, Vitality} +import net.psforever.objects.zones.Zone import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import scala.concurrent.duration.FiniteDuration @@ -28,7 +29,7 @@ class TurretDeployable(tdef: TurretDeployableDefinition) with JammableUnit with Hackable with AutomatedTurret { - WeaponTurret.LoadDefinition(this) + WeaponTurret.LoadDefinition(turret = this) override def Definition: TurretDeployableDefinition = tdef } @@ -81,6 +82,7 @@ class TurretControl(turret: TurretDeployable) super.postStop() deployableBehaviorPostStop() damageableWeaponTurretPostStop() + automaticTurretPostStop() } def receive: Receive = @@ -103,7 +105,42 @@ class TurretControl(turret: TurretDeployable) (!turret.Definition.FactionLocked || player.Faction == obj.Faction) && !obj.Destroyed } + override def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = { + val startsUnjammed = !JammableObject.Jammed + super.TryJammerEffectActivate(target, cause) + if (startsUnjammed && JammableObject.Jammed) { + AutomaticOperation = false + //look in direction of source of jamming + val zone = JammableObject.Zone + TurretControl.getAttackerFromCause(zone, cause).foreach { + attacker => + val channel = zone.id + val guid = AutomatedTurretObject.GUID + AutomatedTurretBehavior.startTrackingTargets(zone, channel, guid, List(attacker.GUID)) + AutomatedTurretBehavior.stopTrackingTargets(zone, channel, guid) //TODO delay by a few milliseconds? + } + } + } + + override def CancelJammeredStatus(target: Any): Unit = { + val startsJammed = JammableObject.Jammed + super.CancelJammeredStatus(target) + startsJammed && AutomaticOperation_=(state = true) + } + + override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any): Unit = { + //turret retribution + if (AutomaticOperation) { + TurretControl.getAttackerFromCause(target.Zone, cause).foreach { + attacker => + engageNewDetectedTarget(attacker) + } + } + super.DamageAwareness(target, cause, amount) + } + override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = { + AutomaticOperation = false super.DestructionAwareness(target, cause) CancelJammeredSound(target) CancelJammeredStatus(target) @@ -111,6 +148,7 @@ class TurretControl(turret: TurretDeployable) } override def deconstructDeployable(time: Option[FiniteDuration]) : Unit = { + AutomaticOperation = false val zone = turret.Zone val seats = turret.Seats.values //either we have no seats or no one gets to sit @@ -136,8 +174,50 @@ class TurretControl(turret: TurretDeployable) super.deconstructDeployable(retime) } + override def finalizeDeployable(callback: ActorRef): Unit = { + super.finalizeDeployable(callback) + AutomaticOperation = true + } + override def unregisterDeployable(obj: Deployable): Unit = { val zone = obj.Zone TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(zone.GUID, turret)) } } + +object TurretControl { + private def getAttackerFromCause(zone: Zone, cause: DamageResult): Option[PlanetSideServerObject with Vitality] = { + import net.psforever.objects.sourcing._ + cause + .interaction + .adversarial + .collect { adversarial => + adversarial.attacker match { + case p: PlayerSource => + p.seatedIn + .map { _._1.unique } + .collect { + case v: UniqueVehicle => zone.Vehicles.find(SourceEntry(_).unique == v) + case a: UniqueAmenity => zone.GUID(a.guid) + case d: UniqueDeployable => zone.DeployableList.find(SourceEntry(_).unique == d) + } + .flatten + .orElse { + val name = p.Name + zone.LivePlayers.find(_.Name.equals(name)) + } + case o => + o.unique match { + case v: UniqueVehicle => zone.Vehicles.find(SourceEntry(_).unique == v) + case a: UniqueAmenity => zone.GUID(a.guid) + case d: UniqueDeployable => zone.DeployableList.find(SourceEntry(_).unique == d) + case _ => None + } + } + } + .flatten + .collect { + case out: PlanetSideServerObject with Vitality => out + } + } +} diff --git a/src/main/scala/net/psforever/objects/ce/InteractsWithTurrets.scala b/src/main/scala/net/psforever/objects/ce/InteractsWithTurrets.scala index dea206d7f..f1794cc2e 100644 --- a/src/main/scala/net/psforever/objects/ce/InteractsWithTurrets.scala +++ b/src/main/scala/net/psforever/objects/ce/InteractsWithTurrets.scala @@ -56,7 +56,7 @@ class InteractWithTurrets(val range: Float) target.getInteractionSector(), target.Position.xy ).foreach { turret => - turret.Actor ! AutomatedTurretBehavior.ResetAlerts + turret.Actor ! AutomatedTurretBehavior.Reset } } } diff --git a/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala b/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala index ac33cae0d..84443d560 100644 --- a/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala +++ b/src/main/scala/net/psforever/objects/equipment/JammingUnit.scala @@ -139,7 +139,7 @@ trait JammableBehavior { * @param target the objects to be determined if affected by the source's jammering * @param cause the source of the "jammered" status */ - def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = + def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = { target match { case obj: PlanetSideServerObject => val interaction = cause.interaction @@ -157,8 +157,9 @@ trait JammableBehavior { } case None => } - case _ => ; + case _ => () } + } /** * Activate a distinctive buzzing sound effect. diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/AutomatedTurretBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/turret/AutomatedTurretBehavior.scala index 586d22ee5..1817111e7 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/AutomatedTurretBehavior.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/AutomatedTurretBehavior.scala @@ -5,14 +5,14 @@ import akka.actor.{Actor, Cancellable} import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.{Default, Player} import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.serverobject.damage.DamageableEntity import net.psforever.objects.sourcing.{SourceEntry, SourceUniqueness} import net.psforever.objects.vital.Vitality import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ObjectDetectedMessage} -import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} +import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.types.{PlanetSideGUID, Vector3} -import scala.collection.mutable import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global @@ -20,8 +20,23 @@ trait AutomatedTurret extends PlanetSideServerObject with WeaponTurret { import AutomatedTurret.Target + private var currentTarget: Option[Target] = None + private var targets: List[Target] = List[Target]() + def Target: Option[Target] = currentTarget + + def Target_=(newTarget: Target): Option[Target] = { + Target_=(Some(newTarget)) + } + + def Target_=(newTarget: Option[Target]): Option[Target] = { + if (newTarget.isDefined != currentTarget.isDefined) { + currentTarget = newTarget + } + currentTarget + } + def Targets: List[Target] = targets def Detected(target: Target): Option[Target] = { @@ -56,125 +71,212 @@ object AutomatedTurret { } trait AutomatedTurretBehavior { - _: Actor => + _: Actor with DamageableEntity => import AutomatedTurret.Target - import AutomatedTurretBehavior.TurretTargetEntry - private val targets: mutable.HashMap[SourceUniqueness, TurretTargetEntry] = - mutable.HashMap[SourceUniqueness, TurretTargetEntry]() + private var automaticOperation: Boolean = false - private var targetDistanceCheck: Cancellable = Default.Cancellable + private var currentTargetToken: Option[SourceUniqueness] = None + + private var currentTarget: Option[Target] = None + + private var currentTargetLastShotReported: Long = 0L + + private var periodicValidationTest: Cancellable = Default.Cancellable def AutomatedTurretObject: AutomatedTurret val automatedTurretBehavior: Actor.Receive = { case AutomatedTurretBehavior.Alert(target) => - val targets = AutomatedTurretObject.Targets - val size = targets.size - AutomatedTurretObject.Detected(target) - .orElse { - AutomatedTurretObject.AddTarget(target) - retimeDistanceCheck(size) - Some(target) - } - .foreach { newDetectedTarget } + bringAttentionToTarget(target) + + case AutomatedTurretBehavior.ConfirmShot(target) => + confirmShot(target) case AutomatedTurretBehavior.Unalert(target) => - val targets = AutomatedTurretObject.Targets - val size = targets.size - AutomatedTurretObject.Detected(target) - .collect { out => - AutomatedTurretObject.RemoveTarget(target) - testDistanceCheckQualifications(size) - out + disregardTarget(target) + + case AutomatedTurretBehavior.Reset => + resetAlerts() + + case AutomatedTurretBehavior.PeriodicCheck => + performPeriodicTargetValidation() + } + + def AutomaticOperation: Boolean = automaticOperation + + def AutomaticOperation_=(state: Boolean): Boolean = { + val previousState = automaticOperation + automaticOperation = state + if (!previousState && state) { + trySelectNewTarget() + } else if (previousState && !state) { + currentTarget.foreach { noLongerEngageDetectedTarget } + } + state + } + + private def bringAttentionToTarget(target: Target): Unit = { + val targets = AutomatedTurretObject.Targets + val size = targets.size + AutomatedTurretObject.Detected(target) + .orElse { + AutomatedTurretObject.AddTarget(target) + retimePeriodicTargetChecks(size) + Some(target) + } + } + + private def confirmShot(target: Target): Unit = { + val now = System.currentTimeMillis() + if (currentTargetToken.isEmpty || now - currentTargetLastShotReported > 1500L) { + currentTargetLastShotReported = now + engageNewDetectedTarget(target) + } else if (currentTargetToken.contains(SourceEntry(target).unique) && now - currentTargetLastShotReported < 3000L) { + currentTargetLastShotReported = now + } + } + + private def disregardTarget(target: Target): Unit = { + val targets = AutomatedTurretObject.Targets + val size = targets.size + AutomatedTurretObject.Detected(target) + .collect { out => + AutomatedTurretObject.RemoveTarget(target) + testTargetListQualifications(size) + out + } + .flatMap { + noLongerDetectTargetIfCurrent + } + } + + private def resetAlerts(): Unit = { + currentTarget.foreach { noLongerEngageDetectedTarget } + AutomatedTurretObject.Clear() + testTargetListQualifications(beforeSize = 1) + } + + private def testNewDetectedTarget(target: Target, channel: String): Unit = { + val zone = target.Zone + AutomatedTurretBehavior.startTrackingTargets(zone, channel, AutomatedTurretObject.GUID, List(target.GUID)) + AutomatedTurretBehavior.startShooting(zone, channel, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID) + AutomatedTurretBehavior.stopShooting(zone, channel, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID) + } + + protected def engageNewDetectedTarget(target: Target): Unit = { + val zone = target.Zone + val zoneid = zone.id + AutomatedTurretObject.Target = target + currentTarget = Some(target) + currentTargetToken = Some(SourceEntry(target).unique) + AutomatedTurretBehavior.startTrackingTargets(zone, zoneid, AutomatedTurretObject.GUID, List(target.GUID)) + AutomatedTurretBehavior.startShooting(zone, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID) + } + + protected def noLongerDetectTargetIfCurrent(target: Target): Option[Target] = { + if (currentTargetToken.contains(SourceEntry(target).unique)) { + noLongerEngageDetectedTarget(target) + } else { + currentTarget + } + } + + protected def noLongerEngageDetectedTarget(target: Target): Option[Target] = { + val zone = target.Zone + val zoneid = zone.id + AutomatedTurretObject.Target = None + currentTarget = None + currentTargetToken = None + AutomatedTurretBehavior.stopTrackingTargets(zone, zoneid, AutomatedTurretObject.GUID) + AutomatedTurretBehavior.stopShooting(zone, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID) + None + } + + private def trySelectNewTarget(): Option[Target] = { + currentTarget.orElse { + val turretPosition = AutomatedTurretObject.Position + AutomatedTurretObject.Targets + .filter { target => + !target.Destroyed && (target match { + case p: Player => validTargetCheck(p) + case _ => false + }) } - .foreach { noLongerDetectedTarget } - - case AutomatedTurretBehavior.ResetAlerts => - AutomatedTurretObject.Clear().foreach { noLongerDetectedTarget } - testDistanceCheckQualifications(beforeSize = 1) - - case AutomatedTurretBehavior.PeriodicDistanceCheck => - performPeriodicDistanceCheck() - } - - private def newDetectedTarget(target: Target): Unit = { - target match { - case target: Player => - startTrackingTargets(target.Zone, target.Name, List(target.GUID)) - startShooting(target.Zone, target.Name, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID) - case _ => () + .sortBy { + target => Vector3.DistanceSquared(target.Position, turretPosition) + } + .flatMap { case target: Player => + testNewDetectedTarget(target, target.Name) + Some(target) + } + .headOption } } - private def noLongerDetectedTarget(target: Target): Unit = { - target match { - case target: Player => - stopTrackingTargets(target.Zone, target.Name) - stopShooting(target.Zone, target.Name, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID) - case _ => () - } + private def validTargetCheck(target: Target): Boolean = { + !target.Destroyed && (target match { + case p: Player => + if (p.Cloaked) false + else if (p.Crouching) false + else p.isMoving(test = 3f) + case _ => false + }) } - def startTrackingTargets(zone: Zone, channel: String, list: List[PlanetSideGUID]): Unit = { - zone.AvatarEvents ! AvatarServiceMessage( - channel, - AvatarAction.SendResponse(PlanetSideGUID(0), ObjectDetectedMessage(AutomatedTurretObject.GUID, AutomatedTurretObject.GUID, 0, list)) - ) + private def performPeriodicTargetValidation(): List[Target] = { + val size = AutomatedTurretObject.Targets.size + val list = performDistanceCheck() + performCurrentTargetDecayCheck() + testTargetListQualifications(size) + list } - def stopTrackingTargets(zone: Zone, channel: String): Unit = { - zone.AvatarEvents ! AvatarServiceMessage( - channel, - AvatarAction.SendResponse(PlanetSideGUID(0), ObjectDetectedMessage(AutomatedTurretObject.GUID, AutomatedTurretObject.GUID, 0, List(PlanetSideGUID(0)))) - ) - } - - def startShooting(zone: Zone, channel: String, weaponGuid: PlanetSideGUID): Unit = { - zone.AvatarEvents ! AvatarServiceMessage( - channel, - AvatarAction.SendResponse(PlanetSideGUID(0), ChangeFireStateMessage_Start(weaponGuid)) - ) - } - - def stopShooting(zone: Zone, channel: String, weaponGuid: PlanetSideGUID): Unit = { - zone.AvatarEvents ! AvatarServiceMessage( - channel, - AvatarAction.SendResponse(PlanetSideGUID(0), ChangeFireStateMessage_Stop(weaponGuid)) - ) - } - - private def testDistanceCheckQualifications(beforeSize: Int): Unit = { - if (beforeSize > 0 && AutomatedTurretObject.Targets.isEmpty) { - targetDistanceCheck.cancel() - } - } - - private def retimeDistanceCheck(beforeSize: Int): Unit = { - if (beforeSize == 0 && AutomatedTurretObject.Targets.nonEmpty) { - targetDistanceCheck = context.system.scheduler.scheduleAtFixedRate( - 1.second, - 1.second, - self, - AutomatedTurretBehavior.PeriodicDistanceCheck - ) - } - } - - private def performPeriodicDistanceCheck(): List[Target] = { + private def performDistanceCheck(): List[Target] = { + //cull targets val pos = AutomatedTurretObject.Position - val earlyTargets = AutomatedTurretObject.Targets - val earlySize = earlyTargets.size - val removedTargets = earlyTargets + val removedTargets = AutomatedTurretObject.Targets .collect { - case t if Vector3.DistanceSquared(t.Position, pos) > 625 => + case t if t.Destroyed || Vector3.DistanceSquared(t.Position, pos) > 625 => AutomatedTurretObject.RemoveTarget(t) t } - removedTargets.foreach { noLongerDetectedTarget } - testDistanceCheckQualifications(earlySize) removedTargets } + + private def testTargetListQualifications(beforeSize: Int): Boolean = { + beforeSize > 0 && AutomatedTurretObject.Targets.isEmpty && periodicValidationTest.cancel() + } + + private def retimePeriodicTargetChecks(beforeSize: Int): Boolean = { + if (beforeSize == 0 && AutomatedTurretObject.Targets.nonEmpty) { + periodicValidationTest = context.system.scheduler.scheduleWithFixedDelay( + Duration.Zero, + 1.second, + self, + AutomatedTurretBehavior.PeriodicCheck + ) + true + } else { + false + } + } + + private def performCurrentTargetDecayCheck(): Unit = { + //complete culling and/or check the current selected target + if (System.currentTimeMillis() - currentTargetLastShotReported > 3000L) { + currentTarget.foreach { noLongerEngageDetectedTarget } + if (automaticOperation) { + //trySelectNewTarget() + } + } + } + + def automaticTurretPostStop(): Unit = { + periodicValidationTest.cancel() + currentTarget.foreach { noLongerEngageDetectedTarget } + AutomatedTurretObject.Targets.foreach { AutomatedTurretObject.RemoveTarget } + } } object AutomatedTurretBehavior { @@ -183,20 +285,37 @@ object AutomatedTurretBehavior { final case class Unalert(target: Target) - final case object ResetAlerts + final case class ConfirmShot(target: Target) - private case object PeriodicDistanceCheck + final case object Reset - final case class TurretTargetEntry(target: Target, regard: RegardTargetAs.TurretOpinion) + private case object PeriodicCheck - object RegardTargetAs { - trait TurretOpinion + def startTrackingTargets(zone: Zone, channel: String, guid: PlanetSideGUID, list: List[PlanetSideGUID]): Unit = { + zone.LocalEvents ! LocalServiceMessage( + channel, + LocalAction.SendResponse(ObjectDetectedMessage(guid, guid, 0, list)) + ) + } - final case object Friendly extends TurretOpinion - final case object Testing extends TurretOpinion - final case object Unreachable extends TurretOpinion - final case object Blocked extends TurretOpinion - final case object Invalid extends TurretOpinion - final case object Attack extends TurretOpinion + def stopTrackingTargets(zone: Zone, channel: String, guid: PlanetSideGUID): Unit = { + zone.LocalEvents ! LocalServiceMessage( + channel, + LocalAction.SendResponse(ObjectDetectedMessage(guid, guid, 0, List(PlanetSideGUID(0)))) + ) + } + + private def startShooting(zone: Zone, channel: String, weaponGuid: PlanetSideGUID): Unit = { + zone.LocalEvents ! LocalServiceMessage( + channel, + LocalAction.SendResponse(ChangeFireStateMessage_Start(weaponGuid)) + ) + } + + private def stopShooting(zone: Zone, channel: String, weaponGuid: PlanetSideGUID): Unit = { + zone.LocalEvents ! LocalServiceMessage( + channel, + LocalAction.SendResponse(ChangeFireStateMessage_Stop(weaponGuid)) + ) } }