diff --git a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala index 73d98304d..7fcda14b3 100644 --- a/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala +++ b/src/main/scala/net/psforever/actors/session/support/SessionMountHandlers.scala @@ -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") diff --git a/src/main/scala/net/psforever/objects/avatar/interaction/WithWater.scala b/src/main/scala/net/psforever/objects/avatar/interaction/WithWater.scala index 235ec0b8d..ca17db45c 100644 --- a/src/main/scala/net/psforever/objects/avatar/interaction/WithWater.scala +++ b/src/main/scala/net/psforever/objects/avatar/interaction/WithWater.scala @@ -26,21 +26,23 @@ class WithWater(val channel: String) body: PieceOfEnvironment, data: Option[Any] ): Unit = { - val (effect, time, percentage) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime) - val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage) val extra = data.collect { case t: OxygenStateTarget => Some(t) case w: Watery => w.Condition }.flatten - if (effect) { - 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)) - } else if (extra.isDefined) { - //inform the player that their mounted vehicle is in trouble (that they are in trouble) - obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, None)) + 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)) + } } } @@ -78,6 +80,9 @@ class WithWater(val channel: String) 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 } diff --git a/src/main/scala/net/psforever/objects/serverobject/environment/PieceOfEnvironment.scala b/src/main/scala/net/psforever/objects/serverobject/environment/PieceOfEnvironment.scala index 01e271a1c..c6e762519 100644 --- a/src/main/scala/net/psforever/objects/serverobject/environment/PieceOfEnvironment.scala +++ b/src/main/scala/net/psforever/objects/serverobject/environment/PieceOfEnvironment.scala @@ -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 + }) } } diff --git a/src/main/scala/net/psforever/objects/serverobject/environment/interaction/InteractWithEnvironment.scala b/src/main/scala/net/psforever/objects/serverobject/environment/interaction/InteractWithEnvironment.scala index 2866b2407..8c7c749e2 100644 --- a/src/main/scala/net/psforever/objects/serverobject/environment/interaction/InteractWithEnvironment.scala +++ b/src/main/scala/net/psforever/objects/serverobject/environment/interaction/InteractWithEnvironment.scala @@ -5,7 +5,7 @@ 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, SectorPopulation} +import net.psforever.objects.zones.blockmap.{BlockMapEntity, SectorGroup, SectorPopulation} import scala.collection.mutable @@ -41,7 +41,7 @@ class InteractWithEnvironment() def interaction(sector: SectorPopulation, target: InteractsWithZone): Unit = { target match { case t: InteractsWithZone => - interactWith = interactionBehavior.perform(t, interactWith, allow=true) + interactWith = interactionBehavior.perform(t, sector, interactWith, allow=true) interactionBehavior = interactionBehavior.next case _ => () } @@ -60,8 +60,8 @@ class InteractWithEnvironment() */ def resetInteraction(target: InteractsWithZone) : Unit = { target match { - case t: InteractsWithZone => - AwaitOngoingInteraction(target.Zone).perform(t, interactWith, allow=false) + case t: InteractsWithZone with BlockMapEntity => + AwaitOngoingInteraction(target.Zone).perform(t, SectorGroup.emptySector, interactWith, allow=false) case _ => () } interactWith = Set() @@ -110,19 +110,17 @@ object InteractWithEnvironment { /** * 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): Set[PieceOfEnvironment] = { + def checkAllEnvironmentInteractions( + obj: PlanetSideServerObject, + sector: SectorPopulation + ): Set[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 - }).filter { body => - body.attribute.canInteractWith(obj) && body.testInteraction(position, depth) - } + sector.environmentList + .filter(body => body.attribute.canInteractWith(obj) && body.testInteraction(position, depth)) .distinctBy(_.attribute) .toSet } @@ -134,7 +132,11 @@ object InteractWithEnvironment { * @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] = { + 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 { @@ -146,7 +148,12 @@ object InteractWithEnvironment { trait InteractionBehavior { protected var nextstep: InteractionBehavior = this - def perform(obj: InteractsWithZone, existing: Set[PieceOfEnvironment], allow: Boolean): Set[PieceOfEnvironment] + def perform( + obj: InteractsWithZone, + sector: SectorPopulation, + existing: Set[PieceOfEnvironment], + allow: Boolean + ): Set[PieceOfEnvironment] def next: InteractionBehavior = { val out = nextstep @@ -166,15 +173,20 @@ case class OnStableEnvironment() extends InteractionBehavior { * @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 the function literal that represents the next iterative call of ongoing interaction testing; - * may return itself + * @return applicable interactive environmental fields */ - def perform(obj: InteractsWithZone, existing: Set[PieceOfEnvironment], allow: Boolean): Set[PieceOfEnvironment] = { + 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) + 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) @@ -201,21 +213,26 @@ final case class AwaitOngoingInteraction(zone: Zone) extends InteractionBehavior * @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 the function literal that represents the next iterative call of ongoing interaction testing; - * may return itself + * @return applicable interactive environmental fields */ - def perform(obj: InteractsWithZone, existing: Set[PieceOfEnvironment], allow: Boolean): Set[PieceOfEnvironment] = { + 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) + 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, Set(), allow) + val out = n.perform(obj, sector, Set(), allow) nextstep = n.next out } else { @@ -236,12 +253,17 @@ case class BlockedFromInteracting() extends InteractionBehavior { * 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 the function literal that represents the next iterative call of ongoing interaction testing; - * may return itself + * @return an empty set */ - def perform(obj: InteractsWithZone, existing: Set[PieceOfEnvironment], allow: Boolean): Set[PieceOfEnvironment] = { + def perform( + obj: InteractsWithZone, + sector: SectorPopulation, + existing: Set[PieceOfEnvironment], + allow: Boolean + ): Set[PieceOfEnvironment] = { if (allow) { nextstep = OnStableEnvironment() } diff --git a/src/main/scala/net/psforever/objects/serverobject/environment/interaction/InteractingWithEnvironment.scala b/src/main/scala/net/psforever/objects/serverobject/environment/interaction/InteractingWithEnvironment.scala index 0060ad0de..25d49d90f 100644 --- a/src/main/scala/net/psforever/objects/serverobject/environment/interaction/InteractingWithEnvironment.scala +++ b/src/main/scala/net/psforever/objects/serverobject/environment/interaction/InteractingWithEnvironment.scala @@ -26,6 +26,11 @@ final case class EscapeFromEnvironment( ) /** - * Completely reset any internal actions or processes related to environment clipping. + * 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 diff --git a/src/main/scala/net/psforever/objects/serverobject/environment/interaction/RespondsToZoneEnvironment.scala b/src/main/scala/net/psforever/objects/serverobject/environment/interaction/RespondsToZoneEnvironment.scala index ac30b86ed..a94677c26 100644 --- a/src/main/scala/net/psforever/objects/serverobject/environment/interaction/RespondsToZoneEnvironment.scala +++ b/src/main/scala/net/psforever/objects/serverobject/environment/interaction/RespondsToZoneEnvironment.scala @@ -56,6 +56,10 @@ trait RespondsToZoneEnvironment { applicableInteractions .get(attribute) .foreach(_.recoverFromInteracting(InteractiveObject)) + + case ResetAllEnvironmentInteractions => + applicableInteractions.values.foreach(_.recoverFromInteracting(InteractiveObject)) + interactionTimers.values.foreach(_.cancel()) } def respondToEnvironmentPostStop(): Unit = { diff --git a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala index 8bc342cf1..d84214dac 100644 --- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala +++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala @@ -215,8 +215,8 @@ class VehicleControl(vehicle: Vehicle) ) } - case VehicleControl.Disable() => - PrepareForDisabled(kickPassengers = false) + case VehicleControl.Disable(kickPassengers) => + PrepareForDisabled(kickPassengers) context.become(Disabled) case Vehicle.Deconstruct(time) => @@ -384,28 +384,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)) + } } } } @@ -722,7 +714,7 @@ object VehicleControl { private case class PrepareForDeletion() - private[vehicles] case class Disable() + final case class Disable(kickPassengers: Boolean = false) private case class Deletion() diff --git a/src/main/scala/net/psforever/objects/vehicles/interaction/WithWater.scala b/src/main/scala/net/psforever/objects/vehicles/interaction/WithWater.scala index 1435e4ec9..39fd899bf 100644 --- a/src/main/scala/net/psforever/objects/vehicles/interaction/WithWater.scala +++ b/src/main/scala/net/psforever/objects/vehicles/interaction/WithWater.scala @@ -7,6 +7,7 @@ import net.psforever.objects.serverobject.environment.interaction.{InteractionWi 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 @@ -38,20 +39,15 @@ class WithWater() (a, b, c) } } - val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage) if (effect) { - condition = Some(cond) + 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 { - case seat if seat.isOccupied => seat.occupants - case _ => Nil - } - .filter { p => p.isAlive && (p.Zone eq obj.Zone) } + vehicle.Seats.values.flatMap(_.occupants).filter(p => p.isAlive && (p.Zone eq obj.Zone)) ) } case _ => () @@ -75,21 +71,15 @@ class WithWater() case vehicle: Vehicle => val (effect: Boolean, time: Long, percentage: Float) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime) - val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage) if (effect) { - condition = Some(cond) + 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 { - case seat if seat.isOccupied => seat.occupants - case _ => Nil - } - .filter { p => p.isAlive && (p.Zone eq obj.Zone) } + vehicle.Seats.values.flatMap(_.occupants).filter(p => p.isAlive && (p.Zone eq obj.Zone)) ) } case _ => () @@ -98,6 +88,9 @@ class WithWater() 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 } diff --git a/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala b/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala index 60ce74e65..f76e27f8c 100644 --- a/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala +++ b/src/main/scala/net/psforever/objects/zones/blockmap/Sector.scala @@ -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 {