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.definition.{BasicDefinition, ObjectDefinition}
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.environment.interaction.ResetAllEnvironmentInteractions 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.mount.Mountable
import net.psforever.objects.serverobject.structures.WarpGate import net.psforever.objects.serverobject.structures.WarpGate
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech 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) ops.MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, 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}") log.info(s"${player.Name} mounts the driver seat of the ${obj.Definition.Name}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID val obj_guid: PlanetSideGUID = obj.GUID
@ -134,13 +134,9 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
ops.MountingAction(tplayer, obj, seatNumber) ops.MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, seatNumber, _) case Mountable.CanMount(obj: Vehicle, seatNumber, _)
if obj.Definition.MaxCapacitor > 0 => if obj.Definition.MaxCapacitor > 0 &&
log.info(s"${player.Name} mounts ${ obj.SeatPermissionGroup(seatNumber).contains(AccessPermissionGroup.Gunner) =>
obj.SeatPermissionGroup(seatNumber) match { log.info(s"${player.Name} mounts the #$seatNumber gunner seat of the ${obj.Definition.Name}")
case Some(seatType) => s"a $seatType seat (#$seatNumber)"
case None => "a seat"
}
} of the ${obj.Definition.Name}")
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount") sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits() 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)) sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=113, obj.Capacitor))
sessionLogic.general.accessContainer(obj) sessionLogic.general.accessContainer(obj)
ops.updateWeaponAtSeatPosition(obj, seatNumber) ops.updateWeaponAtSeatPosition(obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
tplayer.Actor ! ResetAllEnvironmentInteractions tplayer.Actor ! ResetAllEnvironmentInteractions
ops.MountingAction(tplayer, obj, seatNumber) ops.MountingAction(tplayer, obj, seatNumber)
case Mountable.CanMount(obj: Vehicle, seatNumber, _) => case Mountable.CanMount(obj: Vehicle, seatNumber, _)
log.info(s"${player.Name} mounts the ${ if obj.Definition.MaxCapacitor > 0 =>
obj.SeatPermissionGroup(seatNumber) match { log.info(s"${player.Name} mounts the #$seatNumber seat of the ${obj.Definition.Name}")
case Some(seatType) => s"a $seatType seat (#$seatNumber)" sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
case None => "a seat" val obj_guid: PlanetSideGUID = obj.GUID
} sessionLogic.terminals.CancelAllProximityUnits()
} of the ${obj.Definition.Name}") 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") sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_mount")
val obj_guid: PlanetSideGUID = obj.GUID val obj_guid: PlanetSideGUID = obj.GUID
sessionLogic.terminals.CancelAllProximityUnits() 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)) sendResponse(PlanetsideAttributeMessage(obj_guid, obj.Definition.shieldUiAttribute, obj.Shields))
sessionLogic.general.accessContainer(obj) sessionLogic.general.accessContainer(obj)
ops.updateWeaponAtSeatPosition(obj, seatNumber) ops.updateWeaponAtSeatPosition(obj, seatNumber)
sessionLogic.keepAliveFunc = sessionLogic.keepAlivePersistenceFunc
tplayer.Actor ! ResetAllEnvironmentInteractions tplayer.Actor ! ResetAllEnvironmentInteractions
ops.MountingAction(tplayer, obj, seatNumber) 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, _) case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
if obj.Definition == GlobalDefinitions.vanu_sentry_turret => if obj.Definition == GlobalDefinitions.vanu_sentry_turret =>
log.info(s"${player.Name} mounts the ${obj.Definition.Name}") 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.AvatarActor
import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations} import net.psforever.actors.session.support.{SessionData, VehicleFunctions, VehicleOperations}
import net.psforever.objects.serverobject.PlanetSideServerObject 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.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles.control.BfrFlight import net.psforever.objects.vehicles.control.BfrFlight
import net.psforever.objects.zones.Zone 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.packet.game.{ChatMsg, ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, VehicleStateMessage, VehicleSubStateMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{ChatMessageType, DriveState, Vector3} import net.psforever.types.{ChatMessageType, DriveState, Vector3}
@ -94,7 +95,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
) )
) )
sessionLogic.squad.updateSquad() sessionLogic.squad.updateSquad()
obj.zoneInteractions() VehicleOperations.updateMountableZoneInteractionFromEarliestSeat(obj, player)
case (None, _) => case (None, _) =>
//log.error(s"VehicleState: no vehicle $vehicle_guid found in zone") //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 //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.Velocity = None
obj.Flying = None obj.Flying = None
} }
obj.zoneInteractions() VehicleOperations.updateMountableZoneInteractionFromEarliestSeat(obj, player)
} else { } else {
obj.Velocity = None obj.Velocity = None
obj.Flying = 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 Some(mount: Mountable) => (o, mount.PassengerInSeat(player))
case _ => (None, None) case _ => (None, None)
}) match { }) 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.zoning.spawn.tryQueuedActivity() //todo conditionals?
sessionLogic.persist() sessionLogic.persist()
sessionLogic.turnCounterFunc(player.GUID) sessionLogic.turnCounterFunc(player.GUID)

