mirror of
https://github.com/psforever/PSF-LoginServer.git
synced 2026-01-19 18:44:45 +00:00
completely retooled water interactions for players and vehicles to handle a wading status, before drowning; when player with llu or vehicle with player with llu come into contact with water in this wading state, the llu is lost and the facility hack ends; adding messages to support this and differentiate from an llu facility capture timeout
This commit is contained in:
parent
d522a09335
commit
a4084e52ce
|
|
@ -51,7 +51,6 @@ import net.psforever.types.{CapacitorStateType, ChatMessageType, Cosmetic, Drive
|
|||
import net.psforever.util.Config
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Success
|
||||
|
||||
object GeneralLogic {
|
||||
def apply(ops: GeneralOperations): GeneralLogic = {
|
||||
|
|
@ -170,6 +169,9 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
|
|||
}
|
||||
case None => ()
|
||||
}
|
||||
//llu destruction check
|
||||
sessionLogic.localResponse.loseFlagViolently(ops.specialItemSlotGuid, player)
|
||||
//
|
||||
val eagleEye: Boolean = ops.canSeeReallyFar
|
||||
val isNotVisible: Boolean = sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing ||
|
||||
(player.isAlive && sessionLogic.zoning.spawn.deadState == DeadState.RespawnTime)
|
||||
|
|
|
|||
|
|
@ -414,6 +414,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
|
|||
|
||||
case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed)
|
||||
if obj.DeploymentState == DriveState.AutoPilot =>
|
||||
//todo @Vehicle_CannotBailInWarpgateEnvelope
|
||||
sendResponse(ChatMsg(ChatMessageType.UNK_224, "@SA_CannotBailAtThisTime"))
|
||||
log.warn(s"DismountVehicleMsg: ${tplayer.Name} can not bail from $obj's when in autopilot")
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ package net.psforever.actors.session.normal
|
|||
import akka.actor.{ActorContext, ActorRef, typed}
|
||||
import net.psforever.actors.session.AvatarActor
|
||||
import net.psforever.actors.session.support.{SessionData, SessionVehicleHandlers, VehicleHandlerFunctions}
|
||||
import net.psforever.objects.avatar.SpecialCarry
|
||||
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle, Vehicles}
|
||||
import net.psforever.objects.equipment.{Equipment, JammableMountedWeapons, JammableUnit}
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
|
|
@ -62,6 +63,13 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
|
|||
player.Orientation = orient
|
||||
player.Velocity = vel
|
||||
sessionLogic.updateLocalBlockMap(pos)
|
||||
if (player.Carrying.contains(SpecialCarry.CaptureFlag)) {
|
||||
continent
|
||||
.GUID(player.VehicleSeated)
|
||||
.collect { case vehicle: Vehicle =>
|
||||
sessionLogic.localResponse.loseFlagViolently(sessionLogic.general.specialItemSlotGuid, vehicle)
|
||||
}
|
||||
}
|
||||
|
||||
case VehicleResponse.VehicleState(
|
||||
vehicleGuid,
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
|
|||
obj.Velocity = None
|
||||
obj.Flying = None
|
||||
}
|
||||
//
|
||||
continent.VehicleEvents ! VehicleServiceMessage(
|
||||
continent.id,
|
||||
VehicleAction.VehicleState(
|
||||
|
|
|
|||
|
|
@ -2,12 +2,17 @@
|
|||
package net.psforever.actors.session.support
|
||||
|
||||
import akka.actor.ActorContext
|
||||
import net.psforever.objects.{Players, TurretDeployable}
|
||||
import net.psforever.objects.{PlanetSideGameObject, Players, TurretDeployable}
|
||||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
|
||||
import net.psforever.objects.serverobject.environment.EnvironmentAttribute
|
||||
import net.psforever.objects.serverobject.environment.interaction.InteractWithEnvironment
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery
|
||||
import net.psforever.objects.serverobject.interior.Sidedness
|
||||
import net.psforever.objects.serverobject.llu.CaptureFlag
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
import net.psforever.packet.game.GenericObjectActionMessage
|
||||
import net.psforever.services.local.LocalResponse
|
||||
import net.psforever.services.local.{LocalAction, LocalResponse, LocalServiceMessage}
|
||||
import net.psforever.types.PlanetSideGUID
|
||||
|
||||
trait LocalHandlerFunctions extends CommonSessionInterfacingFunctionality {
|
||||
|
|
@ -47,4 +52,40 @@ class SessionLocalHandlers(
|
|||
else
|
||||
400f
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param target evaluate this to determine if to continue with this loss
|
||||
* @return whether or not we are sufficiently submerged in water
|
||||
*/
|
||||
def wadingInWater(target: PlanetSideGameObject with InteractsWithZone): Boolean = {
|
||||
target
|
||||
.interaction()
|
||||
.collectFirst {
|
||||
case env: InteractWithEnvironment =>
|
||||
env
|
||||
.Interactions
|
||||
.get(EnvironmentAttribute.Water)
|
||||
.collectFirst {
|
||||
case water: Watery => water.Depth > 0f
|
||||
}
|
||||
}
|
||||
.flatten
|
||||
.contains(true)
|
||||
}
|
||||
|
||||
/**
|
||||
* na
|
||||
* @param flagGuid flag that may exist
|
||||
* @param target evaluate this to determine if to continue with this loss
|
||||
*/
|
||||
def loseFlagViolently(flagGuid: Option[PlanetSideGUID], target: PlanetSideGameObject with InteractsWithZone): Unit = {
|
||||
continent
|
||||
.GUID(flagGuid)
|
||||
.collect {
|
||||
case flag: CaptureFlag if wadingInWater(target) =>
|
||||
flag.Destroyed = true
|
||||
continent.LocalEvents ! LocalServiceMessage("", LocalAction.LluLost(flag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.avatar.interaction
|
||||
|
||||
import net.psforever.objects.Player
|
||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player}
|
||||
import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment}
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
|
||||
import net.psforever.objects.serverobject.environment.{PieceOfEnvironment, interaction}
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment, interaction}
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.OxygenState
|
||||
|
|
@ -15,9 +15,11 @@ import scala.concurrent.duration._
|
|||
class WithWater(val channel: String)
|
||||
extends InteractionWith
|
||||
with Watery {
|
||||
/** do this every time we're in sufficient contact with water */
|
||||
private var doInteractingWithBehavior: (InteractsWithZone, PieceOfEnvironment, Option[Any]) => Unit = wadingBeforeDrowning
|
||||
|
||||
/**
|
||||
* Water causes players to slowly suffocate.
|
||||
* When they (finally) drown, they will die.
|
||||
* Water is wet.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
|
|
@ -26,22 +28,84 @@ class WithWater(val channel: String)
|
|||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
val extra = data.collect {
|
||||
case t: OxygenStateTarget => Some(t)
|
||||
case w: Watery => w.Condition
|
||||
}.flatten
|
||||
if (extra.isDefined) {
|
||||
//inform the player that their mounted vehicle is in trouble (that they are in trouble (but not from drowning (yet)))
|
||||
stopInteractingWith(obj, body, data)
|
||||
if (getExtra(data).nonEmpty) {
|
||||
inheritAndPushExtraData(obj, body, data)
|
||||
} else {
|
||||
val (effect, time, percentage) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime)
|
||||
if (effect) {
|
||||
val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage)
|
||||
waterInteractionTime = System.currentTimeMillis() + time
|
||||
condition = Some(cond)
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, Player.Die())
|
||||
//inform the player that they are in trouble
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra))
|
||||
depth = math.max(0f, body.collision.altitude - obj.Position.z)
|
||||
doInteractingWithBehavior(obj, body, data)
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = 500 milliseconds, obj.Actor, interaction.InteractingWithEnvironment(body, Some("wading")))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wading only happens while the player's head is above the water.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
private def wadingBeforeDrowning(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
//we're already "wading", let's see if we're drowning
|
||||
if (depth >= GlobalDefinitions.MaxDepth(obj)) {
|
||||
//drowning
|
||||
beginDrowning(obj, body, data)
|
||||
} else {
|
||||
//inform the player that their mounted vehicle is in trouble (that they are in trouble (but not from drowning (yet)))
|
||||
val extra = getExtra(data)
|
||||
if (extra.nonEmpty) {
|
||||
displayOxygenState(
|
||||
obj,
|
||||
condition.getOrElse(OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, 95f)),
|
||||
extra
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Too much water causes players to slowly suffocate.
|
||||
* When they (finally) drown, they will die.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
private def beginDrowning(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
val (effect, time, percentage) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime)
|
||||
if (effect) {
|
||||
val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage)
|
||||
waterInteractionTime = System.currentTimeMillis() + time
|
||||
condition = Some(cond)
|
||||
obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, Player.Die())
|
||||
//inform the player that they are in trouble
|
||||
displayOxygenState(obj, cond, getExtra(data))
|
||||
doInteractingWithBehavior = drowning
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Too much water causes players to slowly suffocate.
|
||||
* When they (finally) drown, they will die.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
private def drowning(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
//test if player ever gets head above the water level
|
||||
if (depth < GlobalDefinitions.MaxDepth(obj)) {
|
||||
val (_, _, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
|
||||
//switch to recovery
|
||||
if (percentage > 0) {
|
||||
recoverFromDrowning(obj, body, data)
|
||||
doInteractingWithBehavior = recoverFromDrowning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -52,16 +116,22 @@ class WithWater(val channel: String)
|
|||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
override def stopInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
|
||||
if (percentage > 99f) {
|
||||
recoverFromInteracting(obj)
|
||||
private def recoverFromDrowning(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
val state = condition.map(_.state)
|
||||
if (state.contains(OxygenState.Suffocation)) {
|
||||
//set up for recovery
|
||||
val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
|
||||
if (percentage < 99f) {
|
||||
//we're not too far gone
|
||||
recoverFromDrowning(obj, body, data, effect, time, percentage)
|
||||
}
|
||||
doInteractingWithBehavior = recovering
|
||||
} else {
|
||||
stopInteractingAction(obj, body, data, effect, time, percentage)
|
||||
doInteractingWithBehavior = wadingBeforeDrowning
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -74,38 +144,169 @@ class WithWater(val channel: String)
|
|||
* @param time current time until completion of the next effect
|
||||
* @param percentage value to display in the drowning UI progress bar
|
||||
*/
|
||||
private def stopInteractingAction(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any],
|
||||
effect: Boolean,
|
||||
time: Long,
|
||||
percentage: Float
|
||||
): Unit = {
|
||||
private def recoverFromDrowning(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any],
|
||||
effect: Boolean,
|
||||
time: Long,
|
||||
percentage: Float
|
||||
): Unit = {
|
||||
val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage)
|
||||
val extra = data.collect {
|
||||
case t: OxygenStateTarget => Some(t)
|
||||
case w: Watery => w.Condition
|
||||
}.flatten
|
||||
if (effect) {
|
||||
if (effect) {
|
||||
condition = Some(cond)
|
||||
waterInteractionTime = System.currentTimeMillis() + time
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
|
||||
obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
|
||||
//inform the player
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra))
|
||||
displayOxygenState(obj, cond, extra)
|
||||
} else if (extra.isDefined) {
|
||||
//inform the player
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra))
|
||||
displayOxygenState(obj, cond, extra)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The recovery period is much faster than the drowning process.
|
||||
* Check for when the player fully recovers,
|
||||
* and that the player does not regress back to drowning.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
def recovering(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
lazy val state = condition.map(_.state)
|
||||
if (depth >= GlobalDefinitions.MaxDepth(obj)) {
|
||||
//go back to drowning
|
||||
beginDrowning(obj, body, data)
|
||||
} else if (state.contains(OxygenState.Recovery)) {
|
||||
//check recovery conditions
|
||||
val (_, _, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
|
||||
if (percentage < 1f) {
|
||||
doInteractingWithBehavior = wadingBeforeDrowning
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When out of water, the player is no longer suffocating.
|
||||
* He's even stopped wading.
|
||||
* The only thing we should let complete now is recovery.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
override def stopInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
if (getExtra(data).nonEmpty) {
|
||||
inheritAndPushExtraData(obj, body, data)
|
||||
} else {
|
||||
stopInteractingWithAction(obj, body, data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When out of water, the player is no longer suffocating.
|
||||
* He's even stopped wading.
|
||||
* The only thing we should let complete now is recovery.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
private def stopInteractingWithAction(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
val cond = condition.map(_.state)
|
||||
if (cond.contains(OxygenState.Suffocation)) {
|
||||
//go from suffocating to recovery
|
||||
recoverFromDrowning(obj, body, data)
|
||||
} else if (cond.isEmpty) {
|
||||
//neither suffocating nor recovering, so just reset everything
|
||||
recoverFromInteracting(obj)
|
||||
obj.Actor ! RespondsToZoneEnvironment.StopTimer(attribute)
|
||||
waterInteractionTime = 0L
|
||||
depth = 0f
|
||||
condition = None
|
||||
doInteractingWithBehavior = wadingBeforeDrowning
|
||||
}
|
||||
}
|
||||
|
||||
override def recoverFromInteracting(obj: InteractsWithZone): Unit = {
|
||||
super.recoverFromInteracting(obj)
|
||||
if (condition.exists(_.state == OxygenState.Suffocation)) {
|
||||
val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
|
||||
stopInteractingAction(obj, condition.map(_.body).get, None, effect, time, percentage)
|
||||
val cond = condition.map(_.state)
|
||||
//whether or not we were suffocating or recovering, we need to undo the visuals for that
|
||||
if (cond.nonEmpty) {
|
||||
obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
|
||||
displayOxygenState(
|
||||
obj,
|
||||
OxygenStateTarget(obj.GUID, condition.map(_.body).get, OxygenState.Recovery, 100f),
|
||||
None
|
||||
)
|
||||
}
|
||||
waterInteractionTime = 0L
|
||||
condition = None
|
||||
}
|
||||
|
||||
/**
|
||||
* From the "condition" of someone else's drowning status,
|
||||
* extract target information and progress.
|
||||
* @param data any information
|
||||
* @return target information and drowning progress
|
||||
*/
|
||||
private def getExtra(data: Option[Any]): Option[OxygenStateTarget] = {
|
||||
data.collect {
|
||||
case t: OxygenStateTarget => Some(t)
|
||||
case w: Watery => w.Condition
|
||||
}.flatten
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the message regarding drowning and recovery
|
||||
* that includes additional information about a related target that is drowning or recovering.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data essential information about someone else's interaction with water
|
||||
*/
|
||||
private def inheritAndPushExtraData(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
val state = condition.map(_.state).getOrElse(OxygenState.Recovery)
|
||||
val Some((_, _, percentage)) = state match {
|
||||
case OxygenState.Suffocation => Some(Watery.drowningInWateryConditions(obj, Some(state), waterInteractionTime))
|
||||
case OxygenState.Recovery => Some(Watery.recoveringFromWateryConditions(obj, Some(state), waterInteractionTime))
|
||||
}
|
||||
displayOxygenState(obj, OxygenStateTarget(obj.GUID, body, state, percentage), getExtra(data))
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the message regarding drowning and recovery.
|
||||
* @param obj the target
|
||||
* @param cond the environment
|
||||
*/
|
||||
private def displayOxygenState(
|
||||
obj: InteractsWithZone,
|
||||
cond: OxygenStateTarget,
|
||||
data: Option[OxygenStateTarget]
|
||||
): Unit = {
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, data))
|
||||
}
|
||||
}
|
||||
|
||||
object WithWater {
|
||||
/** special environmental trait to queue actions independent from the primary wading test */
|
||||
case object WaterAction extends EnvironmentTrait {
|
||||
override def canInteractWith(obj: PlanetSideGameObject): Boolean = false
|
||||
override def testingDepth: Float = Float.PositiveInfinity
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import net.psforever.types.Vector3
|
|||
*/
|
||||
abstract class EnvironmentTrait {
|
||||
def canInteractWith(obj: PlanetSideGameObject): Boolean
|
||||
|
||||
def testingDepth: Float
|
||||
}
|
||||
|
||||
object EnvironmentAttribute {
|
||||
|
|
@ -25,16 +27,22 @@ object EnvironmentAttribute {
|
|||
case _ => false
|
||||
})
|
||||
}
|
||||
|
||||
def testingDepth: Float = 0.2f
|
||||
}
|
||||
|
||||
case object Lava extends EnvironmentTrait {
|
||||
/** lava can only interact with anything capable of registering damage */
|
||||
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithDamagingFields(obj)
|
||||
|
||||
val testingDepth: Float = 0f
|
||||
}
|
||||
|
||||
case object Death extends EnvironmentTrait {
|
||||
/** death can only interact with anything capable of registering damage */
|
||||
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithDamagingFields(obj)
|
||||
|
||||
val testingDepth: Float = 0f
|
||||
}
|
||||
|
||||
case object GantryDenialField
|
||||
|
|
@ -46,18 +54,24 @@ object EnvironmentAttribute {
|
|||
case _ => false
|
||||
}
|
||||
}
|
||||
|
||||
val testingDepth: Float = 0f
|
||||
}
|
||||
|
||||
case object MovementFieldTrigger
|
||||
extends EnvironmentTrait {
|
||||
/** only interact with living player characters or vehicles */
|
||||
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithPlayersAndVehicles(obj)
|
||||
|
||||
val testingDepth: Float = 0f
|
||||
}
|
||||
|
||||
case object InteriorField
|
||||
extends EnvironmentTrait {
|
||||
/** only interact with living player characters or vehicles */
|
||||
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithPlayersAndVehicles(obj)
|
||||
|
||||
val testingDepth: Float = 0f
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.serverobject.environment.interaction
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment}
|
||||
import net.psforever.objects.zones._
|
||||
|
|
@ -117,9 +116,8 @@ object InteractWithEnvironment {
|
|||
obj: PlanetSideServerObject,
|
||||
sector: SectorPopulation
|
||||
): Set[PieceOfEnvironment] = {
|
||||
val depth = GlobalDefinitions.MaxDepth(obj)
|
||||
sector.environmentList
|
||||
.filter(body => body.attribute.canInteractWith(obj) && body.testInteraction(obj, depth))
|
||||
.filter(body => body.attribute.canInteractWith(obj) && body.testInteraction(obj, body.attribute.testingDepth))
|
||||
.distinctBy(_.attribute)
|
||||
.toSet
|
||||
}
|
||||
|
|
@ -136,7 +134,7 @@ object InteractWithEnvironment {
|
|||
body: PieceOfEnvironment,
|
||||
obj: PlanetSideServerObject
|
||||
): Option[PieceOfEnvironment] = {
|
||||
if ((obj.Zone eq zone) && body.testInteraction(obj, GlobalDefinitions.MaxDepth(obj))) {
|
||||
if ((obj.Zone eq zone) && body.testInteraction(obj, body.attribute.testingDepth)) {
|
||||
Some(body)
|
||||
} else {
|
||||
None
|
||||
|
|
|
|||
|
|
@ -8,13 +8,16 @@ import net.psforever.types.{OxygenState, PlanetSideGUID}
|
|||
|
||||
trait Watery {
|
||||
val attribute: EnvironmentTrait = EnvironmentAttribute.Water
|
||||
|
||||
/** how long the current interaction has been progressing in the current way */
|
||||
protected var waterInteractionTime: Long = 0
|
||||
/** information regarding the drowning state */
|
||||
protected var condition: Option[OxygenStateTarget] = None
|
||||
/** information regarding the drowning state */
|
||||
def Condition: Option[OxygenStateTarget] = condition
|
||||
/** how far the player's feet are below the surface of the water */
|
||||
protected var depth: Float = 0f
|
||||
/** how far the player's feet are below the surface of the water */
|
||||
def Depth: Float = depth
|
||||
}
|
||||
|
||||
object Watery {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ class CaptureFlag(private val tDef: CaptureFlagDefinition) extends Amenity {
|
|||
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
|
||||
private var carrier: Option[Player] = None
|
||||
private var lastTimeCollected: Long = System.currentTimeMillis()
|
||||
private val spawnedTime: Long = lastTimeCollected
|
||||
|
||||
def Target: Building = target
|
||||
def Target_=(newTarget: Building): Building = {
|
||||
|
|
@ -56,10 +57,11 @@ class CaptureFlag(private val tDef: CaptureFlagDefinition) extends Amenity {
|
|||
* When the flag is carried by a player, the position returned should be that of the carrier not the flag.
|
||||
* @return the position of the carrier, if there is a player carrying the flag, or the flag itself
|
||||
*/
|
||||
override def Position: Vector3 = if (Carrier.nonEmpty) {
|
||||
carrier.get.Position
|
||||
} else {
|
||||
super.Position
|
||||
override def Position: Vector3 = {
|
||||
carrier match {
|
||||
case Some(player) => player.Position
|
||||
case None => super.Position
|
||||
}
|
||||
}
|
||||
|
||||
def Carrier: Option[Player] = carrier
|
||||
|
|
@ -70,6 +72,8 @@ class CaptureFlag(private val tDef: CaptureFlagDefinition) extends Amenity {
|
|||
}
|
||||
|
||||
def LastCollectionTime: Long = carrier.map { _ => lastTimeCollected }.getOrElse { System.currentTimeMillis() }
|
||||
|
||||
def InitialSpawnTime: Long = spawnedTime
|
||||
}
|
||||
|
||||
object CaptureFlag {
|
||||
|
|
|
|||
|
|
@ -606,9 +606,9 @@ class VehicleControl(vehicle: Vehicle)
|
|||
c
|
||||
}
|
||||
}
|
||||
watery.doInteractingWithTargets(player, percentage, watery.Condition.map(_.body).get, List(player))
|
||||
WithWater.doInteractingWithTargets(player, percentage, watery.Condition.map(_.body).get, List(player))
|
||||
case watery: WithWater if watery.Condition.map(_.state).contains(OxygenState.Recovery) =>
|
||||
watery.stopInteractingWithTargets(
|
||||
WithWater.stopInteractingWithTargets(
|
||||
player,
|
||||
Watery.recoveringFromWater(vehicle, watery)._3,
|
||||
watery.Condition.map(_.body).get,
|
||||
|
|
|
|||
|
|
@ -1,37 +1,71 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.vehicles.interaction
|
||||
|
||||
import net.psforever.objects.{GlobalDefinitions, Vehicle}
|
||||
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Vehicle}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment}
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
|
||||
import net.psforever.objects.serverobject.environment.{PieceOfEnvironment, interaction}
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment, interaction}
|
||||
import net.psforever.objects.vehicles.control.VehicleControl
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
import net.psforever.types.OxygenState
|
||||
|
||||
import scala.annotation.unused
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class WithWater()
|
||||
extends InteractionWith
|
||||
with Watery {
|
||||
/** do this every time we're in sufficient contact with water */
|
||||
private var doInteractingWithBehavior: (InteractsWithZone, PieceOfEnvironment, Option[Any]) => Unit = wadingBeforeDrowning
|
||||
|
||||
/**
|
||||
* Water causes vehicles to become disabled if they dive off too far, too deep.
|
||||
* Flying vehicles do not display progress towards being waterlogged.
|
||||
* They just disable outright.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
* Water is wet.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
def doInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
depth = math.max(0f, body.collision.altitude - obj.Position.z)
|
||||
doInteractingWithBehavior(obj, body, data)
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = 500 milliseconds, obj.Actor, interaction.InteractingWithEnvironment(body, Some("wading")))
|
||||
}
|
||||
|
||||
/**
|
||||
* Wading only happens while the vehicle's wheels are mostly above the water.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
private def wadingBeforeDrowning(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
//we're already "wading", let's see if we're drowning
|
||||
if (depth >= GlobalDefinitions.MaxDepth(obj)) {
|
||||
//drowning
|
||||
beginDrowning(obj, body, data)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Too much water causes players to slowly suffocate.
|
||||
* When they (finally) drown, they will die.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
private def beginDrowning(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
@unused data: Option[Any]
|
||||
): Unit = {
|
||||
obj match {
|
||||
case vehicle: Vehicle =>
|
||||
val (effect: Boolean, time: Long, percentage: Float) = {
|
||||
val (effect, time, percentage): (Boolean, Long, Float) = {
|
||||
val (a, b, c) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime)
|
||||
if (a && GlobalDefinitions.isFlightVehicle(vehicle.Definition)) {
|
||||
(true, 0L, 0f) //no progress bar
|
||||
|
|
@ -42,8 +76,94 @@ class WithWater()
|
|||
if (effect) {
|
||||
condition = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage))
|
||||
waterInteractionTime = System.currentTimeMillis() + time
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, VehicleControl.Disable(true))
|
||||
doInteractingWithTargets(
|
||||
obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, VehicleControl.Disable(true))
|
||||
WithWater.doInteractingWithTargets(
|
||||
obj,
|
||||
percentage,
|
||||
body,
|
||||
vehicle.Seats.values.flatMap(_.occupants).filter(p => p.isAlive && (p.Zone eq obj.Zone))
|
||||
)
|
||||
doInteractingWithBehavior = drowning
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Too much water causes vehicles to slowly disable.
|
||||
* When fully waterlogged, the vehicle is completely immobile.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
private def drowning(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
//test if player ever gets head above the water level
|
||||
if (depth < GlobalDefinitions.MaxDepth(obj)) {
|
||||
val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
|
||||
//switch to recovery
|
||||
if (percentage > 0) {
|
||||
recoverFromDrowning(obj, body, data, effect, time, percentage)
|
||||
doInteractingWithBehavior = recovering
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When out of water, the vehicle is no longer being waterlogged.
|
||||
* It does have to endure a recovery period to get back to normal, though.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
private def recoverFromDrowning(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
val state = condition.map(_.state)
|
||||
if (state.contains(OxygenState.Suffocation)) {
|
||||
//set up for recovery
|
||||
val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
|
||||
if (percentage < 99f) {
|
||||
//we're not too far gone
|
||||
recoverFromDrowning(obj, body, data, effect, time, percentage)
|
||||
}
|
||||
doInteractingWithBehavior = recovering
|
||||
} else {
|
||||
doInteractingWithBehavior = wadingBeforeDrowning
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When out of water, the vehicle is no longer being waterlogged.
|
||||
* It does have to endure a recovery period to get back to normal, though.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param effect na
|
||||
* @param time current time until completion of the next effect
|
||||
* @param percentage value to display in the drowning UI progress bar
|
||||
*/
|
||||
private def recoverFromDrowning(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
@unused data: Option[Any],
|
||||
effect: Boolean,
|
||||
time: Long,
|
||||
percentage: Float
|
||||
): Unit = {
|
||||
obj match {
|
||||
case vehicle: Vehicle =>
|
||||
val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage)
|
||||
if (effect) {
|
||||
condition = Some(cond)
|
||||
waterInteractionTime = System.currentTimeMillis() + time
|
||||
obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
|
||||
//inform the players
|
||||
WithWater.stopInteractingWithTargets(
|
||||
obj,
|
||||
percentage,
|
||||
body,
|
||||
|
|
@ -55,34 +175,52 @@ class WithWater()
|
|||
}
|
||||
|
||||
/**
|
||||
* When out of water, the vehicle no longer risks becoming disabled.
|
||||
* It does have to endure a recovery period to get back to full dehydration
|
||||
* Flying vehicles are exempt from this process due to the abrupt disability they experience.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
* The recovery period is much faster than the waterlogging process.
|
||||
* Check for when the vehicle fully recovers,
|
||||
* and that the vehicle does not regress back to waterlogging.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
def recovering(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
lazy val state = condition.map(_.state)
|
||||
if (depth >= GlobalDefinitions.MaxDepth(obj)) {
|
||||
//go back to drowning
|
||||
beginDrowning(obj, body, data)
|
||||
} else if (state.contains(OxygenState.Recovery)) {
|
||||
//check recovery conditions
|
||||
val (_, _, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
|
||||
if (percentage < 1f) {
|
||||
doInteractingWithBehavior = wadingBeforeDrowning
|
||||
}
|
||||
}
|
||||
}/**
|
||||
* When out of water, the vehicle no longer risks becoming disabled.
|
||||
* It does have to endure a recovery period to get back to full dehydration
|
||||
* Flying vehicles are exempt from this process due to the abrupt disability they experience.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
override def stopInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
obj match {
|
||||
case vehicle: Vehicle =>
|
||||
val (effect: Boolean, time: Long, percentage: Float) =
|
||||
Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
|
||||
if (effect) {
|
||||
condition = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage))
|
||||
waterInteractionTime = System.currentTimeMillis() + time
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
|
||||
stopInteractingWithTargets(
|
||||
obj,
|
||||
percentage,
|
||||
body,
|
||||
vehicle.Seats.values.flatMap(_.occupants).filter(p => p.isAlive && (p.Zone eq obj.Zone))
|
||||
)
|
||||
}
|
||||
case _ => ()
|
||||
val cond = condition.map(_.state)
|
||||
if (cond.contains(OxygenState.Suffocation)) {
|
||||
//go from suffocating to recovery
|
||||
recoverFromDrowning(obj, body, data)
|
||||
} else if (cond.isEmpty) {
|
||||
//neither suffocating nor recovering, so just reset everything
|
||||
recoverFromInteracting(obj)
|
||||
obj.Actor ! RespondsToZoneEnvironment.StopTimer(attribute)
|
||||
waterInteractionTime = 0L
|
||||
depth = 0f
|
||||
condition = None
|
||||
doInteractingWithBehavior = wadingBeforeDrowning
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -91,46 +229,53 @@ class WithWater()
|
|||
if (condition.exists(_.state == OxygenState.Suffocation)) {
|
||||
stopInteractingWith(obj, condition.map(_.body).get, None)
|
||||
}
|
||||
waterInteractionTime = 0L
|
||||
condition = None
|
||||
}
|
||||
}
|
||||
|
||||
object WithWater {
|
||||
/** special environmental trait to queue actions independent from the primary wading test */
|
||||
case object WaterAction extends EnvironmentTrait {
|
||||
override def canInteractWith(obj: PlanetSideGameObject): Boolean = false
|
||||
override def testingDepth: Float = Float.PositiveInfinity
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the given targets that water causes vehicles to become disabled if they dive off too far, too deep.
|
||||
* @see `InteractingWithEnvironment`
|
||||
* @see `OxygenState`
|
||||
* @see `OxygenStateTarget`
|
||||
* @param obj the target
|
||||
* @param percentage the progress bar completion state
|
||||
* @param body the environment
|
||||
* @param targets recipients of the information
|
||||
*/
|
||||
* Tell the given targets that water causes vehicles to become disabled if they dive off too far, too deep.
|
||||
* @see `InteractingWithEnvironment`
|
||||
* @see `OxygenState`
|
||||
* @see `OxygenStateTarget`
|
||||
* @param obj the target
|
||||
* @param percentage the progress bar completion state
|
||||
* @param body the environment
|
||||
* @param targets recipients of the information
|
||||
*/
|
||||
def doInteractingWithTargets(
|
||||
obj: PlanetSideServerObject,
|
||||
percentage: Float,
|
||||
body: PieceOfEnvironment,
|
||||
targets: Iterable[PlanetSideServerObject]
|
||||
): Unit = {
|
||||
obj: PlanetSideServerObject,
|
||||
percentage: Float,
|
||||
body: PieceOfEnvironment,
|
||||
targets: Iterable[PlanetSideServerObject]
|
||||
): Unit = {
|
||||
val state = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage))
|
||||
targets.foreach(_.Actor ! interaction.InteractingWithEnvironment(body, state))
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the given targets that, when out of water, the vehicle no longer risks becoming disabled.
|
||||
* @see `EscapeFromEnvironment`
|
||||
* @see `OxygenState`
|
||||
* @see `OxygenStateTarget`
|
||||
* @param obj the target
|
||||
* @param percentage the progress bar completion state
|
||||
* @param body the environment
|
||||
* @param targets recipients of the information
|
||||
*/
|
||||
* Tell the given targets that, when out of water, the vehicle no longer risks becoming disabled.
|
||||
* @see `EscapeFromEnvironment`
|
||||
* @see `OxygenState`
|
||||
* @see `OxygenStateTarget`
|
||||
* @param obj the target
|
||||
* @param percentage the progress bar completion state
|
||||
* @param body the environment
|
||||
* @param targets recipients of the information
|
||||
*/
|
||||
def stopInteractingWithTargets(
|
||||
obj: PlanetSideServerObject,
|
||||
percentage: Float,
|
||||
body: PieceOfEnvironment,
|
||||
targets: Iterable[PlanetSideServerObject]
|
||||
): Unit = {
|
||||
obj: PlanetSideServerObject,
|
||||
percentage: Float,
|
||||
body: PieceOfEnvironment,
|
||||
targets: Iterable[PlanetSideServerObject]
|
||||
): Unit = {
|
||||
val state = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage))
|
||||
targets.foreach(_.Actor ! interaction.EscapeFromEnvironment(body, state))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -134,7 +134,17 @@ object OxygenStateMessage extends Marshallable[OxygenStateMessage] {
|
|||
204.8f,
|
||||
11
|
||||
) :: //hackish: 2^11 == 2047, so it should be 204.7; but, 204.8 allows decode == encode
|
||||
OxygenState.codec
|
||||
bool.xmap[OxygenState](
|
||||
{
|
||||
case false => OxygenState.Recovery
|
||||
case true => OxygenState.Suffocation
|
||||
},
|
||||
{
|
||||
case OxygenState.Recovery => false
|
||||
case OxygenState.Suffocation => true
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
).as[DrowningTarget]
|
||||
|
||||
implicit val codec: Codec[OxygenStateMessage] = (
|
||||
|
|
|
|||
|
|
@ -120,6 +120,8 @@ class LocalService(zone: Zone) extends Actor {
|
|||
hackCapturer ! HackCaptureActor.StartCaptureTerminalHack(target, zone, 0, 8L)
|
||||
case LocalAction.LluCaptured(llu) =>
|
||||
hackCapturer ! HackCaptureActor.FlagCaptured(llu)
|
||||
case LocalAction.LluLost(llu) =>
|
||||
hackCapturer ! HackCaptureActor.FlagLost(llu)
|
||||
|
||||
case LocalAction.LluSpawned(player_guid, llu) =>
|
||||
// Forward to all clients to create object locally
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ object LocalAction {
|
|||
final case class ResecureCaptureTerminal(target: CaptureTerminal, hacker: PlayerSource) extends Action
|
||||
final case class StartCaptureTerminalHack(target: CaptureTerminal) extends Action
|
||||
final case class LluCaptured(llu: CaptureFlag) extends Action
|
||||
final case class LluLost(llu: CaptureFlag) extends Action
|
||||
final case class LluSpawned(player_guid: PlanetSideGUID, llu: CaptureFlag) extends Action
|
||||
final case class LluDespawned(player_guid: PlanetSideGUID, guid: PlanetSideGUID, position: Vector3) extends Action
|
||||
|
||||
|
|
|
|||
|
|
@ -98,6 +98,12 @@ class CaptureFlagManager(zone: Zone) extends Actor {
|
|||
flag.Zone,
|
||||
CaptureFlagChatMessageStrings.CTF_Failed_TimedOut(building.Name, flag.Target.Name, flag.Faction)
|
||||
)
|
||||
case CaptureFlagLostReasonEnum.FlagLost =>
|
||||
val building = flag.Owner.asInstanceOf[Building]
|
||||
ChatBroadcast(
|
||||
flag.Zone,
|
||||
CaptureFlagChatMessageStrings.CTF_Failed_FlagLost(building.Name, flag.Faction)
|
||||
)
|
||||
case CaptureFlagLostReasonEnum.Ended =>
|
||||
()
|
||||
}
|
||||
|
|
@ -197,6 +203,7 @@ class CaptureFlagManager(zone: Zone) extends Actor {
|
|||
}
|
||||
|
||||
private def ChatBroadcast(zone: Zone, message: String, fanfare: Boolean = true): Unit = {
|
||||
//todo use UNK_222 sometimes
|
||||
val messageType: ChatMessageType = if (fanfare) {
|
||||
ChatMessageType.UNK_223
|
||||
} else {
|
||||
|
|
@ -224,15 +231,6 @@ object CaptureFlagManager {
|
|||
}
|
||||
|
||||
object CaptureFlagChatMessageStrings {
|
||||
/*
|
||||
@CTF_Failed_TargetLost=%1's LLU target facility %2 was lost!\nHack canceled!
|
||||
@CTF_Failed_FlagLost=The %1 lost %2's LLU!\nHack canceled!
|
||||
@CTF_Warning_Carrier=%1 of the %2 has %3's LLU.\nIt must be taken to %4 within %5 minutes!
|
||||
@CTF_Warning_NoCarrier=%1's LLU is in the field.\nThe %2 must take it to %3 within %4 minutes!
|
||||
@CTF_Warning_Carrier1Min=%1 of the %2 has %3's LLU.\nIt must be taken to %4 within the next minute!
|
||||
@CTF_Warning_NoCarrier1Min=%1's LLU is in the field.\nThe %2 must take it to %3 within the next minute!
|
||||
*/
|
||||
|
||||
// @CTF_Success=%1 captured %2's LLU for the %3!
|
||||
/** {player.Name} captured {ownerName}'s LLU for the {player.Faction}! */
|
||||
private[support] def CTF_Success(playerName:String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
|
||||
|
|
@ -243,10 +241,20 @@ object CaptureFlagChatMessageStrings {
|
|||
private[support] def CTF_Failed_TimedOut(ownerName: String, name: String, faction: PlanetSideEmpire.Value): String =
|
||||
s"@CTF_Failed_TimedOut^@${GetFactionString(faction)}~^@$ownerName~^@$name~"
|
||||
|
||||
// @CTF_Failed_Lost=The %1 lost %2's LLU!\nHack canceled!
|
||||
/** The {faction} lost {ownerName}'s LLU!\nHack canceled! */
|
||||
private[support] def CTF_Failed_FlagLost(ownerName: String, faction: PlanetSideEmpire.Value): String =
|
||||
s"@CTF_Failed_FlagLost^@${GetFactionString(faction)}~^@$ownerName~"
|
||||
|
||||
// @CTF_Failed_TargetLost=%1's LLU target facility %2 was lost!\nHack canceled!
|
||||
/** {hackFacility}'s LLU target facility {targetFacility} was lost!\nHack canceled! */
|
||||
private[support] def CTF_Failed_TargetLost(hackFacility: String, targetFacility: String): String =
|
||||
s"@CTF_Failed_TargetLost^@$hackFacility~^@$targetFacility~"
|
||||
|
||||
// @CTF_Failed_SourceResecured=The %1 resecured %2!\nThe LLU was lost!
|
||||
/** The {faction} resecured {name}!\nThe LLU was lost! */
|
||||
private[support] def CTF_Failed_SourceResecured(name: String, faction: PlanetSideEmpire.Value): String =
|
||||
s"@CTF_Failed_SourceResecured^@${CaptureFlagChatMessageStrings.GetFactionString(faction)}~^@$name~"
|
||||
s"@CTF_Failed_SourceResecured^@${GetFactionString(faction)}~^@$name~"
|
||||
|
||||
// @CTF_FlagSpawned=%1 %2 has spawned a LLU.\nIt must be taken to %3 %4's Control Console within %5 minutes or the hack will fail!
|
||||
/** {facilityType} {facilityName} has spawned a LLU.\nIt must be taken to {targetFacilityType} {targetFacilityName}'s Control Console within 15 minutes or the hack will fail! */
|
||||
|
|
@ -256,12 +264,56 @@ object CaptureFlagChatMessageStrings {
|
|||
// @CTF_FlagPickedUp=%1 of the %2 picked up %3's LLU
|
||||
/** {player.Name} of the {player.Faction} picked up {ownerName}'s LLU */
|
||||
def CTF_FlagPickedUp(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
|
||||
s"@CTF_FlagPickedUp^$playerName~^@${CaptureFlagChatMessageStrings.GetFactionString(playerFaction)}~^@$ownerName~"
|
||||
s"@CTF_FlagPickedUp^$playerName~^@${GetFactionString(playerFaction)}~^@$ownerName~"
|
||||
|
||||
// @CTF_FlagDropped=%1 of the %2 dropped %3's LLU
|
||||
/** {playerName} of the {faction} dropped {facilityName}'s LLU */
|
||||
def CTF_FlagDropped(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
|
||||
s"@CTF_FlagDropped^$playerName~^@${CaptureFlagChatMessageStrings.GetFactionString(playerFaction)}~^@$ownerName~"
|
||||
s"@CTF_FlagDropped^$playerName~^@${GetFactionString(playerFaction)}~^@$ownerName~"
|
||||
|
||||
// @CTF_Warning_Carrier=%1's LLU is in the field.\nThe %2 must take it to %3 within %4 minutes!
|
||||
/** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within {time} minutes! */
|
||||
def CTF_Warning_Carrier(
|
||||
playerName:String,
|
||||
playerFaction: PlanetSideEmpire.Value,
|
||||
facilityName: String,
|
||||
targetFacilityName: String,
|
||||
time: Int
|
||||
): String = {
|
||||
s"@CTF_Warning_Carrier^$playerName~^@${GetFactionString(playerFaction)}~^@$facilityName~^@$targetFacilityName~^@$time~"
|
||||
}
|
||||
|
||||
// @CTF_Warning_Carrier1Min=%1 of the %2 has %3's LLU.\nIt must be taken to %4 within the next minute!
|
||||
/** {playerName} of the {faction} has {facilityName}'s LLU.\nIt must be taken to {targetFacilityName} within the next minute! */
|
||||
def CTF_Warning_Carrier1Min(
|
||||
playerName:String,
|
||||
playerFaction: PlanetSideEmpire.Value,
|
||||
facilityName: String,
|
||||
targetFacilityName: String
|
||||
): String = {
|
||||
s"@CTF_Warning_Carrier1Min^$playerName~^@${GetFactionString(playerFaction)}~^@$facilityName~^@$targetFacilityName~"
|
||||
}
|
||||
|
||||
// @CTF_Warning_NoCarrier=%1's LLU is in the field.\nThe %2 must take it to %3 within %4 minutes!
|
||||
/** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within {time} minute! */
|
||||
def CTF_Warning_NoCarrier(
|
||||
facilityName: String,
|
||||
playerFaction: PlanetSideEmpire.Value,
|
||||
targetFacilityName: String,
|
||||
time: Int
|
||||
): String = {
|
||||
s"@CTF_Warning_NoCarrier^@$facilityName~^@${GetFactionString(playerFaction)}~^@$targetFacilityName~^$time~"
|
||||
}
|
||||
|
||||
// @CTF_Warning_NoCarrier1Min=%1's LLU is in the field.\nThe %2 must take it to %3 within the next minute!
|
||||
/** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within the next minute! */
|
||||
def CTF_Warning_NoCarrier1Min(
|
||||
facilityName: String,
|
||||
playerFaction: PlanetSideEmpire.Value,
|
||||
targetFacilityName: String
|
||||
): String = {
|
||||
s"@CTF_Warning_NoCarrier1Min^@$facilityName~^@${GetFactionString(playerFaction)}~^@$targetFacilityName~"
|
||||
}
|
||||
|
||||
private def GetFactionString: PlanetSideEmpire.Value=>String = {
|
||||
case PlanetSideEmpire.TR => "TerranRepublic"
|
||||
|
|
@ -277,4 +329,5 @@ object CaptureFlagLostReasonEnum {
|
|||
final case object Resecured extends CaptureFlagLostReasonEnum
|
||||
final case object TimedOut extends CaptureFlagLostReasonEnum
|
||||
final case object Ended extends CaptureFlagLostReasonEnum
|
||||
final case object FlagLost extends CaptureFlagLostReasonEnum
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,9 +62,14 @@ class HackCaptureActor extends Actor {
|
|||
// If the base has a socket, but no flag spawned it means the hacked base is neutral with no friendly neighbouring bases to deliver to, making it a timed hack.
|
||||
val building = terminal.Owner.asInstanceOf[Building]
|
||||
building.GetFlag match {
|
||||
case Some(llu) if llu.LastCollectionTime == llu.InitialSpawnTime =>
|
||||
// LLU was never once collected. Send resecured notifications
|
||||
terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.TimedOut)
|
||||
NotifyHackStateChange(terminal, isResecured = true)
|
||||
|
||||
case Some(llu) =>
|
||||
// LLU was not delivered in time. Send resecured notifications
|
||||
terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.TimedOut)
|
||||
terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.FlagLost)
|
||||
NotifyHackStateChange(terminal, isResecured = true)
|
||||
|
||||
case _ =>
|
||||
|
|
@ -138,6 +143,26 @@ class HackCaptureActor extends Actor {
|
|||
log.error(s"Attempted LLU capture for ${flag.Owner.asInstanceOf[Building].Name} but CC GUID ${flag.Owner.asInstanceOf[Building].CaptureTerminal.get.GUID} was not in list of hacked objects")
|
||||
}
|
||||
|
||||
case HackCaptureActor.FlagLost(flag) =>
|
||||
val terminalOpt = flag.Owner.asInstanceOf[Building].CaptureTerminal
|
||||
hackedObjects
|
||||
.find(entry => terminalOpt.contains(entry.target))
|
||||
.collect { entry =>
|
||||
val terminal = terminalOpt.get
|
||||
hackedObjects = hackedObjects.filterNot(x => x == entry)
|
||||
log.info(s"FlagLost: ${flag.Carrier.map(_.Name).getOrElse("")} the flag carrier screwed up the capture for ${flag.Target.Name} and the LLU has been lost")
|
||||
terminal.Actor ! CommonMessages.ClearHack()
|
||||
NotifyHackStateChange(terminal, isResecured = true)
|
||||
// If there's hacked objects left in the list restart the timer with the shortest hack time left
|
||||
RestartTimer()
|
||||
}
|
||||
.orElse{
|
||||
log.warn(s"FlagLost: flag data does not match to an entry in the hacked objects list")
|
||||
None
|
||||
}
|
||||
context.parent ! CaptureFlagManager.Lost(flag, CaptureFlagLostReasonEnum.FlagLost)
|
||||
|
||||
|
||||
case _ => ()
|
||||
}
|
||||
|
||||
|
|
@ -256,6 +281,7 @@ object HackCaptureActor {
|
|||
|
||||
final case class ResecureCaptureTerminal(target: CaptureTerminal, zone: Zone, hacker: PlayerSource)
|
||||
final case class FlagCaptured(flag: CaptureFlag)
|
||||
final case class FlagLost(flag: CaptureFlag)
|
||||
|
||||
private final case class ProcessCompleteHacks()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
package net.psforever.types
|
||||
|
||||
import enumeratum.{Enum, EnumEntry}
|
||||
import net.psforever.packet.PacketHelpers
|
||||
import scodec.Codec
|
||||
import scodec.codecs.uint
|
||||
|
||||
/**
|
||||
* The progress state of being a drowning victim.
|
||||
|
|
@ -22,6 +19,4 @@ object OxygenState extends Enum[OxygenState] {
|
|||
|
||||
case object Recovery extends OxygenState
|
||||
case object Suffocation extends OxygenState
|
||||
|
||||
implicit val codec: Codec[OxygenState] = PacketHelpers.createEnumCodec(e = this, uint(bits = 1))
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue