extending the force dome protection over a variety of entities in a different manner, with a focus on how to perform state reset (dismounting and dome protect end); completely refactored and reworked the self-reported zone interaction timer for vehicles; separated passenger seat mounting from gunner seat mounting

This commit is contained in:
Fate-JH 2026-01-09 23:28:54 -05:00
parent ba266d0a3e
commit 52dbe6a649
21 changed files with 322 additions and 82 deletions

View file

@ -9,7 +9,6 @@ import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player, V
import net.psforever.objects.definition.{BasicDefinition, ObjectDefinition}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.environment.interaction.ResetAllEnvironmentInteractions
import net.psforever.objects.serverobject.hackable.GenericHackables
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.serverobject.structures.WarpGate
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
@ -105,7 +104,8 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
ops.MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
if seatNumber == 0 && obj.Definition.MaxCapacitor > 0 =>
if seatNumber == 0 &&
obj.Definition.MaxCapacitor > 0 =>
log.info(s"${player.Name} mounts the driver seat of the ${obj.Definition.Name}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
@ -134,13 +134,9 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
ops.MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
if obj.Definition.MaxCapacitor > 0 =>
log.info(s"${player.Name} mounts ${
obj.SeatPermissionGroup(seatNumber) match {
case Some(seatType) => s"a $seatType seat (#$seatNumber)"
case None => "a seat"
}
} of the ${obj.Definition.Name}")
if obj.Definition.MaxCapacitor > 0 &&
obj.SeatPermissionGroup(seatNumber).contains(AccessPermissionGroup.Gunner) =>
log.info(s"${player.Name} mounts the #$seatNumber gunner seat of the ${obj.Definition.Name}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
@ -149,17 +145,26 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=113, obj.Capacitor))
sessionLogic.general.accessContainer(obj)
ops.updateWeaponAtSeatPosition(obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
tplayer.Actor ! ResetAllEnvironmentInteractions
ops.MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, seatNumber, _) =>
log.info(s"${player.Name} mounts the ${
obj.SeatPermissionGroup(seatNumber) match {
case Some(seatType) => s"a $seatType seat (#$seatNumber)"
case None => "a seat"
}
} of the ${obj.Definition.Name}")
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
if obj.Definition.MaxCapacitor > 0 =>
log.info(s"${player.Name} mounts the #$seatNumber seat of the ${obj.Definition.Name}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=113, obj.Capacitor))
sessionLogic.general.accessContainer(obj)
tplayer.Actor ! ResetAllEnvironmentInteractions
ops.MountingAction(tplayer, obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
if obj.SeatPermissionGroup(seatNumber).contains(AccessPermissionGroup.Gunner) =>
log.info(s"${player.Name} mounts the #$seatNumber gunner seat of the ${obj.Definition.Name}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
@ -167,10 +172,21 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
sessionLogic.general.accessContainer(obj)
ops.updateWeaponAtSeatPosition(obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
tplayer.Actor ! ResetAllEnvironmentInteractions
ops.MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, seatNumber, _) =>
log.info(s"${player.Name} mounts the #$seatNumber seat of the ${obj.Definition.Name}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits()
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=0, obj.Health))
sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
sessionLogic.general.accessContainer(obj)
tplayer.Actor ! ResetAllEnvironmentInteractions
ops.MountingAction(tplayer, obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
if obj.Definition == GlobalDefinitions.vanu_sentry_turret =>
log.info(s"${player.Name} mounts the ${obj.Definition.Name}")

View file

@ -5,11 +5,12 @@ import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.{Vehicle, Vehicles}
import net.psforever.objects.{PlanetSideGameObject, Vehicle, Vehicles}
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles.control.BfrFlight
import net.psforever.objects.zones.Zone
import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.packet.game.{ChatMsg, ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, VehicleStateMessage, VehicleSubStateMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{ChatMessageType, DriveState, Vector3}
@ -94,7 +95,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
)
)
sessionLogic.squad.updateSquad()
obj.zoneInteractions()
VehicleOperations.updateMountableZoneInteractionFromEarliestSeat(obj, player)
case (None, _) =>
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone")
//TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle
@ -168,7 +169,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
obj.Velocity = None
obj.Flying = None
}
obj.zoneInteractions()
VehicleOperations.updateMountableZoneInteractionFromEarliestSeat(obj, player)
} else {
obj.Velocity = None
obj.Flying = None
@ -216,9 +217,16 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
case Some(mount: Mountable) => (o, mount.PassengerInSeat(player))
case _ => (None, None)
}) match {
case (None, None) | (_, None) | (Some(_: Vehicle), Some(0)) =>
case (None, _) | (_, None) => //error - we do not recognize being mounted or controlling anything, but what can we do about it?
()
case _ =>
case (Some(_: Vehicle), Some(0)) => //no (see: VSM or FVSM for valid cases)
()
case (Some(entity: PlanetSideGameObject with InteractsWithZone), Some(_)) => //yes
sessionLogic.zoning.spawn.tryQueuedActivity() //todo conditionals?
sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID)
VehicleOperations.updateMountableZoneInteractionFromEarliestSeat(entity, player)
case _ => //yes
sessionLogic.zoning.spawn.tryQueuedActivity() //todo conditionals?
sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID)

