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,