attempted cleanup of previous test fire condition; division of turret callbacks between generic targets and vehicle targets; facility turret stops automatic fire when being mounted and resumes automatic mode when being dismounted

This commit is contained in:
Fate-JH 2024-01-12 02:03:46 -05:00
parent 20b5de34ab
commit 3ffb817f4a
8 changed files with 192 additions and 76 deletions

View file

@ -435,9 +435,14 @@ private[support] class WeaponAndProjectileOperations(
def handleAIDamage(pkt: AIDamage): Unit = {
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
case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality with OwnableByPlayer)
if tobj.GUID == targetGuid && tobj.OwnerGuid.contains(player.GUID) =>
Some(tobj)
case _
if player.GUID == targetGuid =>
Some(player)
case _ =>
None
}).foreach { target =>
sessionData.validObject(attackerGuid, decorator = "AIDamage/Attacker")
.collect {

View file

@ -10057,7 +10057,8 @@ object GlobalDefinitions {
targetValidation = List(EffectTarget.Validation.MaxOnRadar, EffectTarget.Validation.VehiclesOnRadar),
retaliatoryDuration = 8000L,
cylindricalCheck = true,
cylindricalHeight = 25f
cylindricalHeight = 25f,
detectionSpeed = 2.seconds
)
manned_turret.innateDamage = new DamageWithPosition {
CausesDamageType = DamageType.One

View file

@ -109,8 +109,8 @@ class TurretControl(turret: TurretDeployable)
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?
AutomatedTurretBehavior.startTracking(attacker, channel, guid, List(attacker.GUID))
AutomatedTurretBehavior.stopTracking(attacker, channel, guid) //TODO delay by a few milliseconds?
}
}
}

View file

