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

This commit is contained in:
Fate-JH 2024-01-04 01:51:16 -05:00
parent fc2ce70aae
commit 20b5de34ab
13 changed files with 271 additions and 136 deletions

View file

@ -2,6 +2,8 @@
package net.psforever.actors.session.support package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed} 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.zones.Zoning
import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior, VanuSentry} import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior, VanuSentry}
import net.psforever.objects.zones.exp.ToDatabase import net.psforever.objects.zones.exp.ToDatabase
@ -443,21 +445,19 @@ private[support] class WeaponAndProjectileOperations(
turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target) turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
None None
case turret: AutomatedTurret with OwnableByPlayer => case turret: AutomatedTurret with OwnableByPlayer => //most likely a deployable
turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target) turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
val owner = continent.GUID(turret.OwnerGuid) val owner = continent.GUID(turret.OwnerGuid)
.collect { case obj: PlanetSideGameObject with FactionAffinity => SourceEntry(obj) } .collect { case obj: PlanetSideGameObject with FactionAffinity => SourceEntry(obj) }
.getOrElse { SourceEntry.None } .getOrElse(SourceEntry(turret))
turret.Weapons CompileAutomatedTurretDamageData(turret, owner, projectileTypeId)
.values
.flatMap { _.Equipment }
.collect { case weapon: Tool => (turret, weapon, owner, weapon.Projectile) }
.find { case (_, _, _, p) => p.ObjectId == projectileTypeId }
case turret: Amenity with AutomatedTurret => //most likely a facility turret
turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
CompileAutomatedTurretDamageData(turret, SourceEntry(turret.Owner), projectileTypeId)
} }
.collect { .collect {
case Some((obj, tool, owner, projectileInfo)) => case Some((obj, tool, owner, projectileInfo)) =>
val angle = Vector3.Unit(target.Position - obj.Position) val angle = Vector3.Unit(target.Position - obj.Position)
val proj = new Projectile( val proj = new Projectile(
projectileInfo, projectileInfo,
@ -1458,6 +1458,18 @@ private[support] class WeaponAndProjectileOperations(
ToDatabase.reportToolDischarge(avatarId, EquipmentStat(weaponId, fired, landed, 0, 0)) 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 = { override protected[session] def stop(): Unit = {
if (player != null && player.HasGUID) { if (player != null && player.HasGUID) {
(prefire ++ shooting).foreach { guid => (prefire ++ shooting).foreach { guid =>

View file

@ -9074,8 +9074,10 @@ object GlobalDefinitions {
spitfire_turret.Model = ComplexDeployableResolutions.calculate spitfire_turret.Model = ComplexDeployableResolutions.calculate
spitfire_turret.deployAnimation = DeployAnimation.Standard spitfire_turret.deployAnimation = DeployAnimation.Standard
spitfire_turret.AutoFire = Automation( spitfire_turret.AutoFire = Automation(
targetingRange = 40f, targetDetectionRange = 60f,
targetValidation = List(EffectTarget.Validation.ObviousPlayer, EffectTarget.Validation.Vehicle), targetTriggerRange = 50f,
targetEscapeRange = 50f,
targetValidation = List(EffectTarget.Validation.PlayerOnRadar, EffectTarget.Validation.Vehicle),
retaliatoryDuration = 8000L retaliatoryDuration = 8000L
) )
spitfire_turret.innateDamage = new DamageWithPosition { spitfire_turret.innateDamage = new DamageWithPosition {
@ -9105,8 +9107,10 @@ object GlobalDefinitions {
spitfire_cloaked.deployAnimation = DeployAnimation.Standard spitfire_cloaked.deployAnimation = DeployAnimation.Standard
spitfire_cloaked.Model = ComplexDeployableResolutions.calculate spitfire_cloaked.Model = ComplexDeployableResolutions.calculate
spitfire_cloaked.AutoFire = Automation( spitfire_cloaked.AutoFire = Automation(
targetingRange = 40f, targetDetectionRange = 50f,
targetValidation = List(EffectTarget.Validation.ObviousPlayer, EffectTarget.Validation.Vehicle), targetTriggerRange = 30f,
targetEscapeRange = 50f,
targetValidation = List(EffectTarget.Validation.PlayerOnRadar, EffectTarget.Validation.VehiclesOnRadar),
retaliatoryDuration = 8000L retaliatoryDuration = 8000L
) )
spitfire_cloaked.innateDamage = new DamageWithPosition { spitfire_cloaked.innateDamage = new DamageWithPosition {
@ -9136,9 +9140,14 @@ object GlobalDefinitions {
spitfire_aa.deployAnimation = DeployAnimation.Standard spitfire_aa.deployAnimation = DeployAnimation.Standard
spitfire_aa.Model = ComplexDeployableResolutions.calculate spitfire_aa.Model = ComplexDeployableResolutions.calculate
spitfire_aa.AutoFire = Automation( spitfire_aa.AutoFire = Automation(
targetingRange = 80f, targetDetectionRange = 100f,
targetValidation = List(EffectTarget.Validation.Aircraft), targetTriggerRange = 90f,
retaliatoryDuration = 8000L targetEscapeRange = 200f,
targetValidation = List(EffectTarget.Validation.AircraftOnRadar),
retaliatoryDuration = 2000L,
retaliationOverridesTarget = false,
cylindricalCheck = true,
cylindricalHeight = 25f
) )
spitfire_aa.innateDamage = new DamageWithPosition { spitfire_aa.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One CausesDamageType = DamageType.One
@ -10041,6 +10050,15 @@ object GlobalDefinitions {
manned_turret.FactionLocked = true manned_turret.FactionLocked = true
manned_turret.ReserveAmmunition = false manned_turret.ReserveAmmunition = false
manned_turret.RadiationShielding = 0.5f 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 { manned_turret.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One CausesDamageType = DamageType.One
Damage0 = 150 Damage0 = 150

View file

@ -38,7 +38,7 @@ class Player(var avatar: Avatar)
with MountableEntity { with MountableEntity {
interaction(new InteractWithEnvironment()) interaction(new InteractWithEnvironment())
interaction(new InteractWithMinesUnlessSpectating(obj = this, range = 10)) interaction(new InteractWithMinesUnlessSpectating(obj = this, range = 10))
interaction(new InteractWithTurrets(range = 100f)) interaction(new InteractWithTurrets())
interaction(new InteractWithRadiationClouds(range = 10f, Some(this))) interaction(new InteractWithRadiationClouds(range = 10f, Some(this)))
private var backpack: Boolean = false private var backpack: Boolean = false

View file

@ -89,6 +89,8 @@ class TurretControl(turret: TurretDeployable)
case _ => () case _ => ()
} }
protected def AutomaticOperationFunctionalityChecks: Boolean = true
override protected def mountTest( override protected def mountTest(
obj: PlanetSideServerObject with Mountable, obj: PlanetSideServerObject with Mountable,
seatNumber: Int, seatNumber: Int,
@ -120,13 +122,7 @@ class TurretControl(turret: TurretDeployable)
} }
override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any): Unit = { override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any): Unit = {
if (AutomaticOperation && AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDuration > 0)) { attemptRetaliation(target, cause)
//turret retribution
AutomatedTurretBehavior.getAttackerFromCause(target.Zone, cause).collect {
case attacker if attacker.Faction != target.Faction =>
engageNewDetectedTarget(attacker)
}
}
super.DamageAwareness(target, cause, amount) 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 //it's possible to request deconstruction of one's own field turret while seated in it
val wasKickedByDriver = false val wasKickedByDriver = false
seats.foreach { seat => seats.foreach { seat =>
seat.occupant match { seat.occupant.collect {
case Some(tplayer) => case player: Player =>
seat.unmount(tplayer) seat.unmount(player)
tplayer.VehicleSeated = None player.VehicleSeated = None
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.id, 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) Some(time.getOrElse(Deployable.cleanup) + Deployable.cleanup)

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects 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.definition.{ToolDefinition, VehicleDefinition}
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit} import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile}
@ -92,6 +92,7 @@ class Vehicle(private val vehicleDef: VehicleDefinition)
with MountableEntity { with MountableEntity {
interaction(new InteractWithEnvironment()) interaction(new InteractWithEnvironment())
interaction(new InteractWithMines(range = 20)) interaction(new InteractWithMines(range = 20))
interaction(new InteractWithTurrets())
interaction(new InteractWithRadiationCloudsSeatedInVehicle(obj = this, range = 20)) interaction(new InteractWithRadiationCloudsSeatedInVehicle(obj = this, range = 20))
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL

View file

@ -1,6 +1,7 @@
// Copyright (c) 2021 PSForever // Copyright (c) 2021 PSForever
package net.psforever.objects.ce package net.psforever.objects.ce
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior} import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior}
import net.psforever.objects.zones.blockmap.SectorPopulation 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 { extends ZoneInteraction {
def range: Float = InteractWithTurrets.Range
def Type: TurretInteraction.type = TurretInteraction def Type: TurretInteraction.type = TurretInteraction
/** /**
@ -34,8 +37,8 @@ class InteractWithTurrets(val range: Float)
private def getTurretTargets( private def getTurretTargets(
sector: SectorPopulation, sector: SectorPopulation,
position: Vector3 position: Vector3
): List[PlanetSideServerObject with AutomatedTurret] = { ): Iterable[PlanetSideServerObject with AutomatedTurret] = {
(sector val list: Iterable[AutomatedTurret] = sector
.deployableList .deployableList
.collect { .collect {
case turret: AutomatedTurret => turret case turret: AutomatedTurret => turret
@ -43,8 +46,15 @@ class InteractWithTurrets(val range: Float)
.amenityList .amenityList
.collect { .collect {
case turret: AutomatedTurret => turret 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
}
}

View file

@ -188,19 +188,19 @@ object EffectTarget {
false false
} }
def ObviousPlayer(target: PlanetSideGameObject): Boolean = def PlayerOnRadar(target: PlanetSideGameObject): Boolean =
!target.Destroyed && (target match { !target.Destroyed && (target match {
case p: Player => case p: Player =>
//TODO attacking breaks stealth //TODO attacking breaks stealth
p.LastDamage.map(_.interaction.hitTime).exists(System.currentTimeMillis() - _ < 3000L) || p.LastDamage.map(_.interaction.hitTime).exists(System.currentTimeMillis() - _ < 3000L) ||
p.avatar.implants.flatten.find(a => a.definition.implantType == ImplantType.SilentRun).exists(_.active) || 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 p.Jumping
case _ => case _ =>
false false
}) })
def ObviousMax(target: PlanetSideGameObject): Boolean = def MaxOnRadar(target: PlanetSideGameObject): Boolean =
!target.Destroyed && (target match { !target.Destroyed && (target match {
case p: Player => case p: Player =>
p.ExoSuit == ExoSuitType.MAX && p.isMoving(test = 17d) p.ExoSuit == ExoSuitType.MAX && p.isMoving(test = 17d)
@ -212,11 +212,22 @@ object EffectTarget {
!target.Destroyed && (target match { !target.Destroyed && (target match {
case v: Vehicle => case v: Vehicle =>
val vdef = v.Definition val vdef = v.Definition
!(GlobalDefinitions.isAtvVehicle(vdef) || !(v.Cloaked ||
GlobalDefinitions.isAtvVehicle(vdef) ||
vdef == GlobalDefinitions.two_man_assault_buggy || vdef == GlobalDefinitions.two_man_assault_buggy ||
vdef == GlobalDefinitions.skyguard) vdef == GlobalDefinitions.skyguard)
case _ => case _ =>
false false
}) })
def AircraftOnRadar(target: PlanetSideGameObject): Boolean =
target match {
case v: Vehicle =>
GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && !v.Cloaked
case _ =>
false
}
} }
} }

View file

@ -65,7 +65,7 @@ trait MountableBehavior {
!obj.Destroyed !obj.Destroyed
} }
private def tryMount( protected def tryMount(
obj: PlanetSideServerObject with Mountable, obj: PlanetSideServerObject with Mountable,
seatNumber: Int, seatNumber: Int,
player: Player player: Player
@ -110,7 +110,7 @@ trait MountableBehavior {
}) })
} }
private def tryDismount( protected def tryDismount(
obj: Mountable, obj: Mountable,
seatNumber: Int, seatNumber: Int,
user: Player, user: Player,

View file

@ -2,9 +2,8 @@
package net.psforever.objects.serverobject.turret package net.psforever.objects.serverobject.turret
import akka.actor.{Actor, Cancellable} import akka.actor.{Actor, Cancellable}
import net.psforever.objects.ce.TurretInteraction
import net.psforever.objects.definition.ObjectDefinition 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.PlanetSideServerObject
import net.psforever.objects.serverobject.damage.DamageableEntity import net.psforever.objects.serverobject.damage.DamageableEntity
import net.psforever.objects.sourcing.{SourceEntry, SourceUniqueness} 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 net.psforever.types.{PlanetSideGUID, Vector3}
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration.FiniteDuration import scala.concurrent.duration._
trait AutomatedTurret trait AutomatedTurret
extends PlanetSideServerObject extends PlanetSideServerObject
@ -80,8 +79,6 @@ trait AutomatedTurretBehavior {
private var currentTargetToken: Option[SourceUniqueness] = None private var currentTargetToken: Option[SourceUniqueness] = None
private var currentTarget: Option[Target] = None
private var currentTargetLastShotReported: Long = 0L private var currentTargetLastShotReported: Long = 0L
private var periodicValidationTest: Cancellable = Default.Cancellable private var periodicValidationTest: Cancellable = Default.Cancellable
@ -118,7 +115,7 @@ trait AutomatedTurretBehavior {
if (!previousState && state) { if (!previousState && state) {
trySelectNewTarget() trySelectNewTarget()
} else if (previousState && !state) { } else if (previousState && !state) {
currentTarget.foreach { AutomatedTurretObject.Target.foreach {
noLongerEngageDetectedTarget noLongerEngageDetectedTarget
} }
} }
@ -128,6 +125,8 @@ trait AutomatedTurretBehavior {
} }
} }
protected def AutomaticOperationFunctionalityChecks: Boolean
private def bringAttentionToTarget(target: Target): Unit = { private def bringAttentionToTarget(target: Target): Unit = {
val targets = AutomatedTurretObject.Targets val targets = AutomatedTurretObject.Targets
val size = targets.size val size = targets.size
@ -166,7 +165,7 @@ trait AutomatedTurretBehavior {
} }
private def resetAlerts(): Unit = { private def resetAlerts(): Unit = {
currentTarget.foreach { noLongerEngageDetectedTarget } AutomatedTurretObject.Target.foreach { noLongerEngageDetectedTarget }
AutomatedTurretObject.Clear() AutomatedTurretObject.Clear()
testTargetListQualifications(beforeSize = 1) testTargetListQualifications(beforeSize = 1)
} }
@ -175,14 +174,15 @@ trait AutomatedTurretBehavior {
val zone = target.Zone val zone = target.Zone
AutomatedTurretBehavior.startTrackingTargets(zone, channel, AutomatedTurretObject.GUID, List(target.GUID)) AutomatedTurretBehavior.startTrackingTargets(zone, channel, AutomatedTurretObject.GUID, List(target.GUID))
AutomatedTurretBehavior.startShooting(zone, channel, AutomatedTurretObject.Weapons.values.head.Equipment.get.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 = { protected def engageNewDetectedTarget(target: Target): Unit = {
val zone = target.Zone val zone = target.Zone
val zoneid = zone.id val zoneid = zone.id
AutomatedTurretObject.Target = target AutomatedTurretObject.Target = target
currentTarget = Some(target)
currentTargetToken = Some(SourceEntry(target).unique) currentTargetToken = Some(SourceEntry(target).unique)
AutomatedTurretBehavior.startTrackingTargets(zone, zoneid, AutomatedTurretObject.GUID, List(target.GUID)) AutomatedTurretBehavior.startTrackingTargets(zone, zoneid, AutomatedTurretObject.GUID, List(target.GUID))
AutomatedTurretBehavior.startShooting(zone, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID) AutomatedTurretBehavior.startShooting(zone, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID)
@ -192,7 +192,7 @@ trait AutomatedTurretBehavior {
if (currentTargetToken.contains(SourceEntry(target).unique)) { if (currentTargetToken.contains(SourceEntry(target).unique)) {
noLongerEngageDetectedTarget(target) noLongerEngageDetectedTarget(target)
} else { } else {
currentTarget AutomatedTurretObject.Target
} }
} }
@ -200,7 +200,6 @@ trait AutomatedTurretBehavior {
val zone = target.Zone val zone = target.Zone
val zoneid = zone.id val zoneid = zone.id
AutomatedTurretObject.Target = None AutomatedTurretObject.Target = None
currentTarget = None
currentTargetToken = None currentTargetToken = None
AutomatedTurretBehavior.stopTrackingTargets(zone, zoneid, AutomatedTurretObject.GUID) AutomatedTurretBehavior.stopTrackingTargets(zone, zoneid, AutomatedTurretObject.GUID)
AutomatedTurretBehavior.stopShooting(zone, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID) AutomatedTurretBehavior.stopShooting(zone, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID)
@ -208,28 +207,32 @@ trait AutomatedTurretBehavior {
} }
protected def trySelectNewTarget(): Option[Target] = { protected def trySelectNewTarget(): Option[Target] = {
currentTarget.orElse { AutomatedTurretObject.Target.orElse {
val turretPosition = AutomatedTurretObject.Position val turretPosition = AutomatedTurretObject.Position
val radiusSquared = autoStats.get.targetingRange * autoStats.get.targetingRange val radius = autoStats.get.targetTriggerRange
val validation = autoStats.get.targetValidation val validation = autoStats.get.targetValidation
val faction = AutomatedTurretObject.Faction val faction = AutomatedTurretObject.Faction
AutomatedTurretObject AutomatedTurretObject
.Targets .Targets
.map { target => .collect { case target
(target, Vector3.DistanceSquared(target.Position, turretPosition))
}
.collect { case out @ (target, distance)
if /*target.Faction != faction &&*/ if /*target.Faction != faction &&*/
distance < radiusSquared && AutomatedTurretBehavior.shapedDistanceCheckAgainstValue(autoStats, target.Position, turretPosition, radius, result = -1) &&
validation.exists(func => func(target)) => validation.exists(func => func(target)) =>
out target
} }
.sortBy(_._2) .sortBy(target => Vector3.DistanceSquared(target.Position, turretPosition))
.flatMap { case (target: Player, _) => .collectFirst {
testNewDetectedTarget(target, target.Name) case target: Player =>
Some(target) 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] = { private def performDistanceCheck(): List[Target] = {
//cull targets //cull targets
val pos = AutomatedTurretObject.Position val pos = AutomatedTurretObject.Position
val range = autoStats.map(_.targetDetectionRange).getOrElse(0f)
val removedTargets = AutomatedTurretObject.Targets val removedTargets = AutomatedTurretObject.Targets
.collect { .collect {
case t: InteractsWithZone case t: InteractsWithZone
if t.Destroyed || { if t.Destroyed || AutomatedTurretBehavior.shapedDistanceCheckAgainstValue(autoStats, t.Position, pos, range) =>
val range = t.interaction().find(_.Type == TurretInteraction).map(_.range).getOrElse(100f)
Vector3.DistanceSquared(t.Position, pos) > range * range
} =>
AutomatedTurretObject.RemoveTarget(t) AutomatedTurretObject.RemoveTarget(t)
t t
} }
@ -259,54 +260,73 @@ trait AutomatedTurretBehavior {
private def performCurrentTargetDecayCheck(): Unit = { private def performCurrentTargetDecayCheck(): Unit = {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val delay = autoStats.map(_.missedShotCooldown).getOrElse(3000L) val selectDelay = autoStats.map(_.targetSelectCooldown).getOrElse(3000L)
currentTarget AutomatedTurretObject.Target
.collect { target => .collect { target =>
if (target.Destroyed) { //test target
//if the target died while we were shooting at it, immediately switch to the next target generalDecayCheck(
noLongerEngageDetectedTarget(target) now,
currentTargetLastShotReported = now - delay autoStats.map(_.targetEscapeRange).getOrElse(400f),
None selectDelay,
} else if (System.currentTimeMillis() - currentTargetLastShotReported >= delay) { autoStats.map(_.missedShotCooldown).getOrElse(3000L),
//if the target goes mia, evaluate a possible cooldown phase before selecting next target autoStats.map(_.targetEliminationCooldown).getOrElse(0L)
noLongerEngageDetectedTarget(target) )(target)
None
} else {
//continue shooting
Some(target)
}
} }
.orElse { .orElse {
//no target; unless we are deactivated or have any unfinished delays, search for new target //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() trySelectNewTarget()
} }
None 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 = { private def testTargetListQualifications(beforeSize: Int): Boolean = {
beforeSize > 0 && AutomatedTurretObject.Targets.isEmpty && periodicValidationTest.cancel() beforeSize > 0 && AutomatedTurretObject.Targets.isEmpty && periodicValidationTest.cancel()
} }
private def retimePeriodicTargetChecks(beforeSize: Int): Boolean = { private def retimePeriodicTargetChecks(beforeSize: Int): Boolean = {
if (beforeSize == 0 && AutomatedTurretObject.Targets.nonEmpty && autoStats.isDefined) { if (beforeSize == 0 && AutomatedTurretObject.Targets.nonEmpty && autoStats.isDefined) {
val (initial, repeated) = autoStats val repeated = autoStats.map(_.detectionSpeed).getOrElse(0.seconds)
.map { retimePeriodicTargetChecks(repeated)
ta => (ta.initialDetectionSpeed, ta.detectionSpeed)
}
.get
retimePeriodicTargetChecks(initial, repeated)
true true
} else { } else {
false false
} }
} }
private def retimePeriodicTargetChecks(initial: FiniteDuration, repeated: FiniteDuration): Unit = { private def retimePeriodicTargetChecks(repeated: FiniteDuration): Unit = {
periodicValidationTest.cancel() periodicValidationTest.cancel()
periodicValidationTest = context.system.scheduler.scheduleWithFixedDelay( periodicValidationTest = context.system.scheduler.scheduleWithFixedDelay(
initial, 0.seconds,
repeated, repeated,
self, self,
AutomatedTurretBehavior.PeriodicCheck AutomatedTurretBehavior.PeriodicCheck
@ -315,9 +335,34 @@ trait AutomatedTurretBehavior {
def automaticTurretPostStop(): Unit = { def automaticTurretPostStop(): Unit = {
periodicValidationTest.cancel() periodicValidationTest.cancel()
currentTarget.foreach { noLongerEngageDetectedTarget } AutomatedTurretObject.Target.foreach { noLongerEngageDetectedTarget }
AutomatedTurretObject.Targets.foreach { AutomatedTurretObject.RemoveTarget } 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 { object AutomatedTurretBehavior {
@ -394,4 +439,21 @@ object AutomatedTurretBehavior {
case out: PlanetSideServerObject with Vitality => out 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
}
}
} }

View file

@ -1,7 +1,6 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.turret package net.psforever.objects.serverobject.turret
import net.psforever.objects.entity.WorldEntity
import net.psforever.objects.{GlobalDefinitions, Player, Tool} import net.psforever.objects.{GlobalDefinitions, Player, Tool}
import net.psforever.objects.equipment.Ammo import net.psforever.objects.equipment.Ammo
import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} 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.packet.game.ChangeFireModeMessage
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.BailType
/** /**
* An `Actor` that handles messages being dispatched to a specific `MannedTurret`.<br> * A control agency that handles messages being dispatched to a specific `FacilityTurret`.
* <br> * These turrets are attached specifically to surface-level facilities and field towers.
* Mounted turrets have only slightly different entry requirements than a normal vehicle * @param turret the `FacilityTurret` object being governed
* because they encompass both faction-specific facility turrets */
* and faction-blind cavern sentry turrets.
*
* @param turret the `MannedTurret` object being governed
*/
class FacilityTurretControl(turret: FacilityTurret) class FacilityTurretControl(turret: FacilityTurret)
extends PoweredAmenityControl extends PoweredAmenityControl
with AmenityAutoRepair with AmenityAutoRepair
@ -92,27 +88,31 @@ class FacilityTurretControl(turret: FacilityTurret)
seatNumber: Int, seatNumber: Int,
player: Player): Boolean = { player: Player): Boolean = {
super.mountTest(obj, seatNumber, player) && super.mountTest(obj, seatNumber, player) &&
(!TurretObject.isUpgrading || System.currentTimeMillis() - GenericHackables.getTurretUpgradeTime >= 1500L) && (!TurretObject.isUpgrading || System.currentTimeMillis() - GenericHackables.getTurretUpgradeTime >= 1500L)
AutomaticOperation_=(state = false)
} }
override protected def dismountTest(obj: Mountable with WorldEntity, seatNumber: Int, user: Player): Boolean = { override protected def tryMount(obj: PlanetSideServerObject with Mountable, seatNumber: Int, player: Player): Boolean = {
super.dismountTest(obj, seatNumber, user) && val result = super.tryMount(obj, seatNumber, player)
AutomaticOperation_=(autoTurretFunctionalityChecks) 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 = { override protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any) : Unit = {
tryAutoRepair() tryAutoRepair()
if (AutomaticOperation) { if (TurretObject.Health < TurretObject.Definition.DamageDisablesAt) {
if (TurretObject.Health < TurretObject.Definition.DamageDisablesAt) { AutomaticOperation = false
AutomaticOperation = false } else {
} else if (AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDuration > 0)) { attemptRetaliation(target, cause)
//turret retribution
AutomatedTurretBehavior.getAttackerFromCause(target.Zone, cause).collect {
case attacker if attacker.Faction != target.Faction =>
engageNewDetectedTarget(attacker)
}
}
} }
super.DamageAwareness(target, cause, amount) super.DamageAwareness(target, cause, amount)
} }
@ -133,7 +133,7 @@ class FacilityTurretControl(turret: FacilityTurret)
override def Restoration(obj: Damageable.Target): Unit = { override def Restoration(obj: Damageable.Target): Unit = {
super.Restoration(obj) super.Restoration(obj)
AutomaticOperation = autoTurretFunctionalityChecks AutomaticOperation = AutomaticOperationFunctionalityChecks
} }
override def tryAutoRepair() : Boolean = { override def tryAutoRepair() : Boolean = {
@ -163,7 +163,7 @@ class FacilityTurretControl(turret: FacilityTurret)
def powerTurnOnCallback(): Unit = { def powerTurnOnCallback(): Unit = {
tryAutoRepair() tryAutoRepair()
AutomaticOperation = autoTurretFunctionalityChecks AutomaticOperation = AutomaticOperationFunctionalityChecks
} }
override def AutomaticOperation_=(state: Boolean): Boolean = { override def AutomaticOperation_=(state: Boolean): Boolean = {
@ -172,7 +172,7 @@ class FacilityTurretControl(turret: FacilityTurret)
result result
} }
private def autoTurretFunctionalityChecks: Boolean = { protected def AutomaticOperationFunctionalityChecks: Boolean = {
isPowered && isPowered &&
!JammableObject.Jammed && !JammableObject.Jammed &&
TurretObject.Health > TurretObject.Definition.DamageDisablesAt && TurretObject.Health > TurretObject.Definition.DamageDisablesAt &&
@ -198,7 +198,7 @@ class FacilityTurretControl(turret: FacilityTurret)
} }
override protected def trySelectNewTarget(): Option[AutomatedTurret.Target] = { override protected def trySelectNewTarget(): Option[AutomatedTurret.Target] = {
if (autoTurretFunctionalityChecks) { if (AutomaticOperationFunctionalityChecks) {
primaryWeaponFireModeOnly() primaryWeaponFireModeOnly()
super.trySelectNewTarget() super.trySelectNewTarget()
} else { } else {
@ -207,7 +207,7 @@ class FacilityTurretControl(turret: FacilityTurret)
} }
override def engageNewDetectedTarget(target: AutomatedTurret.Target): Unit = { override def engageNewDetectedTarget(target: AutomatedTurret.Target): Unit = {
if (autoTurretFunctionalityChecks) { if (AutomaticOperationFunctionalityChecks) {
primaryWeaponFireModeOnly() primaryWeaponFireModeOnly()
super.engageNewDetectedTarget(target) super.engageNewDetectedTarget(target)
} }
@ -233,6 +233,6 @@ class FacilityTurretControl(turret: FacilityTurret)
override def CancelJammeredStatus(target: Any): Unit = { override def CancelJammeredStatus(target: Any): Unit = {
val startsJammed = JammableObject.Jammed val startsJammed = JammableObject.Jammed
super.CancelJammeredStatus(target) super.CancelJammeredStatus(target)
startsJammed && AutomaticOperation_=(autoTurretFunctionalityChecks) startsJammed && AutomaticOperation_=(AutomaticOperationFunctionalityChecks)
} }
} }

View file

@ -13,13 +13,13 @@ import net.psforever.objects.vital.interaction.DamageResult
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
trait MountableTurretControl trait MountableTurretControl
extends FactionAffinityBehavior.Check extends Actor
with FactionAffinityBehavior.Check
with MountableBehavior with MountableBehavior
with DamageableWeaponTurret with DamageableWeaponTurret
with RepairableWeaponTurret with RepairableWeaponTurret
with JammableMountedWeapons with JammableMountedWeapons { /* note: jammable status is reported as vehicle events, not local events */
with Actor { /* note: jammable status is reported as vehicle events, not local events */ def TurretObject: PlanetSideServerObject with WeaponTurret with Mountable
def TurretObject: PlanetSideServerObject with WeaponTurret
override def postStop(): Unit = { override def postStop(): Unit = {
super.postStop() super.postStop()

View file

@ -11,16 +11,23 @@ import scala.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
final case class Automation( final case class Automation(
targetingRange: Float, targetDetectionRange: Float,
targetTriggerRange: Float,
targetEscapeRange: Float,
targetValidation: List[PlanetSideGameObject => Boolean], targetValidation: List[PlanetSideGameObject => Boolean],
cylindricalCheck: Boolean = false,
cylindricalHeight: Float = 0,
retaliatoryDuration: Long = 0, retaliatoryDuration: Long = 0,
retaliationOverridesTarget: Boolean = true,
initialDetectionSpeed: FiniteDuration = Duration.Zero, initialDetectionSpeed: FiniteDuration = Duration.Zero,
detectionSpeed: FiniteDuration = 1.seconds, detectionSpeed: FiniteDuration = 1.seconds,
targetSelectCooldown: Long = 1500L, //ms targetSelectCooldown: Long = 1500L, //ms
missedShotCooldown: Long = 3000L, //ms missedShotCooldown: Long = 3000L, //ms
targetEliminationCooldown: Long = 0L, //ms targetEliminationCooldown: Long = 0L, //ms
revertToDefaultFireMode: Boolean = true revertToDefaultFireMode: Boolean = true
) ) {
assert(targetDetectionRange > targetTriggerRange, "trigger range must be less or equal to detection range")
}
/** /**
* The definition for any `WeaponTurret`. * The definition for any `WeaponTurret`.

View file

@ -10,15 +10,20 @@ import net.psforever.types.Vector3
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._ 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) class VanuSentryControl(turret: FacilityTurret)
extends ServerObjectControl extends ServerObjectControl
with MountableTurretControl { with MountableTurretControl {
def TurretObject: FacilityTurret = turret def TurretObject: FacilityTurret = turret
def FactionObject: FacilityTurret = turret def FactionObject: FacilityTurret = turret
def MountableObject: FacilityTurret = turret def MountableObject: FacilityTurret = turret
def JammableObject: FacilityTurret = turret def JammableObject: FacilityTurret = turret
def DamageableObject: FacilityTurret = turret def DamageableObject: FacilityTurret = turret
def RepairableObject: FacilityTurret = turret def RepairableObject: FacilityTurret = turret
// Used for timing ammo recharge for vanu turrets in caves // Used for timing ammo recharge for vanu turrets in caves
private var weaponAmmoRechargeTimer: Cancellable = Default.Cancellable private var weaponAmmoRechargeTimer: Cancellable = Default.Cancellable