From 20b5de34ab1d3a866ba9161e8962e8fa3d1b88bb Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Thu, 4 Jan 2024 01:51:16 -0500 Subject: [PATCH] adjust mounting code to betterhandle automation with the facility turrets; basic operation of automation has also been changed, adding a variety of ranges to test against, and cylindrical distance checks --- .../WeaponAndProjectileOperations.scala | 28 ++- .../psforever/objects/GlobalDefinitions.scala | 32 +++- .../scala/net/psforever/objects/Player.scala | 2 +- .../psforever/objects/TurretDeployable.scala | 21 +-- .../scala/net/psforever/objects/Vehicle.scala | 3 +- .../objects/ce/InteractWithTurrets.scala | 34 +++- .../objects/equipment/EffectTarget.scala | 19 +- .../mount/MountableBehavior.scala | 4 +- .../turret/AutomatedTurretBehavior.scala | 168 ++++++++++++------ .../turret/FacilityTurretControl.scala | 60 +++---- .../turret/MountableTurretControl.scala | 8 +- .../turret/TurretDefinition.scala | 11 +- .../turret/VanuSentryControl.scala | 17 +- 13 files changed, 271 insertions(+), 136 deletions(-) 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 101c3445f..6e9f7df92 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -2,6 +2,8 @@ package net.psforever.actors.session.support import akka.actor.{ActorContext, typed} +import net.psforever.objects.definition.ProjectileDefinition +import net.psforever.objects.serverobject.structures.Amenity import net.psforever.objects.zones.Zoning import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior, VanuSentry} import net.psforever.objects.zones.exp.ToDatabase @@ -443,21 +445,19 @@ private[support] class WeaponAndProjectileOperations( turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target) None - case turret: AutomatedTurret with OwnableByPlayer => + case turret: AutomatedTurret with OwnableByPlayer => //most likely a deployable 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 } + .getOrElse(SourceEntry(turret)) + CompileAutomatedTurretDamageData(turret, owner, projectileTypeId) + case turret: Amenity with AutomatedTurret => //most likely a facility turret + turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target) + CompileAutomatedTurretDamageData(turret, SourceEntry(turret.Owner), projectileTypeId) } .collect { case Some((obj, tool, owner, projectileInfo)) => - val angle = Vector3.Unit(target.Position - obj.Position) val proj = new Projectile( projectileInfo, @@ -1458,6 +1458,18 @@ private[support] class WeaponAndProjectileOperations( ToDatabase.reportToolDischarge(avatarId, EquipmentStat(weaponId, fired, landed, 0, 0)) } + private def CompileAutomatedTurretDamageData( + turret: AutomatedTurret, + owner: SourceEntry, + projectileTypeId: Long + ): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = { + turret.Weapons + .values + .flatMap { _.Equipment } + .collect { case weapon: Tool => (turret, weapon, owner, weapon.Projectile) } + .find { case (_, _, _, p) => p.ObjectId == projectileTypeId } + } + override protected[session] def stop(): Unit = { if (player != null && player.HasGUID) { (prefire ++ shooting).foreach { guid => diff --git a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 398d2fcb0..745a40bc3 100644 --- a/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -9074,8 +9074,10 @@ object GlobalDefinitions { spitfire_turret.Model = ComplexDeployableResolutions.calculate spitfire_turret.deployAnimation = DeployAnimation.Standard spitfire_turret.AutoFire = Automation( - targetingRange = 40f, - targetValidation = List(EffectTarget.Validation.ObviousPlayer, EffectTarget.Validation.Vehicle), + targetDetectionRange = 60f, + targetTriggerRange = 50f, + targetEscapeRange = 50f, + targetValidation = List(EffectTarget.Validation.PlayerOnRadar, EffectTarget.Validation.Vehicle), retaliatoryDuration = 8000L ) spitfire_turret.innateDamage = new DamageWithPosition { @@ -9105,8 +9107,10 @@ object GlobalDefinitions { spitfire_cloaked.deployAnimation = DeployAnimation.Standard spitfire_cloaked.Model = ComplexDeployableResolutions.calculate spitfire_cloaked.AutoFire = Automation( - targetingRange = 40f, - targetValidation = List(EffectTarget.Validation.ObviousPlayer, EffectTarget.Validation.Vehicle), + targetDetectionRange = 50f, + targetTriggerRange = 30f, + targetEscapeRange = 50f, + targetValidation = List(EffectTarget.Validation.PlayerOnRadar, EffectTarget.Validation.VehiclesOnRadar), retaliatoryDuration = 8000L ) spitfire_cloaked.innateDamage = new DamageWithPosition { @@ -9136,9 +9140,14 @@ object GlobalDefinitions { spitfire_aa.deployAnimation = DeployAnimation.Standard spitfire_aa.Model = ComplexDeployableResolutions.calculate spitfire_aa.AutoFire = Automation( - targetingRange = 80f, - targetValidation = List(EffectTarget.Validation.Aircraft), - retaliatoryDuration = 8000L + targetDetectionRange = 100f, + targetTriggerRange = 90f, + targetEscapeRange = 200f, + targetValidation = List(EffectTarget.Validation.AircraftOnRadar), + retaliatoryDuration = 2000L, + retaliationOverridesTarget = false, + cylindricalCheck = true, + cylindricalHeight = 25f ) spitfire_aa.innateDamage = new DamageWithPosition { CausesDamageType = DamageType.One @@ -10041,6 +10050,15 @@ object GlobalDefinitions { manned_turret.FactionLocked = true manned_turret.ReserveAmmunition = false manned_turret.RadiationShielding = 0.5f + manned_turret.AutoFire = Automation( + targetDetectionRange = 100f, + targetTriggerRange = 90f, + targetEscapeRange = 200f, + targetValidation = List(EffectTarget.Validation.MaxOnRadar, EffectTarget.Validation.VehiclesOnRadar), + retaliatoryDuration = 8000L, + cylindricalCheck = true, + cylindricalHeight = 25f + ) manned_turret.innateDamage = new DamageWithPosition { CausesDamageType = DamageType.One Damage0 = 150 diff --git a/src/main/scala/net/psforever/objects/Player.scala b/src/main/scala/net/psforever/objects/Player.scala index 73e6ee266..52b65fcc0 100644 --- a/src/main/scala/net/psforever/objects/Player.scala +++ b/src/main/scala/net/psforever/objects/Player.scala @@ -38,7 +38,7 @@ class Player(var avatar: Avatar) with MountableEntity { interaction(new InteractWithEnvironment()) interaction(new InteractWithMinesUnlessSpectating(obj = this, range = 10)) - interaction(new InteractWithTurrets(range = 100f)) + interaction(new InteractWithTurrets()) interaction(new InteractWithRadiationClouds(range = 10f, Some(this))) private var backpack: Boolean = false diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala index af6bcd069..75289bc48 100644 --- a/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -89,6 +89,8 @@ class TurretControl(turret: TurretDeployable) case _ => () } + protected def AutomaticOperationFunctionalityChecks: Boolean = true + override protected def mountTest( obj: PlanetSideServerObject with Mountable, seatNumber: Int, @@ -120,13 +122,7 @@ class TurretControl(turret: TurretDeployable) } override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any): Unit = { - if (AutomaticOperation && AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDuration > 0)) { - //turret retribution - AutomatedTurretBehavior.getAttackerFromCause(target.Zone, cause).collect { - case attacker if attacker.Faction != target.Faction => - engageNewDetectedTarget(attacker) - } - } + attemptRetaliation(target, cause) super.DamageAwareness(target, cause, amount) } @@ -147,15 +143,14 @@ class TurretControl(turret: TurretDeployable) //it's possible to request deconstruction of one's own field turret while seated in it val wasKickedByDriver = false seats.foreach { seat => - seat.occupant match { - case Some(tplayer) => - seat.unmount(tplayer) - tplayer.VehicleSeated = None + seat.occupant.collect { + case player: Player => + seat.unmount(player) + player.VehicleSeated = None zone.VehicleEvents ! VehicleServiceMessage( zone.id, - VehicleAction.KickPassenger(tplayer.GUID, 4, wasKickedByDriver, turret.GUID) + VehicleAction.KickPassenger(player.GUID, 4, wasKickedByDriver, turret.GUID) ) - case None => () } } Some(time.getOrElse(Deployable.cleanup) + Deployable.cleanup) diff --git a/src/main/scala/net/psforever/objects/Vehicle.scala b/src/main/scala/net/psforever/objects/Vehicle.scala index 962e19cff..d6bbd19d1 100644 --- a/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/src/main/scala/net/psforever/objects/Vehicle.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects -import net.psforever.objects.ce.InteractWithMines +import net.psforever.objects.ce.{InteractWithMines, InteractWithTurrets} import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition} import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile} @@ -92,6 +92,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition) with MountableEntity { interaction(new InteractWithEnvironment()) interaction(new InteractWithMines(range = 20)) + interaction(new InteractWithTurrets()) interaction(new InteractWithRadiationCloudsSeatedInVehicle(obj = this, range = 20)) private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL diff --git a/src/main/scala/net/psforever/objects/ce/InteractWithTurrets.scala b/src/main/scala/net/psforever/objects/ce/InteractWithTurrets.scala index 82301ce7b..2732bc6fd 100644 --- a/src/main/scala/net/psforever/objects/ce/InteractWithTurrets.scala +++ b/src/main/scala/net/psforever/objects/ce/InteractWithTurrets.scala @@ -1,6 +1,7 @@ // Copyright (c) 2021 PSForever package net.psforever.objects.ce +import net.psforever.objects.GlobalDefinitions import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior} import net.psforever.objects.zones.blockmap.SectorPopulation @@ -13,8 +14,10 @@ case object TurretInteraction extends ZoneInteractionType /** * ... */ -class InteractWithTurrets(val range: Float) +class InteractWithTurrets() extends ZoneInteraction { + def range: Float = InteractWithTurrets.Range + def Type: TurretInteraction.type = TurretInteraction /** @@ -34,8 +37,8 @@ class InteractWithTurrets(val range: Float) private def getTurretTargets( sector: SectorPopulation, position: Vector3 - ): List[PlanetSideServerObject with AutomatedTurret] = { - (sector + ): Iterable[PlanetSideServerObject with AutomatedTurret] = { + val list: Iterable[AutomatedTurret] = sector .deployableList .collect { case turret: AutomatedTurret => turret @@ -43,8 +46,15 @@ class InteractWithTurrets(val range: Float) .amenityList .collect { case turret: AutomatedTurret => turret - }) - .filter { turret => Vector3.DistanceSquared(turret.Position.xy, position) < 625 } + } + list.collect { + case turret: AutomatedTurret + if { + val stats = turret.Definition.AutoFire + stats.nonEmpty && + AutomatedTurretBehavior.shapedDistanceCheckAgainstValue(stats, turret.Position, position, range, result = -1) + } => turret + } } /** @@ -60,3 +70,17 @@ class InteractWithTurrets(val range: Float) } } } + +object InteractWithTurrets { + private val Range: Float = { + Seq( + GlobalDefinitions.spitfire_turret, + GlobalDefinitions.spitfire_cloaked, + GlobalDefinitions.spitfire_aa, + GlobalDefinitions.manned_turret + ) + .flatMap(_.AutoFire) + .map(_.targetDetectionRange) + .max + } +} diff --git a/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala b/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala index c326b5761..b552661c9 100644 --- a/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala +++ b/src/main/scala/net/psforever/objects/equipment/EffectTarget.scala @@ -188,19 +188,19 @@ object EffectTarget { false } - def ObviousPlayer(target: PlanetSideGameObject): Boolean = + def PlayerOnRadar(target: PlanetSideGameObject): Boolean = !target.Destroyed && (target match { case p: Player => //TODO attacking breaks stealth p.LastDamage.map(_.interaction.hitTime).exists(System.currentTimeMillis() - _ < 3000L) || p.avatar.implants.flatten.find(a => a.definition.implantType == ImplantType.SilentRun).exists(_.active) || - (p.isMoving(test = 17d) && !p.Crouching) || + (p.isMoving(test = 17d) && !(p.Crouching || p.Cloaked)) || p.Jumping case _ => false }) - def ObviousMax(target: PlanetSideGameObject): Boolean = + def MaxOnRadar(target: PlanetSideGameObject): Boolean = !target.Destroyed && (target match { case p: Player => p.ExoSuit == ExoSuitType.MAX && p.isMoving(test = 17d) @@ -212,11 +212,22 @@ object EffectTarget { !target.Destroyed && (target match { case v: Vehicle => val vdef = v.Definition - !(GlobalDefinitions.isAtvVehicle(vdef) || + !(v.Cloaked || + GlobalDefinitions.isAtvVehicle(vdef) || vdef == GlobalDefinitions.two_man_assault_buggy || vdef == GlobalDefinitions.skyguard) case _ => false }) + + + + def AircraftOnRadar(target: PlanetSideGameObject): Boolean = + target match { + case v: Vehicle => + GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && !v.Cloaked + case _ => + false + } } } diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala index 05628c740..1c60938c6 100644 --- a/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala +++ b/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala @@ -65,7 +65,7 @@ trait MountableBehavior { !obj.Destroyed } - private def tryMount( + protected def tryMount( obj: PlanetSideServerObject with Mountable, seatNumber: Int, player: Player @@ -110,7 +110,7 @@ trait MountableBehavior { }) } - private def tryDismount( + protected def tryDismount( obj: Mountable, seatNumber: Int, user: Player, 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 585a43062..8953dfd46 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/AutomatedTurretBehavior.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/AutomatedTurretBehavior.scala @@ -2,9 +2,8 @@ package net.psforever.objects.serverobject.turret import akka.actor.{Actor, Cancellable} -import net.psforever.objects.ce.TurretInteraction import net.psforever.objects.definition.ObjectDefinition -import net.psforever.objects.{Default, Player} +import net.psforever.objects.{Default, Player, Vehicle} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.damage.DamageableEntity import net.psforever.objects.sourcing.{SourceEntry, SourceUniqueness} @@ -16,7 +15,7 @@ import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.types.{PlanetSideGUID, Vector3} import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration.FiniteDuration +import scala.concurrent.duration._ trait AutomatedTurret extends PlanetSideServerObject @@ -80,8 +79,6 @@ trait AutomatedTurretBehavior { private var currentTargetToken: Option[SourceUniqueness] = None - private var currentTarget: Option[Target] = None - private var currentTargetLastShotReported: Long = 0L private var periodicValidationTest: Cancellable = Default.Cancellable @@ -118,7 +115,7 @@ trait AutomatedTurretBehavior { if (!previousState && state) { trySelectNewTarget() } else if (previousState && !state) { - currentTarget.foreach { + AutomatedTurretObject.Target.foreach { noLongerEngageDetectedTarget } } @@ -128,6 +125,8 @@ trait AutomatedTurretBehavior { } } + protected def AutomaticOperationFunctionalityChecks: Boolean + private def bringAttentionToTarget(target: Target): Unit = { val targets = AutomatedTurretObject.Targets val size = targets.size @@ -166,7 +165,7 @@ trait AutomatedTurretBehavior { } private def resetAlerts(): Unit = { - currentTarget.foreach { noLongerEngageDetectedTarget } + AutomatedTurretObject.Target.foreach { noLongerEngageDetectedTarget } AutomatedTurretObject.Clear() testTargetListQualifications(beforeSize = 1) } @@ -175,14 +174,15 @@ trait AutomatedTurretBehavior { 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) + context.system.scheduler.scheduleOnce(100.milliseconds, (() => { + AutomatedTurretBehavior.stopShooting(zone, channel, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID) + }).asInstanceOf[Runnable]) } 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) @@ -192,7 +192,7 @@ trait AutomatedTurretBehavior { if (currentTargetToken.contains(SourceEntry(target).unique)) { noLongerEngageDetectedTarget(target) } else { - currentTarget + AutomatedTurretObject.Target } } @@ -200,7 +200,6 @@ trait AutomatedTurretBehavior { 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) @@ -208,28 +207,32 @@ trait AutomatedTurretBehavior { } protected def trySelectNewTarget(): Option[Target] = { - currentTarget.orElse { + AutomatedTurretObject.Target.orElse { val turretPosition = AutomatedTurretObject.Position - val radiusSquared = autoStats.get.targetingRange * autoStats.get.targetingRange + val radius = autoStats.get.targetTriggerRange val validation = autoStats.get.targetValidation val faction = AutomatedTurretObject.Faction AutomatedTurretObject .Targets - .map { target => - (target, Vector3.DistanceSquared(target.Position, turretPosition)) - } - .collect { case out @ (target, distance) + .collect { case target if /*target.Faction != faction &&*/ - distance < radiusSquared && + AutomatedTurretBehavior.shapedDistanceCheckAgainstValue(autoStats, target.Position, turretPosition, radius, result = -1) && validation.exists(func => func(target)) => - out + target } - .sortBy(_._2) - .flatMap { case (target: Player, _) => - testNewDetectedTarget(target, target.Name) - Some(target) + .sortBy(target => Vector3.DistanceSquared(target.Position, turretPosition)) + .collectFirst { + case target: Player => + testNewDetectedTarget(target, target.Name) + target + case target: Vehicle => + target.Seats.values + .flatMap(_.occupants) + .foreach { target => + testNewDetectedTarget(target, target.Name) + } + target } - .headOption } } @@ -244,13 +247,11 @@ trait AutomatedTurretBehavior { private def performDistanceCheck(): List[Target] = { //cull targets val pos = AutomatedTurretObject.Position + val range = autoStats.map(_.targetDetectionRange).getOrElse(0f) val removedTargets = AutomatedTurretObject.Targets .collect { case t: InteractsWithZone - if t.Destroyed || { - val range = t.interaction().find(_.Type == TurretInteraction).map(_.range).getOrElse(100f) - Vector3.DistanceSquared(t.Position, pos) > range * range - } => + if t.Destroyed || AutomatedTurretBehavior.shapedDistanceCheckAgainstValue(autoStats, t.Position, pos, range) => AutomatedTurretObject.RemoveTarget(t) t } @@ -259,54 +260,73 @@ trait AutomatedTurretBehavior { private def performCurrentTargetDecayCheck(): Unit = { val now = System.currentTimeMillis() - val delay = autoStats.map(_.missedShotCooldown).getOrElse(3000L) - currentTarget + val selectDelay = autoStats.map(_.targetSelectCooldown).getOrElse(3000L) + AutomatedTurretObject.Target .collect { target => - if (target.Destroyed) { - //if the target died while we were shooting at it, immediately switch to the next target - noLongerEngageDetectedTarget(target) - currentTargetLastShotReported = now - delay - None - } else if (System.currentTimeMillis() - currentTargetLastShotReported >= delay) { - //if the target goes mia, evaluate a possible cooldown phase before selecting next target - noLongerEngageDetectedTarget(target) - None - } else { - //continue shooting - Some(target) - } + //test target + generalDecayCheck( + now, + autoStats.map(_.targetEscapeRange).getOrElse(400f), + selectDelay, + autoStats.map(_.missedShotCooldown).getOrElse(3000L), + autoStats.map(_.targetEliminationCooldown).getOrElse(0L) + )(target) } .orElse { //no target; unless we are deactivated or have any unfinished delays, search for new target - if (automaticOperation && now - currentTargetLastShotReported >= delay) { + if (automaticOperation && now - currentTargetLastShotReported >= selectDelay) { trySelectNewTarget() } None } } + private def generalDecayCheck( + now: Long, + escapeRange: Float, + selectDelay: Long, + cooldownDelay: Long, + eliminationDelay: Long + )(target: Target): Option[Target] = { + if (target.Destroyed) { + //if the target died while we were shooting at it + noLongerEngageDetectedTarget(target) + currentTargetLastShotReported = now - eliminationDelay + None + } else if (AutomatedTurretBehavior.shapedDistanceCheckAgainstValue(autoStats, target.Position, AutomatedTurretObject.Position, escapeRange)) { + //if the target made sufficient distance from the turret + noLongerEngageDetectedTarget(target) + currentTargetLastShotReported = now - cooldownDelay + None + } else if (now - currentTargetLastShotReported >= cooldownDelay) { + //if the target goes mia through lack of response + noLongerEngageDetectedTarget(target) + currentTargetLastShotReported = now - selectDelay + None + } else { + //continue shooting + Some(target) + } + } + 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 && autoStats.isDefined) { - val (initial, repeated) = autoStats - .map { - ta => (ta.initialDetectionSpeed, ta.detectionSpeed) - } - .get - retimePeriodicTargetChecks(initial, repeated) + val repeated = autoStats.map(_.detectionSpeed).getOrElse(0.seconds) + retimePeriodicTargetChecks(repeated) true } else { false } } - private def retimePeriodicTargetChecks(initial: FiniteDuration, repeated: FiniteDuration): Unit = { + private def retimePeriodicTargetChecks(repeated: FiniteDuration): Unit = { periodicValidationTest.cancel() periodicValidationTest = context.system.scheduler.scheduleWithFixedDelay( - initial, + 0.seconds, repeated, self, AutomatedTurretBehavior.PeriodicCheck @@ -315,9 +335,34 @@ trait AutomatedTurretBehavior { def automaticTurretPostStop(): Unit = { periodicValidationTest.cancel() - currentTarget.foreach { noLongerEngageDetectedTarget } + AutomatedTurretObject.Target.foreach { noLongerEngageDetectedTarget } AutomatedTurretObject.Targets.foreach { AutomatedTurretObject.RemoveTarget } } + + protected def attemptRetaliation(target: Target, cause: DamageResult): Option[Target] = { + if (automaticOperation && autoStats.exists(_.retaliatoryDuration > 0)) { + AutomatedTurretBehavior.getAttackerFromCause(target.Zone, cause).collect { + case attacker if attacker.Faction != target.Faction => + performRetaliation(attacker) + attacker + } + } else { + None + } + } + + private def performRetaliation(target: Target): Option[Target] = { + AutomatedTurretObject.Target + .collect { + case _ if autoStats.exists(_.retaliationOverridesTarget) => + engageNewDetectedTarget(target) + target + } + .orElse { + engageNewDetectedTarget(target) + Some(target) + } + } } object AutomatedTurretBehavior { @@ -394,4 +439,21 @@ object AutomatedTurretBehavior { case out: PlanetSideServerObject with Vitality => out } } + + def shapedDistanceCheckAgainstValue( + stats: Option[Automation], + position: Vector3, + testPosition: Vector3, + testRange: Float, + result: Int = 1 //by default, calculation > testRange^2 + ): Boolean = { + val testRangeSq = testRange * testRange + if (stats.exists(_.cylindricalCheck)) { + val height = testRange + stats.map(_.cylindricalHeight).getOrElse(0f) + math.abs(position.z - testPosition.z).compareTo(height) == result && + Vector3.DistanceSquared(position.xy, testPosition.xy).compareTo(testRangeSq) == result + } else { + Vector3.DistanceSquared(position, testPosition).compareTo(testRangeSq) == result + } + } } 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 b41b61190..30ca857a3 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -1,7 +1,6 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.turret -import net.psforever.objects.entity.WorldEntity import net.psforever.objects.{GlobalDefinitions, Player, Tool} import net.psforever.objects.equipment.Ammo import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} @@ -15,16 +14,13 @@ 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 /** - * An `Actor` that handles messages being dispatched to a specific `MannedTurret`.
- *
- * Mounted turrets have only slightly different entry requirements than a normal vehicle - * because they encompass both faction-specific facility turrets - * and faction-blind cavern sentry turrets. - * - * @param turret the `MannedTurret` object being governed - */ + * A control agency that handles messages being dispatched to a specific `FacilityTurret`. + * These turrets are attached specifically to surface-level facilities and field towers. + * @param turret the `FacilityTurret` object being governed + */ class FacilityTurretControl(turret: FacilityTurret) extends PoweredAmenityControl with AmenityAutoRepair @@ -92,27 +88,31 @@ class FacilityTurretControl(turret: FacilityTurret) seatNumber: Int, player: Player): Boolean = { super.mountTest(obj, seatNumber, player) && - (!TurretObject.isUpgrading || System.currentTimeMillis() - GenericHackables.getTurretUpgradeTime >= 1500L) && - AutomaticOperation_=(state = false) + (!TurretObject.isUpgrading || System.currentTimeMillis() - GenericHackables.getTurretUpgradeTime >= 1500L) } - override protected def dismountTest(obj: Mountable with WorldEntity, seatNumber: Int, user: Player): Boolean = { - super.dismountTest(obj, seatNumber, user) && - AutomaticOperation_=(autoTurretFunctionalityChecks) + override protected def tryMount(obj: PlanetSideServerObject with Mountable, seatNumber: Int, player: Player): Boolean = { + val result = super.tryMount(obj, seatNumber, player) + if (result) { + AutomaticOperation = false + } + result + } + + override protected def tryDismount(obj: Mountable, seatNumber: Int, player: Player, bailType: BailType.Value): Boolean = { + val result = super.tryDismount(obj, seatNumber, player, bailType) + if (result) { + AutomaticOperation = AutomaticOperationFunctionalityChecks + } + result } override protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any) : Unit = { tryAutoRepair() - if (AutomaticOperation) { - if (TurretObject.Health < TurretObject.Definition.DamageDisablesAt) { - AutomaticOperation = false - } else if (AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDuration > 0)) { - //turret retribution - AutomatedTurretBehavior.getAttackerFromCause(target.Zone, cause).collect { - case attacker if attacker.Faction != target.Faction => - engageNewDetectedTarget(attacker) - } - } + if (TurretObject.Health < TurretObject.Definition.DamageDisablesAt) { + AutomaticOperation = false + } else { + attemptRetaliation(target, cause) } super.DamageAwareness(target, cause, amount) } @@ -133,7 +133,7 @@ class FacilityTurretControl(turret: FacilityTurret) override def Restoration(obj: Damageable.Target): Unit = { super.Restoration(obj) - AutomaticOperation = autoTurretFunctionalityChecks + AutomaticOperation = AutomaticOperationFunctionalityChecks } override def tryAutoRepair() : Boolean = { @@ -163,7 +163,7 @@ class FacilityTurretControl(turret: FacilityTurret) def powerTurnOnCallback(): Unit = { tryAutoRepair() - AutomaticOperation = autoTurretFunctionalityChecks + AutomaticOperation = AutomaticOperationFunctionalityChecks } override def AutomaticOperation_=(state: Boolean): Boolean = { @@ -172,7 +172,7 @@ class FacilityTurretControl(turret: FacilityTurret) result } - private def autoTurretFunctionalityChecks: Boolean = { + protected def AutomaticOperationFunctionalityChecks: Boolean = { isPowered && !JammableObject.Jammed && TurretObject.Health > TurretObject.Definition.DamageDisablesAt && @@ -198,7 +198,7 @@ class FacilityTurretControl(turret: FacilityTurret) } override protected def trySelectNewTarget(): Option[AutomatedTurret.Target] = { - if (autoTurretFunctionalityChecks) { + if (AutomaticOperationFunctionalityChecks) { primaryWeaponFireModeOnly() super.trySelectNewTarget() } else { @@ -207,7 +207,7 @@ class FacilityTurretControl(turret: FacilityTurret) } override def engageNewDetectedTarget(target: AutomatedTurret.Target): Unit = { - if (autoTurretFunctionalityChecks) { + if (AutomaticOperationFunctionalityChecks) { primaryWeaponFireModeOnly() super.engageNewDetectedTarget(target) } @@ -233,6 +233,6 @@ class FacilityTurretControl(turret: FacilityTurret) override def CancelJammeredStatus(target: Any): Unit = { val startsJammed = JammableObject.Jammed super.CancelJammeredStatus(target) - startsJammed && AutomaticOperation_=(autoTurretFunctionalityChecks) + startsJammed && AutomaticOperation_=(AutomaticOperationFunctionalityChecks) } } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/MountableTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/MountableTurretControl.scala index f677798d6..046bccb95 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/MountableTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/MountableTurretControl.scala @@ -13,13 +13,13 @@ import net.psforever.objects.vital.interaction.DamageResult import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} trait MountableTurretControl - extends FactionAffinityBehavior.Check + extends Actor + with FactionAffinityBehavior.Check with MountableBehavior with DamageableWeaponTurret with RepairableWeaponTurret - with JammableMountedWeapons - with Actor { /* note: jammable status is reported as vehicle events, not local events */ - def TurretObject: PlanetSideServerObject with WeaponTurret + with JammableMountedWeapons { /* note: jammable status is reported as vehicle events, not local events */ + def TurretObject: PlanetSideServerObject with WeaponTurret with Mountable override def postStop(): Unit = { super.postStop() diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala index 77c4dffba..cf6bdf33c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala @@ -11,16 +11,23 @@ import scala.collection.mutable import scala.concurrent.duration._ final case class Automation( - targetingRange: Float, + targetDetectionRange: Float, + targetTriggerRange: Float, + targetEscapeRange: Float, targetValidation: List[PlanetSideGameObject => Boolean], + cylindricalCheck: Boolean = false, + cylindricalHeight: Float = 0, retaliatoryDuration: Long = 0, + retaliationOverridesTarget: Boolean = true, initialDetectionSpeed: FiniteDuration = Duration.Zero, detectionSpeed: FiniteDuration = 1.seconds, targetSelectCooldown: Long = 1500L, //ms missedShotCooldown: Long = 3000L, //ms targetEliminationCooldown: Long = 0L, //ms revertToDefaultFireMode: Boolean = true - ) + ) { + assert(targetDetectionRange > targetTriggerRange, "trigger range must be less or equal to detection range") +} /** * The definition for any `WeaponTurret`. diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/VanuSentryControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/VanuSentryControl.scala index 6f0f32d36..4a6cb991c 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/VanuSentryControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/VanuSentryControl.scala @@ -10,15 +10,20 @@ import net.psforever.types.Vector3 import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.duration._ +/** + * A control agency that handles messages being dispatched to a specific `FacilityTurret`. + * These turrets are installed tangential to cavern facilities but are independent of the facility. + * @param turret the `FacilityTurret` object being governed + */ class VanuSentryControl(turret: FacilityTurret) extends ServerObjectControl with MountableTurretControl { - def TurretObject: FacilityTurret = turret - def FactionObject: FacilityTurret = turret - def MountableObject: FacilityTurret = turret - def JammableObject: FacilityTurret = turret - def DamageableObject: FacilityTurret = turret - def RepairableObject: FacilityTurret = turret + def TurretObject: FacilityTurret = turret + def FactionObject: FacilityTurret = turret + def MountableObject: FacilityTurret = turret + def JammableObject: FacilityTurret = turret + def DamageableObject: FacilityTurret = turret + def RepairableObject: FacilityTurret = turret // Used for timing ammo recharge for vanu turrets in caves private var weaponAmmoRechargeTimer: Cancellable = Default.Cancellable