mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-01-19 18:14:44 +00:00
Environmental Redo (#1169)
* switched environment handling from the control agency side to the entity side to match other zone interactions * reusing the existing sector data; better resets, negating an issue during mounting where the player does not switch back to recovery; disabling was bugged due to missed timer upgrade * making lava no longer deadly; wait ... * only one method of cancelling a lava interaction at a time
This commit is contained in:
parent
ea77d4728f
commit
b43e7a6993
|
|
@ -3,6 +3,7 @@ package net.psforever.actors.session.support
|
|||
|
||||
import akka.actor.{ActorContext, typed}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.environment.interaction.ResetAllEnvironmentInteractions
|
||||
import net.psforever.objects.vital.InGameHistory
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -61,6 +62,7 @@ class SessionMountHandlers(
|
|||
sendResponse(PlanetsideAttributeMessage(obj_guid, attribute_type=45, obj.NtuCapacitorScaled))
|
||||
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
|
||||
sessionData.accessContainer(obj)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
|
|
@ -76,6 +78,7 @@ class SessionMountHandlers(
|
|||
obj.Cloaked = tplayer.Cloaked
|
||||
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
|
||||
sessionData.accessContainer(obj)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
|
|
@ -90,6 +93,7 @@ class SessionMountHandlers(
|
|||
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
|
||||
sessionData.accessContainer(obj)
|
||||
sessionData.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
|
|
@ -103,6 +107,7 @@ class SessionMountHandlers(
|
|||
sendResponse(GenericObjectActionMessage(obj_guid, code=11))
|
||||
sessionData.accessContainer(obj)
|
||||
sessionData.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _)
|
||||
|
|
@ -122,6 +127,7 @@ class SessionMountHandlers(
|
|||
sessionData.accessContainer(obj)
|
||||
sessionData.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
sessionData.keepAliveFunc = sessionData.keepAlivePersistence
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Vehicle, seatNumber, _) =>
|
||||
|
|
@ -139,6 +145,7 @@ class SessionMountHandlers(
|
|||
sessionData.accessContainer(obj)
|
||||
sessionData.updateWeaponAtSeatPosition(obj, seatNumber)
|
||||
sessionData.keepAliveFunc = sessionData.keepAlivePersistence
|
||||
tplayer.Actor ! ResetAllEnvironmentInteractions
|
||||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: FacilityTurret, seatNumber, _)
|
||||
|
|
@ -173,7 +180,7 @@ class SessionMountHandlers(
|
|||
MountingAction(tplayer, obj, seatNumber)
|
||||
|
||||
case Mountable.CanMount(obj: Mountable, _, _) =>
|
||||
log.warn(s"MountVehicleMsg: $obj is some mountable object and nothing will happen for ${player.Name}")
|
||||
log.warn(s"MountVehicleMsg: $obj is some kind of mountable object but nothing will happen for ${player.Name}")
|
||||
|
||||
case Mountable.CanDismount(obj: ImplantTerminalMech, seatNum, _) =>
|
||||
log.info(s"${tplayer.Name} dismounts the implant terminal")
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.objects.avatar.interaction.{WithGantry, WithLava, WithWater}
|
||||
import net.psforever.objects.avatar.{Avatar, LoadoutManager, SpecialCarry}
|
||||
import net.psforever.objects.ballistics.InteractWithRadiationClouds
|
||||
import net.psforever.objects.ce.{Deployable, InteractWithMines, InteractWithTurrets}
|
||||
import net.psforever.objects.definition.{AvatarDefinition, ExoSuitDefinition, SpecialExoSuitDefinition}
|
||||
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.{PlanetSideServerObject, environment}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.aura.AuraContainer
|
||||
import net.psforever.objects.serverobject.environment.InteractWithEnvironment
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.{WithDeath, WithMovementTrigger}
|
||||
import net.psforever.objects.serverobject.mount.MountableEntity
|
||||
import net.psforever.objects.vital.resistance.ResistanceProfile
|
||||
import net.psforever.objects.vital.{HealFromEquipment, InGameActivity, RepairFromEquipment, Vitality}
|
||||
|
|
@ -19,7 +20,7 @@ import net.psforever.objects.vital.interaction.DamageInteraction
|
|||
import net.psforever.objects.vital.resolution.DamageResistanceModel
|
||||
import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorPopulation}
|
||||
import net.psforever.objects.zones.{InteractsWithZone, ZoneAware, Zoning}
|
||||
import net.psforever.types.{PlanetSideGUID, _}
|
||||
import net.psforever.types._
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Success, Try}
|
||||
|
|
@ -36,7 +37,13 @@ class Player(var avatar: Avatar)
|
|||
with ZoneAware
|
||||
with AuraContainer
|
||||
with MountableEntity {
|
||||
interaction(new InteractWithEnvironment())
|
||||
interaction(environment.interaction.InteractWithEnvironment(Seq(
|
||||
new WithWater(avatar.name),
|
||||
new WithLava(),
|
||||
new WithDeath(),
|
||||
new WithGantry(avatar.name),
|
||||
new WithMovementTrigger()
|
||||
)))
|
||||
interaction(new InteractWithMinesUnlessSpectating(obj = this, range = 10))
|
||||
interaction(new InteractWithTurrets())
|
||||
interaction(new InteractWithRadiationClouds(range = 10f, Some(this)))
|
||||
|
|
|
|||
|
|
@ -6,14 +6,15 @@ import net.psforever.objects.definition.{ToolDefinition, VehicleDefinition}
|
|||
import net.psforever.objects.equipment.{Equipment, EquipmentSize, EquipmentSlot, JammableUnit}
|
||||
import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile}
|
||||
import net.psforever.objects.serverobject.mount.{MountableEntity, Seat, SeatDefinition}
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.{PlanetSideServerObject, environment}
|
||||
import net.psforever.objects.serverobject.affinity.FactionAffinity
|
||||
import net.psforever.objects.serverobject.aura.AuraContainer
|
||||
import net.psforever.objects.serverobject.deploy.Deployment
|
||||
import net.psforever.objects.serverobject.environment.InteractWithEnvironment
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.{WithDeath, WithMovementTrigger}
|
||||
import net.psforever.objects.serverobject.hackable.Hackable
|
||||
import net.psforever.objects.serverobject.structures.AmenityOwner
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vehicles.interaction.{WithLava, WithWater}
|
||||
import net.psforever.objects.vital.resistance.StandardResistanceProfile
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.resolution.DamageResistanceModel
|
||||
|
|
@ -76,21 +77,26 @@ import scala.util.{Success, Try}
|
|||
*/
|
||||
class Vehicle(private val vehicleDef: VehicleDefinition)
|
||||
extends AmenityOwner
|
||||
with BlockMapEntity
|
||||
with MountableWeapons
|
||||
with InteractsWithZone
|
||||
with Hackable
|
||||
with FactionAffinity
|
||||
with Deployment
|
||||
with Vitality
|
||||
with OwnableByPlayer
|
||||
with StandardResistanceProfile
|
||||
with JammableUnit
|
||||
with CommonNtuContainer
|
||||
with Container
|
||||
with AuraContainer
|
||||
with MountableEntity {
|
||||
interaction(new InteractWithEnvironment())
|
||||
with BlockMapEntity
|
||||
with MountableWeapons
|
||||
with InteractsWithZone
|
||||
with Hackable
|
||||
with FactionAffinity
|
||||
with Deployment
|
||||
with Vitality
|
||||
with OwnableByPlayer
|
||||
with StandardResistanceProfile
|
||||
with JammableUnit
|
||||
with CommonNtuContainer
|
||||
with Container
|
||||
with AuraContainer
|
||||
with MountableEntity {
|
||||
interaction(environment.interaction.InteractWithEnvironment(Seq(
|
||||
new WithWater(),
|
||||
new WithLava(),
|
||||
new WithDeath(),
|
||||
new WithMovementTrigger()
|
||||
)))
|
||||
interaction(new InteractWithMines(range = 20))
|
||||
interaction(new InteractWithTurrets())
|
||||
interaction(new InteractWithRadiationCloudsSeatedInVehicle(obj = this, range = 20))
|
||||
|
|
|
|||
|
|
@ -30,15 +30,12 @@ import net.psforever.services.Service
|
|||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
|
||||
import net.psforever.objects.locker.LockerContainerControl
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.environment.interaction.RespondsToZoneEnvironment
|
||||
import net.psforever.objects.serverobject.repair.Repairable
|
||||
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
|
||||
import net.psforever.objects.sourcing.{AmenitySource, PlayerSource}
|
||||
import net.psforever.objects.vital.collision.CollisionReason
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.{PainboxReason, SuicideReason}
|
||||
import net.psforever.objects.vital.interaction.{Adversarial, DamageInteraction, DamageResult}
|
||||
import net.psforever.services.hart.ShuttleState
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
|
@ -66,12 +63,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
ApplicableEffect(Aura.Fire)
|
||||
|
||||
def InteractiveObject: Player = player
|
||||
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
|
||||
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
|
||||
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
|
||||
SetInteraction(EnvironmentAttribute.GantryDenialField, doInteractingWithGantryField)
|
||||
SetInteraction(EnvironmentAttribute.MovementFieldTrigger, doInteractingWithMovementTrigger)
|
||||
SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
|
||||
|
||||
private[this] val log = org.log4s.getLogger(player.Name)
|
||||
private[this] val damageLog = org.log4s.getLogger(Damageable.LogChannel)
|
||||
/** suffocating, or regaining breath? */
|
||||
|
|
@ -93,6 +85,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
player.avatar.locker.Actor = Default.Actor
|
||||
EndAllEffects()
|
||||
EndAllAggravation()
|
||||
respondToEnvironmentPostStop()
|
||||
}
|
||||
|
||||
def receive: Receive =
|
||||
|
|
@ -1215,159 +1208,6 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
|
|||
val value = target.Aura.foldLeft(0)(_ + PlayerControl.auraEffectToAttributeValue(_))
|
||||
zone.AvatarEvents ! AvatarServiceMessage(zone.id, AvatarAction.PlanetsideAttributeToAll(target.GUID, 54, value))
|
||||
}
|
||||
|
||||
/**
|
||||
* Water causes players to slowly suffocate.
|
||||
* When they (finally) drown, they will die.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable;
|
||||
* for players, this will be data from any mounted vehicles
|
||||
*/
|
||||
def doInteractingWithWater(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val (effect: Boolean, time: Long, percentage: Float) =
|
||||
RespondsToZoneEnvironment.drowningInWateryConditions(obj, submergedCondition, interactionTime)
|
||||
if (effect) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
interactionTime = System.currentTimeMillis() + time
|
||||
submergedCondition = Some(OxygenState.Suffocation)
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = time milliseconds, self, Player.Die())
|
||||
//inform the player that they are in trouble
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.OxygenState(OxygenStateTarget(player.GUID, OxygenState.Suffocation, percentage), data)
|
||||
)
|
||||
} else if (data.isDefined) {
|
||||
//inform the player that their mounted vehicle is in trouble (that they are in trouble)
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.OxygenState(OxygenStateTarget(player.GUID, OxygenState.Suffocation, percentage), data)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lava causes players to take (considerable) damage until they inevitably die.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWithLava(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
if (player.isAlive) {
|
||||
PerformDamage(
|
||||
player,
|
||||
DamageInteraction(
|
||||
PlayerSource(player),
|
||||
EnvironmentReason(body, player),
|
||||
player.Position
|
||||
).calculate()
|
||||
)
|
||||
if (player.Health > 0) {
|
||||
StartAuraEffect(Aura.Fire, duration = 1250L) //burn
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = 250 milliseconds, self, InteractingWithEnvironment(player, body, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Death causes players to die outright.
|
||||
* It's not even considered as environmental damage anymore.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWithDeath(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
player.History.findLast { entry => entry.isInstanceOf[ReconstructionActivity] } match {
|
||||
case Some(entry) if System.currentTimeMillis() - entry.time > 4000L => suicide()
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection ScalaUnusedSymbol
|
||||
def doInteractingWithGantryField(
|
||||
obj: PlanetSideServerObject,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[OxygenStateTarget]
|
||||
): Unit = {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
val field = body.asInstanceOf[GantryDenialField]
|
||||
val zone = player.Zone
|
||||
(zone.GUID(field.obbasemesh) match {
|
||||
case Some(pad : OrbitalShuttlePad) => zone.GUID(pad.shuttle)
|
||||
case _ => None
|
||||
}) match {
|
||||
case Some(shuttle: Vehicle)
|
||||
if shuttle.Flying.contains(ShuttleState.State11.id) || shuttle.Faction != player.Faction =>
|
||||
val (pos, zang) = Vehicles.dismountShuttle(shuttle, field.mountPoint)
|
||||
shuttle.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
PlayerStateShiftMessage(ShiftState(0, pos, zang, None)))
|
||||
)
|
||||
case Some(_: Vehicle) =>
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(
|
||||
delay = 250 milliseconds,
|
||||
self,
|
||||
InteractingWithEnvironment(player, body, None)
|
||||
)
|
||||
case _ => ;
|
||||
//something configured incorrectly; no need to keep checking
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The player will be affected by this action.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWithMovementTrigger(
|
||||
obj: PlanetSideServerObject,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[OxygenStateTarget]
|
||||
): Unit = {
|
||||
body.asInstanceOf[GeneralMovementField].triggerAction(obj)
|
||||
}
|
||||
|
||||
/**
|
||||
* When out of water, the player is no longer suffocating.
|
||||
* The player does have to endure a recovery period to get back to normal, though.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable;
|
||||
* for players, this will be data from any mounted vehicles
|
||||
*/
|
||||
def stopInteractingWithWater(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val (effect: Boolean, time: Long, percentage: Float) =
|
||||
RespondsToZoneEnvironment.recoveringFromWateryConditions(obj, submergedCondition, interactionTime)
|
||||
if (percentage == 100f) {
|
||||
recoverFromEnvironmentInteracting()
|
||||
}
|
||||
if (effect) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
submergedCondition = Some(OxygenState.Recovery)
|
||||
interactionTime = System.currentTimeMillis() + time
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = time milliseconds, self, RecoveredFromEnvironmentInteraction())
|
||||
//inform the player
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.OxygenState(OxygenStateTarget(player.GUID, OxygenState.Recovery, percentage), data)
|
||||
)
|
||||
} else if (data.isDefined) {
|
||||
//inform the player
|
||||
player.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
player.Name,
|
||||
AvatarAction.OxygenState(OxygenStateTarget(player.GUID, OxygenState.Recovery, percentage), data)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override def recoverFromEnvironmentInteracting(): Unit = {
|
||||
super.recoverFromEnvironmentInteracting()
|
||||
submergedCondition = None
|
||||
}
|
||||
}
|
||||
|
||||
object PlayerControl {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.avatar.interaction
|
||||
|
||||
import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment}
|
||||
import net.psforever.objects.{Vehicle, Vehicles}
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, GantryDenialField, PieceOfEnvironment, interaction}
|
||||
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
import net.psforever.packet.game.{PlayerStateShiftMessage, ShiftState}
|
||||
import net.psforever.services.Service
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.services.hart.ShuttleState
|
||||
|
||||
import scala.annotation.unused
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class WithGantry(val channel: String)
|
||||
extends InteractionWith {
|
||||
val attribute: EnvironmentTrait = EnvironmentAttribute.GantryDenialField
|
||||
|
||||
def doInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
@unused data: Option[Any]
|
||||
): Unit = {
|
||||
val field = body.asInstanceOf[GantryDenialField]
|
||||
val zone = obj.Zone
|
||||
(zone.GUID(field.obbasemesh) match {
|
||||
case Some(pad : OrbitalShuttlePad) => zone.GUID(pad.shuttle)
|
||||
case _ => None
|
||||
}) match {
|
||||
case Some(shuttle: Vehicle)
|
||||
if shuttle.Flying.contains(ShuttleState.State11.id) || shuttle.Faction != obj.Faction =>
|
||||
val (pos, ang) = Vehicles.dismountShuttle(shuttle, field.mountPoint)
|
||||
shuttle.Zone.AvatarEvents ! AvatarServiceMessage(
|
||||
channel,
|
||||
AvatarAction.SendResponse(
|
||||
Service.defaultPlayerGUID,
|
||||
PlayerStateShiftMessage(ShiftState(0, pos, ang, None)))
|
||||
)
|
||||
case Some(_: Vehicle) =>
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(
|
||||
attribute,
|
||||
delay = 250 milliseconds,
|
||||
obj.Actor,
|
||||
interaction.InteractingWithEnvironment(body, None)
|
||||
)
|
||||
case _ => () //something configured incorrectly; no need to keep checking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.avatar.interaction
|
||||
|
||||
import net.psforever.objects.serverobject.aura.{Aura, AuraEffectBehavior}
|
||||
import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment}
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment, interaction}
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class WithLava()
|
||||
extends InteractionWith {
|
||||
val attribute: EnvironmentTrait = EnvironmentAttribute.Lava
|
||||
|
||||
private var stopBurn: Boolean = false
|
||||
|
||||
/**
|
||||
* Lava causes players to take (considerable) damage until they inevitably die.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
def doInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
if (stopBurn && data.nonEmpty) {
|
||||
stopBurn = false
|
||||
} else if (!obj.Destroyed) {
|
||||
obj.Actor ! Vitality.Damage(
|
||||
DamageInteraction(
|
||||
SourceEntry(obj),
|
||||
EnvironmentReason(body, obj),
|
||||
obj.Position
|
||||
).calculate()
|
||||
)
|
||||
obj.Actor ! AuraEffectBehavior.StartEffect(Aura.Fire, duration = 1250L) //burn
|
||||
//keep doing damage
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = 250 milliseconds, obj.Actor, interaction.InteractingWithEnvironment(body, Some("burning")))
|
||||
}
|
||||
}
|
||||
|
||||
override def stopInteractingWith(obj: InteractsWithZone, body: PieceOfEnvironment, parentInfo: Option[Any]): Unit = {
|
||||
stopBurn = true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.avatar.interaction
|
||||
|
||||
import net.psforever.objects.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.zones.InteractsWithZone
|
||||
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
|
||||
import net.psforever.types.OxygenState
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class WithWater(val channel: String)
|
||||
extends InteractionWith
|
||||
with Watery {
|
||||
/**
|
||||
* Water causes players to slowly suffocate.
|
||||
* When they (finally) drown, they will die.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
*/
|
||||
def doInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
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)
|
||||
} 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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When out of water, the player is no longer suffocating.
|
||||
* The player does have to endure a recovery period to get back to normal, though.
|
||||
* @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)
|
||||
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 (percentage > 99f) {
|
||||
recoverFromInteracting(obj)
|
||||
}
|
||||
if (effect) {
|
||||
condition = Some(cond)
|
||||
waterInteractionTime = System.currentTimeMillis() + time
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
|
||||
//inform the player
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra))
|
||||
} else if (extra.isDefined) {
|
||||
//inform the player
|
||||
obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra))
|
||||
}
|
||||
}
|
||||
|
||||
override def recoverFromInteracting(obj: InteractsWithZone): Unit = {
|
||||
super.recoverFromInteracting(obj)
|
||||
if (condition.exists(_.state == OxygenState.Suffocation)) {
|
||||
stopInteractingWith(obj, condition.map(_.body).get, None)
|
||||
}
|
||||
waterInteractionTime = 0L
|
||||
condition = None
|
||||
}
|
||||
}
|
||||
|
|
@ -1,176 +0,0 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.serverobject.environment
|
||||
|
||||
import net.psforever.objects.GlobalDefinitions
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.zones._
|
||||
import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorPopulation}
|
||||
|
||||
case object EnvironmentInteraction extends ZoneInteractionType
|
||||
|
||||
/**
|
||||
* This game entity may infrequently test whether it may interact with game world environment.
|
||||
*/
|
||||
class InteractWithEnvironment()
|
||||
extends ZoneInteraction {
|
||||
private var interactingWithEnvironment: (PlanetSideServerObject, Boolean) => Any =
|
||||
InteractWithEnvironment.onStableEnvironment()
|
||||
|
||||
def Type = EnvironmentInteraction
|
||||
|
||||
def range: Float = 0f
|
||||
|
||||
/**
|
||||
* The method by which zone interactions are tested or a current interaction maintained.
|
||||
* Utilize a function literal that, when called, returns a function literal of the same type;
|
||||
* the function that is returned will not necessarily be the same as the one that was used
|
||||
* but will represent the existing and ongoing status of interaction with the environment.
|
||||
* Calling one function and exchanging it for another function to be called like this creates a procedure
|
||||
* that controls and limits the interactions with the environment to only what is necessary.
|
||||
* @see `InteractsWithEnvironment.blockedFromInteracting`
|
||||
* @see `InteractsWithEnvironment.onStableEnvironment`
|
||||
* @see `InteractsWithEnvironment.awaitOngoingInteraction`
|
||||
* @param sector the portion of the block map being tested
|
||||
* @param target the fixed element in this test
|
||||
*/
|
||||
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
|
||||
interactingWithEnvironment = interactingWithEnvironment(target, true)
|
||||
.asInstanceOf[(PlanetSideServerObject, Boolean) => Any]
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspend any current interaction procedures through the proper channels
|
||||
* or deactivate a previously flagged interaction blocking procedure
|
||||
* and reset the system to its neutral state.
|
||||
* The main difference between resetting and flagging the blocking procedure
|
||||
* is that resetting will (probably) restore the previously active procedure on the next `zoneInteraction` call
|
||||
* while blocking will halt all attempts to establish a new active interaction procedure
|
||||
* and unblocking will immediately install whatever is the current active interaction.
|
||||
* @see `InteractsWithEnvironment.onStableEnvironment`
|
||||
*/
|
||||
def resetInteraction(target: InteractsWithZone) : Unit = {
|
||||
interactingWithEnvironment(target, false)
|
||||
interactingWithEnvironment = InteractWithEnvironment.onStableEnvironment()
|
||||
}
|
||||
}
|
||||
|
||||
object InteractWithEnvironment {
|
||||
/**
|
||||
* While on stable non-interactive terrain,
|
||||
* test whether any special terrain component has an affect upon the target entity.
|
||||
* If so, instruct the target that an interaction should occur.
|
||||
* Considered tail recursive, but not treated that way.
|
||||
* @see `blockedFromInteracting`
|
||||
* @see `checkAllEnvironmentInteractions`
|
||||
* @see `awaitOngoingInteraction`
|
||||
* @param obj the target entity
|
||||
* @return the function literal that represents the next iterative call of ongoing interaction testing;
|
||||
* may return itself
|
||||
*/
|
||||
def onStableEnvironment()(obj: PlanetSideServerObject, allow: Boolean): Any = {
|
||||
if(allow) {
|
||||
checkAllEnvironmentInteractions(obj) match {
|
||||
case Some(body) =>
|
||||
obj.Actor ! InteractingWithEnvironment(obj, body, None)
|
||||
awaitOngoingInteraction(obj.Zone, body)(_,_)
|
||||
case None =>
|
||||
onStableEnvironment()(_,_)
|
||||
}
|
||||
} else {
|
||||
blockedFromInteracting()(_,_)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* While on unstable, interactive, or special terrain,
|
||||
* test whether that special terrain component has an affect upon the target entity.
|
||||
* If no interaction exists,
|
||||
* treat the target as if it had been previously affected by the given terrain,
|
||||
* and instruct it to cease that assumption.
|
||||
* Transition between the affects of different special terrains is possible.
|
||||
* Considered tail recursive, but not treated that way.
|
||||
* @see `blockedFromInteracting`
|
||||
* @see `checkAllEnvironmentInteractions`
|
||||
* @see `checkSpecificEnvironmentInteraction`
|
||||
* @see `onStableEnvironment`
|
||||
* @param zone the zone in which the terrain is located
|
||||
* @param body the special terrain
|
||||
* @param obj the target entity
|
||||
* @return the function literal that represents the next iterative call of ongoing interaction testing;
|
||||
* may return itself
|
||||
*/
|
||||
def awaitOngoingInteraction(zone: Zone, body: PieceOfEnvironment)(obj: PlanetSideServerObject, allow: Boolean): Any = {
|
||||
if (allow) {
|
||||
checkSpecificEnvironmentInteraction(zone, body)(obj) match {
|
||||
case Some(_) =>
|
||||
awaitOngoingInteraction(obj.Zone, body)(_, _)
|
||||
case None =>
|
||||
checkAllEnvironmentInteractions(obj) match {
|
||||
case Some(newBody) if newBody.attribute == body.attribute =>
|
||||
obj.Actor ! InteractingWithEnvironment(obj, newBody, None)
|
||||
awaitOngoingInteraction(obj.Zone, newBody)(_, _)
|
||||
case Some(newBody) =>
|
||||
obj.Actor ! EscapeFromEnvironment(obj, body, None)
|
||||
obj.Actor ! InteractingWithEnvironment(obj, newBody, None)
|
||||
awaitOngoingInteraction(obj.Zone, newBody)(_, _)
|
||||
case None =>
|
||||
obj.Actor ! EscapeFromEnvironment(obj, body, None)
|
||||
onStableEnvironment()(_, _)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
obj.Actor ! EscapeFromEnvironment(obj, body, None)
|
||||
blockedFromInteracting()(_,_)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Do not care whether on stable non-interactive terrain or on unstable interactive terrain.
|
||||
* Wait until allowed to test again (external flag).
|
||||
* Considered tail recursive, but not treated that way.
|
||||
* @see `onStableEnvironment`
|
||||
* @param obj the target entity
|
||||
* @return the function literal that represents the next iterative call of ongoing interaction testing;
|
||||
* may return itself
|
||||
*/
|
||||
def blockedFromInteracting()(obj: PlanetSideServerObject, allow: Boolean): Any = {
|
||||
if (allow) {
|
||||
onStableEnvironment()(obj, allow)
|
||||
} else {
|
||||
blockedFromInteracting()(_,_)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether any special terrain component has an affect upon the target entity.
|
||||
* @param obj the target entity
|
||||
* @return any unstable, interactive, or special terrain that is being interacted
|
||||
*/
|
||||
def checkAllEnvironmentInteractions(obj: PlanetSideServerObject): Option[PieceOfEnvironment] = {
|
||||
val position = obj.Position
|
||||
val depth = GlobalDefinitions.MaxDepth(obj)
|
||||
(obj match {
|
||||
case bme: BlockMapEntity =>
|
||||
obj.Zone.blockMap.sector(bme).environmentList
|
||||
case _ =>
|
||||
obj.Zone.map.environment
|
||||
}).find { body =>
|
||||
body.attribute.canInteractWith(obj) && body.testInteraction(position, depth)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a special terrain component has an affect upon the target entity.
|
||||
* @param zone the zone in which the terrain is located
|
||||
* @param body the special terrain
|
||||
* @param obj the target entity
|
||||
* @return any unstable, interactive, or special terrain that is being interacted
|
||||
*/
|
||||
private def checkSpecificEnvironmentInteraction(zone: Zone, body: PieceOfEnvironment)(obj: PlanetSideServerObject): Option[PieceOfEnvironment] = {
|
||||
if ((obj.Zone eq zone) && body.testInteraction(obj.Position, GlobalDefinitions.MaxDepth(obj))) {
|
||||
Some(body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.environment
|
||||
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.types.{OxygenState, PlanetSideGUID}
|
||||
|
||||
/**
|
||||
* Related to the progress of interacting with a body of water deeper than you are tall or
|
||||
* deeper than your vehicle is off the ground.
|
||||
* @param guid the target
|
||||
* @param state whether they are recovering or suffocating
|
||||
* @param progress the percentage of completion towards the state
|
||||
*/
|
||||
final case class OxygenStateTarget(
|
||||
guid: PlanetSideGUID,
|
||||
state: OxygenState,
|
||||
progress: Float
|
||||
)
|
||||
|
||||
/**
|
||||
* The target has clipped into a critical region of a piece of environment.
|
||||
* @param obj the target
|
||||
* @param environment the terrain clipping region
|
||||
* @param mountedVehicle whether or not the target is mounted
|
||||
* (specifically, if the target is a `Player` who is mounted in a `Vehicle`)
|
||||
*/
|
||||
final case class InteractingWithEnvironment(
|
||||
obj: PlanetSideServerObject,
|
||||
environment: PieceOfEnvironment,
|
||||
mountedVehicle: Option[OxygenStateTarget]
|
||||
)
|
||||
|
||||
/**
|
||||
* The target has ceased to clip into a critical region of a piece of environment.
|
||||
* @param obj the target
|
||||
* @param environment the previous terrain clipping region
|
||||
* @param mountedVehicle whether or not the target is mounted
|
||||
* (specifically, if the target is a `Player` who is mounted in a `Vehicle`)
|
||||
*/
|
||||
final case class EscapeFromEnvironment(
|
||||
obj: PlanetSideServerObject,
|
||||
environment: PieceOfEnvironment,
|
||||
mountedVehicle: Option[OxygenStateTarget]
|
||||
)
|
||||
|
||||
/**
|
||||
* Completely reset any internal actions or processes related to environment clipping.
|
||||
*/
|
||||
final case class RecoveredFromEnvironmentInteraction()
|
||||
|
|
@ -67,7 +67,11 @@ object EnvironmentAttribute extends Enum[EnvironmentTrait] {
|
|||
/** water can only interact with objects that are negatively affected by being exposed to water;
|
||||
* it's better this way */
|
||||
def canInteractWith(obj: PlanetSideGameObject): Boolean = {
|
||||
obj.Definition.DrownAtMaxDepth || obj.Definition.DisableAtMaxDepth
|
||||
obj.Definition.DrownAtMaxDepth || obj.Definition.DisableAtMaxDepth || (obj match {
|
||||
case p: Player => p.VehicleSeated.isEmpty
|
||||
case v: Vehicle => v.MountedIn.isEmpty
|
||||
case _ => true
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,180 +0,0 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.environment
|
||||
|
||||
import akka.actor.{Actor, Cancellable}
|
||||
import net.psforever.objects.Default
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
import net.psforever.types.OxygenState
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
/**
|
||||
* The mixin code for any server object that responds to environmental representations in the game world.
|
||||
* Specific types of environmental region is bound by geometry,
|
||||
* designated by attributes,
|
||||
* and targets react when coming into contact with it.
|
||||
* Ideally, the target under control instigates the responses towards the environment
|
||||
* by independently re-evaluating the conditions of its interactions.
|
||||
* Only one kind of environment can elicit a response at a time.
|
||||
* While a reversal of this trigger scheme is possible, it is not ideal.
|
||||
* @see `InteractsWithEnvironment`
|
||||
* @see `PieceOfEnvironment`
|
||||
*/
|
||||
trait RespondsToZoneEnvironment {
|
||||
_: Actor =>
|
||||
/** how long the current interaction has been progressing in the current way */
|
||||
var interactionTime : Long = 0
|
||||
/** the environment that we are currently in interaction with */
|
||||
var interactWith : Option[PieceOfEnvironment] = None
|
||||
/** a gesture of automation added to the interaction */
|
||||
var interactionTimer : Cancellable = Default.Cancellable
|
||||
/** a mapping of responses when specific interactions occur;
|
||||
* select from these options when starting an effect;
|
||||
* key - type of environment, value - reaction function */
|
||||
private var interactWithEnvironmentStart: mutable.HashMap[EnvironmentTrait, RespondsToZoneEnvironment.Interaction] =
|
||||
mutable.HashMap[EnvironmentTrait, RespondsToZoneEnvironment.Interaction]()
|
||||
/** a mapping of responses when specific interactions cease;
|
||||
* select from these options when ending an effect;
|
||||
* key - type of environment, value - reaction function */
|
||||
private var interactWithEnvironmentStop: mutable.HashMap[EnvironmentTrait, RespondsToZoneEnvironment.Interaction] =
|
||||
mutable.HashMap[EnvironmentTrait, RespondsToZoneEnvironment.Interaction]()
|
||||
|
||||
def InteractiveObject: PlanetSideServerObject with InteractsWithZone
|
||||
|
||||
val environmentBehavior: Receive = {
|
||||
case InteractingWithEnvironment(target, body, optional) =>
|
||||
doEnvironmentInteracting(target, body, optional)
|
||||
|
||||
case EscapeFromEnvironment(target, body, optional) =>
|
||||
stopEnvironmentInteracting(target, body, optional)
|
||||
|
||||
case RecoveredFromEnvironmentInteraction() =>
|
||||
recoverFromEnvironmentInteracting()
|
||||
}
|
||||
|
||||
def InteractWith: Option[PieceOfEnvironment] = interactWith
|
||||
|
||||
def SetInteraction(attribute: EnvironmentTrait, action: RespondsToZoneEnvironment.Interaction): Unit = {
|
||||
interactWithEnvironmentStart += attribute -> action
|
||||
}
|
||||
|
||||
def SetInteractionStop(attribute: EnvironmentTrait, action: RespondsToZoneEnvironment.Interaction): Unit = {
|
||||
interactWithEnvironmentStop += attribute -> action
|
||||
}
|
||||
|
||||
def doEnvironmentInteracting(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val attribute = body.attribute
|
||||
if (interactWith.isEmpty || interactWith.get.attribute == attribute) {
|
||||
interactWith = Some(body)
|
||||
interactionTimer.cancel()
|
||||
interactWithEnvironmentStart.get(attribute) match {
|
||||
case Some(func) => func(obj, body, data)
|
||||
case None => ;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def stopEnvironmentInteracting(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val attribute = body.attribute
|
||||
if (interactWith.nonEmpty && interactWith.get.attribute == attribute) {
|
||||
interactWith = None
|
||||
interactionTimer.cancel()
|
||||
interactWithEnvironmentStop.get(attribute) match {
|
||||
case Some(func) => func(obj, body, data)
|
||||
case _ => recoverFromEnvironmentInteracting()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the environment encounter fields and completely stop whatever is the current mechanic.
|
||||
* This does not perform messaging relay either with mounted occupants or with any other service.
|
||||
*/
|
||||
def recoverFromEnvironmentInteracting(): Unit = {
|
||||
interactionTimer.cancel()
|
||||
interactionTime = 0
|
||||
interactWith = None
|
||||
}
|
||||
}
|
||||
|
||||
object RespondsToZoneEnvironment {
|
||||
type Interaction = (PlanetSideServerObject, PieceOfEnvironment, Option[OxygenStateTarget]) => Unit
|
||||
|
||||
/**
|
||||
* Calculate the effect of being exposed to a watery environment beyond its critical region.
|
||||
* @param obj the target
|
||||
* @param condition the current environment progressive event of the target, e.g., already drowning
|
||||
* @param completionTime how long since the current environment progressive event started
|
||||
* @return three values:
|
||||
* whether any change in effect will occur,
|
||||
* for how long this new change if effect will occur after starting,
|
||||
* and what the starting progress value of this new effect looks like
|
||||
*/
|
||||
def drowningInWateryConditions(
|
||||
obj: PlanetSideServerObject,
|
||||
condition: Option[OxygenState],
|
||||
completionTime: Long
|
||||
): (Boolean, Long, Float) = {
|
||||
condition match {
|
||||
case None =>
|
||||
//start suffocation process
|
||||
(true, obj.Definition.UnderwaterLifespan(OxygenState.Suffocation), 100f)
|
||||
case Some(OxygenState.Recovery) =>
|
||||
//switching from recovery to suffocation
|
||||
val oldDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Recovery)
|
||||
val newDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Suffocation)
|
||||
val oldTimeRemaining: Long = completionTime - System.currentTimeMillis()
|
||||
val oldTimeRatio: Float = 1f - oldTimeRemaining / oldDuration.toFloat
|
||||
val percentage: Float = oldTimeRatio * 100
|
||||
val newDrownTime: Long = (newDuration * oldTimeRatio).toLong
|
||||
(true, newDrownTime, percentage)
|
||||
case Some(OxygenState.Suffocation) =>
|
||||
//interrupted while suffocating, calculate the progress and keep suffocating
|
||||
val oldDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Suffocation)
|
||||
val oldTimeRemaining: Long = completionTime - System.currentTimeMillis()
|
||||
val percentage: Float = (oldTimeRemaining / oldDuration.toFloat) * 100f
|
||||
(false, oldTimeRemaining, percentage)
|
||||
case _ =>
|
||||
(false, 0L, 0f)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the effect of being removed from a watery environment beyond its critical region.
|
||||
* @param obj the target
|
||||
* @param condition the current environment progressive event of the target, e.g., already drowning
|
||||
* @param completionTime how long since the current environment progressive event started
|
||||
* @return three values:
|
||||
* whether any change in effect will occur,
|
||||
* for how long this new change if effect will occur after starting,
|
||||
* and what the starting progress value of this new effect looks like
|
||||
*/
|
||||
def recoveringFromWateryConditions(
|
||||
obj: PlanetSideServerObject,
|
||||
condition: Option[OxygenState],
|
||||
completionTime: Long
|
||||
): (Boolean, Long, Float) = {
|
||||
condition match {
|
||||
case Some(OxygenState.Suffocation) =>
|
||||
//switching from suffocation to recovery
|
||||
val oldDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Suffocation)
|
||||
val newDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Recovery)
|
||||
val oldTimeRemaining: Long = completionTime - System.currentTimeMillis()
|
||||
val oldTimeRatio: Float = oldTimeRemaining / oldDuration.toFloat
|
||||
val percentage: Float = oldTimeRatio * 100
|
||||
val recoveryTime: Long = newDuration - (newDuration * oldTimeRatio).toLong
|
||||
(true, recoveryTime, percentage)
|
||||
case Some(OxygenState.Recovery) =>
|
||||
//interrupted while recovering, calculate the progress and keep recovering
|
||||
val currTime = System.currentTimeMillis()
|
||||
val duration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Recovery)
|
||||
val startTime: Long = completionTime - duration
|
||||
val timeRemaining: Long = completionTime - currTime
|
||||
val percentage: Float = ((currTime - startTime) / duration.toFloat) * 100f
|
||||
(false, timeRemaining, percentage)
|
||||
case _ =>
|
||||
(false, 0L, 100f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,272 @@
|
|||
// 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._
|
||||
import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorGroup, SectorPopulation}
|
||||
|
||||
import scala.collection.mutable
|
||||
|
||||
case object EnvironmentInteraction extends ZoneInteractionType
|
||||
|
||||
/**
|
||||
* This game entity may infrequently test whether it may interact with game world environment.
|
||||
*/
|
||||
class InteractWithEnvironment()
|
||||
extends ZoneInteraction {
|
||||
private var interactionBehavior: InteractionBehavior = OnStableEnvironment()
|
||||
|
||||
def Type: EnvironmentInteraction.type = EnvironmentInteraction
|
||||
|
||||
def range: Float = 0f
|
||||
|
||||
/** the environment that we are currently in interaction with */
|
||||
private[environment] var interactWith: Set[PieceOfEnvironment] = Set[PieceOfEnvironment]()
|
||||
|
||||
/**
|
||||
* The method by which zone interactions are tested or a current interaction maintained.
|
||||
* Utilize a function literal that, when called, returns a function literal of the same type;
|
||||
* the function that is returned will not necessarily be the same as the one that was used
|
||||
* but will represent the existing and ongoing status of interaction with the environment.
|
||||
* Calling one function and exchanging it for another function to be called like this creates a procedure
|
||||
* that controls and limits the interactions with the environment to only what is necessary.
|
||||
* @see `AwaitOngoingInteraction`
|
||||
* @see `BlockedFromInteracting`
|
||||
* @see `OnStableEnvironment`
|
||||
* @param sector the portion of the block map being tested
|
||||
* @param target the fixed element in this test
|
||||
*/
|
||||
def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = {
|
||||
target match {
|
||||
case t: InteractsWithZone =>
|
||||
interactWith = interactionBehavior.perform(t, sector, interactWith, allow=true)
|
||||
interactionBehavior = interactionBehavior.next
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Suspend any current interaction procedures through the proper channels
|
||||
* or deactivate a previously flagged interaction blocking procedure
|
||||
* and reset the system to its neutral state.
|
||||
* The main difference between resetting and flagging the blocking procedure
|
||||
* is that resetting will (probably) restore the previously active procedure on the next `zoneInteraction` call
|
||||
* while blocking will halt all attempts to establish a new active interaction procedure
|
||||
* and unblocking will immediately install whatever is the current active interaction.
|
||||
* @see `AwaitOngoingInteraction`
|
||||
* @see `OnStableEnvironment`
|
||||
*/
|
||||
def resetInteraction(target: InteractsWithZone) : Unit = {
|
||||
target match {
|
||||
case t: InteractsWithZone with BlockMapEntity =>
|
||||
AwaitOngoingInteraction(target.Zone).perform(t, SectorGroup.emptySector, interactWith, allow=false)
|
||||
case _ => ()
|
||||
}
|
||||
interactWith = Set()
|
||||
interactionBehavior = OnStableEnvironment()
|
||||
}
|
||||
|
||||
private val interactWithEnvironment: mutable.HashMap[EnvironmentTrait, InteractionWith] =
|
||||
mutable.HashMap[EnvironmentTrait, InteractionWith]()
|
||||
|
||||
def Interactions: Map[EnvironmentTrait, InteractionWith] = interactWithEnvironment.toMap
|
||||
|
||||
def SetInteraction(attribute: EnvironmentTrait, action: InteractionWith): Unit = {
|
||||
interactWithEnvironment += attribute -> action
|
||||
}
|
||||
|
||||
def SetInteractionStop(attribute: EnvironmentTrait, action: InteractionWith): Unit = {
|
||||
interactWithEnvironment += attribute -> action
|
||||
}
|
||||
|
||||
def doEnvironmentInteracting(obj: InteractsWithZone, body: PieceOfEnvironment): Unit = {
|
||||
val attribute = body.attribute
|
||||
if (interactWith.isEmpty || interactWith.exists(_.attribute == attribute)) {
|
||||
interactWithEnvironment
|
||||
.get(attribute)
|
||||
.foreach(_.doInteractingWith(obj, body, None))
|
||||
}
|
||||
}
|
||||
|
||||
def stopEnvironmentInteracting(obj: InteractsWithZone, body: PieceOfEnvironment): Unit = {
|
||||
val attribute = body.attribute
|
||||
if (interactWith.exists(_.attribute == attribute)) {
|
||||
interactWithEnvironment
|
||||
.get(attribute)
|
||||
.foreach(_.stopInteractingWith(obj, body, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object InteractWithEnvironment {
|
||||
def apply(list: Iterable[InteractionWith]): InteractWithEnvironment = {
|
||||
val obj = new InteractWithEnvironment()
|
||||
list.foreach(env => obj.SetInteraction(env.attribute, env))
|
||||
obj
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether any special terrain component has an affect upon the target entity.
|
||||
* @param obj the target entity
|
||||
* @param sector the portion of the block map being tested
|
||||
* @return any unstable, interactive, or special terrain that is being interacted
|
||||
*/
|
||||
def checkAllEnvironmentInteractions(
|
||||
obj: PlanetSideServerObject,
|
||||
sector: SectorPopulation
|
||||
): Set[PieceOfEnvironment] = {
|
||||
val position = obj.Position
|
||||
val depth = GlobalDefinitions.MaxDepth(obj)
|
||||
sector.environmentList
|
||||
.filter(body => body.attribute.canInteractWith(obj) && body.testInteraction(position, depth))
|
||||
.distinctBy(_.attribute)
|
||||
.toSet
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether a special terrain component has an affect upon the target entity.
|
||||
* @param zone the zone in which the terrain is located
|
||||
* @param body the special terrain
|
||||
* @param obj the target entity
|
||||
* @return any unstable, interactive, or special terrain that is being interacted
|
||||
*/
|
||||
def checkSpecificEnvironmentInteraction(
|
||||
zone: Zone,
|
||||
body: PieceOfEnvironment,
|
||||
obj: PlanetSideServerObject
|
||||
): Option[PieceOfEnvironment] = {
|
||||
if ((obj.Zone eq zone) && body.testInteraction(obj.Position, GlobalDefinitions.MaxDepth(obj))) {
|
||||
Some(body)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
trait InteractionBehavior {
|
||||
protected var nextstep: InteractionBehavior = this
|
||||
|
||||
def perform(
|
||||
obj: InteractsWithZone,
|
||||
sector: SectorPopulation,
|
||||
existing: Set[PieceOfEnvironment],
|
||||
allow: Boolean
|
||||
): Set[PieceOfEnvironment]
|
||||
|
||||
def next: InteractionBehavior = {
|
||||
val out = nextstep
|
||||
nextstep = this
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
case class OnStableEnvironment() extends InteractionBehavior {
|
||||
/**
|
||||
* While on stable non-interactive terrain,
|
||||
* test whether any special terrain component has an affect upon the target entity.
|
||||
* If so, instruct the target that an interaction should occur.
|
||||
* Considered tail recursive, but not treated that way.
|
||||
* @see `BlockedFromInteracting`
|
||||
* @see `InteractWithEnvironment.checkAllEnvironmentInteractions`
|
||||
* @see `AwaitOngoingInteraction`
|
||||
* @see `OnStableEnvironment`
|
||||
* @param obj target entity
|
||||
* @param sector the portion of the block map being tested
|
||||
* @param existing not applicable
|
||||
* @param allow is this permitted, or will it be blocked?
|
||||
* @return applicable interactive environmental fields
|
||||
*/
|
||||
def perform(
|
||||
obj: InteractsWithZone,
|
||||
sector: SectorPopulation,
|
||||
existing: Set[PieceOfEnvironment],
|
||||
allow: Boolean
|
||||
): Set[PieceOfEnvironment] = {
|
||||
if (allow) {
|
||||
val interactions = obj.interaction().collectFirst { case inter: InteractWithEnvironment => inter.Interactions }
|
||||
val env = InteractWithEnvironment.checkAllEnvironmentInteractions(obj, sector)
|
||||
env.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.doInteractingWith(obj, body, None)))
|
||||
if (env.nonEmpty) {
|
||||
nextstep = AwaitOngoingInteraction(obj.Zone)
|
||||
}
|
||||
env
|
||||
} else {
|
||||
nextstep = BlockedFromInteracting()
|
||||
Set()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final case class AwaitOngoingInteraction(zone: Zone) extends InteractionBehavior {
|
||||
/**
|
||||
* While on unstable, interactive, or special terrain,
|
||||
* test whether that special terrain component has an affect upon the target entity.
|
||||
* If no interaction exists,
|
||||
* treat the target as if it had been previously affected by the given terrain,
|
||||
* and instruct it to cease that assumption.
|
||||
* Transition between the affects of different special terrains is possible.
|
||||
* Considered tail recursive, but not treated that way.
|
||||
* @see `BlockedFromInteracting`
|
||||
* @see `InteractWithEnvironment.checkAllEnvironmentInteractions`
|
||||
* @see `InteractWithEnvironment.checkSpecificEnvironmentInteraction`
|
||||
* @see `OnStableEnvironment`
|
||||
* @param obj target entity
|
||||
* @param sector the portion of the block map being tested
|
||||
* @param existing environment fields from the previous step
|
||||
* @param allow is this permitted, or will it be blocked?
|
||||
* @return applicable interactive environmental fields
|
||||
*/
|
||||
def perform(
|
||||
obj: InteractsWithZone,
|
||||
sector: SectorPopulation,
|
||||
existing: Set[PieceOfEnvironment],
|
||||
allow: Boolean
|
||||
): Set[PieceOfEnvironment] = {
|
||||
val interactions = obj.interaction().collectFirst { case inter: InteractWithEnvironment => inter.Interactions }
|
||||
if (allow) {
|
||||
val env = InteractWithEnvironment.checkAllEnvironmentInteractions(obj, sector)
|
||||
val (in, out) = existing.partition(body => InteractWithEnvironment.checkSpecificEnvironmentInteraction(zone, body, obj).nonEmpty)
|
||||
env.diff(in).foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.doInteractingWith(obj, body, None)))
|
||||
out.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.stopInteractingWith(obj, body, None)))
|
||||
if (env.isEmpty) {
|
||||
val n = OnStableEnvironment()
|
||||
val out = n.perform(obj, sector, Set(), allow)
|
||||
nextstep = n.next
|
||||
out
|
||||
} else {
|
||||
env
|
||||
}
|
||||
} else {
|
||||
existing.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.stopInteractingWith(obj, body, None)))
|
||||
nextstep = BlockedFromInteracting()
|
||||
Set()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case class BlockedFromInteracting() extends InteractionBehavior {
|
||||
/**
|
||||
* Do not care whether on stable non-interactive terrain or on unstable interactive terrain.
|
||||
* Wait until allowed to test again (external flag).
|
||||
* Considered tail recursive, but not treated that way.
|
||||
* @see `OnStableEnvironment`
|
||||
* @param obj target entity
|
||||
* @param sector the portion of the block map being tested
|
||||
* @param existing not applicable
|
||||
* @param allow is this permitted, or will it be blocked?
|
||||
* @return an empty set
|
||||
*/
|
||||
def perform(
|
||||
obj: InteractsWithZone,
|
||||
sector: SectorPopulation,
|
||||
existing: Set[PieceOfEnvironment],
|
||||
allow: Boolean
|
||||
): Set[PieceOfEnvironment] = {
|
||||
if (allow) {
|
||||
nextstep = OnStableEnvironment()
|
||||
}
|
||||
Set()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.environment.interaction
|
||||
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment}
|
||||
|
||||
/**
|
||||
* The target has clipped into a critical region of a piece of environment.
|
||||
* @param environment the terrain clipping region
|
||||
* @param mountedVehicle whether or not the target is mounted
|
||||
* (specifically, if the target is a `Player` who is mounted in a `Vehicle`)
|
||||
*/
|
||||
final case class InteractingWithEnvironment(
|
||||
environment: PieceOfEnvironment,
|
||||
mountedVehicle: Option[Any]
|
||||
)
|
||||
|
||||
/**
|
||||
* The target has ceased to clip into a critical region of a piece of environment.
|
||||
* @param environment the previous terrain clipping region
|
||||
* @param mountedVehicle whether or not the target is mounted
|
||||
* (specifically, if the target is a `Player` who is mounted in a `Vehicle`)
|
||||
*/
|
||||
final case class EscapeFromEnvironment(
|
||||
environment: PieceOfEnvironment,
|
||||
mountedVehicle: Option[Any]
|
||||
)
|
||||
|
||||
/**
|
||||
* Completely reset internal actions or processes related to environment clipping.
|
||||
*/
|
||||
final case class RecoveredFromEnvironmentInteraction(attribute: EnvironmentTrait)
|
||||
|
||||
/**
|
||||
* Completely reset internal actions or processes related to environment clipping.
|
||||
*/
|
||||
case object ResetAllEnvironmentInteractions
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package net.psforever.objects.serverobject.environment.interaction
|
||||
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment}
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
|
||||
trait InteractionWith {
|
||||
def attribute: EnvironmentTrait
|
||||
|
||||
//noinspection ScalaUnusedSymbol
|
||||
def doInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
parentInfo: Option[Any]
|
||||
): Unit
|
||||
|
||||
//noinspection ScalaUnusedSymbol
|
||||
def stopInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
parentInfo: Option[Any]
|
||||
): Unit = { /*mainly for overriding*/ }
|
||||
|
||||
//noinspection ScalaUnusedSymbol
|
||||
def recoverFromInteracting(obj: InteractsWithZone): Unit = { /*mainly for overriding*/ }
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright (c) 2020 PSForever
|
||||
package net.psforever.objects.serverobject.environment.interaction
|
||||
|
||||
import akka.actor.{Actor, ActorRef, Cancellable}
|
||||
import net.psforever.objects.Default
|
||||
import net.psforever.objects.serverobject.environment.EnvironmentTrait
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration.FiniteDuration
|
||||
|
||||
/**
|
||||
* The mixin code for any server entity that responds to environmental representations in the game world.
|
||||
* Specific types of environmental region is bound by geometry,
|
||||
* designated by attributes,
|
||||
* and targets react when maneuvering contact with it.
|
||||
* @see `EnvironmentTrait`
|
||||
* @see `EscapeFromEnvironment`
|
||||
* @see `InteractingWithEnvironment`
|
||||
* @see `InteractionWith`
|
||||
* @see `InteractsWithEnvironment`
|
||||
* @see `PieceOfEnvironment`
|
||||
* @see `RecoveredFromEnvironmentInteraction`
|
||||
*/
|
||||
trait RespondsToZoneEnvironment {
|
||||
_: Actor =>
|
||||
/** a gesture of automation added to the interaction */
|
||||
private val interactionTimers: mutable.HashMap[EnvironmentTrait, Cancellable] = mutable.HashMap[EnvironmentTrait, Cancellable]()
|
||||
|
||||
def InteractiveObject: InteractsWithZone
|
||||
|
||||
private lazy val applicableInteractions: Map[EnvironmentTrait, InteractionWith] = InteractiveObject
|
||||
.interaction()
|
||||
.collectFirst { case inter: InteractWithEnvironment => inter.Interactions }
|
||||
.getOrElse(RespondsToZoneEnvironment.defaultInteractions)
|
||||
|
||||
val environmentBehavior: Receive = {
|
||||
case RespondsToZoneEnvironment.Timer(attribute, delay, to, msg) =>
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
interactionTimers.get(attribute).foreach(_.cancel())
|
||||
interactionTimers.update(attribute, context.system.scheduler.scheduleOnce(delay, to, msg))
|
||||
|
||||
case RespondsToZoneEnvironment.StopTimer(attribute) =>
|
||||
interactionTimers.get(attribute).foreach(_.cancel())
|
||||
interactionTimers.update(attribute, Default.Cancellable)
|
||||
|
||||
case InteractingWithEnvironment(body, optional) =>
|
||||
applicableInteractions
|
||||
.get(body.attribute)
|
||||
.foreach(_.doInteractingWith(InteractiveObject, body, optional))
|
||||
|
||||
case EscapeFromEnvironment(body, optional) =>
|
||||
applicableInteractions
|
||||
.get(body.attribute)
|
||||
.foreach(_.stopInteractingWith(InteractiveObject, body, optional))
|
||||
|
||||
case RecoveredFromEnvironmentInteraction(attribute) =>
|
||||
applicableInteractions
|
||||
.get(attribute)
|
||||
.foreach(_.recoverFromInteracting(InteractiveObject))
|
||||
|
||||
case ResetAllEnvironmentInteractions =>
|
||||
applicableInteractions.values.foreach(_.recoverFromInteracting(InteractiveObject))
|
||||
interactionTimers.values.foreach(_.cancel())
|
||||
}
|
||||
|
||||
def respondToEnvironmentPostStop(): Unit = {
|
||||
interactionTimers.values.foreach(_.cancel())
|
||||
}
|
||||
}
|
||||
|
||||
object RespondsToZoneEnvironment {
|
||||
val defaultInteractions: Map[EnvironmentTrait, InteractionWith] = Map.empty[EnvironmentTrait, InteractionWith]
|
||||
|
||||
final case class Timer(attribute: EnvironmentTrait, delay: FiniteDuration, to: ActorRef, msg: Any)
|
||||
|
||||
final case class StopTimer(attribute: EnvironmentTrait)
|
||||
}
|
||||
|
|
@ -0,0 +1,142 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.serverobject.environment.interaction.common
|
||||
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment}
|
||||
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
|
||||
}
|
||||
|
||||
object Watery {
|
||||
/**
|
||||
* Related to the progress of interacting with a body of water deeper than you are tall or
|
||||
* deeper than your vehicle is off the ground.
|
||||
* @param guid target
|
||||
* @param body environment being interacted with
|
||||
* @param state whether recovering or suffocating
|
||||
* @param progress the percentage of completion towards the state
|
||||
*/
|
||||
final case class OxygenStateTarget(
|
||||
guid: PlanetSideGUID,
|
||||
body: PieceOfEnvironment,
|
||||
state: OxygenState,
|
||||
progress: Float
|
||||
)
|
||||
|
||||
/**
|
||||
* Calculate the effect of being exposed to a watery environment beyond an entity's critical region.
|
||||
* @param obj the target
|
||||
* @param interaction na
|
||||
* @return three values:
|
||||
* whether any change in effect will occur,
|
||||
* for how long this new change if effect will occur after starting,
|
||||
* and what the starting progress value of this new effect looks like
|
||||
*/
|
||||
def drowningInWater(
|
||||
obj: PlanetSideServerObject,
|
||||
interaction: Watery
|
||||
): (Boolean, Long, Float) = {
|
||||
drowningInWateryConditions(obj, interaction.condition.map(_.state), interaction.waterInteractionTime)
|
||||
}
|
||||
/**
|
||||
* Calculate the effect of being exposed to a watery environment beyond an entity's critical region.
|
||||
* @param obj the target
|
||||
* @param condition the current environment progressive event of the target, e.g., already drowning
|
||||
* @param completionTime how long since the current environment progressive event started
|
||||
* @return three values:
|
||||
* whether any change in effect will occur,
|
||||
* for how long this new change if effect will occur after starting,
|
||||
* and what the starting progress value of this new effect looks like
|
||||
*/
|
||||
def drowningInWateryConditions(
|
||||
obj: PlanetSideServerObject,
|
||||
condition: Option[OxygenState],
|
||||
completionTime: Long
|
||||
): (Boolean, Long, Float) = {
|
||||
condition match {
|
||||
case None =>
|
||||
//start suffocation process
|
||||
(true, obj.Definition.UnderwaterLifespan(OxygenState.Suffocation), 100f)
|
||||
case Some(OxygenState.Recovery) =>
|
||||
//switching from recovery to suffocation
|
||||
val oldDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Recovery)
|
||||
val newDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Suffocation)
|
||||
val oldTimeRemaining: Long = completionTime - System.currentTimeMillis()
|
||||
val oldTimeRatio: Float = 1f - oldTimeRemaining / oldDuration.toFloat
|
||||
val percentage: Float = oldTimeRatio * 100
|
||||
val newDrownTime: Long = (newDuration * oldTimeRatio).toLong
|
||||
(true, newDrownTime, percentage)
|
||||
case Some(OxygenState.Suffocation) =>
|
||||
//interrupted while suffocating, calculate the progress and keep suffocating
|
||||
val oldDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Suffocation)
|
||||
val oldTimeRemaining: Long = completionTime - System.currentTimeMillis()
|
||||
val percentage: Float = (oldTimeRemaining / oldDuration.toFloat) * 100f
|
||||
(false, oldTimeRemaining, percentage)
|
||||
case _ =>
|
||||
(false, 0L, 0f)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the effect of being removed from a watery environment over an entity's critical region.
|
||||
* @param obj the target
|
||||
* @param interaction na
|
||||
* @return three values:
|
||||
* whether any change in effect will occur,
|
||||
* for how long this new change if effect will occur after starting,
|
||||
* and what the starting progress value of this new effect looks like
|
||||
*/
|
||||
def recoveringFromWater(
|
||||
obj: PlanetSideServerObject,
|
||||
interaction: Watery
|
||||
): (Boolean, Long, Float) = {
|
||||
recoveringFromWateryConditions(obj, interaction.condition.map(_.state), interaction.waterInteractionTime)
|
||||
}
|
||||
/**
|
||||
* Calculate the effect of being removed from a watery environment over an entity's critical region.
|
||||
* @param obj the target
|
||||
* @param condition the current environment progressive event of the target, e.g., already drowning
|
||||
* @param completionTime how long since the current environment progressive event started
|
||||
* @return three values:
|
||||
* whether any change in effect will occur,
|
||||
* for how long this new change if effect will occur after starting,
|
||||
* and what the starting progress value of this new effect looks like
|
||||
*/
|
||||
def recoveringFromWateryConditions(
|
||||
obj: PlanetSideServerObject,
|
||||
condition: Option[OxygenState],
|
||||
completionTime: Long
|
||||
): (Boolean, Long, Float) = {
|
||||
condition match {
|
||||
case Some(OxygenState.Suffocation) =>
|
||||
//switching from suffocation to recovery
|
||||
val oldDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Suffocation)
|
||||
val newDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Recovery)
|
||||
val oldTimeRemaining: Long = completionTime - System.currentTimeMillis()
|
||||
val oldTimeRatio: Float = oldTimeRemaining / oldDuration.toFloat
|
||||
val percentage: Float = oldTimeRatio * 100
|
||||
val recoveryTime: Long = newDuration - (newDuration * oldTimeRatio).toLong
|
||||
(true, recoveryTime, percentage)
|
||||
case Some(OxygenState.Recovery) =>
|
||||
//interrupted while recovering, calculate the progress and keep recovering
|
||||
val currTime = System.currentTimeMillis()
|
||||
val duration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Recovery)
|
||||
val startTime: Long = completionTime - duration
|
||||
val timeRemaining: Long = completionTime - currTime
|
||||
val percentage: Float = ((currTime - startTime) / duration.toFloat) * 100f
|
||||
(false, timeRemaining, percentage)
|
||||
case _ =>
|
||||
(false, 0L, 100f)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.serverobject.environment.interaction.common
|
||||
|
||||
import net.psforever.objects.serverobject.environment.interaction.InteractionWith
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment}
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.etc.SuicideReason
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.vital.{ReconstructionActivity, Vitality}
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
|
||||
import scala.annotation.unused
|
||||
|
||||
class WithDeath()
|
||||
extends InteractionWith {
|
||||
val channel: String = ""
|
||||
val attribute: EnvironmentTrait = EnvironmentAttribute.Death
|
||||
|
||||
/**
|
||||
* Death causes target to be destroyed outright.
|
||||
* It's not even considered as environmental damage anymore.
|
||||
* @param obj target
|
||||
* @param body environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
@unused body: PieceOfEnvironment,
|
||||
@unused data: Option[Any]
|
||||
): Unit = {
|
||||
if (!obj.Destroyed) {
|
||||
obj.History.findLast { entry => entry.isInstanceOf[ReconstructionActivity] } match {
|
||||
case Some(entry) if System.currentTimeMillis() - entry.time > 4000L =>
|
||||
obj.Actor ! Vitality.Damage(
|
||||
DamageInteraction(
|
||||
SourceEntry(obj),
|
||||
SuicideReason(),
|
||||
obj.Position
|
||||
).calculate()
|
||||
)
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.serverobject.environment.interaction.common
|
||||
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.environment.interaction.InteractionWith
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
|
||||
import scala.annotation.unused
|
||||
|
||||
class WithMovementTrigger()
|
||||
extends InteractionWith {
|
||||
val attribute: EnvironmentTrait = EnvironmentAttribute.MovementFieldTrigger
|
||||
/**
|
||||
* The target will be affected by this action.
|
||||
* @param obj target
|
||||
* @param body environment
|
||||
* //@param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
@unused data: Option[Any]
|
||||
): Unit = {
|
||||
body.asInstanceOf[GeneralMovementField].triggerAction(obj)
|
||||
}
|
||||
}
|
||||
|
|
@ -16,6 +16,8 @@ import net.psforever.objects.serverobject.containable.{Containable, ContainableB
|
|||
import net.psforever.objects.serverobject.damage.Damageable.Target
|
||||
import net.psforever.objects.serverobject.damage.{AggravatedBehavior, DamageableVehicle}
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery
|
||||
import net.psforever.objects.serverobject.environment.interaction.{InteractWithEnvironment, RespondsToZoneEnvironment}
|
||||
import net.psforever.objects.serverobject.hackable.GenericHackables
|
||||
import net.psforever.objects.serverobject.mount.{Mountable, MountableBehavior, RadiationInMountableInteraction}
|
||||
import net.psforever.objects.serverobject.repair.RepairableVehicle
|
||||
|
|
@ -23,10 +25,9 @@ import net.psforever.objects.serverobject.terminals.Terminal
|
|||
import net.psforever.objects.serverobject.turret.auto.AffectedByAutomaticTurretFire
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry, VehicleSource}
|
||||
import net.psforever.objects.vehicles._
|
||||
import net.psforever.objects.vital.interaction.{DamageInteraction, DamageResult}
|
||||
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, ReconstructionActivity, ShieldCharge, SpawningActivity, VehicleDismountActivity, VehicleMountActivity}
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.etc.SuicideReason
|
||||
import net.psforever.objects.vehicles.interaction.WithWater
|
||||
import net.psforever.objects.vital.interaction.DamageResult
|
||||
import net.psforever.objects.vital.{DamagingActivity, InGameActivity, ShieldCharge, SpawningActivity, VehicleDismountActivity, VehicleMountActivity}
|
||||
import net.psforever.objects.zones._
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game._
|
||||
|
|
@ -74,14 +75,14 @@ class VehicleControl(vehicle: Vehicle)
|
|||
def CargoObject: Vehicle = vehicle
|
||||
def AffectedObject: Vehicle = vehicle
|
||||
|
||||
SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
|
||||
SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
|
||||
SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
|
||||
SetInteraction(EnvironmentAttribute.MovementFieldTrigger, doInteractingWithMovementTrigger)
|
||||
if (!GlobalDefinitions.isFlightVehicle(vehicle.Definition)) {
|
||||
//can recover from sinking disability
|
||||
SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
|
||||
}
|
||||
// SetInteraction(EnvironmentAttribute.Water, doInteractingWithWater)
|
||||
// SetInteraction(EnvironmentAttribute.Lava, doInteractingWithLava)
|
||||
// SetInteraction(EnvironmentAttribute.Death, doInteractingWithDeath)
|
||||
// SetInteraction(EnvironmentAttribute.MovementFieldTrigger, doInteractingWithMovementTrigger)
|
||||
// if (!GlobalDefinitions.isFlightVehicle(vehicle.Definition)) {
|
||||
// //can recover from sinking disability
|
||||
// SetInteractionStop(EnvironmentAttribute.Water, stopInteractingWithWater)
|
||||
// }
|
||||
|
||||
/** cheap flag for whether the vehicle is decaying */
|
||||
var decaying : Boolean = false
|
||||
|
|
@ -104,7 +105,7 @@ class VehicleControl(vehicle: Vehicle)
|
|||
context.stop(util().Actor)
|
||||
util().Actor = Default.Actor
|
||||
}
|
||||
recoverFromEnvironmentInteracting()
|
||||
respondToEnvironmentPostStop()
|
||||
endAllCargoOperations()
|
||||
}
|
||||
|
||||
|
|
@ -218,8 +219,8 @@ class VehicleControl(vehicle: Vehicle)
|
|||
)
|
||||
}
|
||||
|
||||
case VehicleControl.Disable() =>
|
||||
PrepareForDisabled(kickPassengers = false)
|
||||
case VehicleControl.Disable(kickPassengers) =>
|
||||
PrepareForDisabled(kickPassengers)
|
||||
context.become(Disabled)
|
||||
|
||||
case Vehicle.Deconstruct(time) =>
|
||||
|
|
@ -249,9 +250,9 @@ class VehicleControl(vehicle: Vehicle)
|
|||
case VehicleControl.RadiationTick =>
|
||||
vehicle.interaction().find { _.Type == RadiationInMountableInteraction } match {
|
||||
case Some(func) => func.interaction(vehicle.getInteractionSector(), vehicle)
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
case _ => ;
|
||||
case _ => ()
|
||||
}
|
||||
|
||||
def commonDisabledBehavior: Receive = checkBehavior
|
||||
|
|
@ -387,28 +388,20 @@ class VehicleControl(vehicle: Vehicle)
|
|||
val zone = vehicle.Zone
|
||||
val zoneId = zone.id
|
||||
val events = zone.VehicleEvents
|
||||
//miscellaneous changes
|
||||
recoverFromEnvironmentInteracting()
|
||||
//escape being someone else's cargo
|
||||
vehicle.MountedIn match {
|
||||
case Some(_) =>
|
||||
startCargoDismounting(bailed = true)
|
||||
case _ => ;
|
||||
}
|
||||
vehicle.MountedIn.foreach(_ => startCargoDismounting(bailed = true))
|
||||
if (!vehicle.isFlying || kickPassengers) {
|
||||
//kick all passengers (either not flying, or being explicitly instructed)
|
||||
vehicle.Seats.values.foreach { seat =>
|
||||
seat.occupant match {
|
||||
case Some(player) =>
|
||||
seat.unmount(player, BailType.Kicked)
|
||||
player.VehicleSeated = None
|
||||
if (player.isAlive) {
|
||||
zone.actor ! ZoneActor.AddToBlockMap(player, vehicle.Position)
|
||||
}
|
||||
if (player.HasGUID) {
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, unk2 = true, guid))
|
||||
}
|
||||
case None => ;
|
||||
seat.occupant.foreach { player =>
|
||||
seat.unmount(player, BailType.Kicked)
|
||||
player.VehicleSeated = None
|
||||
if (player.isAlive) {
|
||||
zone.actor ! ZoneActor.AddToBlockMap(player, vehicle.Position)
|
||||
}
|
||||
if (player.HasGUID) {
|
||||
events ! VehicleServiceMessage(zoneId, VehicleAction.KickPassenger(player.GUID, 4, unk2 = true, guid))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -594,219 +587,40 @@ class VehicleControl(vehicle: Vehicle)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
def doInteractingWithWater(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val (effect: Boolean, time: Long, percentage: Float) = {
|
||||
val (a, b, c) = RespondsToZoneEnvironment.drowningInWateryConditions(obj, submergedCondition, interactionTime)
|
||||
if (a && GlobalDefinitions.isFlightVehicle(vehicle.Definition)) {
|
||||
(true, 0L, 0f) //no progress bar
|
||||
} else {
|
||||
(a, b, c)
|
||||
}
|
||||
}
|
||||
if (effect) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
submergedCondition = Some(OxygenState.Suffocation)
|
||||
interactionTime = System.currentTimeMillis() + time
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = time.milliseconds, self, VehicleControl.Disable())
|
||||
doInteractingWithWaterToTargets(
|
||||
percentage,
|
||||
body,
|
||||
vehicle.Seats.values
|
||||
.flatMap {
|
||||
case seat if seat.isOccupied => seat.occupants
|
||||
case _ => Nil
|
||||
}
|
||||
.filter { p => p.isAlive && (p.Zone eq vehicle.Zone) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 percentage the progress bar completion state
|
||||
* @param body the environment
|
||||
* @param targets recipients of the information
|
||||
*/
|
||||
def doInteractingWithWaterToTargets(
|
||||
percentage: Float,
|
||||
body: PieceOfEnvironment,
|
||||
targets: Iterable[PlanetSideServerObject]
|
||||
): Unit = {
|
||||
val vtarget = Some(OxygenStateTarget(vehicle.GUID, OxygenState.Suffocation, percentage))
|
||||
targets.foreach { target =>
|
||||
target.Actor ! InteractingWithEnvironment(target, body, vtarget)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lava causes vehicles to take (considerable) damage until they are inevitably destroyed.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWithLava(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val vehicle = DamageableObject
|
||||
if (!obj.Destroyed) {
|
||||
PerformDamage(
|
||||
vehicle,
|
||||
DamageInteraction(
|
||||
VehicleSource(vehicle),
|
||||
EnvironmentReason(body, vehicle),
|
||||
vehicle.Position
|
||||
).calculate()
|
||||
)
|
||||
//keep doing damage
|
||||
if (vehicle.Health > 0) {
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = 250 milliseconds, self, InteractingWithEnvironment(obj, body, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Death causes vehicles to be destroyed outright.
|
||||
* It's not even considered as environmental damage anymore.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWithDeath(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
if (!obj.Destroyed) {
|
||||
vehicle.History.findLast { entry => entry.isInstanceOf[ReconstructionActivity] } match {
|
||||
case Some(entry) if System.currentTimeMillis() - entry.time > 4000L =>
|
||||
PerformDamage(
|
||||
vehicle,
|
||||
DamageInteraction(
|
||||
VehicleSource(vehicle),
|
||||
SuicideReason(),
|
||||
vehicle.Position
|
||||
).calculate()
|
||||
)
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The vehicle will be affected by this action.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* @param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWithMovementTrigger(
|
||||
obj: PlanetSideServerObject,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[OxygenStateTarget]
|
||||
): Unit = {
|
||||
body.asInstanceOf[GeneralMovementField].triggerAction(obj)
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
def stopInteractingWithWater(obj: PlanetSideServerObject, body: PieceOfEnvironment, data: Option[OxygenStateTarget]): Unit = {
|
||||
val (effect: Boolean, time: Long, percentage: Float) =
|
||||
RespondsToZoneEnvironment.recoveringFromWateryConditions(obj, submergedCondition, interactionTime)
|
||||
if (effect) {
|
||||
recoverFromEnvironmentInteracting()
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
submergedCondition = Some(OxygenState.Recovery)
|
||||
interactionTime = System.currentTimeMillis() + time
|
||||
interactionTimer = context.system.scheduler.scheduleOnce(delay = time milliseconds, self, RecoveredFromEnvironmentInteraction())
|
||||
stopInteractingWithWaterToTargets(
|
||||
percentage,
|
||||
body,
|
||||
vehicle.Seats.values
|
||||
.flatMap {
|
||||
case seat if seat.isOccupied => seat.occupants
|
||||
case _ => Nil
|
||||
}
|
||||
.filter { p => p.isAlive && (p.Zone eq vehicle.Zone) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tell the given targets that,
|
||||
* when out of water, the vehicle no longer risks becoming disabled.
|
||||
* @see `EscapeFromEnvironment`
|
||||
* @see `OxygenState`
|
||||
* @see `OxygenStateTarget`
|
||||
* @param percentage the progress bar completion state
|
||||
* @param body the environment
|
||||
* @param targets recipients of the information
|
||||
*/
|
||||
def stopInteractingWithWaterToTargets(
|
||||
percentage: Float,
|
||||
body: PieceOfEnvironment,
|
||||
targets: Iterable[PlanetSideServerObject]
|
||||
): Unit = {
|
||||
val vtarget = Some(OxygenStateTarget(vehicle.GUID, OxygenState.Recovery, percentage))
|
||||
targets.foreach { target =>
|
||||
target.Actor ! EscapeFromEnvironment(target, body, vtarget)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset the environment encounter fields and completely stop whatever is the current mechanic.
|
||||
* This does not perform messaging relay either with mounted occupants or with any other service.
|
||||
*/
|
||||
override def recoverFromEnvironmentInteracting(): Unit = {
|
||||
super.recoverFromEnvironmentInteracting()
|
||||
submergedCondition = None
|
||||
}
|
||||
|
||||
/**
|
||||
* Without altering the state or progress of a zone interaction related to water,
|
||||
* update the visual progress element (progress bar) that is visible to the recipient's client.
|
||||
* @param player the recipient of this ui update
|
||||
*/
|
||||
def updateZoneInteractionProgressUI(player : Player) : Unit = {
|
||||
submergedCondition match {
|
||||
case Some(OxygenState.Suffocation) =>
|
||||
interactWith match {
|
||||
case Some(body) =>
|
||||
val percentage: Float = {
|
||||
val (a, _, c) = RespondsToZoneEnvironment.drowningInWateryConditions(vehicle, submergedCondition, interactionTime)
|
||||
if (a && GlobalDefinitions.isFlightVehicle(vehicle.Definition)) {
|
||||
0f //no progress bar
|
||||
} else {
|
||||
c
|
||||
}
|
||||
def updateZoneInteractionProgressUI(player: Player) : Unit = {
|
||||
val interactions = vehicle
|
||||
.interaction()
|
||||
.collectFirst { case inter: InteractWithEnvironment => inter.Interactions }
|
||||
.getOrElse(RespondsToZoneEnvironment.defaultInteractions)
|
||||
//water
|
||||
interactions
|
||||
.get(EnvironmentAttribute.Water)
|
||||
.collect {
|
||||
case watery: WithWater if watery.Condition.map(_.state).contains(OxygenState.Suffocation) =>
|
||||
val percentage: Float = {
|
||||
val (a, _, c) = Watery.drowningInWater(vehicle, watery)
|
||||
if (a && GlobalDefinitions.isFlightVehicle(vehicle.Definition)) {
|
||||
0f //no progress bar
|
||||
} else {
|
||||
c
|
||||
}
|
||||
doInteractingWithWaterToTargets(percentage, body, List(player))
|
||||
case _ =>
|
||||
recoverFromEnvironmentInteracting()
|
||||
}
|
||||
case Some(OxygenState.Recovery) =>
|
||||
vehicle.Zone.map.environment.find { _.attribute == EnvironmentAttribute.Water } match {
|
||||
case Some(body) => //any body of water will do ...
|
||||
stopInteractingWithWaterToTargets(
|
||||
RespondsToZoneEnvironment.recoveringFromWateryConditions(vehicle, submergedCondition, interactionTime)._3,
|
||||
body,
|
||||
List(player)
|
||||
)
|
||||
case _ =>
|
||||
recoverFromEnvironmentInteracting()
|
||||
}
|
||||
case None => ;
|
||||
}
|
||||
}
|
||||
watery.doInteractingWithTargets(player, percentage, watery.Condition.map(_.body).get, List(player))
|
||||
case watery: WithWater if watery.Condition.map(_.state).contains(OxygenState.Recovery) =>
|
||||
watery.stopInteractingWithTargets(
|
||||
player,
|
||||
Watery.recoveringFromWater(vehicle, watery)._3,
|
||||
watery.Condition.map(_.body).get,
|
||||
List(player)
|
||||
)
|
||||
case watery: WithWater =>
|
||||
watery.recoverFromInteracting(player)
|
||||
}
|
||||
}
|
||||
|
||||
override def parseAttribute(attribute: Int, value: Long, other: Option[Any]) : Unit = {
|
||||
|
|
@ -904,7 +718,7 @@ object VehicleControl {
|
|||
|
||||
private case class PrepareForDeletion()
|
||||
|
||||
private case class Disable()
|
||||
final case class Disable(kickPassengers: Boolean = false)
|
||||
|
||||
private case class Deletion()
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.vehicles.interaction
|
||||
|
||||
import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment}
|
||||
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment, interaction}
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.vital.Vitality
|
||||
import net.psforever.objects.vital.environment.EnvironmentReason
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class WithLava()
|
||||
extends InteractionWith {
|
||||
val attribute: EnvironmentTrait = EnvironmentAttribute.Lava
|
||||
|
||||
private var stopBurn: Boolean = false
|
||||
|
||||
/**
|
||||
* Lava causes vehicles to take (considerable) damage until they are inevitably destroyed.
|
||||
* @param obj the target
|
||||
* @param body the environment
|
||||
* //@param data additional interaction information, if applicable
|
||||
*/
|
||||
def doInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
if (stopBurn && data.nonEmpty) {
|
||||
stopBurn = false
|
||||
} else if (!obj.Destroyed) {
|
||||
obj.Actor ! Vitality.Damage(
|
||||
DamageInteraction(
|
||||
SourceEntry(obj),
|
||||
EnvironmentReason(body, obj),
|
||||
obj.Position
|
||||
).calculate()
|
||||
)
|
||||
//keep doing damage
|
||||
obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = 250 milliseconds, obj.Actor, interaction.InteractingWithEnvironment(body, Some("burning")))
|
||||
}
|
||||
}
|
||||
|
||||
override def stopInteractingWith(obj: InteractsWithZone, body: PieceOfEnvironment, parentInfo: Option[Any]): Unit = {
|
||||
stopBurn = true
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
// Copyright (c) 2024 PSForever
|
||||
package net.psforever.objects.vehicles.interaction
|
||||
|
||||
import net.psforever.objects.{GlobalDefinitions, 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.vehicles.control.VehicleControl
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
import net.psforever.types.OxygenState
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class WithWater()
|
||||
extends InteractionWith
|
||||
with Watery {
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
def doInteractingWith(
|
||||
obj: InteractsWithZone,
|
||||
body: PieceOfEnvironment,
|
||||
data: Option[Any]
|
||||
): Unit = {
|
||||
obj match {
|
||||
case vehicle: Vehicle =>
|
||||
val (effect: Boolean, time: Long, percentage: Float) = {
|
||||
val (a, b, c) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime)
|
||||
if (a && GlobalDefinitions.isFlightVehicle(vehicle.Definition)) {
|
||||
(true, 0L, 0f) //no progress bar
|
||||
} else {
|
||||
(a, b, c)
|
||||
}
|
||||
}
|
||||
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,
|
||||
percentage,
|
||||
body,
|
||||
vehicle.Seats.values.flatMap(_.occupants).filter(p => p.isAlive && (p.Zone eq obj.Zone))
|
||||
)
|
||||
}
|
||||
case _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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 _ => ()
|
||||
}
|
||||
}
|
||||
|
||||
override def recoverFromInteracting(obj: InteractsWithZone): Unit = {
|
||||
super.recoverFromInteracting(obj)
|
||||
if (condition.exists(_.state == OxygenState.Suffocation)) {
|
||||
stopInteractingWith(obj, condition.map(_.body).get, None)
|
||||
}
|
||||
waterInteractionTime = 0L
|
||||
condition = None
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 = {
|
||||
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
|
||||
*/
|
||||
def stopInteractingWithTargets(
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,8 @@ import net.psforever.objects.vital.base.{DamageReason, DamageResolution}
|
|||
import net.psforever.objects.vital.damage.DamageCalculations
|
||||
import net.psforever.objects.vital.prop.DamageProperties
|
||||
import net.psforever.objects.vital.resolution.{DamageAndResistance, DamageResistanceModel}
|
||||
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions, Vitality}
|
||||
import net.psforever.objects.vital.{NoResistanceSelection, SimpleResolutions}
|
||||
import net.psforever.objects.zones.InteractsWithZone
|
||||
|
||||
/**
|
||||
* A wrapper for a "damage source" in damage calculations
|
||||
|
|
@ -40,11 +41,11 @@ object EnvironmentReason {
|
|||
* @param target the target being involved in this interaction
|
||||
* @return an `EnvironmentReason` object
|
||||
*/
|
||||
def apply(body: PieceOfEnvironment, target: Vitality): EnvironmentReason =
|
||||
def apply(body: PieceOfEnvironment, target: InteractsWithZone): EnvironmentReason =
|
||||
EnvironmentReason(body, target.DamageModel.DamageUsing)
|
||||
|
||||
/** variable, no resisting, quick and simple */
|
||||
def drm(against: DamageCalculations.Selector) = new DamageResistanceModel {
|
||||
def drm(against: DamageCalculations.Selector): DamageResistanceModel = new DamageResistanceModel {
|
||||
DamageUsing = against
|
||||
ResistUsing = NoResistanceSelection
|
||||
Model = SimpleResolutions.calculate
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
// Copyright (c) 2021 PSForever
|
||||
package net.psforever.objects.zones
|
||||
|
||||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.vital.{Vitality, VitalityDefinition}
|
||||
import net.psforever.objects.zones.blockmap.SectorPopulation
|
||||
|
||||
trait InteractsWithZone
|
||||
extends PlanetSideServerObject {
|
||||
extends PlanetSideServerObject
|
||||
with Vitality {
|
||||
/** interactions for this particular entity is allowed */
|
||||
private var _allowInteraction: Boolean = true
|
||||
/** maximum interaction range used to generate the commonly tested sector */
|
||||
|
|
@ -47,12 +50,12 @@ trait InteractsWithZone
|
|||
|
||||
def interaction(): List[ZoneInteraction] = interactions
|
||||
|
||||
def getInteractionSector(): SectorPopulation = {
|
||||
def getInteractionSector: SectorPopulation = {
|
||||
this.Zone.blockMap.sector(this.Position, interactionRange)
|
||||
}
|
||||
|
||||
def doInteractions(): Unit = {
|
||||
val sector = getInteractionSector()
|
||||
val sector = getInteractionSector
|
||||
//println(sector.environmentList.map { _.attribute }.mkString(" "))
|
||||
interactions.foreach { _.interaction(sector, target = this) }
|
||||
}
|
||||
|
|
@ -66,6 +69,8 @@ trait InteractsWithZone
|
|||
def resetInteractions(): Unit = {
|
||||
interactions.foreach { _.resetInteraction(target = this) }
|
||||
}
|
||||
|
||||
override def Definition: ObjectDefinition with VitalityDefinition
|
||||
}
|
||||
|
||||
trait ZoneInteractionType
|
||||
|
|
|
|||
|
|
@ -273,6 +273,9 @@ class SectorGroup(
|
|||
extends SectorPopulation
|
||||
|
||||
object SectorGroup {
|
||||
/** cached sector of no range and no entity coverage */
|
||||
final val emptySector: SectorGroup = SectorGroup(range = 0, sectors = Nil)
|
||||
|
||||
/**
|
||||
* Overloaded constructor that takes a single sector
|
||||
* and transfers the lists of entities into a single conglomeration of the sector populations.
|
||||
|
|
@ -326,7 +329,7 @@ object SectorGroup {
|
|||
*/
|
||||
def apply(sectors: Iterable[Sector]): SectorGroup = {
|
||||
if (sectors.isEmpty) {
|
||||
SectorGroup(range = 0, sectors = Nil)
|
||||
SectorGroup.emptySector
|
||||
} else if (sectors.size == 1) {
|
||||
SectorGroup(sectors.head.rangeX, sectors.head.rangeY, sectors)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -4,11 +4,12 @@ package net.psforever.packet.game
|
|||
import net.psforever.newcodecs.newcodecs
|
||||
import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket}
|
||||
import net.psforever.types.{OxygenState, PlanetSideGUID}
|
||||
import scodec.Codec
|
||||
import scodec.bits.BitVector
|
||||
import scodec.{Attempt, Codec}
|
||||
import scodec.codecs._
|
||||
|
||||
/**
|
||||
* Infomation about the progress bar displayed for a certain target's drowning condition.
|
||||
* Information about the progress bar displayed for a certain target's drowning condition.
|
||||
* @param guid the target
|
||||
* @param progress the remaining countdown
|
||||
* @param condition in what state of drowning the target is progressing
|
||||
|
|
@ -41,8 +42,8 @@ final case class OxygenStateMessage(
|
|||
vehicle: Option[DrowningTarget]
|
||||
) extends PlanetSideGamePacket {
|
||||
type Packet = OxygenStateMessage
|
||||
def opcode = GamePacketOpcode.OxygenStateMessage
|
||||
def encode = OxygenStateMessage.encode(this)
|
||||
def opcode: GamePacketOpcode.Type = GamePacketOpcode.OxygenStateMessage
|
||||
def encode: Attempt[BitVector] = OxygenStateMessage.encode(this)
|
||||
}
|
||||
|
||||
object DrowningTarget {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import net.psforever.objects.ballistics.Projectile
|
|||
import net.psforever.objects.ce.Deployable
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.serverobject.environment.OxygenStateTarget
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.objects.zones.Zone
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import net.psforever.objects.avatar.scoring.KDAStat
|
|||
import net.psforever.objects.ballistics.Projectile
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.inventory.InventoryItem
|
||||
import net.psforever.objects.serverobject.environment.OxygenStateTarget
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
|
||||
import net.psforever.objects.sourcing.SourceEntry
|
||||
import net.psforever.packet.PlanetSideGamePacket
|
||||
import net.psforever.packet.game.objectcreate.ConstructorData
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import base.ActorTest
|
|||
import net.psforever.objects.definition.ObjectDefinition
|
||||
import net.psforever.objects.serverobject.PlanetSideServerObject
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.environment.interaction.{EscapeFromEnvironment, InteractWithEnvironment, InteractingWithEnvironment}
|
||||
import net.psforever.objects.vital.{Vitality, VitalityDefinition}
|
||||
import net.psforever.objects.zones.{InteractsWithZone, Zone, ZoneMap}
|
||||
import net.psforever.types.{PlanetSideEmpire, Vector3}
|
||||
|
|
@ -48,7 +49,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
|
|||
val msg = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg match {
|
||||
case InteractingWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case InteractingWithEnvironment(b, _) => (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -67,7 +68,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
|
|||
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case InteractingWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case InteractingWithEnvironment(b, _) => (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -77,7 +78,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
|
|||
val msg2 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg2 match {
|
||||
case EscapeFromEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case EscapeFromEnvironment(b, _) => (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -96,7 +97,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
|
|||
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case InteractingWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case InteractingWithEnvironment(b, _) => (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -106,7 +107,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
|
|||
val msg2 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg2 match {
|
||||
case InteractingWithEnvironment(o, b, _) => (o eq obj) && (b eq pool2)
|
||||
case InteractingWithEnvironment(b, _) => (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -124,7 +125,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
|
|||
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case InteractingWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case InteractingWithEnvironment(b, _) => (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -134,13 +135,13 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
|
|||
val msgs = testProbe.receiveN(2, max = 250 milliseconds)
|
||||
assert(
|
||||
msgs.head match {
|
||||
case EscapeFromEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case EscapeFromEnvironment(b, _) => (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
assert(
|
||||
msgs(1) match {
|
||||
case InteractingWithEnvironment(o, b, _) => (o eq obj) && (b eq pool3)
|
||||
case InteractingWithEnvironment(b, _) => (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -159,7 +160,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
|
|||
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case InteractingWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case InteractingWithEnvironment(b, _) => (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -167,7 +168,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
|
|||
obj.allowInteraction = false
|
||||
val msg2 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
msg2 match {
|
||||
case EscapeFromEnvironment(o, b, _) => assert((o eq obj) && (b eq pool1))
|
||||
case EscapeFromEnvironment(b, _) => (b eq pool1)
|
||||
case _ => assert( false)
|
||||
}
|
||||
obj.zoneInteractions()
|
||||
|
|
@ -189,7 +190,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
|
|||
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
|
||||
assert(
|
||||
msg1 match {
|
||||
case InteractingWithEnvironment(o, b, _) => (o eq obj) && (b eq pool1)
|
||||
case InteractingWithEnvironment(b, _) => (b eq pool1)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -199,8 +200,7 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
|
|||
object InteractsWithZoneEnvironmentTest {
|
||||
def testObject(): PlanetSideServerObject with InteractsWithZone = {
|
||||
new PlanetSideServerObject
|
||||
with InteractsWithZone
|
||||
with Vitality {
|
||||
with InteractsWithZone {
|
||||
interaction(new InteractWithEnvironment())
|
||||
def Faction: PlanetSideEmpire.Value = PlanetSideEmpire.VS
|
||||
def DamageModel = null
|
||||
|
|
|
|||
|
|
@ -16,7 +16,8 @@ import net.psforever.objects.vital.Vitality
|
|||
import net.psforever.objects.zones.{Zone, ZoneMap}
|
||||
import net.psforever.objects._
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.environment.{DeepSquare, EnvironmentAttribute, OxygenStateTarget, Pool}
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
|
||||
import net.psforever.objects.serverobject.environment.{DeepSquare, EnvironmentAttribute, Pool}
|
||||
import net.psforever.objects.sourcing.{PlayerSource, SourceEntry}
|
||||
import net.psforever.objects.vital.base.DamageResolution
|
||||
import net.psforever.objects.vital.interaction.DamageInteraction
|
||||
|
|
@ -812,7 +813,7 @@ class PlayerControlInteractWithWaterTest extends ActorTest {
|
|||
msg_drown match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter1",
|
||||
AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), OxygenState.Suffocation, 100f), _)
|
||||
AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), _, OxygenState.Suffocation, 100f), _)
|
||||
) => true
|
||||
case _ => false
|
||||
}
|
||||
|
|
@ -866,7 +867,7 @@ class PlayerControlStopInteractWithWaterTest extends ActorTest {
|
|||
msg_drown match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter1",
|
||||
AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), OxygenState.Suffocation, 100f), _)
|
||||
AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), _, OxygenState.Suffocation, 100f), _)
|
||||
) => true
|
||||
case _ => false
|
||||
}
|
||||
|
|
@ -879,7 +880,7 @@ class PlayerControlStopInteractWithWaterTest extends ActorTest {
|
|||
msg_recover match {
|
||||
case AvatarServiceMessage(
|
||||
"TestCharacter1",
|
||||
AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), OxygenState.Recovery, _), _)
|
||||
AvatarAction.OxygenState(OxygenStateTarget(PlanetSideGUID(1), _, OxygenState.Recovery, _), _)
|
||||
) => true
|
||||
case _ => false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import net.psforever.objects.guid.NumberPoolHub
|
|||
import net.psforever.objects.guid.source.MaxNumberSource
|
||||
import net.psforever.objects.serverobject.CommonMessages
|
||||
import net.psforever.objects.serverobject.environment._
|
||||
import net.psforever.objects.serverobject.environment.interaction.{EscapeFromEnvironment, InteractingWithEnvironment}
|
||||
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
|
||||
import net.psforever.objects.serverobject.mount.Mountable
|
||||
import net.psforever.objects.vehicles.VehicleLockState
|
||||
import net.psforever.objects.vehicles.control.VehicleControl
|
||||
|
|
@ -617,10 +619,9 @@ class VehicleControlInteractWithWaterPartialTest extends ActorTest {
|
|||
assert(
|
||||
msg_drown match {
|
||||
case InteractingWithEnvironment(
|
||||
p1,
|
||||
p2,
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), OxygenState.Suffocation, 100f))
|
||||
) => (p1 eq player1) && (p2 eq pool)
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), _, OxygenState.Suffocation, 100f))
|
||||
) => (p2 eq pool)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -679,8 +680,8 @@ class VehicleControlInteractWithWaterTest extends ActorTest {
|
|||
case AvatarServiceMessage(
|
||||
"TestCharacter1",
|
||||
AvatarAction.OxygenState(
|
||||
OxygenStateTarget(PlanetSideGUID(1), OxygenState.Suffocation, 100f),
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), OxygenState.Suffocation, 100f))
|
||||
OxygenStateTarget(PlanetSideGUID(1), _, OxygenState.Suffocation, 100f),
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), _, OxygenState.Suffocation, 100f))
|
||||
)
|
||||
) => true
|
||||
case _ => false
|
||||
|
|
@ -742,10 +743,9 @@ class VehicleControlStopInteractWithWaterTest extends ActorTest {
|
|||
assert(
|
||||
msg_drown match {
|
||||
case InteractingWithEnvironment(
|
||||
p1,
|
||||
p2,
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), OxygenState.Suffocation, 100f))
|
||||
) => (p1 eq player1) && (p2 eq pool)
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), _, OxygenState.Suffocation, 100f))
|
||||
) => (p2 eq pool)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
@ -756,10 +756,9 @@ class VehicleControlStopInteractWithWaterTest extends ActorTest {
|
|||
assert(
|
||||
msg_recover match {
|
||||
case EscapeFromEnvironment(
|
||||
p1,
|
||||
p2,
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), OxygenState.Recovery, _))
|
||||
) => (p1 eq player1) && (p2 eq pool)
|
||||
Some(OxygenStateTarget(PlanetSideGUID(2), _, OxygenState.Recovery, _))
|
||||
) => (p2 eq pool)
|
||||
case _ => false
|
||||
}
|
||||
)
|
||||
|
|
|
|||
Loading…
Reference in a new issue