@ -26,9 +26,9 @@ class InteractWithTurrets()
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
target match {
case clarifiedTarget: AutomatedTurret.Target =>
val posxy = clarifiedTarget.Position.xy
val pos = clarifiedTarget.Position
val unique = SourceEntry(clarifiedTarget).unique
val targets = getTurretTargets(sector, posxy).filter { turret => turret.Definition.AutoFire.nonEmpty && turret.Detected(unique).isEmpty }
val targets = getTurretTargets(sector, pos).filter { turret => turret.Definition.AutoFire.nonEmpty && turret.Detected(unique).isEmpty }
targets.foreach { t => t.Actor ! AutomatedTurretBehavior.Alert(clarifiedTarget) }
case _ => ()
}
@ -72,7 +72,7 @@ class InteractWithTurrets()
}
object InteractWithTurrets {
private val Range: Float = {
private lazy val Range: Float = {
Seq(
GlobalDefinitions.spitfire_turret,
GlobalDefinitions.spitfire_cloaked,

View file

@ -212,7 +212,8 @@ object EffectTarget {
!target.Destroyed && (target match {
case v: Vehicle =>
val vdef = v.Definition
!(v.Cloaked ||
!(v.MountedIn.nonEmpty ||
v.Cloaked ||
GlobalDefinitions.isAtvVehicle(vdef) ||
vdef == GlobalDefinitions.two_man_assault_buggy ||
vdef == GlobalDefinitions.skyguard)

View file

@ -1,7 +1,7 @@
// Copyright (c) 2023 PSForever
package net.psforever.objects.serverobject.turret
import akka.actor.{Actor, Cancellable}
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.{Default, Player, Vehicle}
import net.psforever.objects.serverobject.PlanetSideServerObject
@ -10,8 +10,11 @@ import net.psforever.objects.sourcing.{SourceEntry, SourceUniqueness}
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.interaction.DamageResult
import net.psforever.objects.zones.{InteractsWithZone, Zone}
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game.{ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ObjectDetectedMessage}
import net.psforever.services.Service
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{PlanetSideGUID, Vector3}
import scala.concurrent.ExecutionContext.Implicits.global
@ -85,6 +88,8 @@ trait AutomatedTurretBehavior {
private lazy val autoStats: Option[Automation] = AutomatedTurretObject.Definition.AutoFire
private var previousTestedTargets: Set[Target] = Set()
def AutomatedTurretObject: AutomatedTurret
val automatedTurretBehavior: Actor.Receive = if (autoStats.isDefined) {
@ -127,6 +132,13 @@ trait AutomatedTurretBehavior {
protected def AutomaticOperationFunctionalityChecks: Boolean
protected def CurrentTargetLastShotReported: Long = currentTargetLastShotReported
protected def CurrentTargetLastShotReported_=(value: Long): Long = {
currentTargetLastShotReported = value
CurrentTargetLastShotReported
}
private def bringAttentionToTarget(target: Target): Unit = {
val targets = AutomatedTurretObject.Targets
val size = targets.size
@ -140,12 +152,12 @@ trait AutomatedTurretBehavior {
private def confirmShot(target: Target): Unit = {
val now = System.currentTimeMillis()
if (currentTargetToken.isEmpty || now - currentTargetLastShotReported > autoStats.map { _.targetSelectCooldown }.get) {
if (currentTargetToken.isEmpty) {
currentTargetLastShotReported = now
engageNewDetectedTarget(target)
} else if (
currentTargetToken.contains(SourceEntry(target).unique) &&
now - currentTargetLastShotReported < autoStats.map { _.missedShotCooldown }.get) {
currentTargetToken.contains(SourceEntry(target).unique) &&
now - currentTargetLastShotReported < autoStats.map(_.missedShotCooldown).getOrElse(0L)) {
currentTargetLastShotReported = now
}
}
@ -165,27 +177,24 @@ trait AutomatedTurretBehavior {
}
private def resetAlerts(): Unit = {
AutomatedTurretObject.Target.foreach { noLongerEngageDetectedTarget }
AutomatedTurretObject.Target.foreach(noLongerEngageDetectedTarget)
AutomatedTurretObject.Target = None
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)
context.system.scheduler.scheduleOnce(100.milliseconds, (() => {
AutomatedTurretBehavior.stopShooting(zone, channel, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID)
}).asInstanceOf[Runnable])
previousTestedTargets.foreach(noLongerEngageTestedTarget)
previousTestedTargets = Set()
currentTargetToken = None
}
protected def engageNewDetectedTarget(target: Target): Unit = {
val zone = target.Zone
val zoneid = zone.id
AutomatedTurretObject.Target = target
previousTestedTargets.filterNot(_ == target).foreach(noLongerEngageTestedTarget)
previousTestedTargets = Set(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)
AutomatedTurretObject.Target = target
AutomatedTurretBehavior.startTracking(target, zoneid, AutomatedTurretObject.GUID, List(target.GUID))
AutomatedTurretBehavior.startShooting(target, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID)
}
protected def noLongerDetectTargetIfCurrent(target: Target): Option[Target] = {
@ -197,22 +206,30 @@ trait AutomatedTurretBehavior {
}
protected def noLongerEngageDetectedTarget(target: Target): Option[Target] = {
val zone = target.Zone
val zoneid = zone.id
AutomatedTurretObject.Target = None
currentTargetToken = None
AutomatedTurretBehavior.stopTrackingTargets(zone, zoneid, AutomatedTurretObject.GUID)
AutomatedTurretBehavior.stopShooting(zone, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID)
noLongerEngageTestedTarget(target)
None
}
private def noLongerEngageTestedTarget(target: Target): Option[Target] = {
val zone = target.Zone
val zoneid = zone.id
AutomatedTurretBehavior.stopTracking(target, zoneid, AutomatedTurretObject.GUID)
AutomatedTurretBehavior.stopShooting(target, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID)
None
}
protected def trySelectNewTarget(): Option[Target] = {
AutomatedTurretObject.Target.orElse {
val turretPosition = AutomatedTurretObject.Position
val turretGuid = AutomatedTurretObject.GUID
val weaponGuid = AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID
val radius = autoStats.get.targetTriggerRange
val validation = autoStats.get.targetValidation
val faction = AutomatedTurretObject.Faction
AutomatedTurretObject
//noinspection CollectHeadOption
val (targets, forValidation) = AutomatedTurretObject
.Targets
.collect { case target
if /*target.Faction != faction &&*/
@ -221,18 +238,29 @@ trait AutomatedTurretBehavior {
target
}
.sortBy(target => Vector3.DistanceSquared(target.Position, turretPosition))
.collectFirst {
.collect {
case target: Player =>
testNewDetectedTarget(target, target.Name)
target
AutomatedTurretBehavior.Generic.testNewDetected(target, target.Name, turretGuid, weaponGuid)
Seq((target, target))
case target: Vehicle =>
target.Seats.values
.flatMap(_.occupants)
.foreach { target =>
testNewDetectedTarget(target, target.Name)
.collectFirst { passenger =>
AutomatedTurretBehavior.Generic.testNewDetected(passenger, passenger.Name, turretGuid, weaponGuid)
(target, passenger)
}
target
}
.flatten
.unzip
//call an explicit stop for these targets
(for {
a <- forValidation
b <- previousTestedTargets
if SourceEntry(a).unique != SourceEntry(b).unique
} yield b)
.foreach(noLongerEngageTestedTarget)
previousTestedTargets = forValidation.toSet
targets.headOption
}
}
@ -274,7 +302,7 @@ trait AutomatedTurretBehavior {
}
.orElse {
//no target; unless we are deactivated or have any unfinished delays, search for new target
if (automaticOperation && now - currentTargetLastShotReported >= selectDelay) {
if (automaticOperation && now - currentTargetLastShotReported >= 0) {
trySelectNewTarget()
}
None
@ -291,17 +319,17 @@ trait AutomatedTurretBehavior {
if (target.Destroyed) {
//if the target died while we were shooting at it
noLongerEngageDetectedTarget(target)
currentTargetLastShotReported = now - eliminationDelay
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
currentTargetLastShotReported = now + cooldownDelay
None
} else if (now - currentTargetLastShotReported >= cooldownDelay) {
//if the target goes mia through lack of response
noLongerEngageDetectedTarget(target)
currentTargetLastShotReported = now - selectDelay
currentTargetLastShotReported = now + selectDelay
None
} else {
//continue shooting
@ -377,32 +405,105 @@ object AutomatedTurretBehavior {
private case object PeriodicCheck
def startTrackingTargets(zone: Zone, channel: String, guid: PlanetSideGUID, list: List[PlanetSideGUID]): Unit = {
zone.LocalEvents ! LocalServiceMessage(
channel,
LocalAction.SendResponse(ObjectDetectedMessage(guid, guid, 0, list))
)
trait AutomatedTurretDispatch {
private val noTargets :List[PlanetSideGUID] = List(Service.defaultPlayerGUID)
def getEventBus(target: Target): ActorRef
def composeMessageEnvelope(channel: String, msg: PlanetSideGamePacket): Any
def startTracking(target: Target, channel: String, turretGuid: PlanetSideGUID, list: List[PlanetSideGUID]): Unit = {
getEventBus(target) ! composeMessageEnvelope(channel, startTrackingMsg(turretGuid, list))
}
def stopTracking(target: Target, channel: String, turretGuid: PlanetSideGUID): Unit = {
getEventBus(target) ! composeMessageEnvelope(channel, stopTrackingMsg(turretGuid))
}
def startShooting(target: Target, channel: String, weaponGuid: PlanetSideGUID): Unit = {
getEventBus(target) ! composeMessageEnvelope(channel, startShootingMsg(weaponGuid))
}
def stopShooting(target: Target, channel: String, weaponGuid: PlanetSideGUID): Unit = {
getEventBus(target) ! composeMessageEnvelope(channel, stopShootingMsg(weaponGuid))
}
def testNewDetected(target: Target, channel: String, turretGuid: PlanetSideGUID, weaponGuid: PlanetSideGUID): Unit = {
startTracking(target, channel, turretGuid, List(target.GUID))
startShooting(target, channel, weaponGuid)
stopShooting(target, channel, weaponGuid)
}
private def startTrackingMsg(guid: PlanetSideGUID, list: List[PlanetSideGUID]): PlanetSideGamePacket = {
ObjectDetectedMessage(guid, guid, 0, list)
}
private def stopTrackingMsg(turretGuid: PlanetSideGUID): PlanetSideGamePacket = {
ObjectDetectedMessage(turretGuid, turretGuid, 0, noTargets)
}
private def startShootingMsg(weaponGuid: PlanetSideGUID): PlanetSideGamePacket = {
ChangeFireStateMessage_Start(weaponGuid)
}
private def stopShootingMsg(weaponGuid: PlanetSideGUID): PlanetSideGamePacket = {
ChangeFireStateMessage_Stop(weaponGuid)
}
}
def stopTrackingTargets(zone: Zone, channel: String, guid: PlanetSideGUID): Unit = {
zone.LocalEvents ! LocalServiceMessage(
channel,
LocalAction.SendResponse(ObjectDetectedMessage(guid, guid, 0, List(PlanetSideGUID(0))))
)
object Generic extends AutomatedTurretDispatch {
def getEventBus(target: Target): ActorRef = {
target.Zone.LocalEvents
}
def composeMessageEnvelope(channel: String, msg: PlanetSideGamePacket): Any = {
LocalServiceMessage(channel, LocalAction.SendResponse(msg))
}
}
private def startShooting(zone: Zone, channel: String, weaponGuid: PlanetSideGUID): Unit = {
zone.LocalEvents ! LocalServiceMessage(
channel,
LocalAction.SendResponse(ChangeFireStateMessage_Start(weaponGuid))
)
object Vehicle extends AutomatedTurretDispatch {
def getEventBus(target: Target): ActorRef = {
target.Zone.VehicleEvents
}
def composeMessageEnvelope(channel: String, msg: PlanetSideGamePacket): Any = {
VehicleServiceMessage(channel, VehicleAction.SendResponse(Service.defaultPlayerGUID, msg))
}
}
private def stopShooting(zone: Zone, channel: String, weaponGuid: PlanetSideGUID): Unit = {
zone.LocalEvents ! LocalServiceMessage(
channel,
LocalAction.SendResponse(ChangeFireStateMessage_Stop(weaponGuid))
)
def startTracking(target: Target, channel: String, turretGuid: PlanetSideGUID, list: List[PlanetSideGUID]): Unit = {
target match {
case v: Vehicle => Vehicle.startTracking(v, channel, turretGuid, list)
case _ => Generic.startTracking(target, channel, turretGuid, list)
}
}
def stopTracking(target: Target, channel: String, turretGuid: PlanetSideGUID): Unit = {
target match {
case v: Vehicle => Vehicle.stopTracking(v, channel, turretGuid)
case _ => Generic.stopTracking(target, channel, turretGuid)
}
}
def startShooting(target: Target, channel: String, weaponGuid: PlanetSideGUID): Unit = {
target match {
case v: Vehicle => Vehicle.startShooting(v, channel, weaponGuid)
case _ => Generic.startShooting(target, channel, weaponGuid)
}
}
def stopShooting(target: Target, channel: String, weaponGuid: PlanetSideGUID): Unit = {
target match {
case v: Vehicle => Vehicle.stopShooting(v, channel, weaponGuid)
case _ => Generic.stopShooting(target, channel, weaponGuid)
}
}
def testNewDetected(target: Target, channel: String, turretGuid: PlanetSideGUID, weaponGuid: PlanetSideGUID): Unit = {
target match {
case v: Vehicle => Vehicle.testNewDetected(v, channel, turretGuid, weaponGuid)
case _ => Generic.testNewDetected(target, channel, turretGuid, weaponGuid)
}
}
def getAttackerFromCause(zone: Zone, cause: DamageResult): Option[PlanetSideServerObject with Vitality] = {
@ -445,7 +546,7 @@ object AutomatedTurretBehavior {
position: Vector3,
testPosition: Vector3,
testRange: Float,
result: Int = 1 //by default, calculation > testRange^2
result: Int = 1 //by default, calculation > input
): Boolean = {
val testRangeSq = testRange * testRange
if (stats.exists(_.cylindricalCheck)) {

View file

@ -92,19 +92,24 @@ class FacilityTurretControl(turret: FacilityTurret)
}
override protected def tryMount(obj: PlanetSideServerObject with Mountable, seatNumber: Int, player: Player): Boolean = {
val result = super.tryMount(obj, seatNumber, player)
if (result) {
AutomaticOperation = false
AutomaticOperation = false //turn off
if (!super.tryMount(obj, seatNumber, player)) {
AutomaticOperation = AutomaticOperationFunctionalityChecks //revert?
false
} else {
true
}
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
AutomaticOperation = AutomaticOperationFunctionalityChecksExceptMounting //turn on, if can turn on
if (!super.tryDismount(obj, seatNumber, player, bailType)) {
AutomaticOperation = false //revert
false
} else {
CurrentTargetLastShotReported = System.currentTimeMillis() + 4000L
true
}
result
}
override protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any) : Unit = {
@ -173,10 +178,13 @@ class FacilityTurretControl(turret: FacilityTurret)
}
protected def AutomaticOperationFunctionalityChecks: Boolean = {
AutomaticOperationFunctionalityChecksExceptMounting && !TurretObject.Seats.values.exists(_.isOccupied)
}
protected def AutomaticOperationFunctionalityChecksExceptMounting: Boolean = {
isPowered &&
!JammableObject.Jammed &&
TurretObject.Health > TurretObject.Definition.DamageDisablesAt &&
!TurretObject.Seats.values.exists(_.isOccupied)
TurretObject.Health > TurretObject.Definition.DamageDisablesAt
}
private def primaryWeaponFireModeOnly(): Unit = {
@ -224,8 +232,8 @@ class FacilityTurretControl(turret: FacilityTurret)
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?
AutomatedTurretBehavior.startTracking(attacker, channel, guid, List(attacker.GUID))
AutomatedTurretBehavior.stopTracking(attacker, channel, guid) //TODO delay by a few milliseconds?
}
}
}

View file

@ -26,7 +26,7 @@ final case class Automation(
targetEliminationCooldown: Long = 0L, //ms
revertToDefaultFireMode: Boolean = true
) {
assert(targetDetectionRange > targetTriggerRange, "trigger range must be less or equal to detection range")
assert(targetDetectionRange > targetTriggerRange, "trigger range must be less than detection range")
}
/**