From fc2ce70aae7434495d0c38eb6ac3cd4159d68939 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Mon, 1 Jan 2024 15:20:29 -0500 Subject: [PATCH] separated overworld facility turrets from cavern facility turrets, called vanu sentry turrets; tightened functionality inheritance between turret deployables and facility turrets; corrected issue where recharging vehicle weapons didn't work --- .../support/SessionLocalHandlers.scala | 1 + .../WeaponAndProjectileOperations.scala | 19 +- .../psforever/objects/TurretDeployable.scala | 68 +---- .../turret/AutomatedTurretBehavior.scala | 38 ++- .../serverobject/turret/FacilityTurret.scala | 15 +- .../turret/FacilityTurretControl.scala | 247 ++++++++++-------- .../turret/MountableTurretControl.scala | 72 +++++ .../turret/TurretDefinition.scala | 3 +- .../turret/VanuSentryControl.scala | 91 +++++++ .../scala/net/psforever/zones/Zones.scala | 15 +- 10 files changed, 389 insertions(+), 180 deletions(-) create mode 100644 src/main/scala/net/psforever/objects/serverobject/turret/MountableTurretControl.scala create mode 100644 src/main/scala/net/psforever/objects/serverobject/turret/VanuSentryControl.scala diff --git a/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala index 6d78bd875..f0dc4f03f 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionLocalHandlers.scala @@ -208,6 +208,7 @@ class SessionLocalHandlers( continent.GUID(vehicleGuid) .collect { case vehicle: MountableWeapons => (vehicle, vehicle.PassengerInSeat(player)) } .collect { case (vehicle, Some(seat_num)) => vehicle.WeaponControlledFromSeat(seat_num) } + .getOrElse(Set.empty) .collect { case weapon: Tool if weapon.GUID == weaponGuid => sendResponse(InventoryStateMessage(weapon.AmmoSlot.Box.GUID, weapon.GUID, weapon.Magazine)) } diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala index 217347880..101c3445f 100644 --- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala +++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala @@ -3,7 +3,7 @@ package net.psforever.actors.session.support import akka.actor.{ActorContext, typed} import net.psforever.objects.zones.Zoning -import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior} +import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior, VanuSentry} import net.psforever.objects.zones.exp.ToDatabase import scala.collection.mutable @@ -52,8 +52,7 @@ private[support] class WeaponAndProjectileOperations( private[support] var shotsWhileDead: Int = 0 private val projectiles: Array[Option[Projectile]] = Array.fill[Option[Projectile]](Projectile.rangeUID - Projectile.baseUID)(None) - private var zoningOpt: Option[ZoningOperations] = None - def zoning: ZoningOperations = zoningOpt.orNull + /* packets */ def handleWeaponFire(pkt: WeaponFireMessage): Unit = { @@ -569,11 +568,6 @@ private[support] class WeaponAndProjectileOperations( ) continent.Projectile ! ZoneProjectile.Add(player.GUID, qualityprojectile) } - obj match { - case turret: FacilityTurret if turret.Definition == GlobalDefinitions.vanu_sentry_turret => - turret.Actor ! FacilityTurret.WeaponDischarged() - case _ => () - } } else { log.warn( s"WeaponFireMessage: ${player.Name}'s ${tool.Definition.Name} projectile is too far from owner position at time of discharge ($distanceToOwner > $acceptableDistanceToOwner); suspect" @@ -1224,6 +1218,10 @@ private[support] class WeaponAndProjectileOperations( } private def fireStateStartMountedMessages(itemGuid: PlanetSideGUID): Unit = { + sessionData.findContainedEquipment()._1.collect { + case turret: FacilityTurret if continent.map.cavern => + turret.Actor ! VanuSentry.ChangeFireStart + } continent.VehicleEvents ! VehicleServiceMessage( continent.id, VehicleAction.ChangeFireState_Start(player.GUID, itemGuid) @@ -1286,6 +1284,10 @@ private[support] class WeaponAndProjectileOperations( } private def fireStateStopMountedMessages(itemGuid: PlanetSideGUID): Unit = { + sessionData.findContainedEquipment()._1.collect { + case turret: FacilityTurret if continent.map.cavern => + turret.Actor ! VanuSentry.ChangeFireStop + } continent.VehicleEvents ! VehicleServiceMessage( continent.id, VehicleAction.ChangeFireState_Stop(player.GUID, itemGuid) @@ -1416,6 +1418,7 @@ private[support] class WeaponAndProjectileOperations( addShotsToMap(shotsFired, weaponId, shots) } + //noinspection SameParameterValue private def addShotsLanded(weaponId: Int, shots: Int): Unit = { addShotsToMap(shotsLanded, weaponId, shots) } diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala index 5d5e86333..af6bcd069 100644 --- a/src/main/scala/net/psforever/objects/TurretDeployable.scala +++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala @@ -5,20 +5,17 @@ import akka.actor.{Actor, ActorContext, ActorRef, Props} import net.psforever.objects.ce.{Deployable, DeployableBehavior, DeployedItem} import net.psforever.objects.definition.DeployableDefinition import net.psforever.objects.definition.converter.SmallTurretConverter -import net.psforever.objects.equipment.{JammableMountedWeapons, JammableUnit} +import net.psforever.objects.equipment.JammableUnit import net.psforever.objects.guid.{GUIDTask, TaskWorkflow} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior import net.psforever.objects.serverobject.damage.Damageable.Target -import net.psforever.objects.serverobject.damage.DamageableWeaponTurret import net.psforever.objects.serverobject.hackable.Hackable -import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} -import net.psforever.objects.serverobject.repair.RepairableWeaponTurret -import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior, TurretDefinition, WeaponTurret} +import net.psforever.objects.serverobject.mount.Mountable +import net.psforever.objects.serverobject.turret.{AutomatedTurret, AutomatedTurretBehavior, MountableTurretControl, TurretDefinition, WeaponTurret} import net.psforever.objects.vital.damage.DamageCalculations import net.psforever.objects.vital.interaction.DamageResult -import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance, Vitality} -import net.psforever.objects.zones.Zone +import net.psforever.objects.vital.{SimpleResolutions, StandardVehicleResistance} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import scala.concurrent.duration.FiniteDuration @@ -65,11 +62,9 @@ class TurretControl(turret: TurretDeployable) extends Actor with DeployableBehavior with FactionAffinityBehavior.Check - with JammableMountedWeapons //note: jammable status is reported as vehicle events, not local events - with MountableBehavior - with DamageableWeaponTurret - with RepairableWeaponTurret + with MountableTurretControl with AutomatedTurretBehavior { + def TurretObject: TurretDeployable = turret def DeployableObject: TurretDeployable = turret def MountableObject: TurretDeployable = turret def JammableObject: TurretDeployable = turret @@ -81,18 +76,14 @@ class TurretControl(turret: TurretDeployable) override def postStop(): Unit = { super.postStop() deployableBehaviorPostStop() - damageableWeaponTurretPostStop() automaticTurretPostStop() } def receive: Receive = - deployableBehavior + commonBehavior + .orElse(deployableBehavior) .orElse(checkBehavior) - .orElse(jammableBehavior) .orElse(mountBehavior) - .orElse(dismountBehavior) - .orElse(takesDamage) - .orElse(canBeRepairedByNanoDispenser) .orElse(automatedTurretBehavior) .orElse { case _ => () @@ -112,7 +103,7 @@ class TurretControl(turret: TurretDeployable) AutomaticOperation = false //look in direction of cause of jamming val zone = JammableObject.Zone - TurretControl.getAttackerFromCause(zone, cause).foreach { + AutomatedTurretBehavior.getAttackerFromCause(zone, cause).foreach { attacker => val channel = zone.id val guid = AutomatedTurretObject.GUID @@ -131,7 +122,7 @@ class TurretControl(turret: TurretDeployable) override protected def DamageAwareness(target: Target, cause: DamageResult, amount: Any): Unit = { if (AutomaticOperation && AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDuration > 0)) { //turret retribution - TurretControl.getAttackerFromCause(target.Zone, cause).collect { + AutomatedTurretBehavior.getAttackerFromCause(target.Zone, cause).collect { case attacker if attacker.Faction != target.Faction => engageNewDetectedTarget(attacker) } @@ -141,7 +132,7 @@ class TurretControl(turret: TurretDeployable) override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = { AutomaticOperation = false - super.DestructionAwareness(target, cause) + //super.DestructionAwareness(target, cause) CancelJammeredSound(target) CancelJammeredStatus(target) Deployables.AnnounceDestroyDeployable(turret, None) @@ -184,40 +175,3 @@ class TurretControl(turret: TurretDeployable) TaskWorkflow.execute(GUIDTask.unregisterDeployableTurret(zone.GUID, turret)) } } - -object TurretControl { - private def getAttackerFromCause(zone: Zone, cause: DamageResult): Option[PlanetSideServerObject with Vitality] = { - import net.psforever.objects.sourcing._ - cause - .interaction - .adversarial - .collect { adversarial => - adversarial.attacker match { - case p: PlayerSource => - p.seatedIn - .map { _._1.unique } - .collect { - case v: UniqueVehicle => zone.Vehicles.find(SourceEntry(_).unique == v) - case a: UniqueAmenity => zone.GUID(a.guid) - case d: UniqueDeployable => zone.DeployableList.find(SourceEntry(_).unique == d) - } - .flatten - .orElse { - val name = p.Name - zone.LivePlayers.find(_.Name.equals(name)) - } - case o => - o.unique match { - case v: UniqueVehicle => zone.Vehicles.find(SourceEntry(_).unique == v) - case a: UniqueAmenity => zone.GUID(a.guid) - case d: UniqueDeployable => zone.DeployableList.find(SourceEntry(_).unique == d) - case _ => None - } - } - } - .flatten - .collect { - case out: PlanetSideServerObject with Vitality => out - } - } -} diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/AutomatedTurretBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/turret/AutomatedTurretBehavior.scala index f8cfa8dc8..585a43062 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/AutomatedTurretBehavior.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/AutomatedTurretBehavior.scala @@ -9,6 +9,7 @@ import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.damage.DamageableEntity import net.psforever.objects.sourcing.{SourceEntry, SourceUniqueness} import net.psforever.objects.vital.Vitality +import net.psforever.objects.vital.interaction.DamageResult import net.psforever.objects.zones.{InteractsWithZone, Zone} import net.psforever.packet.game.{ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ObjectDetectedMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage} @@ -206,7 +207,7 @@ trait AutomatedTurretBehavior { None } - private def trySelectNewTarget(): Option[Target] = { + protected def trySelectNewTarget(): Option[Target] = { currentTarget.orElse { val turretPosition = AutomatedTurretObject.Position val radiusSquared = autoStats.get.targetingRange * autoStats.get.targetingRange @@ -358,4 +359,39 @@ object AutomatedTurretBehavior { LocalAction.SendResponse(ChangeFireStateMessage_Stop(weaponGuid)) ) } + + def getAttackerFromCause(zone: Zone, cause: DamageResult): Option[PlanetSideServerObject with Vitality] = { + import net.psforever.objects.sourcing._ + cause + .interaction + .adversarial + .collect { adversarial => + adversarial.attacker match { + case p: PlayerSource => + p.seatedIn + .map { _._1.unique } + .collect { + case v: UniqueVehicle => zone.Vehicles.find(SourceEntry(_).unique == v) + case a: UniqueAmenity => zone.GUID(a.guid) + case d: UniqueDeployable => zone.DeployableList.find(SourceEntry(_).unique == d) + } + .flatten + .orElse { + val name = p.Name + zone.LivePlayers.find(_.Name.equals(name)) + } + case o => + o.unique match { + case v: UniqueVehicle => zone.Vehicles.find(SourceEntry(_).unique == v) + case a: UniqueAmenity => zone.GUID(a.guid) + case d: UniqueDeployable => zone.DeployableList.find(SourceEntry(_).unique == d) + case _ => None + } + } + } + .flatten + .collect { + case out: PlanetSideServerObject with Vitality => out + } + } } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala index ae056a587..ef5cf67ff 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala @@ -2,17 +2,25 @@ package net.psforever.objects.serverobject.turret import net.psforever.objects.equipment.JammableUnit -import net.psforever.objects.serverobject.structures.Amenity +import net.psforever.objects.serverobject.structures.{Amenity, AmenityOwner, Building} import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware import net.psforever.types.Vector3 class FacilityTurret(tDef: FacilityTurretDefinition) extends Amenity - with WeaponTurret + with AutomatedTurret with JammableUnit with CaptureTerminalAware { WeaponTurret.LoadDefinition(this) + override def Owner: AmenityOwner = { + if (Zone.map.cavern) { + Building.NoBuilding + } else { + super.Owner + } + } + def Definition: FacilityTurretDefinition = tDef } @@ -27,9 +35,6 @@ object FacilityTurret { new FacilityTurret(tDef) } - final case class RechargeAmmo() - final case class WeaponDischarged() - import akka.actor.ActorContext /** diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala index b0e58c59d..b41b61190 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurretControl.scala @@ -1,26 +1,21 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.turret -import akka.actor.Cancellable -import net.psforever.objects.{Default, GlobalDefinitions, Player, Tool} -import net.psforever.objects.equipment.{Ammo, JammableMountedWeapons} +import net.psforever.objects.entity.WorldEntity +import net.psforever.objects.{GlobalDefinitions, Player, Tool} +import net.psforever.objects.equipment.Ammo import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} -import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} -import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior -import net.psforever.objects.serverobject.damage.{Damageable, DamageableWeaponTurret} +import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.hackable.GenericHackables -import net.psforever.objects.serverobject.hackable.GenericHackables.getTurretUpgradeTime -import net.psforever.objects.serverobject.repair.{AmenityAutoRepair, RepairableWeaponTurret} +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.vital.interaction.DamageResult -import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} -import net.psforever.services.local.{LocalAction, LocalServiceMessage} +import net.psforever.packet.game.ChangeFireModeMessage +import net.psforever.services.Service import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} -import scala.concurrent.ExecutionContext.Implicits.global -import scala.concurrent.duration._ - /** * An `Actor` that handles messages being dispatched to a specific `MannedTurret`.
*
@@ -31,133 +26,106 @@ import scala.concurrent.duration._ * @param turret the `MannedTurret` object being governed */ class FacilityTurretControl(turret: FacilityTurret) - extends PoweredAmenityControl - with FactionAffinityBehavior.Check - with MountableBehavior - with DamageableWeaponTurret - with RepairableWeaponTurret + extends PoweredAmenityControl with AmenityAutoRepair - with JammableMountedWeapons + with MountableTurretControl + with AutomatedTurretBehavior with CaptureTerminalAwareBehavior { + def TurretObject: FacilityTurret = turret def FactionObject: FacilityTurret = turret def MountableObject: FacilityTurret = turret def JammableObject: FacilityTurret = turret def DamageableObject: FacilityTurret = turret def RepairableObject: FacilityTurret = turret def AutoRepairObject: FacilityTurret = turret + def AutomatedTurretObject: FacilityTurret = turret def CaptureTerminalAwareObject: FacilityTurret = turret - // Used for timing ammo recharge for vanu turrets in caves - var weaponAmmoRechargeTimer: Cancellable = Default.Cancellable + private var testToResetToDefaultFireMode: Boolean = false override def postStop(): Unit = { super.postStop() damageableWeaponTurretPostStop() + automaticTurretPostStop() stopAutoRepair() } - def commonBehavior: Receive = - checkBehavior - .orElse(jammableBehavior) - .orElse(dismountBehavior) - .orElse(takesDamage) - .orElse(canBeRepairedByNanoDispenser) - .orElse(autoRepairBehavior) - .orElse(captureTerminalAwareBehaviour) + private val upgradeableTurret: Receive = { + case CommonMessages.Use(player, Some((item: Tool, upgradeValue: Int))) + if player.Faction == TurretObject.Faction && + item.Definition == GlobalDefinitions.nano_dispenser && item.AmmoType == Ammo.upgrade_canister && + item.Magazine > 0 && TurretObject.Seats.values.forall(!_.isOccupied) => + TurretUpgrade.values.find(_.id == upgradeValue).foreach { + case upgrade + if TurretObject.Upgrade != upgrade && TurretObject.Definition.WeaponPaths.values + .flatMap(_.keySet) + .exists(_ == upgrade) => + TurretObject.setMiddleOfUpgrade(true) + sender() ! CommonMessages.Progress( + 1.25f, + WeaponTurrets.FinishUpgradingMannedTurret(TurretObject, player, item, upgrade), + GenericHackables.TurretUpgradingTickAction(progressType = 2, player, TurretObject, item.GUID) + ) + } + } - def poweredStateLogic: Receive = + override def commonBehavior: Receive = super.commonBehavior + .orElse(automatedTurretBehavior) + .orElse(captureTerminalAwareBehaviour) + + override def poweredStateLogic: Receive = commonBehavior .orElse(mountBehavior) + .orElse(upgradeableTurret) .orElse { - case CommonMessages.Use(player, Some((item: Tool, upgradeValue: Int))) - if player.Faction == turret.Faction && - item.Definition == GlobalDefinitions.nano_dispenser && item.AmmoType == Ammo.upgrade_canister && - item.Magazine > 0 && turret.Seats.values.forall(!_.isOccupied) => - TurretUpgrade.values.find(_.id == upgradeValue) match { - case Some(upgrade) - if turret.Upgrade != upgrade && turret.Definition.WeaponPaths.values - .flatMap(_.keySet) - .exists(_ == upgrade) => - turret.setMiddleOfUpgrade(true) - sender() ! CommonMessages.Progress( - 1.25f, - WeaponTurrets.FinishUpgradingMannedTurret(turret, player, item, upgrade), - GenericHackables.TurretUpgradingTickAction(progressType = 2, player, turret, item.GUID) - ) - case _ => ; - } - - case FacilityTurret.WeaponDischarged() => - if (weaponAmmoRechargeTimer != Default.Cancellable) { - weaponAmmoRechargeTimer.cancel() - } - - weaponAmmoRechargeTimer = context.system.scheduler.scheduleWithFixedDelay( - 3 seconds, - 200 milliseconds, - self, - FacilityTurret.RechargeAmmo() - ) - - case FacilityTurret.RechargeAmmo() => - turret.ControlledWeapon(wepNumber = 1).foreach { - case weapon: Tool => - // recharge when last shot fired 3s delay, +1, 200ms interval - if (weapon.Magazine < weapon.MaxMagazine && System.currentTimeMillis() - weapon.LastDischarge > 3000L) { - weapon.Magazine += 1 - val seat = turret.Seat(0).get - seat.occupant match { - case Some(player : Player) => - turret.Zone.LocalEvents ! LocalServiceMessage( - turret.Zone.id, - LocalAction.RechargeVehicleWeapon(player.GUID, turret.GUID, weapon.GUID) - ) - case _ => ; - } - } - else if (weapon.Magazine == weapon.MaxMagazine && weaponAmmoRechargeTimer != Default.Cancellable) { - weaponAmmoRechargeTimer.cancel() - weaponAmmoRechargeTimer = Default.Cancellable - } - case _ => ; - } - - case _ => ; + case _ => () } - def unpoweredStateLogic: Receive = + override def unpoweredStateLogic: Receive = commonBehavior .orElse { - case _ => ; + case _ => () } override protected def mountTest( obj: PlanetSideServerObject with Mountable, seatNumber: Int, player: Player): Boolean = { - (!turret.Definition.FactionLocked || player.Faction == obj.Faction) && !obj.Destroyed && !turret.isUpgrading || - System.currentTimeMillis() - getTurretUpgradeTime >= 1500L + super.mountTest(obj, seatNumber, player) && + (!TurretObject.isUpgrading || System.currentTimeMillis() - GenericHackables.getTurretUpgradeTime >= 1500L) && + AutomaticOperation_=(state = false) + } + + override protected def dismountTest(obj: Mountable with WorldEntity, seatNumber: Int, user: Player): Boolean = { + super.dismountTest(obj, seatNumber, user) && + AutomaticOperation_=(autoTurretFunctionalityChecks) } override protected def DamageAwareness(target: Damageable.Target, cause: DamageResult, amount: Any) : Unit = { tryAutoRepair() + if (AutomaticOperation) { + if (TurretObject.Health < TurretObject.Definition.DamageDisablesAt) { + AutomaticOperation = false + } else if (AutomatedTurretObject.Definition.AutoFire.exists(_.retaliatoryDuration > 0)) { + //turret retribution + AutomatedTurretBehavior.getAttackerFromCause(target.Zone, cause).collect { + case attacker if attacker.Faction != target.Faction => + engageNewDetectedTarget(attacker) + } + } + } super.DamageAwareness(target, cause, amount) } override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { - tryAutoRepair() super.DestructionAwareness(target, cause) - val zone = target.Zone - val zoneId = zone.id - val events = zone.AvatarEvents - val tguid = target.GUID - events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 1)) - events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 1)) + tryAutoRepair() + AutomaticOperation = false } override def PerformRepairs(target : Damageable.Target, amount : Int) : Int = { val newHealth = super.PerformRepairs(target, amount) - if(newHealth == target.Definition.MaxHealth) { + if (newHealth == target.Definition.MaxHealth) { stopAutoRepair() } newHealth @@ -165,12 +133,7 @@ class FacilityTurretControl(turret: FacilityTurret) override def Restoration(obj: Damageable.Target): Unit = { super.Restoration(obj) - val zone = turret.Zone - val zoneId = zone.id - val events = zone.AvatarEvents - val tguid = turret.GUID - events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 0)) - events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 0)) + AutomaticOperation = autoTurretFunctionalityChecks } override def tryAutoRepair() : Boolean = { @@ -179,12 +142,13 @@ class FacilityTurretControl(turret: FacilityTurret) def powerTurnOffCallback(): Unit = { stopAutoRepair() + AutomaticOperation = false //kick all occupants - val guid = turret.GUID - val zone = turret.Zone + val guid = TurretObject.GUID + val zone = TurretObject.Zone val zoneId = zone.id val events = zone.VehicleEvents - turret.Seats.values.foreach(seat => + TurretObject.Seats.values.foreach(seat => seat.occupant match { case Some(player) => seat.unmount(player) @@ -192,12 +156,83 @@ class FacilityTurretControl(turret: FacilityTurret) if (player.HasGUID) { events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, unk2=false, guid)) } - case None => ; + case None => () } ) } def powerTurnOnCallback(): Unit = { tryAutoRepair() + AutomaticOperation = autoTurretFunctionalityChecks + } + + override def AutomaticOperation_=(state: Boolean): Boolean = { + val result = super.AutomaticOperation_=(state) + testToResetToDefaultFireMode = result && TurretObject.Definition.AutoFire.exists(_.revertToDefaultFireMode) + result + } + + private def autoTurretFunctionalityChecks: Boolean = { + isPowered && + !JammableObject.Jammed && + TurretObject.Health > TurretObject.Definition.DamageDisablesAt && + !TurretObject.Seats.values.exists(_.isOccupied) + } + + private def primaryWeaponFireModeOnly(): Unit = { + if (testToResetToDefaultFireMode) { + val zone = TurretObject.Zone + val zoneid = zone.id + val events = zone.VehicleEvents + TurretObject.Weapons.values + .flatMap(_.Equipment) + .collect { case weapon: Tool if weapon.FireModeIndex > 0 => + weapon.FireModeIndex = 0 + events ! VehicleServiceMessage( + zoneid, + VehicleAction.SendResponse(Service.defaultPlayerGUID, ChangeFireModeMessage(weapon.GUID, 0)) + ) + } + } + testToResetToDefaultFireMode = false + } + + override protected def trySelectNewTarget(): Option[AutomatedTurret.Target] = { + if (autoTurretFunctionalityChecks) { + primaryWeaponFireModeOnly() + super.trySelectNewTarget() + } else { + None + } + } + + override def engageNewDetectedTarget(target: AutomatedTurret.Target): Unit = { + if (autoTurretFunctionalityChecks) { + primaryWeaponFireModeOnly() + super.engageNewDetectedTarget(target) + } + } + + 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)) { + AutomaticOperation = false + //look in direction of cause of jamming + val zone = JammableObject.Zone + AutomatedTurretBehavior.getAttackerFromCause(zone, cause).foreach { + attacker => + val channel = zone.id + val guid = AutomatedTurretObject.GUID + AutomatedTurretBehavior.startTrackingTargets(zone, channel, guid, List(attacker.GUID)) + AutomatedTurretBehavior.stopTrackingTargets(zone, channel, guid) //TODO delay by a few milliseconds? + } + } + } + + override def CancelJammeredStatus(target: Any): Unit = { + val startsJammed = JammableObject.Jammed + super.CancelJammeredStatus(target) + startsJammed && AutomaticOperation_=(autoTurretFunctionalityChecks) } } diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/MountableTurretControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/MountableTurretControl.scala new file mode 100644 index 000000000..f677798d6 --- /dev/null +++ b/src/main/scala/net/psforever/objects/serverobject/turret/MountableTurretControl.scala @@ -0,0 +1,72 @@ +// Copyright (c) 2023 PSForever +package net.psforever.objects.serverobject.turret + +import akka.actor.Actor +import net.psforever.objects.Player +import net.psforever.objects.equipment.JammableMountedWeapons +import net.psforever.objects.serverobject.PlanetSideServerObject +import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior +import net.psforever.objects.serverobject.damage.{Damageable, DamageableWeaponTurret} +import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior} +import net.psforever.objects.serverobject.repair.RepairableWeaponTurret +import net.psforever.objects.vital.interaction.DamageResult +import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} + +trait MountableTurretControl + extends FactionAffinityBehavior.Check + with MountableBehavior + with DamageableWeaponTurret + with RepairableWeaponTurret + with JammableMountedWeapons + with Actor { /* note: jammable status is reported as vehicle events, not local events */ + def TurretObject: PlanetSideServerObject with WeaponTurret + + override def postStop(): Unit = { + super.postStop() + damageableWeaponTurretPostStop() + } + + /** commonBehavior does not implement mountingBehavior; please do so when implementing */ + def commonBehavior: Receive = + checkBehavior + .orElse(jammableBehavior) + .orElse(dismountBehavior) + .orElse(takesDamage) + .orElse(canBeRepairedByNanoDispenser) + + override protected def mountTest( + obj: PlanetSideServerObject with Mountable, + seatNumber: Int, + player: Player): Boolean = { + (!TurretObject.Definition.FactionLocked || player.Faction == obj.Faction) && !obj.Destroyed + } + + /** + * An override for `Restoration`, best for facility turrets. + * @param obj the entity being restored + */ + override def Restoration(obj: Damageable.Target): Unit = { + super.Restoration(obj) + val zone = TurretObject.Zone + val zoneId = zone.id + val events = zone.AvatarEvents + val tguid = TurretObject.GUID + events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 0)) + events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 0)) + } + + /** + * An override for `DamageAwareness`, best for facility turrets. + * @param target the entity being destroyed + * @param cause historical information about the damage + */ + override protected def DestructionAwareness(target: Damageable.Target, cause: DamageResult): Unit = { + super.DestructionAwareness(target, cause) + val zone = target.Zone + val zoneId = zone.id + val events = zone.AvatarEvents + val tguid = target.GUID + events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 50, 1)) + events ! AvatarServiceMessage(zoneId, AvatarAction.PlanetsideAttributeToAll(tguid, 51, 1)) + } +} diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala index 17d26c01b..77c4dffba 100644 --- a/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala +++ b/src/main/scala/net/psforever/objects/serverobject/turret/TurretDefinition.scala @@ -18,7 +18,8 @@ final case class Automation( detectionSpeed: FiniteDuration = 1.seconds, targetSelectCooldown: Long = 1500L, //ms missedShotCooldown: Long = 3000L, //ms - targetEliminationCooldown: Long = 0L //ms + targetEliminationCooldown: Long = 0L, //ms + revertToDefaultFireMode: Boolean = true ) /** diff --git a/src/main/scala/net/psforever/objects/serverobject/turret/VanuSentryControl.scala b/src/main/scala/net/psforever/objects/serverobject/turret/VanuSentryControl.scala new file mode 100644 index 000000000..6f0f32d36 --- /dev/null +++ b/src/main/scala/net/psforever/objects/serverobject/turret/VanuSentryControl.scala @@ -0,0 +1,91 @@ +// Copyright (c) 2023 PSForever +package net.psforever.objects.serverobject.turret + +import akka.actor.Cancellable +import net.psforever.objects.serverobject.ServerObjectControl +import net.psforever.objects.{Default, Player, Tool} +import net.psforever.services.local.{LocalAction, LocalServiceMessage} +import net.psforever.types.Vector3 + +import scala.concurrent.ExecutionContext.Implicits.global +import scala.concurrent.duration._ + +class VanuSentryControl(turret: FacilityTurret) + extends ServerObjectControl + with MountableTurretControl { + def TurretObject: FacilityTurret = turret + def FactionObject: FacilityTurret = turret + def MountableObject: FacilityTurret = turret + def JammableObject: FacilityTurret = turret + def DamageableObject: FacilityTurret = turret + def RepairableObject: FacilityTurret = turret + + // Used for timing ammo recharge for vanu turrets in caves + private var weaponAmmoRechargeTimer: Cancellable = Default.Cancellable + + private val weaponAmmoRecharge: Receive = { + case VanuSentry.ChangeFireStart => + weaponAmmoRechargeTimer.cancel() + weaponAmmoRechargeTimer = Default.Cancellable + + case VanuSentry.ChangeFireStop => + weaponAmmoRechargeTimer.cancel() + weaponAmmoRechargeTimer = context.system.scheduler.scheduleWithFixedDelay( + 3 seconds, + 200 milliseconds, + self, + VanuSentry.RechargeAmmo + ) + + case VanuSentry.RechargeAmmo => + TurretObject.ControlledWeapon(wepNumber = 1).collect { + case weapon: Tool => + // recharge when last shot fired 3s delay, +1, 200ms interval + if (weapon.Magazine < weapon.MaxMagazine && System.currentTimeMillis() - weapon.LastDischarge > 3000L) { + weapon.Magazine += 1 + val seat = TurretObject.Seat(0).get + seat.occupant.collect { + case player: Player => + TurretObject.Zone.LocalEvents ! LocalServiceMessage( + TurretObject.Zone.id, + LocalAction.RechargeVehicleWeapon(player.GUID, TurretObject.GUID, weapon.GUID) + ) + } + } + else if (weapon.Magazine == weapon.MaxMagazine && weaponAmmoRechargeTimer != Default.Cancellable) { + weaponAmmoRechargeTimer.cancel() + weaponAmmoRechargeTimer = Default.Cancellable + } + } + } + + override def postStop(): Unit = { + super.postStop() + weaponAmmoRechargeTimer.cancel() + } + + def receive: Receive = + commonBehavior + .orElse(mountBehavior) + .orElse(weaponAmmoRecharge) + .orElse { + case _ => () + } + + override def parseAttribute(attribute: Int, value: Long, other: Option[Any]): Unit = { /*intentionally blank*/ } +} + +object VanuSentry { + final case object RechargeAmmo + final case object ChangeFireStart + final case object ChangeFireStop + + import akka.actor.ActorContext + def Constructor(pos: Vector3, tdef: FacilityTurretDefinition)(id: Int, context: ActorContext): FacilityTurret = { + import akka.actor.Props + val obj = FacilityTurret(tdef) + obj.Position = pos + obj.Actor = context.actorOf(Props(classOf[VanuSentryControl], obj), s"${tdef.Name}_$id") + obj + } +} diff --git a/src/main/scala/net/psforever/zones/Zones.scala b/src/main/scala/net/psforever/zones/Zones.scala index a0a15f8d8..e82d0f861 100644 --- a/src/main/scala/net/psforever/zones/Zones.scala +++ b/src/main/scala/net/psforever/zones/Zones.scala @@ -23,7 +23,7 @@ import net.psforever.objects.serverobject.structures.{Building, BuildingDefiniti import net.psforever.objects.serverobject.terminals.capture.{CaptureTerminal, CaptureTerminalDefinition} import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech import net.psforever.objects.serverobject.tube.SpawnTube -import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretDefinition} +import net.psforever.objects.serverobject.turret.{FacilityTurret, FacilityTurretDefinition, VanuSentry} import net.psforever.objects.serverobject.zipline.ZipLinePath import net.psforever.objects.sourcing.{DeployableSource, PlayerSource, TurretSource, VehicleSource} import net.psforever.objects.zones.{MapInfo, Zone, ZoneInfo, ZoneMap} @@ -585,7 +585,7 @@ object Zones { case _ => ; } - case "manned_turret" | "vanu_sentry_turret" => + case "manned_turret" => zoneMap.addLocalObject( obj.guid, FacilityTurret.Constructor( @@ -596,6 +596,17 @@ object Zones { ) zoneMap.linkTurretToWeapon(obj.guid, turretWeaponGuid.getAndIncrement()) + case "vanu_sentry_turret" => + zoneMap.addLocalObject( + obj.guid, + VanuSentry.Constructor( + obj.position, + obj.objectDefinition.asInstanceOf[FacilityTurretDefinition] + ), + owningBuildingGuid = ownerGuid + ) + zoneMap.linkTurretToWeapon(obj.guid, turretWeaponGuid.getAndIncrement()) + case "implant_terminal_mech" => zoneMap.addLocalObject( obj.guid,