View file

@ -467,6 +467,11 @@ class SessionData(
persist()
if (player.HasGUID) {
turnCounterFunc(player.GUID)
continent
.GUID(player.VehicleSeated)
.foreach {
VehicleOperations.updateMountableZoneInteractionFromEarliestSeat(_, player)
}
} else {
turnCounterFunc(PlanetSideGUID(0))
}

View file

@ -7,6 +7,7 @@ import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.zones.Zone
import net.psforever.objects._
import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, VehicleSubStateMessage, _}
import net.psforever.types.DriveState
@ -195,3 +196,56 @@ class VehicleOperations(
sendResponse(pkt)
}
}
object VehicleOperations {
def updateMountableZoneInteractionFromEarliestSeat(obj: PlanetSideGameObject, passenger: Player): Unit = {
obj match {
case obj: Vehicle =>
updateVehicleZoneInteractionFromEarliestSeat(obj, passenger)
case obj: Mountable with InteractsWithZone =>
updateEntityZoneInteractionFromEarliestSeat(obj, passenger, obj)
case _ => ()
}
}
private def updateVehicleZoneInteractionFromEarliestSeat(obj: Vehicle, passenger: Player): Unit = {
//vehicle being ferried; check if the ferry has occupants that might have speaking rights before us
var targetVehicle = obj
val carrierSeatVacancy: Boolean = obj match {
case v if v.MountedIn.nonEmpty =>
obj.Zone.GUID(v.MountedIn) match {
case Some(carrier: Vehicle) =>
targetVehicle = carrier
!carrier.Seats.values.exists(_.isOccupied)
case _ =>
true
}
case _ => true
}
if (carrierSeatVacancy) {
updateEntityZoneInteractionFromEarliestSeat(obj, passenger, targetVehicle)
}
}
private def updateEntityZoneInteractionFromEarliestSeat(
obj: Mountable with InteractsWithZone,
passenger: Player,
updateTarget: InteractsWithZone
): Unit = {
val inSeatNumberOpt = obj.PassengerInSeat(passenger)
if (inSeatNumberOpt.contains(0)) {
//we're responsible as the primary operator
updateTarget.zoneInteractions()
} else if (!obj.Seat(seatNumber = 0).exists(_.isOccupied)) {
//there is no primary operator; are we responsible?
//determine if we are the player in the seat closest to the "front"
val noPlayersInEarlierSeats = inSeatNumberOpt
.exists { seatIndex =>
!(1 until seatIndex).exists { i => obj.Seat(i).exists(_.isOccupied) }
}
if (noPlayersInEarlierSeats) {
updateTarget.zoneInteractions()
}
}
}
}

View file