View file

@ -467,6 +467,11 @@ class SessionData(
persist() persist()
if (player.HasGUID) { if (player.HasGUID) {
turnCounterFunc(player.GUID) turnCounterFunc(player.GUID)
continent
.GUID(player.VehicleSeated)
.foreach {
VehicleOperations.updateMountableZoneInteractionFromEarliestSeat(_, player)
}
} else { } else {
turnCounterFunc(PlanetSideGUID(0)) 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.serverobject.mount.Mountable
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.zones.interaction.InteractsWithZone
import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, VehicleSubStateMessage, _} import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, VehicleSubStateMessage, _}
import net.psforever.types.DriveState import net.psforever.types.DriveState
@ -195,3 +196,56 @@ class VehicleOperations(
sendResponse(pkt) 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> /** Upstream message counter<br>
* Checks for server acknowledgement of the following messages in the following conditions:<br> * Checks for server acknowledgement of the following messages in the following conditions:<br>
* `PlayerStateMessageUpstream` (infantry)<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> * `ChildObjectStateMessage` (any gunner mount that is not the driver)<br>
* `KeepAliveMessage` (any passenger 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 * 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.affinity.FactionAffinityBehavior
import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.hackable.Hackable 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.auto.{AffectedByAutomaticTurretFire, AutomatedTurret}
import net.psforever.objects.serverobject.turret.{TurretControl, TurretDefinition, WeaponTurret} import net.psforever.objects.serverobject.turret.{TurretControl, TurretDefinition, WeaponTurret}
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry} 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.interior.{InteriorAwareFromInteraction, Sidedness}
import net.psforever.objects.serverobject.structures.AmenityOwner import net.psforever.objects.serverobject.structures.AmenityOwner
import net.psforever.objects.vehicles._ 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.resistance.StandardResistanceProfile
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.resolution.DamageResistanceModel 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.damage.Damageable
import net.psforever.objects.serverobject.dome.{ForceDomeControl, ForceDomePhysics} import net.psforever.objects.serverobject.dome.{ForceDomeControl, ForceDomePhysics}
import net.psforever.objects.zones.blockmap.SectorPopulation 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 case object ForceZoneProtection extends ZoneInteractionType

View file

@ -32,10 +32,10 @@ trait Damageable {
* cite the `originalTakesDamage` protocol during inheritance overrides */ * cite the `originalTakesDamage` protocol during inheritance overrides */
val takesDamage: Receive = { val takesDamage: Receive = {
case Damageable.MakeVulnerable => case Damageable.MakeVulnerable =>
isVulnerable = false isVulnerable = true
case Damageable.MakeInvulnerable => case Damageable.MakeInvulnerable =>
isVulnerable = true isVulnerable = false
case Vitality.Damage(damage_func) => case Vitality.Damage(damage_func) =>
val obj = DamageableObject val obj = DamageableObject
@ -47,14 +47,14 @@ trait Damageable {
/** a duplicate of the core implementation for the default mixin hook, for use in overriding */ /** a duplicate of the core implementation for the default mixin hook, for use in overriding */
final val originalTakesDamage: Receive = { final val originalTakesDamage: Receive = {
case Damageable.MakeVulnerable => case Damageable.MakeVulnerable =>
isVulnerable = false isVulnerable = true
case Damageable.MakeInvulnerable => case Damageable.MakeInvulnerable =>
isVulnerable = true isVulnerable = false
case Vitality.Damage(damage_func) => case Vitality.Damage(damage_func) =>
val obj = DamageableObject val obj = DamageableObject
if (obj.CanDamage) { if (isVulnerable && obj.CanDamage) {
PerformDamage(obj, damage_func) 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.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.annotation.unused
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
object GenericHackables { 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. * 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. * 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 target the object being affected
* @param tool_guid the tool being used to affest the object * @param tool_guid the tool being used to affest the object
* @param progress the current progress value * @param progress the current progress value
* @param additionalCancellationTests context-specific tests for hack continuation
* @return `true`, if the next cycle of progress should occur; * @return `true`, if the next cycle of progress should occur;
* `false`, otherwise * `false`, otherwise
*/ */
@ -70,7 +74,7 @@ object GenericHackables {
hacker: Player, hacker: Player,
target: PlanetSideServerObject, target: PlanetSideServerObject,
tool_guid: PlanetSideGUID, tool_guid: PlanetSideGUID,
additionalCancellationTests: (PlanetSideServerObject, Player) => Boolean = ForceDomeProtectsFromHacking additionalCancellationTests: (PlanetSideServerObject, Player) => Boolean
)( )(
progress: Float progress: Float
): Boolean = { ): Boolean = {
@ -93,6 +97,30 @@ object GenericHackables {
) )
progressState != HackState.Cancelled 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. * The force dome prevents hacking if its protection has been declared over a capitol.

View file

@ -1,10 +1,11 @@
// Copyright (c) 2025 PSForever // 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.avatar.interaction.InteractWithForceDomeProtection
import net.psforever.objects.serverobject.damage.Damageable import net.psforever.objects.serverobject.damage.Damageable
import net.psforever.objects.serverobject.dome.ForceDomePhysics 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 class InteractWithForceDomeProtectionSeatedInEntity
extends InteractWithForceDomeProtection { extends InteractWithForceDomeProtection {

View file

@ -1,7 +1,8 @@
// Copyright (c) 2024 PSForever // 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.ballistics.{Projectile, ProjectileQuality}
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.sourcing.SourceEntry import net.psforever.objects.sourcing.SourceEntry
import net.psforever.objects.vital.Vitality import net.psforever.objects.vital.Vitality
import net.psforever.objects.vital.base.DamageResolution 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 import net.psforever.objects.zones.interaction.ZoneInteractionType

View file

@ -2,7 +2,8 @@
package net.psforever.objects.serverobject.terminals.implant package net.psforever.objects.serverobject.terminals.implant
import net.psforever.objects.serverobject.hackable.Hackable 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.structures.Amenity
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware
import net.psforever.objects.vital.resistance.StandardResistanceProfile 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.equipment.JammableUnit
import net.psforever.objects.serverobject.interior.Sidedness 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.structures.{Amenity, AmenityOwner, Building}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware import net.psforever.objects.serverobject.terminals.capture.CaptureTerminalAware
import net.psforever.objects.serverobject.turret.auto.AutomatedTurret import net.psforever.objects.serverobject.turret.auto.AutomatedTurret

View file

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

View file

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

View file

@ -1,8 +1,8 @@
// Copyright (c) 2021 PSForever // Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles package net.psforever.objects.vehicles.interaction
import net.psforever.objects.Vehicle 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.blockmap.SectorPopulation
import net.psforever.objects.zones.interaction.InteractsWithZone 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
}