diff --git a/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala
index 86e42e942..ae8399757 100644
--- a/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala
@@ -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}")
diff --git a/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala b/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala
index 77d3a99fa..9d7d436fd 100644
--- a/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala
@@ -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)
diff --git a/src/main/scala/net/psforever/actors/session/support/SessionData.scala b/src/main/scala/net/psforever/actors/session/support/SessionData.scala
index 44769b870..3e8405940 100644
--- a/src/main/scala/net/psforever/actors/session/support/SessionData.scala
+++ b/src/main/scala/net/psforever/actors/session/support/SessionData.scala
@@ -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))
}
diff --git a/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala b/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala
index a9f4dc2e8..a1fdd87ae 100644
--- a/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/VehicleOperations.scala
@@ -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()
+ }
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
index d67de9261..60d27e123 100644
--- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
@@ -1930,7 +1930,7 @@ class ZoningOperations(
/** Upstream message counter
* Checks for server acknowledgement of the following messages in the following conditions:
* `PlayerStateMessageUpstream` (infantry)
- * `VehicleStateMessage` (driver mount only)
+ * `VehicleStateMessage` and `FrameVehicleStateMessage` (driver mount)
* `ChildObjectStateMessage` (any gunner mount that is not the driver)
* `KeepAliveMessage` (any passenger mount that is not the driver)
* As they should arrive roughly every 250 milliseconds this allows for a very crude method of scheduling tasks up to four times per second
diff --git a/src/main/scala/net/psforever/objects/TurretDeployable.scala b/src/main/scala/net/psforever/objects/TurretDeployable.scala
index 5d385f072..f21f74707 100644
--- a/src/main/scala/net/psforever/objects/TurretDeployable.scala
+++ b/src/main/scala/net/psforever/objects/TurretDeployable.scala
@@ -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}
diff --git a/src/main/scala/net/psforever/objects/Vehicle.scala b/src/main/scala/net/psforever/objects/Vehicle.scala
index 122618c39..60ef15996 100644
--- a/src/main/scala/net/psforever/objects/Vehicle.scala
+++ b/src/main/scala/net/psforever/objects/Vehicle.scala
@@ -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
diff --git a/src/main/scala/net/psforever/objects/avatar/interaction/InteractWithForceDomeProtection.scala b/src/main/scala/net/psforever/objects/avatar/interaction/InteractWithForceDomeProtection.scala
index 66b92688d..c2365a224 100644
--- a/src/main/scala/net/psforever/objects/avatar/interaction/InteractWithForceDomeProtection.scala
+++ b/src/main/scala/net/psforever/objects/avatar/interaction/InteractWithForceDomeProtection.scala
@@ -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
diff --git a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala
index b6da16d55..976ea1b2c 100644
--- a/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/damage/Damageable.scala
@@ -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)
}
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/hackable/GenericHackables.scala b/src/main/scala/net/psforever/objects/serverobject/hackable/GenericHackables.scala
index c7c598d8d..b52f4afd6 100644
--- a/src/main/scala/net/psforever/objects/serverobject/hackable/GenericHackables.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/hackable/GenericHackables.scala
@@ -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.
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/InteractWithForceDomeProtectionSeatedInEntity.scala b/src/main/scala/net/psforever/objects/serverobject/mount/interaction/InteractWithForceDomeProtectionSeatedInEntity.scala
similarity index 84%
rename from src/main/scala/net/psforever/objects/serverobject/mount/InteractWithForceDomeProtectionSeatedInEntity.scala
rename to src/main/scala/net/psforever/objects/serverobject/mount/interaction/InteractWithForceDomeProtectionSeatedInEntity.scala
index 2dddc5ba0..272c31c43 100644
--- a/src/main/scala/net/psforever/objects/serverobject/mount/InteractWithForceDomeProtectionSeatedInEntity.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/interaction/InteractWithForceDomeProtectionSeatedInEntity.scala
@@ -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 {
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/InteractWithRadiationCloudsSeatedInEntity.scala b/src/main/scala/net/psforever/objects/serverobject/mount/interaction/InteractWithRadiationCloudsSeatedInEntity.scala
similarity index 94%
rename from src/main/scala/net/psforever/objects/serverobject/mount/InteractWithRadiationCloudsSeatedInEntity.scala
rename to src/main/scala/net/psforever/objects/serverobject/mount/interaction/InteractWithRadiationCloudsSeatedInEntity.scala
index 6e404519c..29a0b2f97 100644
--- a/src/main/scala/net/psforever/objects/serverobject/mount/InteractWithRadiationCloudsSeatedInEntity.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/interaction/InteractWithRadiationCloudsSeatedInEntity.scala
@@ -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
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/RadiationInMountableInteraction.scala b/src/main/scala/net/psforever/objects/serverobject/mount/interaction/RadiationInMountableInteraction.scala
similarity index 69%
rename from src/main/scala/net/psforever/objects/serverobject/mount/RadiationInMountableInteraction.scala
rename to src/main/scala/net/psforever/objects/serverobject/mount/interaction/RadiationInMountableInteraction.scala
index 5d6f32ca1..98c6b0336 100644
--- a/src/main/scala/net/psforever/objects/serverobject/mount/RadiationInMountableInteraction.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/interaction/RadiationInMountableInteraction.scala
@@ -1,4 +1,4 @@
-package net.psforever.objects.serverobject.mount
+package net.psforever.objects.serverobject.mount.interaction
import net.psforever.objects.zones.interaction.ZoneInteractionType
diff --git a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMech.scala b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMech.scala
index d63b8070a..ed0f38e8e 100644
--- a/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMech.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/terminals/implant/ImplantTerminalMech.scala
@@ -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
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 699fdccee..118fca04e 100644
--- a/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/turret/FacilityTurret.scala
@@ -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
diff --git a/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala b/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
index dec138367..d90baa787 100644
--- a/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/CargoBehavior.scala
@@ -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 {
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/CargoCarrierControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/CargoCarrierControl.scala
index a8028d017..5892d974f 100644
--- a/src/main/scala/net/psforever/objects/vehicles/control/CargoCarrierControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/control/CargoCarrierControl.scala
@@ -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()
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
index 4fd8a9ce1..d7b745a86 100644
--- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
@@ -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])
}
diff --git a/src/main/scala/net/psforever/objects/vehicles/interaction/InteractWithForceDomeProtectionSeatedInVehicle.scala b/src/main/scala/net/psforever/objects/vehicles/interaction/InteractWithForceDomeProtectionSeatedInVehicle.scala
index 0a988f818..de9671711 100644
--- a/src/main/scala/net/psforever/objects/vehicles/interaction/InteractWithForceDomeProtectionSeatedInVehicle.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/interaction/InteractWithForceDomeProtectionSeatedInVehicle.scala
@@ -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 {
diff --git a/src/main/scala/net/psforever/objects/vehicles/InteractWithRadiationCloudsSeatedInVehicle.scala b/src/main/scala/net/psforever/objects/vehicles/interaction/InteractWithRadiationCloudsSeatedInVehicle.scala
similarity index 89%
rename from src/main/scala/net/psforever/objects/vehicles/InteractWithRadiationCloudsSeatedInVehicle.scala
rename to src/main/scala/net/psforever/objects/vehicles/interaction/InteractWithRadiationCloudsSeatedInVehicle.scala
index 2a95e783d..51efe167f 100644
--- a/src/main/scala/net/psforever/objects/vehicles/InteractWithRadiationCloudsSeatedInVehicle.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/interaction/InteractWithRadiationCloudsSeatedInVehicle.scala
@@ -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
diff --git a/src/main/scala/net/psforever/objects/zones/interaction/IndependentZoneInteraction.scala b/src/main/scala/net/psforever/objects/zones/interaction/IndependentZoneInteraction.scala
new file mode 100644
index 000000000..ec84d39cc
--- /dev/null
+++ b/src/main/scala/net/psforever/objects/zones/interaction/IndependentZoneInteraction.scala
@@ -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
+}