@ -1930,7 +1930,7 @@ class ZoningOperations(
/** Upstream message counter<br>
* Checks for server acknowledgement of the following messages in the following conditions:<br>
* `PlayerStateMessageUpstream` (infantry)<br>
* `VehicleStateMessage` (driver mount only)<br>
* `VehicleStateMessage` and `FrameVehicleStateMessage` (driver mount)<br>
* `ChildObjectStateMessage` (any gunner mount that is not the driver)<br>
* `KeepAliveMessage` (any passenger mount that is not the driver)<br>
* As they should arrive roughly every 250 milliseconds this allows for a very crude method of scheduling tasks up to four times per second

View file

@ -10,7 +10,7 @@ import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
import net.psforever.objects.serverobject.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.mount.{InteractWithForceDomeProtectionSeatedInEntity, InteractWithRadiationCloudsSeatedInEntity}
import net.psforever.objects.serverobject.mount.interaction.{InteractWithForceDomeProtectionSeatedInEntity, InteractWithRadiationCloudsSeatedInEntity}
import net.psforever.objects.serverobject.turret.auto.{AffectedByAutomaticTurretFire, AutomatedTurret}
import net.psforever.objects.serverobject.turret.{TurretControl, TurretDefinition, WeaponTurret}
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}

View file

@ -16,7 +16,7 @@ import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.interior.{InteriorAwareFromInteraction, Sidedness}
import net.psforever.objects.serverobject.structures.AmenityOwner
import net.psforever.objects.vehicles._
import net.psforever.objects.vehicles.interaction.{InteractWithForceDomeProtectionSeatedInVehicle, TriggerOnVehicleRule, WithLava, WithWater}
import net.psforever.objects.vehicles.interaction.{InteractWithForceDomeProtectionSeatedInVehicle, InteractWithRadiationCloudsSeatedInVehicle, TriggerOnVehicleRule, WithLava, WithWater}
import net.psforever.objects.vital.resistance.StandardResistanceProfile
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.DamageResistanceModel

View file

@ -4,7 +4,7 @@ package net.psforever.objects.avatar.interaction
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.dome.{ForceDomeControl, ForceDomePhysics}
import net.psforever.objects.zones.blockmap.SectorPopulation
import net.psforever.objects.zones.{InteractsWithZone, ZoneInteraction, ZoneInteractionType}
import net.psforever.objects.zones.interaction.{InteractsWithZone, ZoneInteraction, ZoneInteractionType}
case object ForceZoneProtection extends ZoneInteractionType

View file

@ -32,10 +32,10 @@ trait Damageable {
* cite the `originalTakesDamage` protocol during inheritance overrides */
val takesDamage: Receive = {
case Damageable.MakeVulnerable =>
isVulnerable = false
isVulnerable = true
case Damageable.MakeInvulnerable =>
isVulnerable = true
isVulnerable = false
case Vitality.Damage(damage_func) =>
val obj = DamageableObject
@ -47,14 +47,14 @@ trait Damageable {
/** a duplicate of the core implementation for the default mixin hook, for use in overriding */
final val originalTakesDamage: Receive = {
case Damageable.MakeVulnerable =>
isVulnerable = false
isVulnerable = true
case Damageable.MakeInvulnerable =>
isVulnerable = true
isVulnerable = false
case Vitality.Damage(damage_func) =>
val obj = DamageableObject
if (obj.CanDamage) {
if (isVulnerable && obj.CanDamage) {
PerformDamage(obj, damage_func)
}
}

View file

@ -15,6 +15,7 @@ import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.annotation.unused
import scala.util.{Failure, Success}
object GenericHackables {
@ -49,6 +50,8 @@ object GenericHackables {
}
}
private def DontStopHackAttempt(@unused target: PlanetSideServerObject, @unused hacker: Player): Boolean = false
/**
* Evaluate the progress of the user applying a tool to modify some server object.
* This action is using the remote electronics kit to convert an enemy unit into an allied unit, primarily.
@ -62,6 +65,7 @@ object GenericHackables {
* @param target the object being affected
* @param tool_guid the tool being used to affest the object
* @param progress the current progress value
* @param additionalCancellationTests context-specific tests for hack continuation
* @return `true`, if the next cycle of progress should occur;
* `false`, otherwise
*/
@ -70,7 +74,7 @@ object GenericHackables {
hacker: Player,
target: PlanetSideServerObject,
tool_guid: PlanetSideGUID,
additionalCancellationTests: (PlanetSideServerObject, Player) => Boolean = ForceDomeProtectsFromHacking
additionalCancellationTests: (PlanetSideServerObject, Player) => Boolean
)(
progress: Float
): Boolean = {
@ -93,6 +97,30 @@ object GenericHackables {
)
progressState != HackState.Cancelled
}
/**
* Evaluate the progress of the user applying a tool to modify some server object.
* This action is using the remote electronics kit to convert an enemy unit into an allied unit, primarily.
* The act of transforming allied units of one kind into allied units of another kind (facility turret upgrades)
* is also governed by this action per tick of progress.
* @param progressType 1 - remote electronics kit hack (various ...);
* 2 - nano dispenser (upgrade canister) turret upgrade
* @param hacker the player performing the action
* @param target the object being affected
* @param tool_guid the tool being used to affest the object
* @param progress the current progress value
* @return `true`, if the next cycle of progress should occur;
* `false`, otherwise
*/
def HackingTickAction(
progressType: HackState1,
hacker: Player,
target: PlanetSideServerObject,
tool_guid: PlanetSideGUID
)(
progress: Float
): Boolean = {
HackingTickAction(progressType, hacker, target, tool_guid, DontStopHackAttempt)(progress)
}
/**
* The force dome prevents hacking if its protection has been declared over a capitol.

View file

@ -1,10 +1,11 @@
// Copyright (c) 2025 PSForever
package net.psforever.objects.serverobject.mount
package net.psforever.objects.serverobject.mount.interaction
import net.psforever.objects.avatar.interaction.InteractWithForceDomeProtection
import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.dome.ForceDomePhysics
import net.psforever.objects.zones.InteractsWithZone
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.zones.interaction.InteractsWithZone
class InteractWithForceDomeProtectionSeatedInEntity
extends InteractWithForceDomeProtection {

View file

@ -1,7 +1,8 @@
// Copyright (c) 2024 PSForever
package net.psforever.objects.serverobject.mount
package net.psforever.objects.serverobject.mount.interaction
import net.psforever.objects.ballistics.{Projectile, ProjectileQuality}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.DamageResolution

View file

@ -1,4 +1,4 @@
package net.psforever.objects.serverobject.mount
package net.psforever.objects.serverobject.mount.interaction
import net.psforever.objects.zones.interaction.ZoneInteractionType

View file

@ -2,7 +2,8 @@
package net.psforever.objects.serverobject.terminals.implant
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.mount.{InteractWithRadiationCloudsSeatedInEntity, Mountable, Seat}
import net.psforever.objects.serverobject.mount.interaction.InteractWithRadiationCloudsSeatedInEntity
import net.psforever.objects.serverobject.mount.{Mountable, Seat}
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware
import net.psforever.objects.vital.resistance.StandardResistanceProfile

View file

@ -3,7 +3,7 @@ package net.psforever.objects.serverobject.turret
import net.psforever.objects.equipment.JammableUnit
import net.psforever.objects.serverobject.interior.Sidedness
import net.psforever.objects.serverobject.mount.InteractWithRadiationCloudsSeatedInEntity
import net.psforever.objects.serverobject.mount.interaction.InteractWithRadiationCloudsSeatedInEntity
import net.psforever.objects.serverobject.structures.{Amenity, AmenityOwner, Building}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware
import net.psforever.objects.serverobject.turret.auto.AutomatedTurret

View file

@ -19,12 +19,12 @@ trait CargoBehavior {
val zone = obj.Zone
zone.GUID(isMounting) match {
case Some(v : Vehicle) => v.Actor ! CargoBehavior.EndCargoMounting(obj.GUID)
case _ => ;
case _ => ()
}
isMounting = None
zone.GUID(isDismounting) match {
case Some(v: Vehicle) => v.Actor ! CargoBehavior.EndCargoDismounting(obj.GUID)
case _ => ;
case _ => ()
}
isDismounting = None
startCargoDismountingNoCleanup(bailed = false)
@ -38,14 +38,10 @@ trait CargoBehavior {
startCargoDismounting(bailed)
case CargoBehavior.EndCargoMounting(carrier_guid) =>
if (isMounting.contains(carrier_guid)) {
isMounting = None
}
endCargoMounting(carrier_guid)
case CargoBehavior.EndCargoDismounting(carrier_guid) =>
if (isDismounting.contains(carrier_guid)) {
isDismounting = None
}
endCargoDismounting(carrier_guid)
}
def startCargoMounting(carrier_guid: PlanetSideGUID, mountPoint: Int): Unit = {
@ -84,6 +80,18 @@ trait CargoBehavior {
}
.nonEmpty
}
def endCargoMounting(carrierGuid: PlanetSideGUID): Unit = {
if (isMounting.contains(carrierGuid)) {
isMounting = None
}
}
def endCargoDismounting(carrierGuid: PlanetSideGUID): Unit = {
if (isDismounting.contains(carrierGuid)) {
isDismounting = None
}
}
}
object CargoBehavior {

View file

@ -14,7 +14,16 @@ import net.psforever.objects.vital.interaction.DamageResult
class CargoCarrierControl(vehicle: Vehicle)
extends VehicleControl(vehicle)
with CarrierBehavior {
def CarrierObject = vehicle
def CarrierObject: Vehicle = vehicle
override def TestToStartSelfReporting(): Boolean = {
super.TestToStartSelfReporting() &&
!CarrierObject
.CargoHolds
.values
.flatMap(_.occupants)
.exists(_.Seats.values.exists(_.isOccupied))
}
override def postStop() : Unit = {
super.postStop()

View file

@ -20,7 +20,7 @@ import net.psforever.objects.serverobject.environment._
import net.psforever.objects.serverobject.environment.interaction.common.Watery
import net.psforever.objects.serverobject.environment.interaction.{InteractWithEnvironment, RespondsToZoneEnvironment}
import net.psforever.objects.serverobject.hackable.GenericHackables
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior, RadiationInMountableInteraction}
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior}
import net.psforever.objects.serverobject.repair.RepairableVehicle
import net.psforever.objects.serverobject.structures.WarpGate
import net.psforever.objects.serverobject.terminals.Terminal
@ -31,6 +31,7 @@ import net.psforever.objects.vehicles.interaction.WithWater
import net.psforever.objects.vital.interaction.DamageResult
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, ShieldCharge, SpawningActivity, VehicleDismountActivity, VehicleMountActivity}
import net.psforever.objects.zones._
import net.psforever.objects.zones.interaction.IndependentZoneInteraction
import net.psforever.packet.PlanetSideGamePacket
import net.psforever.packet.game._
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
@ -63,7 +64,8 @@ class VehicleControl(vehicle: Vehicle)
with AggravatedBehavior
with RespondsToZoneEnvironment
with CargoBehavior
with AffectedByAutomaticTurretFire {
with AffectedByAutomaticTurretFire
with IndependentZoneInteraction {
//make control actors belonging to utilities when making control actor belonging to vehicle
vehicle.Utilities.foreach { case (_, util) => util.Setup }
@ -77,6 +79,7 @@ class VehicleControl(vehicle: Vehicle)
def InteractiveObject: Vehicle = vehicle
def CargoObject: Vehicle = vehicle
def AffectedObject: Vehicle = vehicle
def ZoneInteractionObject: Vehicle = vehicle
/** cheap flag for whether the vehicle is decaying */
var decaying : Boolean = false
@ -84,8 +87,6 @@ class VehicleControl(vehicle: Vehicle)
var decayTimer : Cancellable = Default.Cancellable
/** becoming waterlogged, or drying out? */
var submergedCondition : Option[OxygenState] = None
/** ... */
var passengerRadiationCloudTimer: Cancellable = Default.Cancellable
def receive : Receive = Enabled
@ -94,7 +95,7 @@ class VehicleControl(vehicle: Vehicle)
damageableVehiclePostStop()
decaying = false
decayTimer.cancel()
passengerRadiationCloudTimer.cancel()
StopInteractionSelfReporting()
vehicle.Utilities.values.foreach { util =>
context.stop(util().Actor)
util().Actor = Default.Actor
@ -113,6 +114,7 @@ class VehicleControl(vehicle: Vehicle)
.orElse(environmentBehavior)
.orElse(cargoBehavior)
.orElse(takeAutomatedDamage)
.orElse(zoneInteractionBehavior)
.orElse {
case Vehicle.Ownership(None) =>
LoseOwnership()
@ -288,11 +290,6 @@ class VehicleControl(vehicle: Vehicle)
final def Enabled: Receive =
commonEnabledBehavior
.orElse {
case VehicleControl.RadiationTick if !passengerRadiationCloudTimer.isCancelled =>
vehicle
.interaction()
.find(_.Type == RadiationInMountableInteraction)
.foreach(_.interaction(vehicle.getInteractionSector, vehicle))
case _ => ()
}
@ -373,7 +370,7 @@ class VehicleControl(vehicle: Vehicle)
decaying = false
decayTimer.cancel()
}
passengerRadiationCloudTimer.cancel()
TryStopInteractionSelfReporting()
updateZoneInteractionProgressUI(user)
case Some(seatNumber) => //literally any other seat
@ -381,9 +378,7 @@ class VehicleControl(vehicle: Vehicle)
user.LogActivity(VehicleMountActivity(vsrc, PlayerSource.inSeat(user, vsrc, seatNumber), vehicle.Zone.Number))
decaying = false
decayTimer.cancel()
if (!vehicle.Seats(0).isOccupied && passengerRadiationCloudTimer.isCancelled) {
StartRadiationSelfReporting()
}
StopInteractionSelfReporting()
updateZoneInteractionProgressUI(user)
case None => ()
@ -400,15 +395,14 @@ class VehicleControl(vehicle: Vehicle)
def dismountCleanup(seatBeingDismounted: Int, user: Player): Unit = {
val obj = MountableObject
val allSeatsUnoccupied = !obj.Seats.values.exists(_.isOccupied)
// Reset velocity to zero when driver dismounts, to allow jacking/repair if vehicle was moving slightly before dismount
if (!obj.Seats(0).isOccupied) {
obj.Velocity = Some(Vector3.Zero)
}
if (allSeatsUnoccupied) {
passengerRadiationCloudTimer.cancel()
} else if (seatBeingDismounted == 0) {
StartRadiationSelfReporting()
val allSeatsUnoccupied = !vehicle.Seats.values.exists(_.isOccupied)
val otherTests = TestToStartSelfReporting()
if (allSeatsUnoccupied && otherTests) {
StartInteractionSelfReporting()
}
if (!obj.Seats(seatBeingDismounted).isOccupied) { //this seat really was vacated
user.LogActivity(VehicleDismountActivity(VehicleSource(vehicle), PlayerSource(user), vehicle.Zone.Number))
@ -432,14 +426,18 @@ class VehicleControl(vehicle: Vehicle)
}
}
private def StartRadiationSelfReporting(): Unit = {
passengerRadiationCloudTimer.cancel()
passengerRadiationCloudTimer = context.system.scheduler.scheduleWithFixedDelay(
250.milliseconds,
250.milliseconds,
self,
VehicleControl.RadiationTick
)
def TestToStartSelfReporting(): Boolean = {
vehicle.MountedIn.isEmpty
}
def PerformSelfReportRunCheck(): Unit = {
val noOccupancy = !vehicle.Seats.values.exists(_.isOccupied)
val otherTests = TestToStartSelfReporting()
if (noOccupancy && otherTests) {
StartInteractionSelfReporting()
} else {
StopInteractionSelfReporting()
}
}
def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
@ -767,9 +765,31 @@ class VehicleControl(vehicle: Vehicle)
}
override protected def DestructionAwareness(target: Target, cause: DamageResult): Unit = {
passengerRadiationCloudTimer.cancel()
StopInteractionSelfReportingNoReset()
super.DestructionAwareness(target, cause)
}
override def endCargoMounting(carrierGuid: PlanetSideGUID): Unit = {
super.endCargoMounting(carrierGuid)
StopInteractionSelfReporting()
vehicle.Zone.GUID(carrierGuid) match {
case Some(v: Vehicle) => v.Actor ! IndependentZoneInteraction.SelfReportRunCheck
case _ => ()
}
}
override def endCargoDismounting(carrierGuid: PlanetSideGUID): Unit = {
super.endCargoDismounting(carrierGuid)
val allSeatsUnoccupied = !vehicle.Seats.values.exists(_.isOccupied)
val otherTests = TestToStartSelfReporting()
if (allSeatsUnoccupied && otherTests) {
StartInteractionSelfReporting()
}
vehicle.Zone.GUID(carrierGuid) match {
case Some(v: Vehicle) => v.Actor ! IndependentZoneInteraction.SelfReportRunCheck
case _ => ()
}
}
}
object VehicleControl {
@ -779,7 +799,5 @@ object VehicleControl {
private case class Deletion()
private case object RadiationTick
final case class AssignOwnership(player: Option[Player])
}

View file

@ -4,8 +4,8 @@ package net.psforever.objects.vehicles.interaction
import net.psforever.objects.Vehicle
import net.psforever.objects.avatar.interaction.{ForceZoneProtection, InteractWithForceDomeProtection}
import net.psforever.objects.serverobject.dome.ForceDomePhysics
import net.psforever.objects.serverobject.mount.InteractWithForceDomeProtectionSeatedInEntity
import net.psforever.objects.zones.InteractsWithZone
import net.psforever.objects.serverobject.mount.interaction.InteractWithForceDomeProtectionSeatedInEntity
import net.psforever.objects.zones.interaction.InteractsWithZone
class InteractWithForceDomeProtectionSeatedInVehicle
extends InteractWithForceDomeProtectionSeatedInEntity {

View file

@ -1,8 +1,8 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles
package net.psforever.objects.vehicles.interaction
import net.psforever.objects.Vehicle
import net.psforever.objects.serverobject.mount.{InteractWithRadiationCloudsSeatedInEntity, RadiationInMountableInteraction}
import net.psforever.objects.serverobject.mount.interaction.{InteractWithRadiationCloudsSeatedInEntity, RadiationInMountableInteraction}
import net.psforever.objects.zones.blockmap.SectorPopulation
import net.psforever.objects.zones.interaction.InteractsWithZone

View file

@ -0,0 +1,91 @@
// Copyright (c) 2026 PSForever
package net.psforever.objects.zones.interaction
import akka.actor.{Actor, Cancellable}
import net.psforever.objects.Default
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
trait IndependentZoneInteraction {
_: Actor =>
/** ... */
private var zoneInteractionIntervalDefault: FiniteDuration = 250.milliseconds
/** ... */
private var zoneInteractionTimer: Cancellable = Default.Cancellable
def ZoneInteractionObject: InteractsWithZone
val zoneInteractionBehavior: Receive = {
case IndependentZoneInteraction.InteractionTick =>
PerformZoneInteractionSelfReporting()
case IndependentZoneInteraction.SelfReportRunCheck =>
PerformSelfReportRunCheck()
}
def ZoneInteractionInterval: FiniteDuration = zoneInteractionIntervalDefault
def ZoneInteractionInterval_=(interval: FiniteDuration): FiniteDuration = {
zoneInteractionIntervalDefault = interval
ZoneInteractionInterval
}
def TestToStartSelfReporting(): Boolean
def PerformZoneInteractionSelfReporting(): Unit = {
if (!zoneInteractionTimer.isCancelled) {
ZoneInteractionObject.zoneInteractions()
}
}
def PerformSelfReportRunCheck(): Unit
final def StartInteractionSelfReporting(): Unit = {
org.log4s.getLogger("ZIT").info("starting timer")
zoneInteractionTimer.cancel()
zoneInteractionTimer = context.system.scheduler.scheduleWithFixedDelay(
0.seconds,
zoneInteractionIntervalDefault,
self,
IndependentZoneInteraction.InteractionTick
)
}
final def StartInteractionSelfReporting(initialDelay: FiniteDuration): Unit = {
org.log4s.getLogger("ZIT").info("starting timer")
zoneInteractionTimer.cancel()
zoneInteractionTimer = context.system.scheduler.scheduleWithFixedDelay(
initialDelay,
zoneInteractionIntervalDefault,
self,
IndependentZoneInteraction.InteractionTick
)
}
final def TryStopInteractionSelfReporting(): Boolean = {
if (!zoneInteractionTimer.isCancelled) {
ZoneInteractionObject.resetInteractions()
zoneInteractionTimer.cancel()
} else {
false
}
}
final def StopInteractionSelfReporting(): Boolean = {
ZoneInteractionObject.resetInteractions()
zoneInteractionTimer.cancel()
}
final def StopInteractionSelfReportingNoReset(): Boolean = {
zoneInteractionTimer.cancel()
}
final def ZoneInteractionSelfReportingIsRunning: Boolean = !zoneInteractionTimer.isCancelled
}
object IndependentZoneInteraction {
private case object InteractionTick
final case object SelfReportRunCheck
}