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

This commit is contained in:
Fate-JH 2024-01-01 15:20:29 -05:00
parent 18c3162dfe
commit fc2ce70aae
10 changed files with 389 additions and 180 deletions

View file

@ -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))
}

View file

@ -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)
}

View file

@ -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
}
}
}

View file

@ -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
}
}
}

View file

@ -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
/**

View file

@ -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`.<br>
* <br>
@ -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)
}
}

View file

@ -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))
}
}

View file

@ -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
)
/**

View file

@ -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
}
}

View file

@ -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,