mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
making specific target validation conditions for different auto turrets, also target blanking, and clarification of how the self-reporting mode cleansup after itself; wrote function documentation to make it all make sense (it doesn't)
This commit is contained in:
parent
02ad42743c
commit
1ff0577db7
|
|
@ -3,8 +3,9 @@ package net.psforever.actors.session.support
|
|||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.objects.definition.ProjectileDefinition
|
||||
import net.psforever.objects.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior}
|
||||
import net.psforever.objects.zones.Zoning
|
||||
import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior, VanuSentry}
|
||||
import net.psforever.objects.serverobject.turret.VanuSentry
|
||||
import net.psforever.objects.zones.exp.ToDatabase
|
||||
|
||||
import scala.collection.mutable
|
||||
|
|
@ -470,7 +471,7 @@ private[support] class WeaponAndProjectileOperations(
|
|||
val hitPos = target.Position + Vector3.z(value = 1f)
|
||||
ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile =>
|
||||
addShotsLanded(resprojectile.cause.attribution, shots = 1)
|
||||
//sessionData.handleDealingDamage(target, resprojectile)
|
||||
sessionData.handleDealingDamage(target, resprojectile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition
|
|||
import net.psforever.objects.serverobject.structures.{AmenityDefinition, AutoRepairStats, BuildingDefinition, WarpGateDefinition}
|
||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalDefinition
|
||||
import net.psforever.objects.serverobject.terminals.implant.{ImplantTerminalDefinition, ImplantTerminalMechDefinition}
|
||||
import net.psforever.objects.serverobject.turret.{Automation, FacilityTurretDefinition, TurretUpgrade}
|
||||
import net.psforever.objects.serverobject.turret.{Automation, AutoChecks, FacilityTurretDefinition, AutoRanges, TurretUpgrade}
|
||||
import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, UtilityType, VehicleSubsystemEntry}
|
||||
import net.psforever.objects.vital.base.DamageType
|
||||
import net.psforever.objects.vital.damage._
|
||||
|
|
@ -9074,11 +9074,19 @@ object GlobalDefinitions {
|
|||
spitfire_turret.Model = ComplexDeployableResolutions.calculate
|
||||
spitfire_turret.deployAnimation = DeployAnimation.Standard
|
||||
spitfire_turret.AutoFire = Automation(
|
||||
targetDetectionRange = 60f,
|
||||
targetTriggerRange = 50f,
|
||||
targetEscapeRange = 50f,
|
||||
targetValidation = List(EffectTarget.Validation.PlayerOnRadar, EffectTarget.Validation.Vehicle),
|
||||
retaliatoryDuration = 8000L,
|
||||
AutoRanges(
|
||||
detection = 75f,
|
||||
trigger = 50f,
|
||||
escape = 50f
|
||||
),
|
||||
AutoChecks(
|
||||
validation = List(
|
||||
EffectTarget.Validation.PlayerDetectedBySpitfireTurret,
|
||||
EffectTarget.Validation.GroundVehicleDetectedByAutoTurret,
|
||||
EffectTarget.Validation.AircraftDetectedByAutoTurret
|
||||
)
|
||||
),
|
||||
retaliatoryDelay = 2000L, //8000L
|
||||
refireTime = 200.milliseconds //150.milliseconds
|
||||
)
|
||||
spitfire_turret.innateDamage = new DamageWithPosition {
|
||||
|
|
@ -9108,11 +9116,19 @@ object GlobalDefinitions {
|
|||
spitfire_cloaked.deployAnimation = DeployAnimation.Standard
|
||||
spitfire_cloaked.Model = ComplexDeployableResolutions.calculate
|
||||
spitfire_cloaked.AutoFire = Automation(
|
||||
targetDetectionRange = 50f,
|
||||
targetTriggerRange = 30f,
|
||||
targetEscapeRange = 50f,
|
||||
targetValidation = List(EffectTarget.Validation.PlayerOnRadar, EffectTarget.Validation.VehiclesOnRadar),
|
||||
retaliatoryDuration = 8000L,
|
||||
AutoRanges(
|
||||
detection = 75f,
|
||||
trigger = 50f,
|
||||
escape = 75f
|
||||
),
|
||||
AutoChecks(
|
||||
validation = List(
|
||||
EffectTarget.Validation.PlayerDetectedBySpitfireTurret,
|
||||
EffectTarget.Validation.GroundVehicleDetectedByAutoTurret,
|
||||
EffectTarget.Validation.AircraftDetectedByAutoTurret
|
||||
)
|
||||
),
|
||||
retaliatoryDelay = 1L, //8000L
|
||||
refireTime = 200.milliseconds //150.milliseconds
|
||||
)
|
||||
spitfire_cloaked.innateDamage = new DamageWithPosition {
|
||||
|
|
@ -9142,15 +9158,19 @@ object GlobalDefinitions {
|
|||
spitfire_aa.deployAnimation = DeployAnimation.Standard
|
||||
spitfire_aa.Model = ComplexDeployableResolutions.calculate
|
||||
spitfire_aa.AutoFire = Automation(
|
||||
targetDetectionRange = 100f,
|
||||
targetTriggerRange = 90f,
|
||||
targetEscapeRange = 200f,
|
||||
targetValidation = List(EffectTarget.Validation.AircraftOnRadar),
|
||||
retaliatoryDuration = 2000L,
|
||||
AutoRanges(
|
||||
detection = 125f,
|
||||
trigger = 100f,
|
||||
escape = 200f
|
||||
),
|
||||
AutoChecks(
|
||||
validation = List(EffectTarget.Validation.AircraftDetectedByAutoTurret)
|
||||
),
|
||||
retaliatoryDelay = 2000L, //8000L
|
||||
retaliationOverridesTarget = false,
|
||||
refireTime = 350.milliseconds, //300.milliseconds
|
||||
cylindricalCheck = true,
|
||||
cylindricalHeight = 25f
|
||||
refireTime = 0.seconds, //300.milliseconds
|
||||
cylindrical = true,
|
||||
cylindricalExtraHeight = 50f
|
||||
)
|
||||
spitfire_aa.innateDamage = new DamageWithPosition {
|
||||
CausesDamageType = DamageType.One
|
||||
|
|
@ -10054,14 +10074,22 @@ object GlobalDefinitions {
|
|||
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,
|
||||
detectionSpeed = 2.seconds,
|
||||
AutoRanges(
|
||||
detection = 125f,
|
||||
trigger = 100f,
|
||||
escape = 200f
|
||||
),
|
||||
AutoChecks(
|
||||
validation = List(
|
||||
EffectTarget.Validation.MaxDetectedByAutoTurret,
|
||||
EffectTarget.Validation.GroundVehicleDetectedByAutoTurret,
|
||||
EffectTarget.Validation.AircraftDetectedByAutoTurret
|
||||
)
|
||||
),
|
||||
retaliatoryDelay = 4000L, //8000L
|
||||
cylindrical = true,
|
||||
cylindricalExtraHeight = 50f,
|
||||
detectionSweepTime = 2.seconds,
|
||||
refireTime = 362.milliseconds //312.milliseconds
|
||||
)
|
||||
manned_turret.innateDamage = new DamageWithPosition {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@ import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
|
|||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior, MountableTurretControl, TurretDefinition, WeaponTurret}
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior}
|
||||
import net.psforever.objects.serverobject.turret.{MountableTurretControl, TurretDefinition, WeaponTurret}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.damage.DamageCalculations
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance}
|
||||
|
|
@ -31,10 +32,11 @@ class TurretDeployable(tdef: TurretDeployableDefinition)
|
|||
|
||||
def TurretOwner: SourceEntry = {
|
||||
Seats
|
||||
.values
|
||||
.headOption
|
||||
.collect { case (_, a) => a }
|
||||
.flatMap(_.occupant)
|
||||
.map(SourceEntry(_))
|
||||
.map(p => PlayerSource.inSeat(PlayerSource(p), SourceEntry(this), seatNumber=0))
|
||||
.orElse(Owners.map(PlayerSource(_, Position)))
|
||||
.getOrElse(SourceEntry(this))
|
||||
}
|
||||
|
||||
|
|
@ -86,6 +88,7 @@ class TurretControl(turret: TurretDeployable)
|
|||
override def postStop(): Unit = {
|
||||
super.postStop()
|
||||
deployableBehaviorPostStop()
|
||||
selfReportingDatabaseUpdate()
|
||||
automaticTurretPostStop()
|
||||
}
|
||||
|
||||
|
|
@ -111,7 +114,7 @@ class TurretControl(turret: TurretDeployable)
|
|||
override def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = {
|
||||
val startsUnjammed = !JammableObject.Jammed
|
||||
super.TryJammerEffectActivate(target, cause)
|
||||
if (startsUnjammed && JammableObject.Jammed && AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDuration > 0)) {
|
||||
if (startsUnjammed && JammableObject.Jammed && AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDelay > 0)) {
|
||||
AutomaticOperation = false
|
||||
//look in direction of cause of jamming
|
||||
val zone = JammableObject.Zone
|
||||
|
|
@ -172,7 +175,7 @@ class TurretControl(turret: TurretDeployable)
|
|||
|
||||
override def finalizeDeployable(callback: ActorRef): Unit = {
|
||||
super.finalizeDeployable(callback)
|
||||
AutomaticOperation = true
|
||||
AutomaticOperation = AutomaticOperationFunctionalityChecks
|
||||
}
|
||||
|
||||
override def unregisterDeployable(obj: Deployable): Unit = {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ 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.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior}
|
||||
import net.psforever.objects.zones.blockmap.SectorPopulation
|
||||
import net.psforever.objects.zones.{InteractsWithZone, ZoneInteraction, ZoneInteractionType}
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
|
|
@ -80,7 +80,7 @@ object InteractWithTurrets {
|
|||
GlobalDefinitions.manned_turret
|
||||
)
|
||||
.flatMap(_.AutoFire)
|
||||
.map(_.targetDetectionRange)
|
||||
.map(_.ranges.detection)
|
||||
.max
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
package net.psforever.objects.equipment
|
||||
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.ce.DeployableCategory
|
||||
import net.psforever.objects.ce.{DeployableCategory, DeployedItem}
|
||||
import net.psforever.objects.serverobject.turret.FacilityTurret
|
||||
import net.psforever.objects.vital.DamagingActivity
|
||||
import net.psforever.types.{ExoSuitType, ImplantType}
|
||||
import net.psforever.objects.zones.blockmap.SectorPopulation
|
||||
import net.psforever.types.{DriveState, ExoSuitType, ImplantType, LatticeBenefit, PlanetSideEmpire, Vector3}
|
||||
|
||||
final case class TargetValidation(category: EffectTarget.Category.Value, test: EffectTarget.Validation.Value)
|
||||
|
||||
|
|
@ -188,47 +189,169 @@ object EffectTarget {
|
|||
false
|
||||
}
|
||||
|
||||
def PlayerOnRadar(target: PlanetSideGameObject): Boolean =
|
||||
def PlayerDetectedBySpitfireTurret(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.Cloaked)) ||
|
||||
p.Jumping
|
||||
val now = System.currentTimeMillis()
|
||||
val pos = p.Position
|
||||
val faction = p.Faction
|
||||
val sector = p.Zone.blockMap.sector(p.Position, range = 51f)
|
||||
lazy val tookDamage = p.LastDamage.exists(dam => dam.adversarial.nonEmpty && now - dam.interaction.hitTime < 2000L)
|
||||
//todo equipment-use usually a violation for any equipment type
|
||||
lazy val usedEquipment = (p.Holsters().flatMap(_.Equipment) ++ p.Inventory.Items.map(_.obj))
|
||||
.collect {
|
||||
case t: Tool
|
||||
if !(t.Projectile == GlobalDefinitions.no_projectile || t.Projectile.GrenadeProjectile || t.Size == EquipmentSize.Melee) =>
|
||||
now - t.LastDischarge
|
||||
}
|
||||
.exists(_ < 2000L)
|
||||
lazy val silentRunActive = p.avatar.implants.flatten.find(a => a.definition.implantType == ImplantType.SilentRun).exists(_.active)
|
||||
lazy val movingFast = p.isMoving(test = 17d) || p.Jumping
|
||||
p.VehicleSeated.isEmpty &&
|
||||
(if (radarCloakedAms(sector, pos) || radarCloakedAegis(sector, pos)) false
|
||||
else if (radarCloakedSensor(sector, pos, faction)) tookDamage || usedEquipment
|
||||
else if (radarEnhancedInterlink(sector, pos, faction) || radarEnhancedSensor(sector, pos, faction)) true
|
||||
else tookDamage || usedEquipment || !silentRunActive && movingFast)
|
||||
case _ =>
|
||||
false
|
||||
})
|
||||
|
||||
def MaxOnRadar(target: PlanetSideGameObject): Boolean =
|
||||
!target.Destroyed && (target match {
|
||||
case p: Player =>
|
||||
p.ExoSuit == ExoSuitType.MAX && p.isMoving(test = 17d)
|
||||
case _ =>
|
||||
false
|
||||
})
|
||||
|
||||
def VehiclesOnRadar(target: PlanetSideGameObject): Boolean =
|
||||
!target.Destroyed && (target match {
|
||||
case v: Vehicle =>
|
||||
val vdef = v.Definition
|
||||
!(v.MountedIn.nonEmpty ||
|
||||
v.Cloaked ||
|
||||
GlobalDefinitions.isAtvVehicle(vdef) ||
|
||||
vdef == GlobalDefinitions.two_man_assault_buggy ||
|
||||
vdef == GlobalDefinitions.skyguard)
|
||||
case _ =>
|
||||
false
|
||||
})
|
||||
|
||||
|
||||
|
||||
def AircraftOnRadar(target: PlanetSideGameObject): Boolean =
|
||||
def PlayerUndetectedByAutoTurret(target: PlanetSideGameObject): Boolean =
|
||||
target match {
|
||||
case v: Vehicle =>
|
||||
GlobalDefinitions.isFlightVehicle(v.Definition) && v.Health > 0 && !v.Cloaked
|
||||
case p: Player =>
|
||||
val pos = p.Position
|
||||
val sector = p.Zone.blockMap.sector(p.Position, range = 51f)
|
||||
p.VehicleSeated.nonEmpty || radarCloakedAms(sector, pos) || radarCloakedAegis(sector, pos)
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
||||
def MaxDetectedByAutoTurret(target: PlanetSideGameObject): Boolean =
|
||||
!target.Destroyed && (target match {
|
||||
case p: Player =>
|
||||
val now = System.currentTimeMillis()
|
||||
val pos = p.Position
|
||||
val faction = p.Faction
|
||||
val sector = p.Zone.blockMap.sector(p.Position, range = 51f)
|
||||
lazy val tookDamage = p.LastDamage.exists(dam => dam.adversarial.nonEmpty && now - dam.interaction.hitTime < 2000L)
|
||||
lazy val usedEquipment = p.Holsters().flatMap(_.Equipment)
|
||||
.collect { case t: Tool => now - t.LastDischarge }
|
||||
.exists(_ < 2000L)
|
||||
lazy val movingFast = p.isMoving(test = 17d)
|
||||
p.ExoSuit == ExoSuitType.MAX &&
|
||||
p.VehicleSeated.isEmpty &&
|
||||
(if (radarCloakedAms(sector, pos) || radarCloakedAegis(sector, pos)) false
|
||||
else if (radarCloakedSensor(sector, pos, faction)) tookDamage || usedEquipment
|
||||
else if (radarEnhancedInterlink(sector, pos, faction) || radarEnhancedSensor(sector, pos, faction)) true
|
||||
else tookDamage || usedEquipment || movingFast)
|
||||
case _ =>
|
||||
false
|
||||
})
|
||||
|
||||
def GroundVehicleDetectedByAutoTurret(target: PlanetSideGameObject): Boolean =
|
||||
!target.Destroyed && (target match {
|
||||
case v: Vehicle =>
|
||||
val now = System.currentTimeMillis()
|
||||
val vdef = v.Definition
|
||||
lazy val tookDamage = v.LastDamage.exists(dam => dam.adversarial.nonEmpty && now - dam.interaction.hitTime< 2000L)
|
||||
lazy val usedEquipment = v.Weapons.values.flatMap(_.Equipment)
|
||||
.collect { case t: Tool => now - t.LastDischarge }
|
||||
.exists(_ < 2000L)
|
||||
!GlobalDefinitions.isFlightVehicle(vdef) && v.MountedIn.isEmpty && (
|
||||
if (vdef == GlobalDefinitions.ams && v.DeploymentState == DriveState.Deployed) false
|
||||
else if (
|
||||
v.Cloaked ||
|
||||
GlobalDefinitions.isAtvVehicle(vdef) ||
|
||||
vdef == GlobalDefinitions.two_man_assault_buggy ||
|
||||
vdef == GlobalDefinitions.skyguard
|
||||
) tookDamage || usedEquipment
|
||||
else true)
|
||||
case _ =>
|
||||
false
|
||||
})
|
||||
|
||||
def VehicleUndetectedByAutoTurret(target: PlanetSideGameObject): Boolean =
|
||||
target match {
|
||||
case v: Vehicle =>
|
||||
(v.Definition == GlobalDefinitions.ams && v.DeploymentState == DriveState.Deployed) || v.MountedIn.nonEmpty || v.Cloaked
|
||||
case _ =>
|
||||
false
|
||||
}
|
||||
|
||||
def AircraftDetectedByAutoTurret(target: PlanetSideGameObject): Boolean =
|
||||
!target.Destroyed && (target match {
|
||||
case v: Vehicle =>
|
||||
GlobalDefinitions.isFlightVehicle(v.Definition) && !v.Cloaked
|
||||
case _ =>
|
||||
false
|
||||
})
|
||||
}
|
||||
|
||||
private def radarEnhancedInterlink(
|
||||
sector: SectorPopulation,
|
||||
position: Vector3,
|
||||
faction: PlanetSideEmpire.Value
|
||||
): Boolean = {
|
||||
sector.buildingList.collect {
|
||||
case b =>
|
||||
b.Faction != faction &&
|
||||
b.hasLatticeBenefit(LatticeBenefit.InterlinkFacility) &&
|
||||
Vector3.DistanceSquared(b.Position, position).toDouble < math.pow(b.Definition.SOIRadius.toDouble, 2d)
|
||||
}.contains(true)
|
||||
}
|
||||
|
||||
private def radarEnhancedSensor(
|
||||
sector: SectorPopulation,
|
||||
position: Vector3,
|
||||
faction: PlanetSideEmpire.Value
|
||||
): Boolean = {
|
||||
sector.deployableList.collect {
|
||||
case d: SensorDeployable =>
|
||||
!d.Destroyed &&
|
||||
d.Definition.Item == DeployedItem.motionalarmsensor &&
|
||||
d.Faction != faction &&
|
||||
!d.Jammed && Vector3.DistanceSquared(d.Position, position) < 2500f
|
||||
}.contains(true)
|
||||
}
|
||||
|
||||
private def radarCloakedAms(
|
||||
sector: SectorPopulation,
|
||||
position: Vector3
|
||||
): Boolean = {
|
||||
sector.vehicleList.collect {
|
||||
case v =>
|
||||
!v.Destroyed &&
|
||||
v.Definition == GlobalDefinitions.ams &&
|
||||
v.DeploymentState == DriveState.Deployed &&
|
||||
!v.Jammed &&
|
||||
Vector3.DistanceSquared(v.Position, position) < 144f
|
||||
}.contains(true)
|
||||
}
|
||||
|
||||
private def radarCloakedAegis(
|
||||
sector: SectorPopulation,
|
||||
position: Vector3
|
||||
): Boolean = {
|
||||
sector.deployableList.collect {
|
||||
case d: ShieldGeneratorDeployable =>
|
||||
!d.Destroyed &&
|
||||
!d.Jammed &&
|
||||
Vector3.DistanceSquared(d.Position, position) < 100f
|
||||
}.contains(true)
|
||||
}
|
||||
|
||||
private def radarCloakedSensor(
|
||||
sector: SectorPopulation,
|
||||
position: Vector3,
|
||||
faction: PlanetSideEmpire.Value
|
||||
): Boolean = {
|
||||
sector.deployableList.collect {
|
||||
case d: SensorDeployable =>
|
||||
!d.Destroyed &&
|
||||
d.Definition.Item == DeployedItem.sensor_shield &&
|
||||
d.Faction == faction &&
|
||||
!d.Jammed &&
|
||||
Vector3.DistanceSquared(d.Position, position) < 900f
|
||||
}.contains(true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.turret
|
|||
import net.psforever.objects.equipment.JammableUnit
|
||||
import net.psforever.objects.serverobject.structures.{Amenity, AmenityOwner, Building}
|
||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware
|
||||
import net.psforever.objects.serverobject.turret.auto.AutomatedTurret
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.types.Vector3
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import net.psforever.objects.serverobject.mount.Mountable
|
|||
import net.psforever.objects.serverobject.repair.AmenityAutoRepair
|
||||
import net.psforever.objects.serverobject.structures.PoweredAmenityControl
|
||||
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAwareBehavior
|
||||
import net.psforever.objects.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior}
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.packet.game.ChangeFireModeMessage
|
||||
import net.psforever.services.Service
|
||||
|
|
@ -39,6 +40,8 @@ class FacilityTurretControl(turret: FacilityTurret)
|
|||
|
||||
private var testToResetToDefaultFireMode: Boolean = false
|
||||
|
||||
AutomaticOperation = AutomaticOperationFunctionalityChecks
|
||||
|
||||
override def postStop(): Unit = {
|
||||
super.postStop()
|
||||
damageableWeaponTurretPostStop()
|
||||
|
|
@ -126,6 +129,7 @@ class FacilityTurretControl(turret: FacilityTurret)
|
|||
super.DestructionAwareness(target, cause)
|
||||
tryAutoRepair()
|
||||
AutomaticOperation = false
|
||||
selfReportingCleanUp()
|
||||
}
|
||||
|
||||
override def PerformRepairs(target : Damageable.Target, amount : Int) : Int = {
|
||||
|
|
@ -224,7 +228,7 @@ class FacilityTurretControl(turret: FacilityTurret)
|
|||
override def TryJammerEffectActivate(target: Any, cause: DamageResult): Unit = {
|
||||
val startsUnjammed = !JammableObject.Jammed
|
||||
super.TryJammerEffectActivate(target, cause)
|
||||
if (startsUnjammed && JammableObject.Jammed && AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDuration > 0)) {
|
||||
if (startsUnjammed && JammableObject.Jammed && AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDelay > 0)) {
|
||||
AutomaticOperation = false
|
||||
//look in direction of cause of jamming
|
||||
val zone = JammableObject.Zone
|
||||
|
|
|
|||
|
|
@ -11,25 +11,60 @@ import scala.collection.mutable
|
|||
import scala.concurrent.duration._
|
||||
|
||||
final case class Automation(
|
||||
targetDetectionRange: Float, //m
|
||||
targetTriggerRange: Float, //m
|
||||
targetEscapeRange: Float, //m
|
||||
targetValidation: List[PlanetSideGameObject => Boolean],
|
||||
cylindricalCheck: Boolean = false,
|
||||
cylindricalHeight: Float = 0, //m
|
||||
retaliatoryDuration: Long = 0, //ms
|
||||
ranges: AutoRanges,
|
||||
checks: AutoChecks,
|
||||
/** the boundary for target searching is typically a sphere of `ranges.detection` radius;
|
||||
* instead, takes the shape of a cylinder of `ranges.detection` radius and height */
|
||||
cylindrical: Boolean = false,
|
||||
/** if target searching is performed in the shape of a cylinder,
|
||||
* add height on top of the cylinder's normal height */
|
||||
cylindricalExtraHeight: Float = 0, //m
|
||||
/** how long after the last target engagement
|
||||
* or how long into the current target engagement
|
||||
* before the turret may counterattack damage;
|
||||
* set to `0L` to never retaliate */
|
||||
retaliatoryDelay: Long = 0, //ms
|
||||
/** if the turret has a current target,
|
||||
* allow for retaliation against a different target */
|
||||
retaliationOverridesTarget: Boolean = true,
|
||||
initialDetectionSpeed: FiniteDuration = Duration.Zero,
|
||||
detectionSpeed: FiniteDuration = 1.seconds,
|
||||
targetSelectCooldown: Long = 1500L, //ms
|
||||
missedShotCooldown: Long = 3000L, //ms
|
||||
targetEliminationCooldown: Long = 0L, //ms
|
||||
/** frequency at which the turret will test target for reachability */
|
||||
detectionSweepTime: FiniteDuration = 1.seconds,
|
||||
cooldowns: AutoCooldowns = AutoCooldowns(),
|
||||
/** if the turret weapon has multiple fire modes,
|
||||
* revert to the base fire mode before engaging in target testing or other automatic operations */
|
||||
revertToDefaultFireMode: Boolean = true,
|
||||
/** the simulated weapon fire rate for self-reporting (internal damage loop) */
|
||||
refireTime: FiniteDuration = 1.seconds //60rpm
|
||||
)
|
||||
|
||||
final case class AutoRanges(
|
||||
/** distance at which a target is first noticed */
|
||||
detection: Float, //m
|
||||
/** distance at which the target is tested */
|
||||
trigger: Float, //m
|
||||
/** distance away from the source of damage before the turret stops engaging */
|
||||
escape: Float //m
|
||||
) {
|
||||
assert(targetDetectionRange > targetTriggerRange, "trigger range must be less than detection range")
|
||||
assert(detection >= trigger, "detection range must be greater than or equal to trigger range")
|
||||
assert(escape >= trigger, "escape range must be greater than or equal to trigger range")
|
||||
}
|
||||
|
||||
final case class AutoChecks(
|
||||
/** reasons why this target should be engaged */
|
||||
validation: List[PlanetSideGameObject => Boolean],
|
||||
/** reasons why an ongoing target engagement should be stopped */
|
||||
blanking: List[PlanetSideGameObject => Boolean] = Nil
|
||||
)
|
||||
|
||||
final case class AutoCooldowns(
|
||||
/** when the target gets switched (generic) */
|
||||
targetSelect: Long = 1500L, //ms
|
||||
/** when the target escapes being damaged */
|
||||
missedShot: Long = 3000L, //ms
|
||||
/** when the target gets destroyed during an ongoing engagement */
|
||||
targetElimination: Long = 0L //ms
|
||||
)
|
||||
|
||||
/**
|
||||
* The definition for any `WeaponTurret`.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.serverobject.turret
|
||||
package net.psforever.objects.serverobject.turret.auto
|
||||
|
||||
import akka.actor.Actor
|
||||
import net.psforever.objects.Tool
|
||||
|
|
@ -20,23 +20,22 @@ import net.psforever.types.Vector3
|
|||
*/
|
||||
trait AffectedByAutomaticTurretFire extends Damageable {
|
||||
_: Actor =>
|
||||
|
||||
def AffectedObject: AutomatedTurret.Target
|
||||
|
||||
val takeAutomatedDamage: Receive = {
|
||||
case AffectedByAutomaticTurretFire.AiDamage(turret) =>
|
||||
case AiDamage(turret) =>
|
||||
performAutomatedDamage(turret)
|
||||
}
|
||||
|
||||
private def performAutomatedDamage(turret: AutomatedTurret): Unit = {
|
||||
protected def performAutomatedDamage(turret: AutomatedTurret): Unit = {
|
||||
val target = AffectedObject
|
||||
val tool = turret.Weapons.values.head.Equipment.collect { case t : Tool => t }.get
|
||||
val tool = turret.Weapons.values.head.Equipment.collect { case t: Tool => t }.get
|
||||
val projectileInfo = tool.Projectile
|
||||
val targetPos = target.Position
|
||||
val turretPos = turret.Position
|
||||
val correctedTargetPosition = targetPos + Vector3.z(value = 1f)
|
||||
val angle = Vector3.Unit(targetPos - turretPos)
|
||||
turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target, Some(SourceEntry(target)))
|
||||
turret.Actor ! SelfReportedConfirmShot(target)
|
||||
val projectile = new Projectile(
|
||||
projectileInfo,
|
||||
tool.Definition,
|
||||
|
|
@ -63,7 +62,3 @@ trait AffectedByAutomaticTurretFire extends Damageable {
|
|||
PerformDamage(target, resolvedProjectile.calculate())
|
||||
}
|
||||
}
|
||||
|
||||
object AffectedByAutomaticTurretFire {
|
||||
case class AiDamage(turret: AutomatedTurret)
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.serverobject.turret
|
||||
package net.psforever.objects.serverobject.turret.auto
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.turret.{TurretDefinition, WeaponTurret}
|
||||
import net.psforever.objects.sourcing.{SourceEntry, SourceUniqueness}
|
||||
import net.psforever.objects.vital.Vitality
|
||||
|
||||
|
|
@ -14,6 +15,12 @@ trait AutomatedTurret
|
|||
|
||||
private var targets: List[Target] = List[Target]()
|
||||
|
||||
/**
|
||||
* The entity that claims responsibility for the actions of the turret
|
||||
* or has authoritative management over the turret.
|
||||
* When no one else steps up to the challenge, the turret can be its own person.
|
||||
* @return owner entity
|
||||
*/
|
||||
def TurretOwner: SourceEntry
|
||||
|
||||
def Target: Option[Target] = currentTarget
|
||||
|
|
@ -1,14 +1,19 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.serverobject.turret
|
||||
package net.psforever.objects.serverobject.turret.auto
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.{Default, Player, Vehicle}
|
||||
import net.psforever.objects.avatar.scoring.EquipmentStat
|
||||
import net.psforever.objects.equipment.EffectTarget
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.damage.DamageableEntity
|
||||
import net.psforever.objects.sourcing.{SourceEntry, SourceUniqueness}
|
||||
import net.psforever.objects.serverobject.damage.{Damageable, DamageableEntity}
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.serverobject.turret.Automation
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, SourceUniqueness}
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.objects.zones.exp.ToDatabase
|
||||
import net.psforever.objects.zones.{InteractsWithZone, Zone}
|
||||
import net.psforever.objects.{Default, PlanetSideGameObject, Player, Vehicle}
|
||||
import net.psforever.types.{PlanetSideGUID, Vector3}
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
|
@ -23,20 +28,26 @@ trait AutomatedTurretBehavior {
|
|||
private var automaticOperation: Boolean = false
|
||||
/** quick reference of the current target, if any */
|
||||
private var currentTargetToken: Option[SourceUniqueness] = None
|
||||
/** time of the current target's selection or the last target's selection */
|
||||
private var currentTargetSwitchTime: Long = 0L
|
||||
/** time of the last confirmed shot hitting the target */
|
||||
private var currentTargetLastShotTime: Long = 0L
|
||||
/** game world position when the last shot's confirmation was recorded */
|
||||
private var currentTargetLocation: Option[Vector3] = None
|
||||
/** targets queued during evaluation of the "next test", and targets to be being confirmed during "this test" */
|
||||
private var previousTestedTargets: Set[Target] = Set()
|
||||
/** timer managing the available target qualifications test
|
||||
* whether or not a previously valid target is still a valid target */
|
||||
private var periodicValidationTest: Cancellable = Default.Cancellable
|
||||
|
||||
/** timer managing the trailing target qualifications self test
|
||||
* where the a source will shot directly at some target */
|
||||
private var selfReportedRefire: Cancellable = Default.Cancellable
|
||||
|
||||
private var confirmShotFunc: Target=>Unit = normalConfirmShot
|
||||
/** self-reported weapon fire produces projectiles that were shot;
|
||||
* due to the call and response nature of this mode, they also count as shots that were landed */
|
||||
private var shotsFired: Int = 0
|
||||
/** self-reported weapon fire produces targets that were eliminated;
|
||||
* due to the call and response nature of this mode, they also count as shots that were landed;
|
||||
* this may duplicate information processed during some other database update call */
|
||||
private var targetsDestroyed: Int = 0
|
||||
|
||||
def AutomatedTurretObject: AutomatedTurret
|
||||
|
||||
|
|
@ -44,11 +55,11 @@ trait AutomatedTurretBehavior {
|
|||
case AutomatedTurretBehavior.Alert(target) =>
|
||||
bringAttentionToTarget(target)
|
||||
|
||||
case AutomatedTurretBehavior.ConfirmShot(target, None) =>
|
||||
confirmShotFunc(target)
|
||||
|
||||
case AutomatedTurretBehavior.ConfirmShot(target, _) =>
|
||||
confirmShotFunc(target)
|
||||
normalConfirmShot(target)
|
||||
|
||||
case SelfReportedConfirmShot(target) =>
|
||||
movementCancelSelfReportingFireConfirmShot(target)
|
||||
|
||||
case AutomatedTurretBehavior.Unalert(target) =>
|
||||
disregardTarget(target)
|
||||
|
|
@ -166,8 +177,6 @@ trait AutomatedTurretBehavior {
|
|||
AutomatedTurretObject.Target.foreach(noLongerEngageDetectedTarget)
|
||||
AutomatedTurretObject.Target = None
|
||||
AutomatedTurretObject.Clear()
|
||||
previousTestedTargets.foreach(noLongerEngageTestedTarget)
|
||||
previousTestedTargets = Set()
|
||||
currentTargetToken = None
|
||||
currentTargetLocation = None
|
||||
}
|
||||
|
|
@ -181,18 +190,26 @@ trait AutomatedTurretBehavior {
|
|||
* and, as a result, a message is sent to the turret to encourage it to continue to shoot.
|
||||
* If there is no primary target yet, this target becomes primary.
|
||||
* @param target something the turret can potentially shoot at
|
||||
* @return `true`, if the target submitted was recognized by the turret;
|
||||
* `false`, if the target can not be the current target
|
||||
*/
|
||||
private def normalConfirmShot(target: Target): Unit = {
|
||||
private def normalConfirmShot(target: Target): Boolean = {
|
||||
val now = System.currentTimeMillis()
|
||||
if (currentTargetToken.isEmpty) {
|
||||
currentTargetLastShotTime = now
|
||||
currentTargetLocation = Some(target.Position)
|
||||
cancelSelfReportedAutoFire()
|
||||
engageNewDetectedTarget(target)
|
||||
true
|
||||
} else if (
|
||||
currentTargetToken.contains(SourceEntry(target).unique) &&
|
||||
now - currentTargetLastShotTime < autoStats.map(_.missedShotCooldown).getOrElse(0L)) {
|
||||
now - currentTargetLastShotTime < autoStats.map(_.cooldowns.missedShot).getOrElse(0L)) {
|
||||
currentTargetLastShotTime = now
|
||||
currentTargetLocation = Some(target.Position)
|
||||
cancelSelfReportedAutoFire()
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -207,10 +224,9 @@ trait AutomatedTurretBehavior {
|
|||
protected def engageNewDetectedTarget(target: Target): Unit = {
|
||||
val zone = target.Zone
|
||||
val zoneid = zone.id
|
||||
previousTestedTargets.filterNot(_ == target).foreach(noLongerEngageTestedTarget)
|
||||
previousTestedTargets = Set(target)
|
||||
currentTargetToken = Some(SourceEntry(target).unique)
|
||||
currentTargetLocation = Some(target.Position)
|
||||
currentTargetSwitchTime = System.currentTimeMillis()
|
||||
AutomatedTurretObject.Target = target
|
||||
AutomatedTurretBehavior.startTracking(target, zoneid, AutomatedTurretObject.GUID, List(target.GUID))
|
||||
AutomatedTurretBehavior.startShooting(target, zoneid, AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID)
|
||||
|
|
@ -280,30 +296,20 @@ trait AutomatedTurretBehavior {
|
|||
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 radius = autoStats.get.ranges.trigger
|
||||
val validation = autoStats.get.checks.validation
|
||||
val faction = AutomatedTurretObject.Faction
|
||||
//noinspection CollectHeadOption
|
||||
val (targets, forValidation) = AutomatedTurretObject
|
||||
val selectedTargets = AutomatedTurretObject
|
||||
.Targets
|
||||
.collect { case target
|
||||
if /*target.Faction != faction &&*/
|
||||
AutomatedTurretBehavior.shapedDistanceCheckAgainstValue(autoStats, target.Position, turretPosition, radius, result = -1) &&
|
||||
validation.exists(func => func(target)) =>
|
||||
validation.exists(func => func(target))=>
|
||||
target
|
||||
}
|
||||
.sortBy(target => Vector3.DistanceSquared(target.Position, turretPosition))
|
||||
.flatMap { processForTestingDetectedTarget(_, turretGuid, weaponGuid) }
|
||||
.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
|
||||
selectedTargets.foreach(processForTestingDetectedTarget(_, turretGuid, weaponGuid))
|
||||
selectedTargets.headOption
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -363,7 +369,7 @@ trait AutomatedTurretBehavior {
|
|||
private def performDistanceCheck(): List[Target] = {
|
||||
//cull targets
|
||||
val pos = AutomatedTurretObject.Position
|
||||
val range = autoStats.map(_.targetDetectionRange).getOrElse(0f)
|
||||
val range = autoStats.map(_.ranges.detection).getOrElse(0f)
|
||||
val removedTargets = AutomatedTurretObject.Targets
|
||||
.collect {
|
||||
case t: InteractsWithZone
|
||||
|
|
@ -388,10 +394,10 @@ trait AutomatedTurretBehavior {
|
|||
generalDecayCheck(
|
||||
target,
|
||||
now,
|
||||
autoStats.map(_.targetEscapeRange).getOrElse(400f),
|
||||
autoStats.map(_.targetSelectCooldown).getOrElse(3000L),
|
||||
autoStats.map(_.missedShotCooldown).getOrElse(3000L),
|
||||
autoStats.map(_.targetEliminationCooldown).getOrElse(0L)
|
||||
autoStats.map(_.ranges.escape).getOrElse(400f),
|
||||
autoStats.map(_.cooldowns.targetSelect).getOrElse(3000L),
|
||||
autoStats.map(_.cooldowns.missedShot).getOrElse(3000L),
|
||||
autoStats.map(_.cooldowns.targetElimination).getOrElse(0L)
|
||||
)
|
||||
}
|
||||
.orElse {
|
||||
|
|
@ -409,10 +415,11 @@ trait AutomatedTurretBehavior {
|
|||
* An important process loop in the target engagement and target management of an automated turret.
|
||||
* If a target has been selected, perform a test to determine whether it remains the selected ("current") target.
|
||||
* If the target has been destroyed,
|
||||
* moved beyond the turret's maximum engagement range,
|
||||
* no longer qualifies as a target due to an internal or external change,
|
||||
* has moved beyond the turret's maximum engagement range,
|
||||
* or has been missing for a certain amount of time,
|
||||
* declare the the turret should no longer be shooting at (whatever) it (was).
|
||||
* Apply appropriate cooldown 6to instruct the turret to wait before attempting to select a new current target.
|
||||
* Apply appropriate cooldown to instruct the turret to wait before attempting to select a new current target.
|
||||
* @param target something the turret can potentially shoot at
|
||||
* @return something the turret can potentially shoot at
|
||||
*/
|
||||
|
|
@ -425,11 +432,17 @@ trait AutomatedTurretBehavior {
|
|||
eliminationDelay: Long
|
||||
): Option[Target] = {
|
||||
if (target.Destroyed) {
|
||||
//if the target died while we were shooting at it
|
||||
//if the target died or is no longer considered a valid target while we were shooting at it
|
||||
cancelSelfReportedAutoFire()
|
||||
noLongerEngageDetectedTarget(target)
|
||||
currentTargetLastShotTime = now + eliminationDelay
|
||||
None
|
||||
} else if ((AutomatedTurretBehavior.commonBlanking ++ autoStats.map(_.checks.blanking).getOrElse(Nil)).exists(func => func(target))) {
|
||||
//if the target, while being engaged, stops counting as a valid target
|
||||
cancelSelfReportedAutoFire()
|
||||
noLongerEngageDetectedTarget(target)
|
||||
currentTargetLastShotTime = now + selectDelay
|
||||
None
|
||||
} else if (AutomatedTurretBehavior.shapedDistanceCheckAgainstValue(autoStats, target.Position, AutomatedTurretObject.Position, escapeRange)) {
|
||||
//if the target made sufficient distance from the turret
|
||||
cancelSelfReportedAutoFire()
|
||||
|
|
@ -469,7 +482,7 @@ trait AutomatedTurretBehavior {
|
|||
*/
|
||||
private def retimePeriodicTargetChecks(beforeSize: Int): Boolean = {
|
||||
if (beforeSize == 0 && AutomatedTurretObject.Targets.nonEmpty && autoStats.isDefined) {
|
||||
val repeated = autoStats.map(_.detectionSpeed).getOrElse(0.seconds)
|
||||
val repeated = autoStats.map(_.detectionSweepTime).getOrElse(1.seconds)
|
||||
retimePeriodicTargetChecks(repeated)
|
||||
true
|
||||
} else {
|
||||
|
|
@ -509,9 +522,32 @@ trait AutomatedTurretBehavior {
|
|||
* It's like nothing ever happened.
|
||||
* @see `Actor.postStop`
|
||||
*/
|
||||
def automaticTurretPostStop(): Unit = {
|
||||
protected def automaticTurretPostStop(): Unit = {
|
||||
resetAlerts()
|
||||
AutomatedTurretObject.Targets.foreach { AutomatedTurretObject.RemoveTarget }
|
||||
selfReportingCleanUp()
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup for the variables involved in self-reporting.
|
||||
* Set them to zero.
|
||||
*/
|
||||
protected def selfReportingCleanUp(): Unit = {
|
||||
shotsFired = 0
|
||||
targetsDestroyed = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* The self-reporting mode for automatic turrets produces weapon fire data that should be sent to the database.
|
||||
* The targets destroyed from self-reported fire are also logged to the database.
|
||||
*/
|
||||
protected def selfReportingDatabaseUpdate(): Unit = {
|
||||
AutomatedTurretObject.TurretOwner match {
|
||||
case p: PlayerSource =>
|
||||
val weaponId = AutomatedTurretObject.Weapons.values.head.Equipment.map(_.Definition.ObjectId).getOrElse(0)
|
||||
ToDatabase.reportToolDischarge(p.CharId, EquipmentStat(weaponId, shotsFired, shotsFired, targetsDestroyed, 0))
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -522,7 +558,11 @@ trait AutomatedTurretBehavior {
|
|||
* @return something the turret can potentially shoot at
|
||||
*/
|
||||
protected def attemptRetaliation(target: Target, cause: DamageResult): Option[Target] = {
|
||||
if (automaticOperation && autoStats.exists(_.retaliatoryDuration > 0)) {
|
||||
if (
|
||||
automaticOperation &&
|
||||
!currentTargetToken.contains(SourceEntry(target).unique) &&
|
||||
autoStats.exists(_.retaliatoryDelay > 0)
|
||||
) {
|
||||
AutomatedTurretBehavior.getAttackVectorFromCause(target.Zone, cause).collect {
|
||||
case attacker if attacker.Faction != target.Faction =>
|
||||
performRetaliation(attacker)
|
||||
|
|
@ -542,7 +582,11 @@ trait AutomatedTurretBehavior {
|
|||
private def performRetaliation(target: Target): Option[Target] = {
|
||||
AutomatedTurretObject.Target
|
||||
.collect {
|
||||
case existingTarget if autoStats.exists(_.retaliationOverridesTarget) =>
|
||||
case existingTarget
|
||||
if autoStats.exists { auto =>
|
||||
auto.retaliationOverridesTarget &&
|
||||
currentTargetSwitchTime + auto.retaliatoryDelay > System.currentTimeMillis()
|
||||
} =>
|
||||
cancelSelfReportedAutoFire()
|
||||
noLongerEngageDetectedTarget(existingTarget)
|
||||
engageNewDetectedTarget(target)
|
||||
|
|
@ -565,7 +609,14 @@ trait AutomatedTurretBehavior {
|
|||
* @param target something the turret can potentially shoot at
|
||||
*/
|
||||
private def movementCancelSelfReportingFireConfirmShot(target: Target): Unit = {
|
||||
normalConfirmShot(target)
|
||||
currentTargetLastShotTime = System.currentTimeMillis()
|
||||
shotsFired += 1
|
||||
target match {
|
||||
case v: Damageable with Mountable
|
||||
if v.Destroyed && v.Seats.values.exists(_.isOccupied) =>
|
||||
targetsDestroyed += 1
|
||||
case _ => ()
|
||||
}
|
||||
if (currentTargetLocation.exists(loc => Vector3.DistanceSquared(loc, target.Position) > 1f)) {
|
||||
cancelSelfReportedAutoFire()
|
||||
noLongerEngageDetectedTarget(target)
|
||||
|
|
@ -575,7 +626,7 @@ trait AutomatedTurretBehavior {
|
|||
AutomatedTurretObject.Weapons.values.head.Equipment.get.GUID
|
||||
)
|
||||
} else {
|
||||
tryStartSelfReportedAutofire(target)
|
||||
tryPerformSelfReportedAutofire(target)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -592,7 +643,9 @@ trait AutomatedTurretBehavior {
|
|||
private def trySelfReportedAutofireIfStationary(): Boolean = {
|
||||
AutomatedTurretObject.Target
|
||||
.collect {
|
||||
case target if currentTargetLocation.exists(loc => Vector3.DistanceSquared(loc, target.Position) > 1f) =>
|
||||
case target
|
||||
if currentTargetLocation.exists(loc => Vector3.DistanceSquared(loc, target.Position) > 1f) &&
|
||||
autoStats.exists(_.refireTime > 0.seconds) =>
|
||||
trySelfReportedAutofireTest(target)
|
||||
}
|
||||
.getOrElse(false)
|
||||
|
|
@ -606,8 +659,7 @@ trait AutomatedTurretBehavior {
|
|||
*/
|
||||
private def trySelfReportedAutofireTest(target: Target): Boolean = {
|
||||
if (selfReportedRefire.isCancelled) {
|
||||
confirmShotFunc = movementCancelSelfReportingFireConfirmShot
|
||||
target.Actor ! AffectedByAutomaticTurretFire.AiDamage(AutomatedTurretObject)
|
||||
target.Actor ! AiDamage(AutomatedTurretObject)
|
||||
true
|
||||
} else {
|
||||
false
|
||||
|
|
@ -621,14 +673,13 @@ trait AutomatedTurretBehavior {
|
|||
* @return `true`, if the self-reporting operation was initiated;
|
||||
* `false`, otherwise
|
||||
*/
|
||||
private def tryStartSelfReportedAutofire(target: Target): Boolean = {
|
||||
private def tryPerformSelfReportedAutofire(target: Target): Boolean = {
|
||||
if (selfReportedRefire.isCancelled) {
|
||||
confirmShotFunc = movementCancelSelfReportingFireConfirmShot
|
||||
selfReportedRefire = context.system.scheduler.scheduleWithFixedDelay(
|
||||
0.seconds,
|
||||
autoStats.map(_.refireTime).getOrElse(1.second),
|
||||
autoStats.map(_.refireTime).getOrElse(1.seconds),
|
||||
target.Actor,
|
||||
AffectedByAutomaticTurretFire.AiDamage(AutomatedTurretObject)
|
||||
AiDamage(AutomatedTurretObject)
|
||||
)
|
||||
true
|
||||
} else {
|
||||
|
|
@ -638,13 +689,13 @@ trait AutomatedTurretBehavior {
|
|||
|
||||
/**
|
||||
* Stop directly communicating with a target to simulate weapons fire damage.
|
||||
* Utilized as a p[art of the auto-fire reset process.
|
||||
* @return `true`, because we can not fail
|
||||
* @see `Default.Cancellable`
|
||||
*/
|
||||
private def cancelSelfReportedAutoFire(): Boolean = {
|
||||
selfReportedRefire.cancel()
|
||||
selfReportedRefire = Default.Cancellable
|
||||
confirmShotFunc = normalConfirmShot
|
||||
true
|
||||
}
|
||||
}
|
||||
|
|
@ -661,6 +712,11 @@ object AutomatedTurretBehavior {
|
|||
|
||||
private case object PeriodicCheck
|
||||
|
||||
final val commonBlanking: List[PlanetSideGameObject => Boolean] = List(
|
||||
EffectTarget.Validation.PlayerUndetectedByAutoTurret,
|
||||
EffectTarget.Validation.VehicleUndetectedByAutoTurret
|
||||
)
|
||||
|
||||
/**
|
||||
* Are we tracking a `Vehicle` entity?
|
||||
* or, is it some other kind of entity?
|
||||
|
|
@ -801,8 +857,8 @@ object AutomatedTurretBehavior {
|
|||
result: Int = 1 //by default, calculation > input
|
||||
): Boolean = {
|
||||
val testRangeSq = range * range
|
||||
if (stats.exists(_.cylindricalCheck)) {
|
||||
val height = range + stats.map(_.cylindricalHeight).getOrElse(0f)
|
||||
if (stats.exists(_.cylindrical)) {
|
||||
val height = range + stats.map(_.cylindricalExtraHeight).getOrElse(0f)
|
||||
math.abs(positionA.z - positionB.z).compareTo(height) == result &&
|
||||
Vector3.DistanceSquared(positionA.xy, positionB.xy).compareTo(testRangeSq) == result
|
||||
} else {
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.serverobject.turret
|
||||
package net.psforever.objects.serverobject.turret.auto
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import net.psforever.objects.serverobject.turret.AutomatedTurret.Target
|
||||
import net.psforever.objects.serverobject.turret.auto.AutomatedTurret.Target
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game.{ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ObjectDetectedMessage}
|
||||
import net.psforever.services.Service
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.serverobject.turret.auto
|
||||
|
||||
private[auto] case class AiDamage(turret: AutomatedTurret)
|
||||
|
||||
private[auto] case class SelfReportedConfirmShot(target: AutomatedTurret.Target)
|
||||
|
|
@ -20,7 +20,7 @@ import net.psforever.objects.serverobject.hackable.GenericHackables
|
|||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
|
||||
import net.psforever.objects.serverobject.repair.RepairableVehicle
|
||||
import net.psforever.objects.serverobject.terminals.Terminal
|
||||
import net.psforever.objects.serverobject.turret.AffectedByAutomaticTurretFire
|
||||
import net.psforever.objects.serverobject.turret.auto.AffectedByAutomaticTurretFire
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
|
|
|
|||
Loading…
Reference in a new issue