diff --git a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala
index bdda54dab..042a3551e 100644
--- a/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/AvatarHandlerLogic.scala
@@ -3,6 +3,7 @@ package net.psforever.actors.session.normal
import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.support.AvatarHandlerFunctions
+import net.psforever.objects.serverobject.containable.ContainableBehavior
import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction}
import net.psforever.types.ImplantType
@@ -295,6 +296,17 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0))
}
//functionally delete
+ if (delete.size > 1 || delete.nonEmpty && !delete.exists {
+ case (e: Tool, _) => GlobalDefinitions.isMaxArms(e.Definition)
+ case _ => false
+ }) {
+ /*
+ if going x -> max, you will have enough space in max inventory for any displaced holster equipment
+ for max -> max, don't care about the max weapon arm being deleted (allow for 1)
+ for any other x -> x, any deleted equipment will raise this comment
+ */
+ sendResponse(ChatMsg(ChatMessageType.UNK_227, "@ItemsDeconstructed"))
+ }
delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) }
//redraw
if (maxhand) {
@@ -330,13 +342,17 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
}
DropLeftovers(player)(drop)
- case AvatarResponse.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, _, delete) =>
+ case AvatarResponse.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, drop, delete) =>
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
//happening to some other player
sendResponse(ObjectHeldMessage(target, slot, unk1 = false))
//cleanup
- (oldHolsters ++ delete).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) }
+ val dropPred = ContainableBehavior.DropPredicate(player)
+ val deleteFromDrop = drop.filterNot(dropPred)
+ (oldHolsters ++ delete ++ deleteFromDrop.map(f =>(f.obj, f.GUID)))
+ .distinctBy(_._2)
+ .foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) }
//draw holsters
holsters.foreach {
case InventoryItem(obj, index) =>
diff --git a/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala b/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala
index dc599e017..e701c0a55 100644
--- a/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/ChatLogic.scala
@@ -145,8 +145,11 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case (CMT_KICK, _, contents) if gmCommandAllowed =>
ops.commandKick(session, message, contents)
+ case (CMT_REPORTUSER, _, contents) =>
+ ops.commandReportUser(session, message, contents)
+
case _ =>
- log.warn(s"Unhandled chat message $message")
+ sendResponse(ChatMsg(ChatMessageType.UNK_227, "@no_permission"))
}
}
diff --git a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala
index 1eb457f9d..ab40f6222 100644
--- a/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/GeneralLogic.scala
@@ -162,6 +162,11 @@ class GeneralLogic(val ops: GeneralOperations, implicit val context: ActorContex
}
case None => ()
}
+ //llu destruction check
+ if (player.Carrying.contains(SpecialCarry.CaptureFlag)) {
+ CaptureFlagManager.ReasonToLoseFlagViolently(continent, sessionLogic.general.specialItemSlotGuid, player)
+ }
+ //
val eagleEye: Boolean = ops.canSeeReallyFar
val isNotVisible: Boolean = sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing ||
(player.isAlive && sessionLogic.zoning.spawn.deadState == DeadState.RespawnTime)
diff --git a/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala
index 211cc48ef..c2f1a8969 100644
--- a/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/MountHandlerLogic.scala
@@ -11,6 +11,7 @@ import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.environment.interaction.ResetAllEnvironmentInteractions
import net.psforever.objects.serverobject.hackable.GenericHackables
import net.psforever.objects.serverobject.mount.Mountable
+import net.psforever.objects.serverobject.structures.WarpGate
import net.psforever.objects.serverobject.terminals.implant.ImplantTerminalMech
import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret}
import net.psforever.objects.vehicles.{AccessPermissionGroup, CargoBehavior}
@@ -19,7 +20,7 @@ import net.psforever.packet.game.{ChatMsg, DelayedPathMountMsg, DismountVehicleC
import net.psforever.services.Service
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
-import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3}
+import net.psforever.types.{BailType, ChatMessageType, DriveState, PlanetSideGUID, Vector3}
import scala.concurrent.duration._
@@ -71,18 +72,6 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
obj.Actor ! Mountable.TryDismount(player, seat_num, bailType)
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that
sessionLogic.zoning.interstellarFerry = None
- // Deconstruct the vehicle if the driver has bailed out and the vehicle is capable of flight
- //todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle
- //todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct.
- //todo: kick cargo passengers out. To be added after PR #216 is merged
- obj match {
- case v: Vehicle
- if bailType == BailType.Bailed &&
- v.SeatPermissionGroup(seat_num).contains(AccessPermissionGroup.Driver) &&
- v.isFlying =>
- v.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
- case _ => ()
- }
case None =>
dError(s"DismountVehicleMsg: can not find where player ${player.Name}_guid is seated in mountable ${player.VehicleSeated}", player)
@@ -368,18 +357,19 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
DismountAction(tplayer, obj, seatNum)
obj.Actor ! Vehicle.Deconstruct()
+ case Mountable.CanDismount(obj: Vehicle, seatNum, _)
+ if tplayer.GUID == player.GUID &&
+ obj.isFlying &&
+ obj.SeatPermissionGroup(seatNum).contains(AccessPermissionGroup.Driver) =>
+ // Deconstruct the vehicle if the driver has bailed out and the vehicle is capable of flight
+ //todo: implement auto landing procedure if the pilot bails but passengers are still present instead of deconstructing the vehicle
+ //todo: continue flight path until aircraft crashes if no passengers present (or no passenger seats), then deconstruct.
+ //todo: kick cargo passengers out. To be added after PR #216 is merged
+ DismountVehicleAction(tplayer, obj, seatNum)
+ obj.Actor ! Vehicle.Deconstruct(None) //immediate deconstruction
+
case Mountable.CanDismount(obj: Vehicle, seatNum, _)
if tplayer.GUID == player.GUID =>
- //disembarking self
- log.info(s"${player.Name} dismounts the ${obj.Definition.Name}'s ${
- obj.SeatPermissionGroup(seatNum) match {
- case Some(AccessPermissionGroup.Driver) => "driver seat"
- case Some(seatType) => s"$seatType seat (#$seatNum)"
- case None => "seat"
- }
- }")
- sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
- sessionLogic.general.unaccessContainer(obj)
DismountVehicleAction(tplayer, obj, seatNum)
case Mountable.CanDismount(obj: Vehicle, seat_num, _) =>
@@ -388,7 +378,7 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
VehicleAction.KickPassenger(tplayer.GUID, seat_num, unk2=true, obj.GUID)
)
- case Mountable.CanDismount(obj: PlanetSideGameObject with PlanetSideGameObject with Mountable with FactionAffinity with InGameHistory, seatNum, _) =>
+ case Mountable.CanDismount(obj: PlanetSideGameObject with Mountable with FactionAffinity with InGameHistory, seatNum, _) =>
log.info(s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name}")
DismountAction(tplayer, obj, seatNum)
@@ -407,7 +397,38 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
case Mountable.CanNotMount(obj: Mountable, seatNumber) =>
log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's seat $seatNumber, but was not allowed")
- case Mountable.CanNotDismount(obj, seatNum) =>
+ case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Normal)
+ if obj.DeploymentState == DriveState.AutoPilot =>
+ sendResponse(ChatMsg(ChatMessageType.UNK_224, "@SA_CannotDismountAtThisTime"))
+
+ case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed)
+ if obj.Definition == GlobalDefinitions.droppod =>
+ sendResponse(ChatMsg(ChatMessageType.UNK_224, "@CannotBailFromDroppod"))
+
+ case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed)
+ if obj.DeploymentState == DriveState.AutoPilot =>
+ sendResponse(ChatMsg(ChatMessageType.UNK_224, "@SA_CannotBailAtThisTime"))
+
+ case Mountable.CanNotDismount(obj: Vehicle, _, BailType.Bailed)
+ if {
+ continent
+ .blockMap
+ .sector(obj)
+ .buildingList
+ .exists {
+ case wg: WarpGate =>
+ Vector3.DistanceSquared(obj.Position, wg.Position) < math.pow(wg.Definition.SOIRadius, 2)
+ case _ =>
+ false
+ }
+ } =>
+ sendResponse(ChatMsg(ChatMessageType.UNK_227, "@Vehicle_CannotBailInWarpgateEnvelope"))
+
+ case Mountable.CanNotDismount(obj: Vehicle, _, _)
+ if obj.isMoving(test = 1f) =>
+ sendResponse(ChatMsg(ChatMessageType.UNK_224, "@TooFastToDismount"))
+
+ case Mountable.CanNotDismount(obj, seatNum, _) =>
log.warn(s"DismountVehicleMsg: ${tplayer.Name} attempted to dismount $obj's mount $seatNum, but was not allowed")
}
}
@@ -465,7 +486,17 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
* @param obj the mountable object
* @param seatNum the mount out of which which the player is disembarking
*/
- private def DismountVehicleAction(tplayer: Player, obj: PlanetSideGameObject with FactionAffinity with InGameHistory, seatNum: Int): Unit = {
+ private def DismountVehicleAction(tplayer: Player, obj: Vehicle, seatNum: Int): Unit = {
+ //disembarking self
+ log.info(s"${player.Name} dismounts the ${obj.Definition.Name}'s ${
+ obj.SeatPermissionGroup(seatNum) match {
+ case Some(AccessPermissionGroup.Driver) => "driver seat"
+ case Some(seatType) => s"$seatType seat (#$seatNum)"
+ case None => "seat"
+ }
+ }")
+ sessionLogic.vehicles.ConditionalDriverVehicleControl(obj)
+ sessionLogic.general.unaccessContainer(obj)
DismountAction(tplayer, obj, seatNum)
//until vehicles maintain synchronized momentum without a driver
obj match {
diff --git a/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala
index 6d8f9c1a4..55d01d2a1 100644
--- a/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/VehicleHandlerLogic.scala
@@ -4,6 +4,7 @@ package net.psforever.actors.session.normal
import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{SessionData, SessionVehicleHandlers, VehicleHandlerFunctions}
+import net.psforever.objects.avatar.SpecialCarry
import net.psforever.objects.{GlobalDefinitions, Player, Tool, Vehicle, Vehicles}
import net.psforever.objects.equipment.{Equipment, JammableMountedWeapons, JammableUnit}
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
@@ -12,6 +13,7 @@ import net.psforever.objects.serverobject.pad.VehicleSpawnPad
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import net.psforever.packet.game.{ChangeAmmoMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, ChatMsg, ChildObjectStateMessage, DeadState, DeployRequestMessage, DismountVehicleMsg, FrameVehicleStateMessage, GenericObjectActionMessage, HitHint, InventoryStateMessage, ObjectAttachMessage, ObjectCreateDetailedMessage, ObjectCreateMessage, ObjectDeleteMessage, ObjectDetachMessage, PlanetsideAttributeMessage, ReloadMessage, ServerVehicleOverrideMsg, VehicleStateMessage, WeaponDryFireMessage}
import net.psforever.services.Service
+import net.psforever.services.local.support.CaptureFlagManager
import net.psforever.services.vehicle.{VehicleResponse, VehicleServiceResponse}
import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3}
@@ -62,6 +64,14 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
player.Orientation = orient
player.Velocity = vel
sessionLogic.updateLocalBlockMap(pos)
+ //llu destruction check
+ if (player.Carrying.contains(SpecialCarry.CaptureFlag)) {
+ continent
+ .GUID(player.VehicleSeated)
+ .collect { case vehicle: Vehicle =>
+ CaptureFlagManager.ReasonToLoseFlagViolently(continent, sessionLogic.general.specialItemSlotGuid, vehicle)
+ }
+ }
case VehicleResponse.VehicleState(
vehicleGuid,
@@ -199,6 +209,9 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid))
sendResponse(PlanetsideAttributeMessage(resolvedPlayerGuid, attribute_type=21, vehicleGuid))
+ case VehicleResponse.LoseOwnership(_, vehicleGuid) =>
+ ops.announceAmsDecay(vehicleGuid,msg = "@ams_decaystarted")
+
case VehicleResponse.PlanetsideAttribute(vehicleGuid, attributeType, attributeValue) if isNotSameTarget =>
sendResponse(PlanetsideAttributeMessage(vehicleGuid, attributeType, attributeValue))
@@ -217,6 +230,16 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
case VehicleResponse.UnloadVehicle(_, vehicleGuid) =>
sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=0))
+ if (sessionLogic.zoning.spawn.prevSpawnPoint.map(_.Owner).exists {
+ case ams: Vehicle =>
+ ams.GUID == vehicleGuid &&
+ ams.OwnerGuid.isEmpty
+ case _ =>
+ false
+ }) {
+ sessionLogic.zoning.spawn.prevSpawnPoint = None
+ sendResponse(ChatMsg(ChatMessageType.UNK_229, "@ams_decayed"))
+ }
case VehicleResponse.UnstowEquipment(itemGuid) if isNotSameTarget =>
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly?
@@ -308,13 +331,13 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle)
case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) =>
- sendResponse(ChatMsg(
- ChatMessageType.CMT_OPEN,
- wideContents=true,
- recipient="",
- s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}",
- note=None
- ))
+ val str = s"${data.getOrElse("The vehicle spawn pad where you placed your order is blocked.")}"
+ val msg = if (str.contains("@")) {
+ ChatMsg(ChatMessageType.UNK_229, str)
+ } else {
+ ChatMsg(ChatMessageType.CMT_OPEN, wideContents = true, recipient = "", str, note = None)
+ }
+ sendResponse(msg)
case VehicleResponse.PeriodicReminder(_, data) =>
val (isType, flag, msg): (ChatMessageType, Boolean, String) = data match {
diff --git a/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala b/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala
index 6b293ed75..5b8ab62d1 100644
--- a/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/VehicleLogic.scala
@@ -10,9 +10,9 @@ import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles.control.BfrFlight
import net.psforever.objects.zones.Zone
-import net.psforever.packet.game.{ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, VehicleStateMessage, VehicleSubStateMessage}
+import net.psforever.packet.game.{ChatMsg, ChildObjectStateMessage, DeployRequestMessage, FrameVehicleStateMessage, VehicleStateMessage, VehicleSubStateMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
-import net.psforever.types.{DriveState, Vector3}
+import net.psforever.types.{ChatMessageType, DriveState, Vector3}
object VehicleLogic {
def apply(ops: VehicleOperations): VehicleLogic = {
@@ -70,6 +70,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
obj.Velocity = None
obj.Flying = None
}
+ //
continent.VehicleEvents ! VehicleServiceMessage(
continent.id,
VehicleAction.VehicleState(
@@ -303,6 +304,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
log.trace(s"DeployRequest: $obj transitioning to deploy state")
} else if (state == DriveState.Deployed) {
log.trace(s"DeployRequest: $obj has been Deployed")
+ sendResponse(ChatMsg(ChatMessageType.UNK_227, "@DeployingMessage"))
} else {
CanNotChangeDeployment(obj, state, "incorrect deploy state")
}
@@ -313,6 +315,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
log.trace(s"DeployRequest: $obj transitioning to undeploy state")
} else if (state == DriveState.Mobile) {
log.trace(s"DeployRequest: $obj is Mobile")
+ sendResponse(ChatMsg(ChatMessageType.UNK_227, "@UndeployingMessage"))
} else {
CanNotChangeDeployment(obj, state, "incorrect undeploy state")
}
diff --git a/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala
index 3b7d10edd..a7e5884e2 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/ChatLogic.scala
@@ -85,7 +85,11 @@ class ChatLogic(val ops: ChatOperations, implicit val context: ActorContext) ext
case (CMT_WARP, _, contents) =>
ops.commandWarp(session, message, contents)
- case _ => ()
+ case (CMT_REPORTUSER, _, contents) =>
+ ops.commandReportUser(session, message, contents)
+
+ case _ =>
+ sendResponse(ChatMsg(ChatMessageType.UNK_227, "@no_permission"))
}
}
diff --git a/src/main/scala/net/psforever/actors/session/spectator/MountHandlerLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/MountHandlerLogic.scala
index f6c3a4a43..2cf441e0f 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/MountHandlerLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/MountHandlerLogic.scala
@@ -203,9 +203,9 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
case Mountable.CanDismount(obj: PlanetSideGameObject with PlanetSideGameObject with Mountable with FactionAffinity with InGameHistory, seatNum, _) =>
DismountAction(tplayer, obj, seatNum)
- case Mountable.CanDismount(obj: Mountable, _, _) => ()
+ case Mountable.CanDismount(_: Mountable, _, _) => ()
- case Mountable.CanNotDismount(obj: Vehicle, seatNum) =>
+ case Mountable.CanNotDismount(obj: Vehicle, _, _) =>
obj.Actor ! Vehicle.Deconstruct()
case _ => ()
diff --git a/src/main/scala/net/psforever/actors/session/spectator/VehicleLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/VehicleLogic.scala
index 10fdb8ed6..7ba9cca0b 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/VehicleLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/VehicleLogic.scala
@@ -279,11 +279,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
def handleCanDeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = { /* intentionally blank */ }
- def handleCanUndeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = {
- if (state != DriveState.Undeploying && state != DriveState.Mobile) {
- CanNotChangeDeployment(obj, state, "incorrect undeploy state")
- }
- }
+ def handleCanUndeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = { /* intentionally blank */ }
def handleCanNotChangeDeployment(obj: Deployment.DeploymentObject, state: DriveState.Value, reason: String): Unit = {
if (Deployment.CheckForDeployState(state) && !Deployment.AngleCheck(obj)) {
diff --git a/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala b/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala
index 412a4c343..02efbc7e4 100644
--- a/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/ChatOperations.scala
@@ -91,11 +91,13 @@ class ChatOperations(
}
def commandWatermark(contents: String): Unit = {
- val connectionState =
+ val connectionState = {
if (contents.contains("40 80")) 100
else if (contents.contains("120 200")) 25
else 50
+ }
context.self ! SessionActor.SetConnectionState(connectionState)
+ context.self ! SessionActor.SendResponse(ChatMsg(ChatMessageType.UNK_227, "@CMT_CULLWATERMARK_success"))
}
def commandSpeed(message: ChatMsg, contents: String): Unit = {
@@ -647,6 +649,11 @@ class ChatOperations(
}
}
+ def commandReportUser(@unused session: Session, @unused message: ChatMsg, @unused contents: String): Unit = {
+ //todo get user from contents
+ sendResponse(ChatMsg(ChatMessageType.UNK_227, "@rpt_i"))
+ }
+
def commandIncomingSendAllIfOnline(session: Session, message: ChatMsg): Unit = {
if (AvatarActor.onlineIfNotIgnored(session.avatar, message.recipient)) {
sendResponse(message)
diff --git a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala
index deea975f9..67bf09d4e 100644
--- a/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/GeneralOperations.scala
@@ -220,8 +220,12 @@ class GeneralOperations(
)
}
}
- continent.LocalEvents ! CaptureFlagManager.DropFlag(llu)
- case Some((llu, Some(carrier: Player))) if carrier.GUID == player.GUID =>
+ if (!CaptureFlagManager.ReasonToLoseFlagViolently(continent, Some(guid), player)) {
+ continent.LocalEvents ! CaptureFlagManager.DropFlag(llu)
+ }
+ case Some((llu, Some(carrier: Player)))
+ if carrier.GUID == player.GUID &&
+ !CaptureFlagManager.ReasonToLoseFlagViolently(continent, Some(guid), player) =>
continent.LocalEvents ! CaptureFlagManager.DropFlag(llu)
case Some((_, Some(carrier: Player))) =>
log.warn(s"${player.toString} tried to drop LLU, but it is currently held by ${carrier.toString}")
@@ -669,6 +673,9 @@ class GeneralOperations(
case _ => ()
}
} else {
+ if (player.Capacitor < 1f && player.UsingSpecial == SpecialExoSuitDefinition.Mode.Shielded) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_227, "@ArmorShieldOff"))
+ }
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
diff --git a/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala b/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala
index 5389b6bb9..b493b1ff2 100644
--- a/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala
+++ b/src/main/scala/net/psforever/actors/session/support/SessionVehicleHandlers.scala
@@ -3,8 +3,10 @@ package net.psforever.actors.session.support
import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.actors.session.AvatarActor
+import net.psforever.objects.Vehicle
+import net.psforever.packet.game.ChatMsg
import net.psforever.services.vehicle.VehicleResponse
-import net.psforever.types.PlanetSideGUID
+import net.psforever.types.{ChatMessageType, DriveState, PlanetSideGUID}
trait VehicleHandlerFunctions extends CommonSessionInterfacingFunctionality {
def ops: SessionVehicleHandlers
@@ -17,4 +19,17 @@ class SessionVehicleHandlers(
val avatarActor: typed.ActorRef[AvatarActor.Command],
val galaxyService: ActorRef,
implicit val context: ActorContext
- ) extends CommonSessionInterfacingFunctionality
+ ) extends CommonSessionInterfacingFunctionality {
+ def announceAmsDecay(vehicleGuid: PlanetSideGUID, msg: String): Unit = {
+ if (sessionLogic.zoning.spawn.prevSpawnPoint.map(_.Owner).exists {
+ case ams: Vehicle =>
+ ams.GUID == vehicleGuid &&
+ ams.DeploymentState == DriveState.Deployed &&
+ ams.OwnerGuid.isEmpty
+ case _ =>
+ false
+ }) {
+ sendResponse(ChatMsg(ChatMessageType.UNK_229, msg))
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala
index 7741fa86f..55753dff9 100644
--- a/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/WeaponAndProjectileOperations.scala
@@ -2,9 +2,11 @@
package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed}
+import net.psforever.objects.definition.SpecialExoSuitDefinition
import net.psforever.objects.zones.Zoning
import net.psforever.objects.serverobject.turret.VanuSentry
import net.psforever.objects.zones.exp.ToDatabase
+import net.psforever.types.ChatMessageType
import scala.collection.mutable
import scala.concurrent.duration._
@@ -83,9 +85,10 @@ class WeaponAndProjectileOperations(
if (player.ZoningRequest != Zoning.Method.None) {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_fire")
}
- if (player.isShielded) {
+ if (player.UsingSpecial == SpecialExoSuitDefinition.Mode.Shielded) {
// Cancel NC MAX shield if it's active
sessionLogic.general.toggleMaxSpecialState(enable = false)
+ sendResponse(ChatMsg(ChatMessageType.UNK_227, "@ArmorShieldOverride"))
}
val (o, tools) = FindContainedWeapon
val (_, enabledTools) = FindEnabledWeaponsToHandleWeaponFireAccountability(o, tools)
diff --git a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
index ebeb512fc..4af23102d 100644
--- a/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
+++ b/src/main/scala/net/psforever/actors/session/support/ZoningOperations.scala
@@ -1792,6 +1792,7 @@ class ZoningOperations(
private[session] var setupAvatarFunc: () => Unit = AvatarCreate
private[session] var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally
private[session] var nextSpawnPoint: Option[SpawnPoint] = None
+ private[session] var prevSpawnPoint: Option[SpawnPoint] = None
private[session] var interimUngunnedVehicle: Option[PlanetSideGUID] = None
private[session] var interimUngunnedVehicleSeat: Option[Int] = None
/** Upstream message counter
@@ -2811,6 +2812,7 @@ class ZoningOperations(
)
)
nextSpawnPoint = physSpawnPoint
+ prevSpawnPoint = physSpawnPoint
shiftPosition = Some(pos)
shiftOrientation = Some(ori)
val toZoneNumber = if (continent.id.equals(zoneId)) {
@@ -2819,10 +2821,14 @@ class ZoningOperations(
Zones.zones.find { _.id.equals(zoneId) }.orElse(Some(Zone.Nowhere)).get.Number
}
val toSide = physSpawnPoint.map(_.Owner) match {
- case Some(_: WarpGate) => Sidedness.OutsideOf
- case Some(_: Building) => Sidedness.InsideOf
- case Some(v: Vehicle) => v.WhichSide //though usually OutsideOf
- case _ => Sidedness.StrictlyBetweenSides //todo needs better determination
+ case Some(_: WarpGate) =>
+ Sidedness.OutsideOf
+ case Some(_: Building) =>
+ Sidedness.InsideOf
+ case Some(v: Vehicle) =>
+ v.WhichSide //though usually OutsideOf
+ case _ =>
+ Sidedness.StrictlyBetweenSides //todo needs better determination
}
val toSpawnPoint = physSpawnPoint.collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) }
respawnTimer = context.system.scheduler.scheduleOnce(respawnTime) {
@@ -3124,11 +3130,17 @@ class ZoningOperations(
Config.app.game.savedMsg.short.fixed,
Config.app.game.savedMsg.short.variable
)
- val effortBy = nextSpawnPoint
+ val effortBy = prevSpawnPoint
.collect { case sp: SpawnTube => (sp, continent.GUID(sp.Owner.GUID)) }
.collect {
- case (_, Some(v: Vehicle)) => continent.GUID(v.OwnerGuid)
- case (sp, Some(_: Building)) => Some(sp)
+ case (_, Some(v: Vehicle)) =>
+ sessionLogic.vehicleResponseOperations.announceAmsDecay(
+ v.GUID,
+ msg = "The AMS you were bound to has lost its' owner. It will auto-deconstruct soon."
+ )
+ continent.GUID(v.OwnerGuid)
+ case (sp, Some(_: Building)) =>
+ Some(sp)
}
.collect { case Some(thing: PlanetSideGameObject with FactionAffinity) => Some(SourceEntry(thing)) }
.flatten
diff --git a/src/main/scala/net/psforever/login/WorldSession.scala b/src/main/scala/net/psforever/login/WorldSession.scala
index ae02c6f64..a83a9df00 100644
--- a/src/main/scala/net/psforever/login/WorldSession.scala
+++ b/src/main/scala/net/psforever/login/WorldSession.scala
@@ -65,7 +65,7 @@ object WorldSession {
Zone.Ground.DropItem(localItem, localContainer.Position, Vector3.z(localContainer.Orientation.z)),
localContainer.Actor
)
- case _ => ;
+ case _ => ()
}
result
}
@@ -163,7 +163,7 @@ object WorldSession {
result.onComplete {
case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) =>
TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem))
- case _ => ;
+ case _ => ()
}
result
}
@@ -361,17 +361,21 @@ object WorldSession {
val future = ask(localZone.Ground, Zone.Ground.PickupItem(item.GUID))
future.onComplete {
case Success(Zone.Ground.ItemInHand(_)) =>
- PutEquipmentInInventoryOrDrop(localContainer)(localItem)
+ PutEquipmentInInventoryOrDrop(localContainer)(localItem).onComplete {
+ case Success(Containable.ItemPutInSlot(_, _, Player.FreeHandSlot, _)) =>
+ localContainer.Actor ! Zone.Ground.CanNotPickupItem(localZone, localItem.GUID, "@InventoryPickupNoRoom")
+ case _ => ()
+ }
case Success(Zone.Ground.CanNotPickupItem(_, item_guid, _)) =>
localZone.GUID(item_guid) match {
- case Some(_) => ;
+ case Some(_) => ()
case None => //acting on old data?
localZone.AvatarEvents ! AvatarServiceMessage(
localZone.id,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item_guid)
)
}
- case _ => ;
+ case _ => ()
}
future
}
@@ -407,7 +411,7 @@ object WorldSession {
.DropItem(localItem, localPos.getOrElse(localContainer.Position), Vector3.z(localContainer.Orientation.z)),
localContainer.Actor
)
- case _ => ;
+ case _ => ()
}
result
}
@@ -584,7 +588,7 @@ object WorldSession {
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
//swapItem is not registered right now, we can not drop the item without re-registering it
TaskWorkflow.execute(PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot))
- case _ => ;
+ case _ => ()
}
override def description(): String = s"unregistering $localItem before stowing in $localDestination"
@@ -597,7 +601,7 @@ object WorldSession {
localChannel,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid)
)
- case None => ;
+ case None => ()
}
val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot)))
moveResult.onComplete(localMoveOnComplete)
@@ -610,7 +614,7 @@ object WorldSession {
moveItemTaskFunc(fromSlot),
GUIDTask.unregisterEquipment(fromSource.Zone.GUID, itemToMove)
))
- case _ => ;
+ case _ => ()
}
val result = ask(source.Actor, Containable.RemoveItemFromSlot(item))
result.onComplete(resultOnComplete)
@@ -689,7 +693,7 @@ object WorldSession {
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
//swapItem is not registered right now, we can not drop the item without re-registering it
TaskWorkflow.execute(PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot))
- case _ => ;
+ case _ => ()
}
override def description(): String = s"registering $localItem in ${localDestination.Zone.id} before removing from $localSource"
@@ -702,7 +706,7 @@ object WorldSession {
localChannel,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid)
)
- case None => ;
+ case None => ()
}
val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot)))
moveResult.onComplete(localMoveOnComplete)
@@ -715,7 +719,7 @@ object WorldSession {
moveItemTaskFunc(fromSlot),
GUIDTask.registerEquipment(fromSource.Zone.GUID, itemToMove)
))
- case _ => ;
+ case _ => ()
}
val result = ask(source.Actor, Containable.RemoveItemFromSlot(item))
result.onComplete(resultOnComplete)
@@ -847,17 +851,17 @@ object WorldSession {
case Some(e) =>
log.info(s"${tplayer.Name} has dropped ${tplayer.Sex.possessive} ${e.Definition.Name}")
PutEquipmentInInventoryOrDrop(tplayer)(e)
- case _ => ;
+ case _ => ()
}
//restore previously-held-up equipment
itemInPreviouslyDrawnSlotToDrop match {
case Some(e) => PutEquipmentInInventorySlot(tplayer)(e, previouslyDrawnSlot)
- case _ => ;
+ case _ => ()
}
log.info(s"${tplayer.Name} has quickly drawn a ${grenade.Definition.Name}")
- case _ => ;
+ case _ => ()
}
- case None => ;
+ case None => ()
}
optGrenadeInSlot.nonEmpty
} else {
diff --git a/src/main/scala/net/psforever/objects/Vehicles.scala b/src/main/scala/net/psforever/objects/Vehicles.scala
index 086d7d04d..6a66afe33 100644
--- a/src/main/scala/net/psforever/objects/Vehicles.scala
+++ b/src/main/scala/net/psforever/objects/Vehicles.scala
@@ -10,8 +10,8 @@ import net.psforever.objects.serverobject.transfer.TransferContainer
import net.psforever.objects.serverobject.structures.WarpGate
import net.psforever.objects.vehicles._
import net.psforever.objects.zones.Zone
-import net.psforever.packet.game.{HackMessage, HackState, HackState1, HackState7, TriggeredSound}
-import net.psforever.types.{DriveState, PlanetSideEmpire, PlanetSideGUID, Vector3}
+import net.psforever.packet.game.{ChatMsg, HackMessage, HackState, HackState1, HackState7, TriggeredSound}
+import net.psforever.types.{ChatMessageType, DriveState, PlanetSideEmpire, PlanetSideGUID, Vector3}
import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
@@ -46,7 +46,7 @@ object Vehicles {
vehicle.Zone.id,
VehicleAction.Ownership(tplayer.GUID, vehicle.GUID)
)
- Vehicles.ReloadAccessPermissions(vehicle, tplayer.Name)
+ Vehicles.ReloadAccessPermissions(vehicle, tplayer.Faction.toString)
Some(vehicle)
case None =>
None
@@ -61,31 +61,37 @@ object Vehicles {
* @return the vehicle, if it had a previous owner;
* `None`, otherwise
*/
- def Disown(guid: PlanetSideGUID, vehicle: Vehicle): Option[Vehicle] =
- vehicle.Zone.GUID(vehicle.OwnerGuid) match {
- case Some(player: Player) =>
- if (player.avatar.vehicle.contains(guid)) {
- player.avatar.vehicle = None
-// vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
-// player.Name,
-// VehicleAction.Ownership(player.GUID, PlanetSideGUID(0))
-// )
- }
- vehicle.AssignOwnership(None)
- val empire = VehicleLockState.Empire.id
- //val factionChannel = s"${vehicle.Faction}"
- (0 to 2).foreach(group => {
- vehicle.PermissionGroup(group, empire)
- /*vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
- factionChannel,
- VehicleAction.SeatPermissions(Service.defaultPlayerGUID, guid, group, empire)
- )*/
- })
- ReloadAccessPermissions(vehicle, player.Name)
- Some(vehicle)
- case _ =>
- None
- }
+ def Disown(guid: PlanetSideGUID, vehicle: Vehicle): Option[Vehicle] = {
+ val zone = vehicle.Zone
+ val ownerGuid = vehicle.OwnerGuid
+ val ownerName = vehicle.OwnerName
+ vehicle.AssignOwnership(None)
+ val result = zone
+ .GUID(ownerGuid)
+ .collect {
+ case player: Player => player.avatar
+ }
+ .orElse {
+ zone
+ .Players
+ .collectFirst {
+ case avatar if ownerName.contains(avatar.name) => avatar
+ }
+ }
+ .collect {
+ case avatar if avatar.vehicle.contains(guid) =>
+ avatar.vehicle = None
+ vehicle
+ }
+ val empire = VehicleLockState.Empire.id
+ (0 to 2).foreach(group => vehicle.PermissionGroup(group, empire))
+ ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
+ zone.VehicleEvents ! VehicleServiceMessage(
+ zone.id,
+ VehicleAction.LoseOwnership(ownerGuid.getOrElse(Service.defaultPlayerGUID), guid)
+ )
+ result
+ }
/**
* Disassociate a player from a vehicle that he owns.
@@ -140,7 +146,7 @@ object Vehicles {
VehicleAction.SeatPermissions(pguid, vguid, group, empire)
)*/
})
- ReloadAccessPermissions(vehicle, player.Name)
+ ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
Some(vehicle)
} else {
None
@@ -239,6 +245,8 @@ object Vehicles {
val zone = target.Zone
val zoneid = zone.id
val vehicleEvents = zone.VehicleEvents
+ val localEvents = zone.LocalEvents
+ val previousOwnerName = target.OwnerName.getOrElse("")
vehicleEvents ! VehicleServiceMessage(
zoneid,
VehicleAction.SendResponse(
@@ -292,10 +300,20 @@ object Vehicles {
AvatarAction.SetEmpire(Service.defaultPlayerGUID, tGuid, hFaction)
)
}
- zone.LocalEvents ! LocalServiceMessage(
+ localEvents ! LocalServiceMessage(
zoneid,
LocalAction.TriggerSound(hGuid, TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f)
)
+ if (zone.Players.exists(_.name.equals(previousOwnerName))) {
+ localEvents ! LocalServiceMessage(
+ previousOwnerName,
+ LocalAction.SendResponse(ChatMsg(ChatMessageType.UNK_226, "@JackStolen"))
+ )
+ }
+ localEvents ! LocalServiceMessage(
+ hacker.Name,
+ LocalAction.SendResponse(ChatMsg(ChatMessageType.UNK_226, "@JackVehicleOwned"))
+ )
// Clean up after specific vehicles, e.g. remove router telepads
// If AMS is deployed, swap it to the new faction
target.Definition match {
diff --git a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
index 1d0746b5d..2ef4fa023 100644
--- a/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
+++ b/src/main/scala/net/psforever/objects/avatar/PlayerControl.scala
@@ -522,6 +522,12 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case Zone.Ground.CanNotPickupItem(_, item_guid, reason) =>
log.warn(s"${player.Name} failed to pick up an item ($item_guid) from the ground because $reason")
+ if (reason.startsWith("@")) {
+ player.Zone.AvatarEvents ! AvatarServiceMessage(
+ player.Name,
+ AvatarAction.SendResponse(Service.defaultPlayerGUID, ChatMsg(ChatMessageType.UNK_227, reason))
+ )
+ }
case Player.BuildDeployable(obj: TelepadDeployable, tool: Telepad) =>
obj.Router = tool.Router //necessary; forwards link to the router that produced the telepad
@@ -631,6 +637,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
if (player.DrawnSlot != Player.HandsDownSlot) {
player.DrawnSlot = Player.HandsDownSlot
}
+ val dropPred = ContainableBehavior.DropPredicate(player)
val (toDelete, toDrop, afterHolsters, afterInventory) = if (originalSuit == ExoSuitType.MAX) {
//was max
val (delete, insert) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
@@ -641,18 +648,21 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
//changing to a vanilla exo-suit
val (newHolsters, unplacedHolsters) = Players.fillEmptyHolsters(player.Holsters().iterator, insert ++ beforeInventory)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(unplacedHolsters, player.Inventory)
- (delete, unplacedInventory.map(InventoryItem(_, -1)), newHolsters, inventory)
+ val (dropFromUnplaced, deleteFromUnplaced) = unplacedInventory.map(InventoryItem(_, -1)).partition(dropPred)
+ (delete ++ deleteFromUnplaced, dropFromUnplaced, newHolsters, inventory)
}
} else if (willBecomeMax) {
//will be max, drop everything but melee slot
val (melee, other) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Melee)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(beforeInventory ++ other, player.Inventory)
- (Nil, unplacedInventory.map(InventoryItem(_, -1)), melee, inventory)
+ val (dropFromUnplaced, deleteFromUnplaced) = unplacedInventory.map(InventoryItem(_, -1)).partition(dropPred)
+ (deleteFromUnplaced, dropFromUnplaced, melee, inventory)
} else {
//was not a max nor will become a max; vanilla exo-suit to a vanilla-exo-suit
val (insert, unplacedHolsters) = Players.fillEmptyHolsters(player.Holsters().iterator, beforeHolsters ++ beforeInventory)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(unplacedHolsters, player.Inventory)
- (Nil, unplacedInventory.map(InventoryItem(_, -1)), insert, inventory)
+ val (dropFromUnplaced, deleteFromUnplaced) = unplacedInventory.map(InventoryItem(_, -1)).partition(dropPred)
+ (deleteFromUnplaced, dropFromUnplaced, insert, inventory)
}
//insert
afterHolsters.foreach(elem => player.Slot(elem.start).Equipment = elem.obj)
diff --git a/src/main/scala/net/psforever/objects/avatar/interaction/WithGantry.scala b/src/main/scala/net/psforever/objects/avatar/interaction/WithGantry.scala
index 5f637e003..35f084055 100644
--- a/src/main/scala/net/psforever/objects/avatar/interaction/WithGantry.scala
+++ b/src/main/scala/net/psforever/objects/avatar/interaction/WithGantry.scala
@@ -2,14 +2,15 @@
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.{Player, 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.packet.game.{ChatMsg, PlayerStateShiftMessage, ShiftState}
import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.hart.ShuttleState
+import net.psforever.types.ChatMessageType
import scala.annotation.unused
import scala.concurrent.duration._
@@ -28,17 +29,26 @@ class WithGantry(val channel: String)
(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 =>
+ }, obj) match {
+ case (Some(shuttle: Vehicle), player: Player)
+ if (shuttle.Flying.contains(ShuttleState.State11.id) || shuttle.Faction != player.Faction) &&
+ player.VehicleSeated.isEmpty =>
val (pos, ang) = Vehicles.dismountShuttle(shuttle, field.mountPoint)
- shuttle.Zone.AvatarEvents ! AvatarServiceMessage(
+ val events = shuttle.Zone.AvatarEvents
+ events ! AvatarServiceMessage(
channel,
AvatarAction.SendResponse(
Service.defaultPlayerGUID,
PlayerStateShiftMessage(ShiftState(0, pos, ang, None)))
)
- case Some(_: Vehicle) =>
+ events ! AvatarServiceMessage(
+ channel,
+ AvatarAction.SendResponse(
+ Service.defaultPlayerGUID,
+ ChatMsg(ChatMessageType.UNK_227, "@Vehicle_OS_PlacedOutsideHallway")
+ )
+ )
+ case (Some(_: Vehicle) , _)=>
obj.Actor ! RespondsToZoneEnvironment.Timer(
attribute,
delay = 250 milliseconds,
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 333ae6cb1..674b6dbfb 100644
--- a/src/main/scala/net/psforever/objects/avatar/interaction/WithWater.scala
+++ b/src/main/scala/net/psforever/objects/avatar/interaction/WithWater.scala
@@ -1,11 +1,11 @@
// Copyright (c) 2024 PSForever
package net.psforever.objects.avatar.interaction
-import net.psforever.objects.Player
+import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Player}
import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment}
import net.psforever.objects.serverobject.environment.interaction.common.Watery
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
-import net.psforever.objects.serverobject.environment.{PieceOfEnvironment, interaction}
+import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment, interaction}
import net.psforever.objects.zones.InteractsWithZone
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.types.OxygenState
@@ -15,9 +15,11 @@ import scala.concurrent.duration._
class WithWater(val channel: String)
extends InteractionWith
with Watery {
+ /** do this every time we're in sufficient contact with water */
+ private var doInteractingWithBehavior: (InteractsWithZone, PieceOfEnvironment, Option[Any]) => Unit = wadingBeforeDrowning
+
/**
- * Water causes players to slowly suffocate.
- * When they (finally) drown, they will die.
+ * Water is wet.
* @param obj the target
* @param body the environment
*/
@@ -26,42 +28,110 @@ class WithWater(val channel: String)
body: PieceOfEnvironment,
data: Option[Any]
): Unit = {
- val extra = data.collect {
- case t: OxygenStateTarget => Some(t)
- case w: Watery => w.Condition
- }.flatten
- if (extra.isDefined) {
- //inform the player that their mounted vehicle is in trouble (that they are in trouble (but not from drowning (yet)))
- stopInteractingWith(obj, body, data)
+ if (getExtra(data).nonEmpty) {
+ inheritAndPushExtraData(obj, body, data)
} else {
- val (effect, time, percentage) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime)
- if (effect) {
- val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage)
- waterInteractionTime = System.currentTimeMillis() + time
- condition = Some(cond)
- obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, Player.Die())
- //inform the player that they are in trouble
- obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra))
- }
+ depth = math.max(0f, body.collision.altitude - obj.Position.z)
+ doInteractingWithBehavior(obj, body, data)
+ obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = 500 milliseconds, obj.Actor, interaction.InteractingWithEnvironment(body, Some("wading")))
}
}
/**
- * 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.
+ * Wading only happens while the player's head is above the water.
* @param obj the target
* @param body the environment
*/
- override def stopInteractingWith(
+ private def wadingBeforeDrowning(
obj: InteractsWithZone,
body: PieceOfEnvironment,
data: Option[Any]
): Unit = {
- val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
- if (percentage > 99f) {
- recoverFromInteracting(obj)
+ //we're already "wading", let's see if we're drowning
+ if (depth >= GlobalDefinitions.MaxDepth(obj)) {
+ //drowning
+ beginDrowning(obj, body, data)
} else {
- stopInteractingAction(obj, body, data, effect, time, percentage)
+ //inform the player that their mounted vehicle is in trouble (that they are in trouble (but not from drowning (yet)))
+ val extra = getExtra(data)
+ if (extra.nonEmpty) {
+ displayOxygenState(
+ obj,
+ condition.getOrElse(OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, 95f)),
+ extra
+ )
+ }
+ }
+ }
+
+ /**
+ * Too much water causes players to slowly suffocate.
+ * When they (finally) drown, they will die.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def beginDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ val (effect, time, percentage) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime)
+ if (effect) {
+ val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage)
+ waterInteractionTime = System.currentTimeMillis() + time
+ condition = Some(cond)
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
+ obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, Player.Die())
+ //inform the player that they are in trouble
+ displayOxygenState(obj, cond, getExtra(data))
+ doInteractingWithBehavior = drowning
+ }
+ }
+
+ /**
+ * Too much water causes players to slowly suffocate.
+ * When they (finally) drown, they will die.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def drowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ //test if player ever gets head above the water level
+ if (depth < GlobalDefinitions.MaxDepth(obj)) {
+ val (_, _, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
+ //switch to recovery
+ if (percentage > 0) {
+ recoverFromDrowning(obj, body, data)
+ doInteractingWithBehavior = recoverFromDrowning
+ }
+ }
+ }
+
+ /**
+ * 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
+ */
+ private def recoverFromDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ val state = condition.map(_.state)
+ if (state.contains(OxygenState.Suffocation)) {
+ //set up for recovery
+ val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
+ if (percentage < 99f) {
+ //we're not too far gone
+ recoverFromDrowning(obj, body, data, effect, time, percentage)
+ }
+ doInteractingWithBehavior = recovering
+ } else {
+ doInteractingWithBehavior = wadingBeforeDrowning
}
}
@@ -74,38 +144,169 @@ class WithWater(val channel: String)
* @param time current time until completion of the next effect
* @param percentage value to display in the drowning UI progress bar
*/
- private def stopInteractingAction(
- obj: InteractsWithZone,
- body: PieceOfEnvironment,
- data: Option[Any],
- effect: Boolean,
- time: Long,
- percentage: Float
- ): Unit = {
+ private def recoverFromDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any],
+ effect: Boolean,
+ time: Long,
+ percentage: Float
+ ): Unit = {
val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage)
val extra = data.collect {
case t: OxygenStateTarget => Some(t)
case w: Watery => w.Condition
}.flatten
- if (effect) {
+ if (effect) {
condition = Some(cond)
waterInteractionTime = System.currentTimeMillis() + time
- obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
+ obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
//inform the player
- obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra))
+ displayOxygenState(obj, cond, extra)
} else if (extra.isDefined) {
//inform the player
- obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra))
+ displayOxygenState(obj, cond, extra)
+ }
+ }
+
+ /**
+ * The recovery period is much faster than the drowning process.
+ * Check for when the player fully recovers,
+ * and that the player does not regress back to drowning.
+ * @param obj the target
+ * @param body the environment
+ */
+ def recovering(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ lazy val state = condition.map(_.state)
+ if (depth >= GlobalDefinitions.MaxDepth(obj)) {
+ //go back to drowning
+ beginDrowning(obj, body, data)
+ } else if (state.contains(OxygenState.Recovery)) {
+ //check recovery conditions
+ val (_, _, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
+ if (percentage < 1f) {
+ doInteractingWithBehavior = wadingBeforeDrowning
+ }
+ }
+ }
+
+ /**
+ * When out of water, the player is no longer suffocating.
+ * He's even stopped wading.
+ * The only thing we should let complete now is recovery.
+ * @param obj the target
+ * @param body the environment
+ */
+ override def stopInteractingWith(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ if (getExtra(data).nonEmpty) {
+ inheritAndPushExtraData(obj, body, data)
+ } else {
+ stopInteractingWithAction(obj, body, data)
+ }
+ }
+
+ /**
+ * When out of water, the player is no longer suffocating.
+ * He's even stopped wading.
+ * The only thing we should let complete now is recovery.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def stopInteractingWithAction(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ val cond = condition.map(_.state)
+ if (cond.contains(OxygenState.Suffocation)) {
+ //go from suffocating to recovery
+ recoverFromDrowning(obj, body, data)
+ } else if (cond.isEmpty) {
+ //neither suffocating nor recovering, so just reset everything
+ recoverFromInteracting(obj)
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(attribute)
+ waterInteractionTime = 0L
+ depth = 0f
+ condition = None
+ doInteractingWithBehavior = wadingBeforeDrowning
}
}
override def recoverFromInteracting(obj: InteractsWithZone): Unit = {
super.recoverFromInteracting(obj)
- if (condition.exists(_.state == OxygenState.Suffocation)) {
- val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
- stopInteractingAction(obj, condition.map(_.body).get, None, effect, time, percentage)
+ val cond = condition.map(_.state)
+ //whether or not we were suffocating or recovering, we need to undo the visuals for that
+ if (cond.nonEmpty) {
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
+ displayOxygenState(
+ obj,
+ OxygenStateTarget(obj.GUID, condition.map(_.body).get, OxygenState.Recovery, 100f),
+ None
+ )
}
- waterInteractionTime = 0L
condition = None
}
+
+ /**
+ * From the "condition" of someone else's drowning status,
+ * extract target information and progress.
+ * @param data any information
+ * @return target information and drowning progress
+ */
+ private def getExtra(data: Option[Any]): Option[OxygenStateTarget] = {
+ data.collect {
+ case t: OxygenStateTarget => Some(t)
+ case w: Watery => w.Condition
+ }.flatten
+ }
+
+ /**
+ * Send the message regarding drowning and recovery
+ * that includes additional information about a related target that is drowning or recovering.
+ * @param obj the target
+ * @param body the environment
+ * @param data essential information about someone else's interaction with water
+ */
+ private def inheritAndPushExtraData(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ val state = condition.map(_.state).getOrElse(OxygenState.Recovery)
+ val Some((_, _, percentage)) = state match {
+ case OxygenState.Suffocation => Some(Watery.drowningInWateryConditions(obj, Some(state), waterInteractionTime))
+ case OxygenState.Recovery => Some(Watery.recoveringFromWateryConditions(obj, Some(state), waterInteractionTime))
+ }
+ displayOxygenState(obj, OxygenStateTarget(obj.GUID, body, state, percentage), getExtra(data))
+ }
+
+ /**
+ * Send the message regarding drowning and recovery.
+ * @param obj the target
+ * @param cond the environment
+ */
+ private def displayOxygenState(
+ obj: InteractsWithZone,
+ cond: OxygenStateTarget,
+ data: Option[OxygenStateTarget]
+ ): Unit = {
+ obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, data))
+ }
+}
+
+object WithWater {
+ /** special environmental trait to queue actions independent from the primary wading test */
+ case object WaterAction extends EnvironmentTrait {
+ override def canInteractWith(obj: PlanetSideGameObject): Boolean = false
+ override def testingDepth(obj: PlanetSideGameObject): Float = Float.PositiveInfinity
+ }
}
diff --git a/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala b/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
index b7b430737..8791b6397 100644
--- a/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
+++ b/src/main/scala/net/psforever/objects/definition/converter/SmallDeployableConverter.scala
@@ -9,15 +9,15 @@ import net.psforever.types.PlanetSideGUID
import scala.util.{Failure, Success, Try}
class SmallDeployableConverter extends ObjectCreateConverter[Deployable]() {
- override def ConstructorData(obj: Deployable): Try[CommonFieldDataWithPlacement] = {
+ override def ConstructorData(obj: Deployable): Try[SmallDeployableData] = {
Success(
- CommonFieldDataWithPlacement(
+ SmallDeployableData(CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation),
CommonFieldData(
obj.Faction,
bops = false,
alternate = obj.Destroyed,
- false,
+ v1 = false,
None,
jammered = obj match {
case o: JammableUnit => o.Jammed
@@ -30,10 +30,10 @@ class SmallDeployableConverter extends ObjectCreateConverter[Deployable]() {
case None => PlanetSideGUID(0)
}
)
- )
+ ))
)
}
- override def DetailedConstructorData(obj: Deployable): Try[CommonFieldDataWithPlacement] =
+ override def DetailedConstructorData(obj: Deployable): Try[SmallDeployableData] =
Failure(new Exception("converter should not be used to generate detailed small deployable data"))
}
diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala
index 11059fbab..3c4650132 100644
--- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala
+++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsMiscellaneous.scala
@@ -801,7 +801,7 @@ object GlobalDefinitionsMiscellaneous {
AutoRanges(
detection = 125f,
trigger = 100f,
- escape = 200f
+ escape = 150f
),
AutoChecks(
validation = List(
@@ -813,7 +813,7 @@ object GlobalDefinitionsMiscellaneous {
),
retaliatoryDelay = 4000L, //8000L
cylindrical = true,
- cylindricalExtraHeight = 50f,
+ cylindricalExtraHeight = 25f,
detectionSweepTime = 2.seconds,
refireTime = 362.milliseconds //312.milliseconds
)
diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala
index c3dd0c9dd..54b540648 100644
--- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala
+++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala
@@ -971,7 +971,7 @@ object GlobalDefinitionsVehicle {
ams.DeployTime = 2000
ams.UndeployTime = 2000
ams.interference = InterferenceRange(main = 125f, sharedGroupId = 3, shared = 30f)
- ams.DeconstructionTime = Some(20 minutes)
+ ams.DeconstructionTime = Some(15 minutes)
ams.AutoPilotSpeeds = (18, 6)
ams.Packet = utilityConverter
ams.DestroyedModel = Some(DestroyedVehicle.Ams)
@@ -1014,6 +1014,7 @@ object GlobalDefinitionsVehicle {
router.Deployment = true
router.DeployTime = 2000
router.UndeployTime = 2000
+ router.interference = InterferenceRange(main = 20f)
router.DeconstructionTime = Duration(20, "minutes")
router.AutoPilotSpeeds = (16, 6)
router.Packet = variantConverter
diff --git a/src/main/scala/net/psforever/objects/serverobject/containable/ContainableBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/containable/ContainableBehavior.scala
index 76be594b3..d3680de99 100644
--- a/src/main/scala/net/psforever/objects/serverobject/containable/ContainableBehavior.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/containable/ContainableBehavior.scala
@@ -676,6 +676,7 @@ object ContainableBehavior {
entry => {
val objDef = entry.obj.Definition
val faction = GlobalDefinitions.isFactionEquipment(objDef)
+ GlobalDefinitions.isCavernEquipment(objDef) ||
objDef == GlobalDefinitions.router_telepad ||
entry.obj.isInstanceOf[BoomerTrigger] ||
(faction != tplayer.Faction && faction != PlanetSideEmpire.NEUTRAL)
diff --git a/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala
index 664facf54..0eb8cf718 100644
--- a/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/deploy/DeploymentBehavior.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.deploy
-import akka.actor.{Actor, Cancellable}
+import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.objects.Default
import net.psforever.types.{DriveState, Vector3}
import net.psforever.services.Service
@@ -33,24 +33,24 @@ trait DeploymentBehavior {
val deployBehavior: Receive = {
case Deployment.TryDeploymentChange(state) =>
- sender() ! TryDeploymentStateChange(state)
+ sender() ! TryDeploymentStateChange(state, sender())
case Deployment.TryDeploy(state) =>
- sender() ! TryDeployStateChange(state)
+ sender() ! TryDeployStateChange(state, sender())
case Deployment.TryUndeploy(state) =>
- sender() ! TryUndeployStateChange(state)
+ sender() ! TryUndeployStateChange(state, sender())
}
- def TryDeploymentStateChange(state: DriveState.Value): Any = {
+ def TryDeploymentStateChange(state: DriveState.Value, replyTo: ActorRef): Any = {
val obj = DeploymentObject
val prevState = obj.DeploymentState
if (TryDeploymentChange(obj, state)) {
if (Deployment.CheckForDeployState(state)) {
- DeploymentAction(obj, state, prevState)
+ DeploymentAction(obj, state, prevState, replyTo)
Deployment.CanDeploy(obj, state)
} else {
- UndeploymentAction(obj, state, prevState)
+ UndeploymentAction(obj, state, prevState, replyTo)
Deployment.CanUndeploy(obj, state)
}
} else {
@@ -58,22 +58,22 @@ trait DeploymentBehavior {
}
}
- def TryDeployStateChange(state: DriveState.Value): Any = {
+ def TryDeployStateChange(state: DriveState.Value, replyTo: ActorRef): Any = {
val obj = DeploymentObject
val prevState = obj.DeploymentState
if (Deployment.CheckForDeployState(state) && TryDeploymentChange(obj, state)) {
- DeploymentAction(obj, state, prevState)
+ DeploymentAction(obj, state, prevState, replyTo)
Deployment.CanDeploy(obj, state)
} else {
Deployment.CanNotChangeDeployment(obj, state, "incorrect deploy transition state")
}
}
- def TryUndeployStateChange(state: DriveState.Value): Any = {
+ def TryUndeployStateChange(state: DriveState.Value, replyTo: ActorRef): Any = {
val obj = DeploymentObject
val prevState = obj.DeploymentState
if (Deployment.CheckForUndeployState(state) && TryUndeploymentChange(obj, state)) {
- UndeploymentAction(obj, state, prevState)
+ UndeploymentAction(obj, state, prevState, replyTo)
Deployment.CanUndeploy(obj, state)
} else {
Deployment.CanNotChangeDeployment(obj, state, "incorrect undeploy transition state")
@@ -91,7 +91,8 @@ trait DeploymentBehavior {
def DeploymentAction(
obj: Deployment.DeploymentObject,
state: DriveState.Value,
- prevState: DriveState.Value
+ prevState: DriveState.Value,
+ replyTo: ActorRef
): DriveState.Value = {
val guid = obj.GUID
val zone = obj.Zone
@@ -108,11 +109,9 @@ trait DeploymentBehavior {
VehicleAction.DeployRequest(GUID0, guid, state, 0, unk2=false, Vector3.Zero)
)
deploymentTimer.cancel()
- deploymentTimer = context.system.scheduler.scheduleOnce(
- obj.DeployTime milliseconds,
- obj.Actor,
- Deployment.TryDeploy(DriveState.Deployed)
- )
+ deploymentTimer = context.system.scheduler.scheduleOnce(obj.DeployTime.milliseconds)({
+ obj.Actor.tell(Deployment.TryDeploy(DriveState.Deployed), replyTo)
+ })
state
} else if (state == DriveState.Deployed) {
obj.Velocity = Some(Vector3.Zero) //no velocity
@@ -129,7 +128,8 @@ trait DeploymentBehavior {
def UndeploymentAction(
obj: Deployment.DeploymentObject,
state: DriveState.Value,
- prevState: DriveState.Value
+ prevState: DriveState.Value,
+ replyTo: ActorRef
): DriveState.Value = {
val guid = obj.GUID
val zone = obj.Zone
@@ -142,11 +142,9 @@ trait DeploymentBehavior {
)
import scala.concurrent.ExecutionContext.Implicits.global
deploymentTimer.cancel()
- deploymentTimer = context.system.scheduler.scheduleOnce(
- obj.UndeployTime milliseconds,
- obj.Actor,
- Deployment.TryUndeploy(DriveState.Mobile)
- )
+ deploymentTimer = context.system.scheduler.scheduleOnce(obj.UndeployTime.milliseconds)({
+ obj.Actor.tell(Deployment.TryUndeploy(DriveState.Mobile), replyTo)
+ })
state
} else if (state == DriveState.Mobile) {
zone.VehicleEvents ! VehicleServiceMessage(
diff --git a/src/main/scala/net/psforever/objects/serverobject/deploy/Interference.scala b/src/main/scala/net/psforever/objects/serverobject/deploy/Interference.scala
index 8b0cae70e..da5fba1a4 100644
--- a/src/main/scala/net/psforever/objects/serverobject/deploy/Interference.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/deploy/Interference.scala
@@ -46,11 +46,11 @@ object Interference {
* @param zone game world in which this test will be conducted;
* entity should be `ZoneAware`, but it may not be set correctly during this part of its internal process
* @param obj entity that may be interfered with
- * @return a different entity that causes the test entity to suffer interference
+ * @return other entities that causes the test entity to suffer interference
*/
- def Test(zone: Zone, obj: PlanetSideGameObject with FactionAffinity): Option[PlanetSideGameObject with FactionAffinity] = {
+ def Test(zone: Zone, obj: PlanetSideGameObject with FactionAffinity): Seq[PlanetSideGameObject with FactionAffinity] = {
val (data, filterFunc) = SetupForTest(zone, obj)
- data.find(filterFunc)
+ data.filter(filterFunc)
}
/**
diff --git a/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala b/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala
index 2ad7d1e87..232923956 100644
--- a/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/environment/EnvironmentAttribute.scala
@@ -10,6 +10,8 @@ import net.psforever.types.Vector3
*/
abstract class EnvironmentTrait {
def canInteractWith(obj: PlanetSideGameObject): Boolean
+
+ def testingDepth(obj: PlanetSideGameObject): Float
}
object EnvironmentAttribute {
@@ -25,16 +27,33 @@ object EnvironmentAttribute {
case _ => false
})
}
+
+ def testingDepth(obj: PlanetSideGameObject): Float = {
+ obj match {
+ case v: Vehicle if v.Flying.nonEmpty =>
+ 0f
+ case _: Vehicle if !obj.Definition.DrownAtMaxDepth =>
+ obj.Definition.MaxDepth * 0.9f
+ case _: Vehicle =>
+ obj.Definition.MaxDepth * 0.6f
+ case _ =>
+ 0.2f
+ }
+ }
}
case object Lava extends EnvironmentTrait {
/** lava can only interact with anything capable of registering damage */
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithDamagingFields(obj)
+
+ def testingDepth(obj: _root_.net.psforever.objects.PlanetSideGameObject): Float = 0f
}
case object Death extends EnvironmentTrait {
/** death can only interact with anything capable of registering damage */
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithDamagingFields(obj)
+
+ def testingDepth(obj: _root_.net.psforever.objects.PlanetSideGameObject): Float = 0f
}
case object GantryDenialField
@@ -46,18 +65,24 @@ object EnvironmentAttribute {
case _ => false
}
}
+
+ def testingDepth(obj: _root_.net.psforever.objects.PlanetSideGameObject): Float = 0f
}
case object MovementFieldTrigger
extends EnvironmentTrait {
/** only interact with living player characters or vehicles */
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithPlayersAndVehicles(obj)
+
+ def testingDepth(obj: _root_.net.psforever.objects.PlanetSideGameObject): Float = 0f
}
case object InteriorField
extends EnvironmentTrait {
/** only interact with living player characters or vehicles */
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithPlayersAndVehicles(obj)
+
+ def testingDepth(obj: _root_.net.psforever.objects.PlanetSideGameObject): Float = 0f
}
/**
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 aacd261b9..33c550209 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
@@ -1,7 +1,6 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.serverobject.environment.interaction
-import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment}
import net.psforever.objects.zones._
@@ -98,6 +97,8 @@ class InteractWithEnvironment()
.foreach(_.stopInteractingWith(obj, body, None))
}
}
+
+ def OngoingInteractions: Set[EnvironmentTrait] = interactWith.map(_.attribute)
}
object InteractWithEnvironment {
@@ -117,9 +118,8 @@ object InteractWithEnvironment {
obj: PlanetSideServerObject,
sector: SectorPopulation
): Set[PieceOfEnvironment] = {
- val depth = GlobalDefinitions.MaxDepth(obj)
sector.environmentList
- .filter(body => body.attribute.canInteractWith(obj) && body.testInteraction(obj, depth))
+ .filter(body => body.attribute.canInteractWith(obj) && body.testInteraction(obj, body.attribute.testingDepth(obj)))
.distinctBy(_.attribute)
.toSet
}
@@ -136,7 +136,7 @@ object InteractWithEnvironment {
body: PieceOfEnvironment,
obj: PlanetSideServerObject
): Option[PieceOfEnvironment] = {
- if ((obj.Zone eq zone) && body.testInteraction(obj, GlobalDefinitions.MaxDepth(obj))) {
+ if ((obj.Zone eq zone) && body.testInteraction(obj, body.attribute.testingDepth(obj))) {
Some(body)
} else {
None
@@ -185,12 +185,12 @@ case class OnStableEnvironment() extends InteractionBehavior {
): 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) {
+ val bodies = InteractWithEnvironment.checkAllEnvironmentInteractions(obj, sector)
+ bodies.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.doInteractingWith(obj, body, None)))
+ if (bodies.nonEmpty) {
nextstep = AwaitOngoingInteraction(obj.Zone)
}
- env
+ bodies
} else {
nextstep = BlockedFromInteracting()
Set()
@@ -225,17 +225,22 @@ final case class AwaitOngoingInteraction(zone: Zone) extends InteractionBehavior
): Set[PieceOfEnvironment] = {
val interactions = obj.interaction().collectFirst { case inter: InteractWithEnvironment => inter.Interactions }
if (allow) {
- val env = InteractWithEnvironment.checkAllEnvironmentInteractions(obj, sector)
+ val bodies = 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 inAttrs = bodies.map(_.attribute)
+ out
+ .filterNot(e => inAttrs.contains(e.attribute))
+ .foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.stopInteractingWith(obj, body, None)))
+ bodies
+ .diff(in)
+ .foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.doInteractingWith(obj, body, None)))
+ if (bodies.isEmpty) {
val n = OnStableEnvironment()
val out = n.perform(obj, sector, Set(), allow)
nextstep = n.next
out
} else {
- env
+ bodies
}
} else {
existing.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.stopInteractingWith(obj, body, None)))
diff --git a/src/main/scala/net/psforever/objects/serverobject/environment/interaction/common/Watery.scala b/src/main/scala/net/psforever/objects/serverobject/environment/interaction/common/Watery.scala
index 799c166c8..e08cd8b27 100644
--- a/src/main/scala/net/psforever/objects/serverobject/environment/interaction/common/Watery.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/environment/interaction/common/Watery.scala
@@ -1,20 +1,26 @@
// Copyright (c) 2024 PSForever
package net.psforever.objects.serverobject.environment.interaction.common
+import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.serverobject.PlanetSideServerObject
+import net.psforever.objects.serverobject.environment.interaction.InteractWithEnvironment
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment}
+import net.psforever.objects.zones.InteractsWithZone
import net.psforever.types.{OxygenState, PlanetSideGUID}
trait Watery {
val attribute: EnvironmentTrait = EnvironmentAttribute.Water
-
/** how long the current interaction has been progressing in the current way */
protected var waterInteractionTime: Long = 0
/** information regarding the drowning state */
protected var condition: Option[OxygenStateTarget] = None
/** information regarding the drowning state */
def Condition: Option[OxygenStateTarget] = condition
+ /** how far the player's feet are below the surface of the water */
+ protected var depth: Float = 0f
+ /** how far the player's feet are below the surface of the water */
+ def Depth: Float = depth
}
object Watery {
@@ -33,6 +39,27 @@ object Watery {
progress: Float
)
+ /**
+ * na
+ * @param target evaluate this to determine if to continue with this loss
+ * @return whether or not we are sufficiently submerged in water
+ */
+ def wading(target: PlanetSideGameObject with InteractsWithZone): Boolean = {
+ target
+ .interaction()
+ .collectFirst {
+ case env: InteractWithEnvironment =>
+ env
+ .Interactions
+ .get(EnvironmentAttribute.Water)
+ .collectFirst {
+ case water: Watery => water.Depth > 0f
+ }
+ }
+ .flatten
+ .contains(true)
+ }
+
/**
* Calculate the effect of being exposed to a watery environment beyond an entity's critical region.
* @param obj the target
@@ -122,10 +149,10 @@ object Watery {
//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 oldTimeRemaining: Long = math.max(0, completionTime - System.currentTimeMillis())
val oldTimeRatio: Float = oldTimeRemaining / oldDuration.toFloat
val percentage: Float = oldTimeRatio * 100
- val recoveryTime: Long = newDuration - (newDuration * oldTimeRatio).toLong
+ val recoveryTime: Long = newDuration * (1f - oldTimeRatio).toLong
(true, recoveryTime, percentage)
case Some(OxygenState.Recovery) =>
//interrupted while recovering, calculate the progress and keep recovering
diff --git a/src/main/scala/net/psforever/objects/serverobject/llu/CaptureFlag.scala b/src/main/scala/net/psforever/objects/serverobject/llu/CaptureFlag.scala
index d13627e9f..bb02a6970 100644
--- a/src/main/scala/net/psforever/objects/serverobject/llu/CaptureFlag.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/llu/CaptureFlag.scala
@@ -34,6 +34,7 @@ class CaptureFlag(private val tDef: CaptureFlagDefinition) extends Amenity {
private var faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var carrier: Option[Player] = None
private var lastTimeCollected: Long = System.currentTimeMillis()
+ private val spawnedTime: Long = lastTimeCollected
def Target: Building = target
def Target_=(newTarget: Building): Building = {
@@ -56,10 +57,11 @@ class CaptureFlag(private val tDef: CaptureFlagDefinition) extends Amenity {
* When the flag is carried by a player, the position returned should be that of the carrier not the flag.
* @return the position of the carrier, if there is a player carrying the flag, or the flag itself
*/
- override def Position: Vector3 = if (Carrier.nonEmpty) {
- carrier.get.Position
- } else {
- super.Position
+ override def Position: Vector3 = {
+ carrier match {
+ case Some(player) => player.Position
+ case None => super.Position
+ }
}
def Carrier: Option[Player] = carrier
@@ -70,6 +72,8 @@ class CaptureFlag(private val tDef: CaptureFlagDefinition) extends Amenity {
}
def LastCollectionTime: Long = carrier.map { _ => lastTimeCollected }.getOrElse { System.currentTimeMillis() }
+
+ def InitialSpawnTime: Long = spawnedTime
}
object CaptureFlag {
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala b/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala
index 3d858c4d6..c797371ac 100644
--- a/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/Mountable.scala
@@ -146,5 +146,5 @@ object Mountable {
* @param obj the `Mountable` object
* @param seat_num the seat index
*/
- final case class CanNotDismount(obj: Mountable, seat_num: Int) extends Exchange
+ final case class CanNotDismount(obj: Mountable, seat_num: Int, bailType: BailType.Value) extends Exchange
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala b/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
index 1c60938c6..aca66da06 100644
--- a/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/mount/MountableBehavior.scala
@@ -94,7 +94,7 @@ trait MountableBehavior {
)
}
else {
- sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(obj, seat_number))
+ sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(obj, seat_number, bail_type))
}
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
index 0fa40b9e8..29bd0f511 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnControl.scala
@@ -56,6 +56,9 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
/** how to process either the first order or every subsequent order */
private var handleOrderFunc: VehicleSpawnPad.VehicleOrder => Unit = NewTasking
+ /** ... */
+ private var reminderSeq: Seq[Int] = Seq()
+
def LogId = ""
/**
@@ -77,7 +80,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
}
override def postStop() : Unit = {
- periodicReminder.cancel()
+ discontinueCurrentReminder()
queueManagement.cancel()
}
@@ -113,36 +116,11 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
case VehicleSpawnControl.ProcessControl.QueueManagement =>
queueManagementTask()
- /*
- When the vehicle is spawned and added to the pad, it will "occupy" the pad and block it from further action.
- Normally, the player who wanted to spawn the vehicle will be automatically put into the driver mount.
- If this is blocked, the vehicle will idle on the pad and must be moved far enough away from the point of origin.
- During this time, a periodic message about the spawn pad being blocked will be broadcast to the order queue.
- */
case VehicleSpawnControl.ProcessControl.Reminder =>
- trackedOrder
- .collect {
- case entry =>
- if (periodicReminder.isCancelled) {
- trace(s"the pad has become blocked by a ${entry.vehicle.Definition.Name} in its current order")
- periodicReminder = context.system.scheduler.scheduleWithFixedDelay(
- VehicleSpawnControl.periodicReminderTestDelay,
- VehicleSpawnControl.periodicReminderTestDelay,
- self,
- VehicleSpawnControl.ProcessControl.Reminder
- )
- } else {
- BlockedReminder(entry, orders)
- }
- trackedOrder
- }
- .orElse {
- periodicReminder.cancel()
- None
- }
+ evaluateBlockedReminder()
case VehicleSpawnControl.ProcessControl.Flush =>
- periodicReminder.cancel()
+ discontinueCurrentReminder()
orders.foreach { CancelOrder(_, Some("@SVCP_RemovedFromVehicleQueue_Generic")) }
orders = Nil
trackedOrder.foreach {
@@ -259,7 +237,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* `None`, if no order found or submitted
*/
private def ProcessOrder(order: Option[VehicleSpawnPad.VehicleOrder]): Unit = {
- periodicReminder.cancel()
+ discontinueCurrentReminder()
order.collect {
case VehicleSpawnPad.VehicleOrder(driver, vehicle, terminal) =>
val size = orders.size + 1
@@ -324,37 +302,6 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
}
}
- /**
- * na
- * @param blockedOrder the previous order whose vehicle is blocking the spawn pad from operating
- * @param recipients all of the other customers who will be receiving the message
- */
- private def BlockedReminder(blockedOrder: VehicleSpawnControl.Order, recipients: Seq[VehicleSpawnPad.VehicleOrder]): Unit = {
- val user = blockedOrder.vehicle
- .Seats(0).occupant
- .orElse(pad.Zone.GUID(blockedOrder.vehicle.OwnerGuid))
- .orElse(pad.Zone.GUID(blockedOrder.DriverGUID))
- val relevantRecipients: Iterator[VehicleSpawnPad.VehicleOrder] = user match {
- case Some(p: Player) if !p.HasGUID =>
- recipients.iterator
- case Some(_: Player) =>
- (VehicleSpawnPad.VehicleOrder(
- blockedOrder.driver,
- blockedOrder.vehicle,
- null //permissible
- ) +: recipients).iterator //one who took possession of the vehicle
- case _ =>
- recipients.iterator
- }
- recursiveBlockedReminder(
- relevantRecipients,
- if (blockedOrder.vehicle.Health == 0)
- Option("Clear the wreckage.")
- else
- None
- )
- }
-
/**
* Cancel this vehicle order and inform the person who made it, if possible.
* @param entry the order being cancelled
@@ -381,21 +328,136 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
}
}
+ /**
+ * When the vehicle is spawned and added to the pad, it will "occupy" the pad and block it from further action.
+ * During this time, a periodic message about the spawn pad being blocked will be broadcast to the order queue.
+ * The vehicle is also queued to deconstruct in 30s if no one assumes the driver seat.
+ */
+ private def evaluateBlockedReminder(): Unit = {
+ /*
+ Normally, the player who wanted to spawn the vehicle will be automatically put into the driver mount.
+ If this is blocked or aborted, the vehicle will idle on the pad and must be moved far enough away from the point of origin.
+ */
+ trackedOrder
+ .collect {
+ case entry =>
+ if (reminderSeq.isEmpty) {
+ //begin reminder
+ trace(s"the pad has become blocked by a ${entry.vehicle.Definition.Name} in its current order")
+ retimePeriodicReminder(
+ shaveOffFirstElementAndDiffSecondElement(pad.Definition.BlockedReminderMessageDelays)
+ )
+ trackedOrder
+ } else if (reminderSeq.size == 1) {
+ //end reminder
+ standaloneBlockedReminder(
+ VehicleSpawnPad.VehicleOrder(entry.driver, entry.vehicle, null),
+ Some("@PadDeconstruct_Done")
+ )
+ None
+ } else {
+ //continue reminder
+ BlockedReminder(entry, orders)
+ retimePeriodicReminder(
+ shaveOffFirstElementAndDiffSecondElement(reminderSeq)
+ )
+ trackedOrder
+ }
+ }
+ .orElse {
+ discontinueCurrentReminder()
+ None
+ }
+ }
+
+ /**
+ * The periodic reminder will no longer be repeated.
+ * Sequences tied to the periodic reminder should be reset.
+ */
+ private def discontinueCurrentReminder(): Unit = {
+ periodicReminder.cancel()
+ periodicReminder = Default.Cancellable
+ reminderSeq = List()
+ }
+
+ /**
+ * na
+ * @param blockedOrder the previous order whose vehicle is blocking the spawn pad from operating
+ * @param recipients all of the other customers who will be receiving the message
+ */
+ private def BlockedReminder(
+ blockedOrder: VehicleSpawnControl.Order,
+ recipients: Seq[VehicleSpawnPad.VehicleOrder]
+ ): Unit = {
+ //everyone else
+ recursiveBlockedReminder(
+ recipients.iterator,
+ if (blockedOrder.vehicle.Health == 0)
+ Option("The vehicle spawn pad where you placed your order is blocked. Clearing the wreckage ...")
+ else
+ Option("The vehicle spawn pad where you placed your order is blocked.")
+ )
+ //would-be driver
+ blockedOrder.vehicle
+ .Seats(0).occupant
+ .orElse(pad.Zone.GUID(blockedOrder.vehicle.OwnerGuid))
+ .orElse(pad.Zone.GUID(blockedOrder.DriverGUID)) collect {
+ case p: Player if p.isAlive =>
+ standaloneBlockedReminder(
+ VehicleSpawnPad.VehicleOrder(blockedOrder.driver, blockedOrder.vehicle, null),
+ Some(s"@PadDeconstruct_secsA^${reminderSeq.head}~")
+ )
+ }
+ }
+
+ /**
+ * Clip the first entry in a list of numbers and
+ * get the difference between the clipped entry and the next entry.
+ * The clipped-off list will be made to be the new sequence of reminder delays.
+ * @param sequence reminder delay test values
+ * @return difference between first delay and second delay
+ */
+ private def shaveOffFirstElementAndDiffSecondElement(sequence: Seq[Int]): Int = {
+ val startTime = sequence.take(1).headOption.getOrElse(0)
+ val restTimes = sequence.drop(1)
+ val headOfRestTimes = restTimes.headOption.getOrElse(startTime)
+ reminderSeq = restTimes
+ startTime - headOfRestTimes
+ }
+
+ /**
+ * Set a single instance of the "periodic reminder" to this kind of delay.
+ * @param delay how long until the next reminder
+ */
+ private def retimePeriodicReminder(delay: Int): Unit = {
+ periodicReminder = context.system.scheduler.scheduleOnce(
+ delay.seconds,
+ self,
+ VehicleSpawnControl.ProcessControl.Reminder
+ )
+ }
+
@tailrec private final def recursiveBlockedReminder(
iter: Iterator[VehicleSpawnPad.VehicleOrder],
cause: Option[Any]
): Unit = {
if (iter.hasNext) {
- val recipient = iter.next()
- pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
- recipient.player.Name,
- VehicleSpawnPad.Reminders.Blocked,
- cause
- )
+ standaloneBlockedReminder(iter.next(), cause)
recursiveBlockedReminder(iter, cause)
}
}
+ private def standaloneBlockedReminder(
+ entry: VehicleSpawnPad.VehicleOrder,
+ cause: Option[Any]
+ ): Unit = {
+ pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
+ entry.player.Name,
+ VehicleSpawnPad.Reminders.Blocked,
+ cause
+ )
+ }
+
@tailrec private final def recursiveOrderReminder(
iter: Iterator[VehicleSpawnPad.VehicleOrder],
size: Int,
@@ -414,19 +476,16 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
}
object VehicleSpawnControl {
- private final val periodicReminderTestDelay: FiniteDuration = 10 seconds
-
/**
* Control messages for the vehicle spawn process.
*/
+ sealed trait ProcessControlOperation
object ProcessControl {
- sealed trait ProcessControl
-
- case object Flush extends ProcessControl
- case object OrderCancelled extends ProcessControl
- case object GetNewOrder extends ProcessControl
- case object Reminder extends ProcessControl
- case object QueueManagement extends ProcessControl
+ case object Flush extends ProcessControlOperation
+ case object OrderCancelled extends ProcessControlOperation
+ case object GetNewOrder extends ProcessControlOperation
+ case object Reminder extends ProcessControlOperation
+ case object QueueManagement extends ProcessControlOperation
}
/**
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala
index 51c4c4a3b..86b072f12 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/VehicleSpawnPadDefinition.scala
@@ -12,7 +12,6 @@ class VehicleSpawnPadDefinition(objectId: Int) extends AmenityDefinition(objectI
// Different pads require a Z offset to stop vehicles falling through the world after the pad rises from the floor, these values are found in game_objects.adb.lst
private var vehicle_creation_z_offset = 0f
-
// Different pads also require an orientation offset when detaching vehicles from the rails associated with the spawn pad, again in game_objects.adb.lst
// For example: 9754:add_property dropship_pad_doors vehiclecreationzorientoffset 90
// However, it seems these values need to be reversed to turn CCW to CW rotation (e.g. +90 to -90)
@@ -35,6 +34,12 @@ class VehicleSpawnPadDefinition(objectId: Int) extends AmenityDefinition(objectI
* @see `net.psforever.objects.serverobject.pad.process.VehicleSpawnControlRailJack` */
var killBox: (VehicleSpawnPad, Boolean)=>(PlanetSideGameObject, PlanetSideGameObject, Float)=> Boolean =
VehicleSpawnPadDefinition.prepareKillBox(forwardLimit = 0, backLimit = 0, sideLimit = 0, aboveLimit = 0)
+
+ private val blockedReminderMessageDelays: Seq[Int] = Seq(30, 23, 15, 8, 0)
+
+ def VehicleCreationDeconstructTime: Int = blockedReminderMessageDelays.head
+
+ def BlockedReminderMessageDelays: Seq[Int] = blockedReminderMessageDelays
}
object VehicleSpawnPadDefinition {
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala
index 516b904b2..67c8d6e61 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlFinalClearance.scala
@@ -50,26 +50,35 @@ class VehicleSpawnControlFinalClearance(pad: VehicleSpawnPad) extends VehicleSpa
self ! VehicleSpawnControlFinalClearance.Test(order)
case test @ VehicleSpawnControlFinalClearance.Test(entry) =>
- //the vehicle has an initial decay of 30s in which time it needs to be mounted
+ //the vehicle has an initial decay in which time it needs to be mounted
//once mounted, it will complain to the current driver that it is blocking the spawn pad
//no time limit exists for that state
val vehicle = entry.vehicle
- if (Vector3.DistanceSquared(vehicle.Position, pad.Position) > 144) { //12m away from pad
- trace("pad cleared")
- pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad)
- context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
- } else if (vehicle.Destroyed) {
- trace("clearing the pad of vehicle wreckage")
+ val distanceTest = Vector3.DistanceSquared(vehicle.Position, pad.Position) > 144 //12m away from pad
+ if (vehicle.Destroyed) {
+ trace("pad cleared of vehicle wreckage")
+ val delay = if (distanceTest) { 2000 } else { 5000 }
VehicleSpawnControl.DisposeVehicle(vehicle, pad.Zone)
- context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
+ temp = context.system.scheduler.scheduleOnce(delay.milliseconds, self, VehicleSpawnControlFinalClearance.NextOrder)
+ } else if (distanceTest) {
+ trace("pad cleared")
+ val delay = if (vehicle.Seats.head._2.occupant.isEmpty) { 4500 } else { 2000 }
+ temp = context.system.scheduler.scheduleOnce(delay.milliseconds, self, VehicleSpawnControlFinalClearance.NextOrder)
} else {
- temp = context.system.scheduler.scheduleOnce(2000 milliseconds, self, test)
+ //retry test
+ temp = context.system.scheduler.scheduleOnce(delay = 2000.milliseconds, self, test)
}
- case _ => ;
+ case VehicleSpawnControlFinalClearance.NextOrder =>
+ pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad)
+ context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder
+
+ case _ => ()
}
}
object VehicleSpawnControlFinalClearance {
- private final case class Test(entry: VehicleSpawnControl.Order)
+ private case class Test(entry: VehicleSpawnControl.Order)
+
+ private case object NextOrder
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala
index bf14a8cb3..520d0c1ec 100644
--- a/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/pad/process/VehicleSpawnControlSeatDriver.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad.process
-import akka.actor.Props
+import akka.actor.{ActorRef, Props}
import net.psforever.objects.{Default, Vehicle}
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
@@ -25,7 +25,7 @@ import scala.concurrent.duration._
class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
def LogId = "-usher"
- val vehicleOverride = context.actorOf(
+ val vehicleOverride: ActorRef = context.actorOf(
Props(classOf[VehicleSpawnControlServerVehicleOverride], pad),
s"${context.parent.path.name}-override"
)
@@ -43,7 +43,7 @@ class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnCo
val driver = entry.driver
val vehicle = entry.vehicle
//avoid unattended vehicle blocking the pad; user should mount (and does so normally) to reset decon timer
- vehicle.Actor ! Vehicle.Deconstruct(Some(30 seconds))
+ vehicle.Actor ! Vehicle.Deconstruct(Some(pad.Definition.VehicleCreationDeconstructTime.seconds))
if (VehicleSpawnControl.validateOrderCredentials(pad, driver, vehicle).isEmpty) {
trace("driver to be made seated in vehicle")
pad.Zone.VehicleEvents ! VehicleSpawnPad.StartPlayerSeatedInVehicle(driver.Name, vehicle, pad)
@@ -67,7 +67,7 @@ class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnCo
case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
context.parent ! msg
- case _ => ;
+ case _ => ()
}
}
diff --git a/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala b/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala
index 05a01682f..6b7b3f958 100644
--- a/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala
+++ b/src/main/scala/net/psforever/objects/serverobject/structures/participation/MajorFacilityHackParticipation.scala
@@ -5,12 +5,16 @@ import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.sourcing.{PlayerSource, UniquePlayer}
import net.psforever.objects.zones.{HotSpotInfo, ZoneHotSpotProjector}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
-import net.psforever.types.{PlanetSideEmpire, Vector3}
+import net.psforever.types.{ChatMessageType, PlanetSideEmpire, Vector3}
import net.psforever.util.Config
import akka.pattern.ask
import akka.util.Timeout
+import net.psforever.objects.Player
import net.psforever.objects.avatar.scoring.Kill
+import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.zones.exp.ToDatabase
+import net.psforever.packet.game.ChatMsg
+import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import scala.collection.mutable
import scala.concurrent.duration._
@@ -31,6 +35,20 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
updateHotSpotInfoOverTime()
updateTime(now)
}
+ building.CaptureTerminal
+ .map(_.HackedBy)
+ .collect {
+ case Some(info@Hackable.HackInfo(_, _, start, length, _))
+ if building.NtuLevel == 0 && {
+ val approximateHackTimeRemaining = math.max(0, start + length - System.currentTimeMillis())
+ approximateHackTimeRemaining <= 300.seconds.toMillis && approximateHackTimeRemaining > 295.seconds.toMillis
+ } =>
+ MajorFacilityHackParticipation.warningMessageForHackOccupiers(
+ building,
+ info,
+ ChatMsg(ChatMessageType.UNK_227, "@FacilityRequiresResourcesForHackCriticalWarning")
+ )
+ }
lastInfoRequest = now
}
@@ -287,8 +305,8 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
if (isResecured) {
hackerScore
} else {
- val flagCarrierScore = flagCarrier.map (p => List((p.CharId, 0L, "llu"))).getOrElse(Nil)
- if (playersInSoi.exists(_.CharId == hackerId) && !flagCarrierScore.exists { case (charId, _,_) => charId == hackerId }) {
+ val flagCarrierScore = flagCarrier.map(p => List((p.CharId, 0L, "llu"))).getOrElse(Nil)
+ if (playersInSoi.exists(_.CharId == hackerId) && !flagCarrierScore.exists { case (charId, _, _) => charId == hackerId }) {
hackerScore ++ flagCarrierScore
} else {
flagCarrierScore
@@ -326,3 +344,61 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
.getOrElse(list)
}
}
+
+object MajorFacilityHackParticipation {
+ /**
+ * Dispatch a message to clients affected by some change.
+ * Establish the hack information by referencing the capture terminal.
+ * @param building building entity
+ * @param msg message to send to affected clients
+ */
+ def warningMessageForHackOccupiers(
+ building: Building,
+ msg: ChatMsg
+ ): Unit = {
+ building
+ .CaptureTerminal
+ .flatMap(_.HackedBy)
+ .foreach { hackedInfo =>
+ warningMessageForHackOccupiers(building, hackedInfo, msg)
+ }
+ }
+
+ /**
+ * Dispatch a message to clients affected by some change.
+ * Select individuals belonging to the hacking faction to be targets for the message.
+ * @param building building entity
+ * @param hackedInfo confirmed information about the hack state
+ * @param msg message to send to affected clients
+ */
+ def warningMessageForHackOccupiers(
+ building: Building,
+ hackedInfo: Hackable.HackInfo,
+ msg: ChatMsg
+ ): Unit = {
+ val hackerFaction = hackedInfo.hackerFaction
+ warningMessageForHackOccupiers(
+ building,
+ building.PlayersInSOI.filter(_.Faction == hackerFaction),
+ msg
+ )
+ }
+
+ /**
+ * Dispatch a message to clients affected by some change.
+ * @param building building entity
+ * @param targets affected clients by player
+ * @param msg message to send to affected clients
+ */
+ private def warningMessageForHackOccupiers(
+ building: Building,
+ targets: Iterable[Player],
+ msg: ChatMsg
+ ): Unit = {
+ val events = building.Zone.LocalEvents
+ val message = LocalAction.SendResponse(msg)
+ targets.foreach { player =>
+ events ! LocalServiceMessage(player.Name, message)
+ }
+ }
+}
diff --git a/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala b/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala
index 14faf3d90..85d567390 100644
--- a/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/control/DeployingVehicleControl.scala
@@ -1,6 +1,7 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles.control
+import akka.actor.ActorRef
import net.psforever.objects._
import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject
import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior}
@@ -58,7 +59,7 @@ class DeployingVehicleControl(vehicle: Vehicle)
* Even when disabled, the vehicle can be made to undeploy.
*/
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
- TryUndeployStateChange(DriveState.Undeploying)
+ TryUndeployStateChange(DriveState.Undeploying, self)
super.PrepareForDisabled(kickPassengers)
}
@@ -77,9 +78,10 @@ class DeployingVehicleControl(vehicle: Vehicle)
override def DeploymentAction(
obj: DeploymentObject,
state: DriveState.Value,
- prevState: DriveState.Value
+ prevState: DriveState.Value,
+ replyTo: ActorRef
): DriveState.Value = {
- val out = super.DeploymentAction(obj, state, prevState)
+ val out = super.DeploymentAction(obj, state, prevState, replyTo)
Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
specificResponseToDeployment(state)
out
@@ -90,9 +92,10 @@ class DeployingVehicleControl(vehicle: Vehicle)
override def UndeploymentAction(
obj: DeploymentObject,
state: DriveState.Value,
- prevState: DriveState.Value
+ prevState: DriveState.Value,
+ replyTo: ActorRef
): DriveState.Value = {
- val out = if (decaying) state else super.UndeploymentAction(obj, state, prevState)
+ val out = if (decaying) state else super.UndeploymentAction(obj, state, prevState, replyTo)
Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
specificResponseToUndeployment(state)
out
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 8a5d48a2e..66b6f2700 100644
--- a/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/control/VehicleControl.scala
@@ -117,7 +117,8 @@ class VehicleControl(vehicle: Vehicle)
case Vehicle.Ownership(Some(player)) =>
GainOwnership(player)
- case Mountable.TryMount(user, mountPoint) if vehicle.DeploymentState == DriveState.AutoPilot =>
+ case Mountable.TryMount(user, mountPoint)
+ if vehicle.DeploymentState == DriveState.AutoPilot =>
sender() ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, mountPoint))
case msg @ Mountable.TryMount(player, mount_point) =>
@@ -125,19 +126,23 @@ class VehicleControl(vehicle: Vehicle)
mountCleanup(mount_point, player)
// Issue 1133. Todo: There may be a better way to address the issue?
- case Mountable.TryDismount(user, seat_num, _) if GlobalDefinitions.isFlightVehicle(vehicle.Definition) &&
+ case Mountable.TryDismount(user, seat_num, bailType) if GlobalDefinitions.isFlightVehicle(vehicle.Definition) &&
(vehicle.History.find { entry => entry.isInstanceOf[SpawningActivity] } match {
case Some(entry) if System.currentTimeMillis() - entry.time < 3000L => true
case _ => false
}) =>
- sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(vehicle, seat_num))
+ sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(vehicle, seat_num, bailType))
- case Mountable.TryDismount(user, seat_num, _) if !GlobalDefinitions.isFlightVehicle(vehicle.Definition) &&
+ case Mountable.TryDismount(user, seat_num, bailType) if !GlobalDefinitions.isFlightVehicle(vehicle.Definition) &&
(vehicle.History.find { entry => entry.isInstanceOf[SpawningActivity] } match {
case Some(entry) if System.currentTimeMillis() - entry.time < 8500L => true
case _ => false
}) =>
- sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(vehicle, seat_num))
+ sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(vehicle, seat_num, bailType))
+
+ case Mountable.TryDismount(user, seat_num, bailType)
+ if vehicle.DeploymentState == DriveState.AutoPilot =>
+ sender() ! Mountable.MountMessages(user, Mountable.CanNotDismount(vehicle, seat_num, bailType))
case msg @ Mountable.TryDismount(player, seat_num, _) =>
dismountBehavior.apply(msg)
@@ -601,9 +606,9 @@ class VehicleControl(vehicle: Vehicle)
c
}
}
- watery.doInteractingWithTargets(player, percentage, watery.Condition.map(_.body).get, List(player))
+ WithWater.doInteractingWithTargets(player, percentage, watery.Condition.map(_.body).get, List(player))
case watery: WithWater if watery.Condition.map(_.state).contains(OxygenState.Recovery) =>
- watery.stopInteractingWithTargets(
+ WithWater.stopInteractingWithTargets(
player,
Watery.recoveringFromWater(vehicle, watery)._3,
watery.Condition.map(_.body).get,
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 39fd899bf..01208856a 100644
--- a/src/main/scala/net/psforever/objects/vehicles/interaction/WithWater.scala
+++ b/src/main/scala/net/psforever/objects/vehicles/interaction/WithWater.scala
@@ -1,37 +1,71 @@
// Copyright (c) 2024 PSForever
package net.psforever.objects.vehicles.interaction
-import net.psforever.objects.{GlobalDefinitions, Vehicle}
+import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, Vehicle}
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment}
import net.psforever.objects.serverobject.environment.interaction.common.Watery
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
-import net.psforever.objects.serverobject.environment.{PieceOfEnvironment, interaction}
+import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment, interaction}
import net.psforever.objects.vehicles.control.VehicleControl
import net.psforever.objects.zones.InteractsWithZone
import net.psforever.types.OxygenState
+import scala.annotation.unused
import scala.concurrent.duration._
class WithWater()
extends InteractionWith
with Watery {
+ /** do this every time we're in sufficient contact with water */
+ private var doInteractingWithBehavior: (InteractsWithZone, PieceOfEnvironment, Option[Any]) => Unit = wadingBeforeDrowning
+
/**
- * Water causes vehicles to become disabled if they dive off too far, too deep.
- * Flying vehicles do not display progress towards being waterlogged.
- * They just disable outright.
- * @param obj the target
- * @param body the environment
- * @param data additional interaction information, if applicable
- */
+ * Water is wet.
+ * @param obj the target
+ * @param body the environment
+ */
def doInteractingWith(
obj: InteractsWithZone,
body: PieceOfEnvironment,
data: Option[Any]
): Unit = {
+ depth = math.max(0f, body.collision.altitude - obj.Position.z)
+ doInteractingWithBehavior(obj, body, data)
+ obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = 500 milliseconds, obj.Actor, interaction.InteractingWithEnvironment(body, Some("wading")))
+ }
+
+ /**
+ * Wading only happens while the vehicle's wheels are mostly above the water.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def wadingBeforeDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ //we're already "wading", let's see if we're drowning
+ if (depth >= GlobalDefinitions.MaxDepth(obj)) {
+ //drowning
+ beginDrowning(obj, body, data)
+ }
+ }
+
+ /**
+ * Too much water causes players to slowly suffocate.
+ * When they (finally) drown, they will die.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def beginDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ @unused data: Option[Any]
+ ): Unit = {
obj match {
case vehicle: Vehicle =>
- val (effect: Boolean, time: Long, percentage: Float) = {
+ val (effect, time, percentage): (Boolean, Long, Float) = {
val (a, b, c) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime)
if (a && GlobalDefinitions.isFlightVehicle(vehicle.Definition)) {
(true, 0L, 0f) //no progress bar
@@ -42,40 +76,94 @@ class WithWater()
if (effect) {
condition = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage))
waterInteractionTime = System.currentTimeMillis() + time
- obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, VehicleControl.Disable(true))
- doInteractingWithTargets(
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
+ obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, VehicleControl.Disable(true))
+ WithWater.doInteractingWithTargets(
obj,
percentage,
body,
vehicle.Seats.values.flatMap(_.occupants).filter(p => p.isAlive && (p.Zone eq obj.Zone))
)
+ doInteractingWithBehavior = drowning
}
case _ => ()
}
}
/**
- * 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 = {
+ * Too much water causes vehicles to slowly disable.
+ * When fully waterlogged, the vehicle is completely immobile.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def drowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ //test if player ever gets head above the water level
+ if (depth < GlobalDefinitions.MaxDepth(obj)) {
+ val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
+ //switch to recovery
+ if (percentage > 0) {
+ recoverFromDrowning(obj, body, data, effect, time, percentage)
+ doInteractingWithBehavior = recovering
+ }
+ }
+ }
+
+ /**
+ * When out of water, the vehicle is no longer being waterlogged.
+ * It does have to endure a recovery period to get back to normal, though.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def recoverFromDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ val state = condition.map(_.state)
+ if (state.contains(OxygenState.Suffocation)) {
+ //set up for recovery
+ val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
+ if (percentage < 99f) {
+ //we're not too far gone
+ recoverFromDrowning(obj, body, data, effect, time, percentage)
+ }
+ doInteractingWithBehavior = recovering
+ } else {
+ doInteractingWithBehavior = wadingBeforeDrowning
+ }
+ }
+
+ /**
+ * When out of water, the vehicle is no longer being waterlogged.
+ * It does have to endure a recovery period to get back to normal, though.
+ * @param obj the target
+ * @param body the environment
+ * @param effect na
+ * @param time current time until completion of the next effect
+ * @param percentage value to display in the drowning UI progress bar
+ */
+ private def recoverFromDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ @unused data: Option[Any],
+ effect: Boolean,
+ time: Long,
+ percentage: Float
+ ): Unit = {
obj match {
case vehicle: Vehicle =>
- val (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(OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage))
+ condition = Some(cond)
waterInteractionTime = System.currentTimeMillis() + time
- obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
- stopInteractingWithTargets(
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
+ obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
+ //inform the players
+ WithWater.stopInteractingWithTargets(
obj,
percentage,
body,
@@ -86,51 +174,108 @@ class WithWater()
}
}
+ /**
+ * The recovery period is much faster than the waterlogging process.
+ * Check for when the vehicle fully recovers,
+ * and that the vehicle does not regress back to waterlogging.
+ * @param obj the target
+ * @param body the environment
+ */
+ def recovering(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ lazy val state = condition.map(_.state)
+ if (depth >= GlobalDefinitions.MaxDepth(obj)) {
+ //go back to drowning
+ beginDrowning(obj, body, data)
+ } else if (state.contains(OxygenState.Recovery)) {
+ //check recovery conditions
+ val (_, _, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
+ if (percentage < 1f) {
+ doInteractingWithBehavior = wadingBeforeDrowning
+ }
+ }
+ }/**
+ * When out of water, the vehicle no longer risks becoming disabled.
+ * It does have to endure a recovery period to get back to full dehydration
+ * Flying vehicles are exempt from this process due to the abrupt disability they experience.
+ * @param obj the target
+ * @param body the environment
+ */
+ override def stopInteractingWith(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ val cond = condition.map(_.state)
+ if (cond.contains(OxygenState.Suffocation)) {
+ //go from suffocating to recovery
+ recoverFromDrowning(obj, body, data)
+ } else if (cond.isEmpty) {
+ //neither suffocating nor recovering, so just reset everything
+ recoverFromInteracting(obj)
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(attribute)
+ waterInteractionTime = 0L
+ depth = 0f
+ condition = None
+ doInteractingWithBehavior = wadingBeforeDrowning
+ }
+ }
+
override def recoverFromInteracting(obj: InteractsWithZone): Unit = {
super.recoverFromInteracting(obj)
if (condition.exists(_.state == OxygenState.Suffocation)) {
stopInteractingWith(obj, condition.map(_.body).get, None)
}
- waterInteractionTime = 0L
condition = None
}
+}
+
+object WithWater {
+ /** special environmental trait to queue actions independent from the primary wading test */
+ case object WaterAction extends EnvironmentTrait {
+ override def canInteractWith(obj: PlanetSideGameObject): Boolean = false
+ override def testingDepth(obj: PlanetSideGameObject): Float = Float.PositiveInfinity
+ }
/**
- * Tell the given targets that water causes vehicles to become disabled if they dive off too far, too deep.
- * @see `InteractingWithEnvironment`
- * @see `OxygenState`
- * @see `OxygenStateTarget`
- * @param obj the target
- * @param percentage the progress bar completion state
- * @param body the environment
- * @param targets recipients of the information
- */
+ * Tell the given targets that water causes vehicles to become disabled if they dive off too far, too deep.
+ * @see `InteractingWithEnvironment`
+ * @see `OxygenState`
+ * @see `OxygenStateTarget`
+ * @param obj the target
+ * @param percentage the progress bar completion state
+ * @param body the environment
+ * @param targets recipients of the information
+ */
def doInteractingWithTargets(
- obj: PlanetSideServerObject,
- percentage: Float,
- body: PieceOfEnvironment,
- targets: Iterable[PlanetSideServerObject]
- ): Unit = {
+ obj: PlanetSideServerObject,
+ percentage: Float,
+ body: PieceOfEnvironment,
+ targets: Iterable[PlanetSideServerObject]
+ ): Unit = {
val state = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage))
targets.foreach(_.Actor ! interaction.InteractingWithEnvironment(body, state))
}
/**
- * Tell the given targets that, when out of water, the vehicle no longer risks becoming disabled.
- * @see `EscapeFromEnvironment`
- * @see `OxygenState`
- * @see `OxygenStateTarget`
- * @param obj the target
- * @param percentage the progress bar completion state
- * @param body the environment
- * @param targets recipients of the information
- */
+ * Tell the given targets that, when out of water, the vehicle no longer risks becoming disabled.
+ * @see `EscapeFromEnvironment`
+ * @see `OxygenState`
+ * @see `OxygenStateTarget`
+ * @param obj the target
+ * @param percentage the progress bar completion state
+ * @param body the environment
+ * @param targets recipients of the information
+ */
def stopInteractingWithTargets(
- obj: PlanetSideServerObject,
- percentage: Float,
- body: PieceOfEnvironment,
- targets: Iterable[PlanetSideServerObject]
- ): Unit = {
+ obj: PlanetSideServerObject,
+ percentage: Float,
+ body: PieceOfEnvironment,
+ targets: Iterable[PlanetSideServerObject]
+ ): Unit = {
val state = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage))
targets.foreach(_.Actor ! interaction.EscapeFromEnvironment(body, state))
}
diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala
index 07c9ed581..4a44e36d6 100644
--- a/src/main/scala/net/psforever/objects/zones/Zone.scala
+++ b/src/main/scala/net/psforever/objects/zones/Zone.scala
@@ -288,7 +288,7 @@ class Zone(val id: String, val map: ZoneMap, zoneNumber: Int) {
* @return synchronized reference to the globally unique identifier system
*/
def GUID(hub: NumberPoolHub): Boolean = {
- if (actor == Default.typed.Actor && guid.Pools.values.foldLeft(0)(_ + _.Count) == 0) {
+ if (!zoneInitialized.isCompleted && guid.Pools.values.foldLeft(0)(_ + _.Count) == 0) {
import org.fusesource.jansi.Ansi.Color.RED
import org.fusesource.jansi.Ansi.ansi
println(
diff --git a/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala b/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
index 3c4a92a53..f5c0197b7 100644
--- a/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
+++ b/src/main/scala/net/psforever/objects/zones/ZoneDeployableActor.scala
@@ -9,6 +9,9 @@ import net.psforever.objects.serverobject.deploy.Interference
import net.psforever.objects.sourcing.ObjectSource
import net.psforever.objects.vehicles.MountedWeapons
import net.psforever.objects.vital.SpawningActivity
+import net.psforever.packet.game.ChatMsg
+import net.psforever.services.local.{LocalAction, LocalServiceMessage}
+import net.psforever.types.ChatMessageType
import scala.annotation.tailrec
import scala.collection.mutable
@@ -103,7 +106,13 @@ object ZoneDeployableActor {
): Boolean = {
val position = obj.Position
deployableList.find(_ eq obj) match {
- case None if Interference.Test(zone, obj).isEmpty =>
+ case _ if Interference.Test(zone, obj).nonEmpty =>
+ zone.LocalEvents ! LocalServiceMessage(
+ obj.OwnerName.getOrElse(""),
+ LocalAction.SendResponse(ChatMsg(ChatMessageType.UNK_227, "@nomove_intersecting"))
+ ) //may not be the correct message but is sufficient at explaining why the deployable can not be built
+ false
+ case None =>
deployableList += obj
zone.actor ! ZoneActor.AddToBlockMap(obj, position)
true
diff --git a/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala b/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
index c1b7780be..4e05d85d3 100644
--- a/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
+++ b/src/main/scala/net/psforever/objects/zones/ZoneVehicleActor.scala
@@ -3,11 +3,14 @@ package net.psforever.objects.zones
import akka.actor.Actor
import net.psforever.actors.zone.ZoneActor
-import net.psforever.objects.definition.VehicleDefinition
+import net.psforever.objects.definition.{ObjectDefinition, VehicleDefinition}
import net.psforever.objects.serverobject.deploy.{Deployment, Interference}
import net.psforever.objects.vital.InGameHistory
import net.psforever.objects.{Default, Vehicle}
-import net.psforever.types.{DriveState, PlanetSideEmpire, Vector3}
+import net.psforever.packet.game.ChatMsg
+import net.psforever.services.Service
+import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
+import net.psforever.types.{ChatMessageType, DriveState, PlanetSideEmpire, Vector3}
import scala.annotation.tailrec
import scala.collection.mutable
@@ -66,15 +69,18 @@ class ZoneVehicleActor(
sender() ! Zone.Vehicle.CanNotDespawn(zone, vehicle, "can not find")
}
- case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState)
- if toDeployState == DriveState.Deploying &&
- (ZoneVehicleActor.temporaryInterferenceTest(vehicle, temporaryInterference) || Interference.Test(zone, vehicle).nonEmpty) =>
- sender() ! Zone.Vehicle.CanNotDeploy(zone, vehicle, toDeployState, "blocked by a nearby entity")
-
- case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState)
- if toDeployState == DriveState.Deploying =>
- tryAddToInterferenceField(vehicle.Position, vehicle.Faction, vehicle.Definition)
- vehicle.Actor.tell(Deployment.TryDeploymentChange(toDeployState), sender())
+ case Zone.Vehicle.TryDeploymentChange(vehicle, DriveState.Deploying) =>
+ if (ZoneVehicleActor.ReportOnInterferenceResults(
+ zone,
+ vehicle,
+ ZoneVehicleActor.temporaryInterferenceTest(vehicle, temporaryInterference) ++
+ Interference.Test(zone, vehicle).map(_.Definition)
+ )) {
+ sender() ! Zone.Vehicle.CanNotDeploy(zone, vehicle, DriveState.Deploying, "blocked by a nearby entity")
+ } else {
+ tryAddToInterferenceField(vehicle.Position, vehicle.Faction, vehicle.Definition)
+ vehicle.Actor.tell(Deployment.TryDeploymentChange(DriveState.Deploying), sender())
+ }
case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState) =>
vehicle.Actor.tell(Deployment.TryDeploymentChange(toDeployState), sender())
@@ -83,7 +89,19 @@ class ZoneVehicleActor(
case Zone.Vehicle.CanNotDespawn(_, _, _) => ()
- case Zone.Vehicle.CanNotDeploy(_, vehicle, _, reason) => ()
+ case Zone.Vehicle.CanNotDeploy(_, vehicle, DriveState.Deploying, reason) =>
+ ZoneVehicleActor.ReportOnInterferenceResults(
+ zone,
+ vehicle,
+ ZoneVehicleActor.temporaryInterferenceTest(vehicle, temporaryInterference) ++
+ Interference.Test(zone, vehicle).map(_.Definition)
+ )
+ val pos = vehicle.Position
+ val driverMoniker = vehicle.Seats.headOption.flatMap(_._2.occupant).map(_.Name).getOrElse("Driver")
+ log.warn(s"$driverMoniker's ${vehicle.Definition.Name} can not deploy in ${zone.id} because $reason")
+ temporaryInterference = temporaryInterference.filterNot(_._1 == pos)
+
+ case Zone.Vehicle.CanNotDeploy(_, vehicle, _, reason) =>
val pos = vehicle.Position
val driverMoniker = vehicle.Seats.headOption.flatMap(_._2.occupant).map(_.Name).getOrElse("Driver")
log.warn(s"$driverMoniker's ${vehicle.Definition.Name} can not deploy in ${zone.id} because $reason")
@@ -133,22 +151,54 @@ object ZoneVehicleActor {
private def temporaryInterferenceTest(
vehicle: Vehicle,
existingInterferences: Seq[(Vector3, PlanetSideEmpire.Value, VehicleDefinition)]
- ): Boolean = {
+ ): Seq[VehicleDefinition] = {
val vPosition = vehicle.Position
val vFaction = vehicle.Faction
val vDefinition = vehicle.Definition
if (vDefinition.interference eq Interference.AllowAll) {
- false
+ Nil
} else {
existingInterferences
.collect { case (p, faction, d) if faction == vFaction => (p, d) }
- .exists { case (position, definition) =>
+ .filter { case (position, definition) =>
val interference = definition.interference
(interference ne Interference.AllowAll) && {
lazy val distanceSq = Vector3.DistanceSquared(position, vPosition)
definition == vDefinition && distanceSq < interference.main * interference.main
}
}
+ .map(_._2)
+ }
+ }
+
+ private def ReportOnInterferenceResults(
+ zone: Zone,
+ vehicle: Vehicle,
+ reportedInterferenceList: Seq[ObjectDefinition]
+ ): Boolean = {
+ if (reportedInterferenceList.nonEmpty) {
+ reportedInterferenceList
+ .find(_.isInstanceOf[VehicleDefinition])
+ .map { definition => s"@nodeploy_${definition.Name}" }
+ .orElse {
+ val sharedGroupId = vehicle.Definition.interference.sharedGroupId
+ if (sharedGroupId > 0) {
+ reportedInterferenceList
+ .find(_.interference.sharedGroupId == sharedGroupId)
+ .map(_ => "@nodeploy_sharedinterference")
+ } else {
+ None
+ }
+ }
+ .foreach { msg =>
+ zone.VehicleEvents ! VehicleServiceMessage(
+ vehicle.Seats.headOption.flatMap(_._2.occupant).map(_.Name).getOrElse(""),
+ VehicleAction.SendResponse(Service.defaultPlayerGUID, ChatMsg(ChatMessageType.UNK_227, msg))
+ )
+ }
+ true
+ } else {
+ false
}
}
}
diff --git a/src/main/scala/net/psforever/packet/game/OxygenStateMessage.scala b/src/main/scala/net/psforever/packet/game/OxygenStateMessage.scala
index d80f16d77..0a7629a70 100644
--- a/src/main/scala/net/psforever/packet/game/OxygenStateMessage.scala
+++ b/src/main/scala/net/psforever/packet/game/OxygenStateMessage.scala
@@ -134,7 +134,17 @@ object OxygenStateMessage extends Marshallable[OxygenStateMessage] {
204.8f,
11
) :: //hackish: 2^11 == 2047, so it should be 204.7; but, 204.8 allows decode == encode
- OxygenState.codec
+ bool.xmap[OxygenState](
+ {
+ case false => OxygenState.Recovery
+ case true => OxygenState.Suffocation
+ },
+ {
+ case OxygenState.Recovery => false
+ case OxygenState.Suffocation => true
+ case _ => false
+ }
+ )
).as[DrowningTarget]
implicit val codec: Codec[OxygenStateMessage] = (
diff --git a/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala b/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala
index b4b2939ed..1be70996c 100644
--- a/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala
+++ b/src/main/scala/net/psforever/packet/game/objectcreate/ObjectClass.scala
@@ -1216,11 +1216,11 @@ object ObjectClass {
case ObjectClass.advanced_ace => DroppedItemData(HandheldData.codec, "advanced ace")
case ObjectClass.router_telepad => DroppedItemData(HandheldData.codec, "router telepad")
case ObjectClass.boomer_trigger => DroppedItemData(HandheldData.codec, "boomer trigger")
- case ObjectClass.boomer => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable")
- case ObjectClass.he_mine => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable")
- case ObjectClass.jammer_mine => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable")
- case ObjectClass.motionalarmsensor => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable")
- case ObjectClass.sensor_shield => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable")
+ case ObjectClass.boomer => ConstructorData(SmallDeployableData.codec, "ace deployable")
+ case ObjectClass.he_mine => ConstructorData(SmallDeployableData.codec, "ace deployable")
+ case ObjectClass.jammer_mine => ConstructorData(SmallDeployableData.codec, "ace deployable")
+ case ObjectClass.motionalarmsensor => ConstructorData(SmallDeployableData.codec, "ace deployable")
+ case ObjectClass.sensor_shield => ConstructorData(SmallDeployableData.codec, "ace deployable")
case ObjectClass.spitfire_aa => ConstructorData(SmallTurretData.codec, "small turret")
case ObjectClass.spitfire_cloaked => ConstructorData(SmallTurretData.codec, "small turret")
case ObjectClass.spitfire_turret => ConstructorData(SmallTurretData.codec, "small turret")
diff --git a/src/main/scala/net/psforever/packet/game/objectcreate/SmallDeployableData.scala b/src/main/scala/net/psforever/packet/game/objectcreate/SmallDeployableData.scala
new file mode 100644
index 000000000..fb08485b1
--- /dev/null
+++ b/src/main/scala/net/psforever/packet/game/objectcreate/SmallDeployableData.scala
@@ -0,0 +1,32 @@
+// Copyright (c) 2024 PSForever
+package net.psforever.packet.game.objectcreate
+
+import net.psforever.packet.Marshallable
+import scodec.{Attempt, Codec, Err}
+import scodec.codecs._
+import shapeless.{::, HNil}
+
+final case class SmallDeployableData(deploy: CommonFieldDataWithPlacement) extends ConstructorData {
+ override def bitsize: Long = {
+ deploy.bitsize + 1
+ }
+}
+
+object SmallDeployableData extends Marshallable[SmallDeployableData] {
+ implicit val codec: Codec[SmallDeployableData] = (
+ ("deploy" | CommonFieldDataWithPlacement.codec) ::
+ ignore(size = 1)
+ ).exmap[SmallDeployableData](
+ {
+ case deploy :: _ :: HNil =>
+ Attempt.successful(SmallDeployableData(deploy))
+
+ case data =>
+ Attempt.failure(Err(s"invalid small deployable data format - $data"))
+ },
+ {
+ case SmallDeployableData(deploy) =>
+ Attempt.successful(deploy :: () :: HNil)
+ }
+ )
+}
diff --git a/src/main/scala/net/psforever/services/local/LocalService.scala b/src/main/scala/net/psforever/services/local/LocalService.scala
index 72aa05aa8..91d900d3b 100644
--- a/src/main/scala/net/psforever/services/local/LocalService.scala
+++ b/src/main/scala/net/psforever/services/local/LocalService.scala
@@ -120,6 +120,8 @@ class LocalService(zone: Zone) extends Actor {
hackCapturer ! HackCaptureActor.StartCaptureTerminalHack(target, zone, 0, 8L)
case LocalAction.LluCaptured(llu) =>
hackCapturer ! HackCaptureActor.FlagCaptured(llu)
+ case LocalAction.LluLost(llu) =>
+ hackCapturer ! HackCaptureActor.FlagLost(llu)
case LocalAction.LluSpawned(player_guid, llu) =>
// Forward to all clients to create object locally
diff --git a/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala b/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala
index e6d70f3af..de4545cc5 100644
--- a/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala
+++ b/src/main/scala/net/psforever/services/local/LocalServiceMessage.scala
@@ -61,6 +61,7 @@ object LocalAction {
final case class ResecureCaptureTerminal(target: CaptureTerminal, hacker: PlayerSource) extends Action
final case class StartCaptureTerminalHack(target: CaptureTerminal) extends Action
final case class LluCaptured(llu: CaptureFlag) extends Action
+ final case class LluLost(llu: CaptureFlag) extends Action
final case class LluSpawned(player_guid: PlanetSideGUID, llu: CaptureFlag) extends Action
final case class LluDespawned(player_guid: PlanetSideGUID, guid: PlanetSideGUID, position: Vector3) extends Action
diff --git a/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala b/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala
index 91bf2499f..df0d5f129 100644
--- a/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala
+++ b/src/main/scala/net/psforever/services/local/support/CaptureFlagManager.scala
@@ -3,12 +3,14 @@ package net.psforever.services.local.support
import akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.login.WorldSession
-import net.psforever.objects.{Default, Player}
+import net.psforever.objects.{Default, PlanetSideGameObject, Player}
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow}
+import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait}
+import net.psforever.objects.serverobject.environment.interaction.InteractWithEnvironment
import net.psforever.objects.serverobject.llu.CaptureFlag
-import net.psforever.objects.serverobject.structures.Building
+import net.psforever.objects.serverobject.structures.{Building, WarpGate}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
-import net.psforever.objects.zones.Zone
+import net.psforever.objects.zones.{InteractsWithZone, Zone}
import net.psforever.packet.game._
import net.psforever.services.{Service, ServiceManager}
import net.psforever.services.ServiceManager.{Lookup, LookupResult}
@@ -22,12 +24,13 @@ import scala.concurrent.duration.DurationInt
* Responsible for handling capture flag related lifecycles
*/
class CaptureFlagManager(zone: Zone) extends Actor {
+ import CaptureFlagManager.CaptureFlagEntry
private[this] val log = org.log4s.getLogger(self.path.name)
private var galaxyService: ActorRef = ActorRef.noSender
private var mapUpdateTick: Cancellable = Default.Cancellable
/** An internally tracked list of cached flags, to avoid querying the amenity owner each second for flag lookups. */
- private var flags: List[CaptureFlag] = Nil
+ private var flags: List[CaptureFlagEntry] = Nil
ServiceManager.serviceManager ! Lookup("galaxy")
@@ -39,7 +42,6 @@ class CaptureFlagManager(zone: Zone) extends Actor {
DoMapUpdate()
case CaptureFlagManager.SpawnCaptureFlag(capture_terminal, target, hackingFaction) =>
- val zone = capture_terminal.Zone
val socket = capture_terminal.Owner.asInstanceOf[Building].GetFlagSocket.get
// Override CC message when looked at
zone.LocalEvents ! LocalServiceMessage(
@@ -62,16 +64,16 @@ class CaptureFlagManager(zone: Zone) extends Actor {
socket.captureFlag = flag
TrackFlag(flag)
TaskWorkflow.execute(WorldSession.CallBackForTask(
- GUIDTask.registerObject(socket.Zone.GUID, flag),
- socket.Zone.LocalEvents,
+ GUIDTask.registerObject(zone.GUID, flag),
+ zone.LocalEvents,
LocalServiceMessage(
- socket.Zone.id,
+ zone.id,
LocalAction.LluSpawned(Service.defaultPlayerGUID, flag)
)
))
// Broadcast chat message for LLU spawn
val owner = flag.Owner.asInstanceOf[Building]
- ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_FlagSpawned(owner, flag.Target))
+ CaptureFlagManager.ChatBroadcast(zone, CaptureFlagChatMessageStrings.CTF_FlagSpawned(owner, flag.Target))
case CaptureFlagManager.Captured(flag: CaptureFlag) =>
val name = flag.Carrier match {
@@ -79,36 +81,46 @@ class CaptureFlagManager(zone: Zone) extends Actor {
case None => "A soldier"
}
// Trigger Install sound
- flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.TriggerSound(PlanetSideGUID(-1), TriggeredSound.LLUInstall, flag.Target.CaptureTerminal.get.Position, 20, 0.8000001f))
+ zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.TriggerSound(PlanetSideGUID(-1), TriggeredSound.LLUInstall, flag.Target.CaptureTerminal.get.Position, 20, 0.8000001f))
// Broadcast capture chat message
- ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_Success(name, flag.Faction, flag.Owner.asInstanceOf[Building].Name))
+ CaptureFlagManager.ChatBroadcast(zone, CaptureFlagChatMessageStrings.CTF_Success(name, flag.Faction, flag.Owner.asInstanceOf[Building].Name))
// Despawn flag
HandleFlagDespawn(flag)
case CaptureFlagManager.Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) =>
reason match {
case CaptureFlagLostReasonEnum.Resecured =>
- ChatBroadcast(
- flag.Zone,
+ CaptureFlagManager.ChatBroadcast(
+ zone,
CaptureFlagChatMessageStrings.CTF_Failed_SourceResecured(flag.Owner.asInstanceOf[Building].Name, flag.Faction)
)
case CaptureFlagLostReasonEnum.TimedOut =>
val building = flag.Owner.asInstanceOf[Building]
- ChatBroadcast(
- flag.Zone,
+ CaptureFlagManager.ChatBroadcast(
+ zone,
CaptureFlagChatMessageStrings.CTF_Failed_TimedOut(building.Name, flag.Target.Name, flag.Faction)
)
+ case CaptureFlagLostReasonEnum.FlagLost =>
+ val building = flag.Owner.asInstanceOf[Building]
+ CaptureFlagManager.ChatBroadcast(
+ zone,
+ CaptureFlagChatMessageStrings.CTF_Failed_FlagLost(building.Name, flag.Faction),
+ fanfare = false
+ )
case CaptureFlagLostReasonEnum.Ended =>
()
}
HandleFlagDespawn(flag)
case CaptureFlagManager.PickupFlag(flag: CaptureFlag, player: Player) =>
- val continent = flag.Zone
flag.Carrier = Some(player)
- continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.SendPacket(ObjectAttachMessage(player.GUID, flag.GUID, 252)))
- continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.TriggerSound(PlanetSideGUID(-1), TriggeredSound.LLUPickup, player.Position, 15, volume = 0.8f))
- ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_FlagPickedUp(player.Name, player.Faction, flag.Owner.asInstanceOf[Building].Name), fanfare = false)
+ zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SendPacket(ObjectAttachMessage(player.GUID, flag.GUID, 252)))
+ zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.TriggerSound(PlanetSideGUID(-1), TriggeredSound.LLUPickup, player.Position, 15, volume = 0.8f))
+ CaptureFlagManager.ChatBroadcast(
+ zone,
+ CaptureFlagChatMessageStrings.CTF_FlagPickedUp(player.Name, player.Faction, flag.Owner.asInstanceOf[Building].Name),
+ fanfare = false
+ )
case CaptureFlagManager.DropFlag(flag: CaptureFlag) =>
flag.Carrier match {
@@ -119,9 +131,13 @@ class CaptureFlagManager(zone: Zone) extends Actor {
// Remove attached player from flag
flag.Carrier = None
// Send drop packet
- flag.Zone.LocalEvents ! LocalServiceMessage(flag.Zone.id, LocalAction.SendPacket(ObjectDetachMessage(player.GUID, flag.GUID, player.Position, 0, 0, 0)))
+ zone.LocalEvents ! LocalServiceMessage(zone.id, LocalAction.SendPacket(ObjectDetachMessage(player.GUID, flag.GUID, player.Position, 0, 0, 0)))
// Send dropped chat message
- ChatBroadcast(flag.Zone, CaptureFlagChatMessageStrings.CTF_FlagDropped(player.Name, player.Faction, flag.Owner.asInstanceOf[Building].Name), fanfare = false)
+ CaptureFlagManager.ChatBroadcast(
+ zone,
+ CaptureFlagChatMessageStrings.CTF_FlagDropped(player.Name, player.Faction, flag.Owner.asInstanceOf[Building].Name),
+ fanfare = false
+ )
HandleFlagDespawn(flag)
// Register LLU object create task and callback to create on clients
val replacementLlu = CaptureFlag.Constructor(
@@ -136,10 +152,10 @@ class CaptureFlagManager(zone: Zone) extends Actor {
socket.captureFlag = replacementLlu
TrackFlag(replacementLlu)
TaskWorkflow.execute(WorldSession.CallBackForTask(
- GUIDTask.registerObject(socket.Zone.GUID, replacementLlu),
- socket.Zone.LocalEvents,
+ GUIDTask.registerObject(zone.GUID, replacementLlu),
+ zone.LocalEvents,
LocalServiceMessage(
- socket.Zone.id,
+ zone.id,
LocalAction.LluSpawned(Service.defaultPlayerGUID, replacementLlu)
)
))
@@ -152,33 +168,42 @@ class CaptureFlagManager(zone: Zone) extends Actor {
}
private def DoMapUpdate(): Unit = {
- val flagInfo = flags.map(flag =>
+ val flagInfo = flags.map { case entry @ CaptureFlagManager.CaptureFlagEntry(flag) =>
+ val owner = flag.Owner.asInstanceOf[Building]
+ val pos = flag.Position
+ val hackTimeRemaining = owner.infoUpdateMessage().hack_time_remaining
+ val nextMessageAfterMinutes = CaptureFlagManager.CaptureFlagCountdownMessages(entry.currentMessageIndex)
+ if (hackTimeRemaining < nextMessageAfterMinutes.minutes.toMillis) {
+ entry.currentMessageIndex += 1
+ val msg = CaptureFlagManager.ComposeWarningMessage(flag, owner.Name, nextMessageAfterMinutes)
+ CaptureFlagManager.ChatBroadcast(zone, msg, fanfare = false)
+ }
FlagInfo(
u1 = 0,
- owner_map_id = flag.Owner.asInstanceOf[Building].MapId,
+ owner_map_id = owner.MapId,
target_map_id = flag.Target.MapId,
- x = flag.Position.x,
- y = flag.Position.y,
- hack_time_remaining = flag.Owner.asInstanceOf[Building].infoUpdateMessage().hack_time_remaining,
+ x = pos.x,
+ y = pos.y,
+ hack_time_remaining = hackTimeRemaining,
is_monolith_unit = false
)
- )
+ }
galaxyService ! GalaxyServiceMessage(GalaxyAction.FlagMapUpdate(CaptureFlagUpdateMessage(zone.Number, flagInfo)))
}
private def TrackFlag(flag: CaptureFlag): Unit = {
flag.Owner.Amenities = flag
- flags = flags :+ flag
+ flags = flags :+ CaptureFlagEntry(flag)
if (mapUpdateTick.isCancelled) {
// Start sending map updates periodically
import scala.concurrent.ExecutionContext.Implicits.global
- mapUpdateTick = context.system.scheduler.scheduleAtFixedRate(0 seconds, 1 second, self, CaptureFlagManager.MapUpdate())
+ mapUpdateTick = context.system.scheduler.scheduleAtFixedRate(initialDelay = 0 seconds, interval = 1 second, self, CaptureFlagManager.MapUpdate())
}
}
private def UntrackFlag(flag: CaptureFlag): Unit = {
flag.Owner.RemoveAmenity(flag)
- flags = flags.filterNot(x => x eq flag)
+ flags = flags.filterNot(x => x.flag eq flag)
if (flags.isEmpty) {
mapUpdateTick.cancel()
DoMapUpdate()
@@ -186,7 +211,6 @@ class CaptureFlagManager(zone: Zone) extends Actor {
}
private def HandleFlagDespawn(flag: CaptureFlag): Unit = {
- val zone = flag.Zone
// Remove the flag as an amenity
flag.Owner.asInstanceOf[Building].GetFlagSocket.get.captureFlag = None
UntrackFlag(flag)
@@ -195,8 +219,27 @@ class CaptureFlagManager(zone: Zone) extends Actor {
// Then unregister it from the GUID pool
TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, flag))
}
+}
+
+object CaptureFlagManager {
+ sealed trait Command
+
+ final case class SpawnCaptureFlag(capture_terminal: CaptureTerminal, target: Building, hackingFaction: PlanetSideEmpire.Value) extends Command
+ final case class PickupFlag(flag: CaptureFlag, player: Player) extends Command
+ final case class DropFlag(flag: CaptureFlag) extends Command
+ final case class Captured(flag: CaptureFlag) extends Command
+ final case class Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) extends Command
+ final case class MapUpdate()
+
+ private case class CaptureFlagEntry(flag: CaptureFlag) {
+ var currentMessageIndex: Int = 0
+ }
+
+ val CaptureFlagCountdownMessages: Seq[Int] = Seq(10, 5, 2, 1, 0)
private def ChatBroadcast(zone: Zone, message: String, fanfare: Boolean = true): Unit = {
+ //todo use UNK_222 sometimes
+ //todo I think the fanfare was relate to whether the message was celebratory is tone, based on the faction
val messageType: ChatMessageType = if (fanfare) {
ChatMessageType.UNK_223
} else {
@@ -206,33 +249,79 @@ class CaptureFlagManager(zone: Zone) extends Actor {
zone.id,
LocalAction.SendChatMsg(
PlanetSideGUID(-1),
- ChatMsg(messageType, wideContents = false, "", message, None)
+ ChatMsg(messageType, wideContents = true, "", message, None)
)
)
}
-}
-object CaptureFlagManager {
- sealed trait Command
+ private def ComposeWarningMessage(flag: CaptureFlag, buildingName: String, minutesLeft: Int): String = {
+ import CaptureFlagChatMessageStrings._
+ val carrier = flag.Carrier
+ val hasCarrier = carrier.nonEmpty
+ minutesLeft match {
+ case 1 if hasCarrier =>
+ CTF_Warning_Carrier1Min(carrier.get.Name, flag.Faction, buildingName, flag.Target.Name)
+ case 1 =>
+ CTF_Warning_NoCarrier1Min(buildingName, flag.Faction, flag.Target.Name)
+ case time if hasCarrier =>
+ CTF_Warning_Carrier(carrier.get.Name, flag.Faction, buildingName, flag.Target.Name, time)
+ case time =>
+ CTF_Warning_NoCarrier(buildingName, flag.Faction, flag.Target.Name, time)
+ }
+ }
- final case class SpawnCaptureFlag(capture_terminal: CaptureTerminal, target: Building, hackingFaction: PlanetSideEmpire.Value) extends Command
- final case class PickupFlag(flag: CaptureFlag, player: Player) extends Command
- final case class DropFlag(flag: CaptureFlag) extends Command
- final case class Captured(flag: CaptureFlag) extends Command
- final case class Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) extends Command
- final case class MapUpdate()
+ /**
+ * na
+ * @param flagGuid flag that may exist
+ * @param target evaluate this to determine if to continue with this loss
+ */
+ def ReasonToLoseFlagViolently(
+ zone: Zone,
+ flagGuid: Option[PlanetSideGUID],
+ target: PlanetSideGameObject with InteractsWithZone
+ ): Boolean = {
+ zone
+ .GUID(flagGuid)
+ .collect {
+ case flag: CaptureFlag
+ if LoseFlagViolentlyToEnvironment(target, Set(EnvironmentAttribute.Water, EnvironmentAttribute.Lava, EnvironmentAttribute.Death)) ||
+ LoseFlagViolentlyToWarpGateEnvelope(zone, target) =>
+ flag.Destroyed = true
+ zone.LocalEvents ! LocalServiceMessage("", LocalAction.LluLost(flag))
+ true
+ }
+ .getOrElse(false)
+ }
+
+ def LoseFlagViolentlyToEnvironment(
+ target: PlanetSideGameObject with InteractsWithZone,
+ deniedEnvironments: Set[EnvironmentTrait]
+ ): Boolean = {
+ target
+ .interaction()
+ .collectFirst { case env: InteractWithEnvironment => env.OngoingInteractions }
+ .map(_.intersect(deniedEnvironments))
+ .getOrElse(Set())
+ .nonEmpty
+ }
+
+ def LoseFlagViolentlyToWarpGateEnvelope(
+ zone: Zone,
+ target: PlanetSideGameObject with InteractsWithZone
+ ): Boolean = {
+ val position = target.Position
+ zone
+ .blockMap
+ .sector(position, range = 10f)
+ .buildingList
+ .collectFirst {
+ case gate: WarpGate if Vector3.DistanceSquared(position, gate.Position) < math.pow(gate.Definition.SOIRadius, 2f) => gate
+ }
+ .nonEmpty
+ }
}
object CaptureFlagChatMessageStrings {
- /*
- @CTF_Failed_TargetLost=%1's LLU target facility %2 was lost!\nHack canceled!
- @CTF_Failed_FlagLost=The %1 lost %2's LLU!\nHack canceled!
- @CTF_Warning_Carrier=%1 of the %2 has %3's LLU.\nIt must be taken to %4 within %5 minutes!
- @CTF_Warning_NoCarrier=%1's LLU is in the field.\nThe %2 must take it to %3 within %4 minutes!
- @CTF_Warning_Carrier1Min=%1 of the %2 has %3's LLU.\nIt must be taken to %4 within the next minute!
- @CTF_Warning_NoCarrier1Min=%1's LLU is in the field.\nThe %2 must take it to %3 within the next minute!
- */
-
// @CTF_Success=%1 captured %2's LLU for the %3!
/** {player.Name} captured {ownerName}'s LLU for the {player.Faction}! */
private[support] def CTF_Success(playerName:String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
@@ -243,10 +332,20 @@ object CaptureFlagChatMessageStrings {
private[support] def CTF_Failed_TimedOut(ownerName: String, name: String, faction: PlanetSideEmpire.Value): String =
s"@CTF_Failed_TimedOut^@${GetFactionString(faction)}~^@$ownerName~^@$name~"
+ // @CTF_Failed_Lost=The %1 lost %2's LLU!\nHack canceled!
+ /** The {faction} lost {ownerName}'s LLU!\nHack canceled! */
+ private[support] def CTF_Failed_FlagLost(ownerName: String, faction: PlanetSideEmpire.Value): String =
+ s"@CTF_Failed_FlagLost^@${GetFactionString(faction)}~^@$ownerName~"
+
+ // @CTF_Failed_TargetLost=%1's LLU target facility %2 was lost!\nHack canceled!
+ /** {hackFacility}'s LLU target facility {targetFacility} was lost!\nHack canceled! */
+ private[support] def CTF_Failed_TargetLost(hackFacility: String, targetFacility: String): String =
+ s"@CTF_Failed_TargetLost^@$hackFacility~^@$targetFacility~"
+
// @CTF_Failed_SourceResecured=The %1 resecured %2!\nThe LLU was lost!
/** The {faction} resecured {name}!\nThe LLU was lost! */
private[support] def CTF_Failed_SourceResecured(name: String, faction: PlanetSideEmpire.Value): String =
- s"@CTF_Failed_SourceResecured^@${CaptureFlagChatMessageStrings.GetFactionString(faction)}~^@$name~"
+ s"@CTF_Failed_SourceResecured^@${GetFactionString(faction)}~^@$name~"
// @CTF_FlagSpawned=%1 %2 has spawned a LLU.\nIt must be taken to %3 %4's Control Console within %5 minutes or the hack will fail!
/** {facilityType} {facilityName} has spawned a LLU.\nIt must be taken to {targetFacilityType} {targetFacilityName}'s Control Console within 15 minutes or the hack will fail! */
@@ -255,13 +354,57 @@ object CaptureFlagChatMessageStrings {
// @CTF_FlagPickedUp=%1 of the %2 picked up %3's LLU
/** {player.Name} of the {player.Faction} picked up {ownerName}'s LLU */
- def CTF_FlagPickedUp(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
- s"@CTF_FlagPickedUp^$playerName~^@${CaptureFlagChatMessageStrings.GetFactionString(playerFaction)}~^@$ownerName~"
+ private[support] def CTF_FlagPickedUp(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
+ s"@CTF_FlagPickedUp^$playerName~^@${GetFactionString(playerFaction)}~^@$ownerName~"
// @CTF_FlagDropped=%1 of the %2 dropped %3's LLU
/** {playerName} of the {faction} dropped {facilityName}'s LLU */
- def CTF_FlagDropped(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
- s"@CTF_FlagDropped^$playerName~^@${CaptureFlagChatMessageStrings.GetFactionString(playerFaction)}~^@$ownerName~"
+ private[support] def CTF_FlagDropped(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
+ s"@CTF_FlagDropped^$playerName~^@${GetFactionString(playerFaction)}~^@$ownerName~"
+
+ // @CTF_Warning_Carrier=%1's LLU is in the field.\nThe %2 must take it to %3 within %4 minutes!
+ /** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within {time} minutes! */
+ private[support] def CTF_Warning_Carrier(
+ playerName:String,
+ playerFaction: PlanetSideEmpire.Value,
+ facilityName: String,
+ targetFacilityName: String,
+ time: Int
+ ): String = {
+ s"@CTF_Warning_Carrier^$playerName~^@${GetFactionString(playerFaction)}~^@$facilityName~^@$targetFacilityName~^@$time~"
+ }
+
+ // @CTF_Warning_Carrier1Min=%1 of the %2 has %3's LLU.\nIt must be taken to %4 within the next minute!
+ /** {playerName} of the {faction} has {facilityName}'s LLU.\nIt must be taken to {targetFacilityName} within the next minute! */
+ private[support] def CTF_Warning_Carrier1Min(
+ playerName:String,
+ playerFaction: PlanetSideEmpire.Value,
+ facilityName: String,
+ targetFacilityName: String
+ ): String = {
+ s"@CTF_Warning_Carrier1Min^$playerName~^@${GetFactionString(playerFaction)}~^@$facilityName~^@$targetFacilityName~"
+ }
+
+ // @CTF_Warning_NoCarrier=%1's LLU is in the field.\nThe %2 must take it to %3 within %4 minutes!
+ /** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within {time} minute! */
+ private[support] def CTF_Warning_NoCarrier(
+ facilityName: String,
+ playerFaction: PlanetSideEmpire.Value,
+ targetFacilityName: String,
+ time: Int
+ ): String = {
+ s"@CTF_Warning_NoCarrier^@$facilityName~^@${GetFactionString(playerFaction)}~^@$targetFacilityName~^$time~"
+ }
+
+ // @CTF_Warning_NoCarrier1Min=%1's LLU is in the field.\nThe %2 must take it to %3 within the next minute!
+ /** {facilityName}'s LLU is in the field.\nThe {faction} must take it to {targetFacilityName} within the next minute! */
+ private[support] def CTF_Warning_NoCarrier1Min(
+ facilityName: String,
+ playerFaction: PlanetSideEmpire.Value,
+ targetFacilityName: String
+ ): String = {
+ s"@CTF_Warning_NoCarrier1Min^@$facilityName~^@${GetFactionString(playerFaction)}~^@$targetFacilityName~"
+ }
private def GetFactionString: PlanetSideEmpire.Value=>String = {
case PlanetSideEmpire.TR => "TerranRepublic"
@@ -277,4 +420,5 @@ object CaptureFlagLostReasonEnum {
final case object Resecured extends CaptureFlagLostReasonEnum
final case object TimedOut extends CaptureFlagLostReasonEnum
final case object Ended extends CaptureFlagLostReasonEnum
+ final case object FlagLost extends CaptureFlagLostReasonEnum
}
diff --git a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala
index b9a4f4700..1a88b0e00 100644
--- a/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala
+++ b/src/main/scala/net/psforever/services/local/support/HackCaptureActor.scala
@@ -6,16 +6,17 @@ import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.llu.CaptureFlag
-import net.psforever.objects.serverobject.structures.Building
+import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.serverobject.terminals.capture.CaptureTerminal
import net.psforever.objects.zones.Zone
import net.psforever.objects.Default
-import net.psforever.packet.game.{GenericAction, HackState7, PlanetsideAttributeEnum}
+import net.psforever.objects.serverobject.structures.participation.MajorFacilityHackParticipation
+import net.psforever.packet.game.{ChatMsg, GenericAction, HackState7, PlanetsideAttributeEnum}
import net.psforever.objects.sourcing.PlayerSource
import net.psforever.services.Service
import net.psforever.services.local.support.HackCaptureActor.GetHackingFaction
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
-import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
+import net.psforever.types.{ChatMessageType, PlanetSideEmpire, PlanetSideGUID}
import scala.concurrent.duration.{FiniteDuration, _}
import scala.util.Random
@@ -62,6 +63,11 @@ class HackCaptureActor extends Actor {
// If the base has a socket, but no flag spawned it means the hacked base is neutral with no friendly neighbouring bases to deliver to, making it a timed hack.
val building = terminal.Owner.asInstanceOf[Building]
building.GetFlag match {
+ case Some(llu) if llu.Destroyed =>
+ // LLU was destroyed while in the field. Send resecured notifications
+ terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.FlagLost)
+ NotifyHackStateChange(terminal, isResecured = true)
+
case Some(llu) =>
// LLU was not delivered in time. Send resecured notifications
terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.TimedOut)
@@ -138,6 +144,28 @@ class HackCaptureActor extends Actor {
log.error(s"Attempted LLU capture for ${flag.Owner.asInstanceOf[Building].Name} but CC GUID ${flag.Owner.asInstanceOf[Building].CaptureTerminal.get.GUID} was not in list of hacked objects")
}
+ case HackCaptureActor.FlagLost(flag) =>
+ val owner = flag.Owner.asInstanceOf[Building]
+ val guid = owner.GUID
+ val terminalOpt = owner.CaptureTerminal
+ hackedObjects
+ .find(entry => guid == entry.target.Owner.GUID)
+ .collect { entry =>
+ val terminal = terminalOpt.get
+ hackedObjects = hackedObjects.filterNot(x => x eq entry)
+ log.info(s"FlagLost: ${flag.Carrier.map(_.Name).getOrElse("")} the flag carrier screwed up the capture for ${flag.Target.Name} and the LLU has been lost")
+ terminal.Actor ! CommonMessages.ClearHack()
+ NotifyHackStateChange(terminal, isResecured = true)
+ // If there's hacked objects left in the list restart the timer with the shortest hack time left
+ RestartTimer()
+ entry
+ }
+ .orElse{
+ log.warn(s"FlagLost: flag data does not match to an entry in the hacked objects list")
+ None
+ }
+ context.parent ! CaptureFlagManager.Lost(flag, CaptureFlagLostReasonEnum.FlagLost)
+
case _ => ()
}
@@ -209,6 +237,18 @@ class HackCaptureActor extends Actor {
val owner = terminal.Owner.asInstanceOf[Building]
// Notify parent building that state has changed
owner.Actor ! BuildingActor.AmenityStateChange(terminal, Some(isResecured))
+ // If major facility, check NTU
+ owner.CaptureTerminal
+ .map(_.HackedBy)
+ .collect {
+ case Some(info: Hackable.HackInfo)
+ if owner.BuildingType == StructureType.Facility && owner.NtuLevel == 0 =>
+ MajorFacilityHackParticipation.warningMessageForHackOccupiers(
+ owner,
+ info,
+ ChatMsg(ChatMessageType.UNK_227, "@FacilityRequiresResourcesForHackWarning")
+ )
+ }
// Push map update to clients
owner.Zone.actor ! ZoneActor.ZoneMapUpdate()
}
@@ -256,6 +296,7 @@ object HackCaptureActor {
final case class ResecureCaptureTerminal(target: CaptureTerminal, zone: Zone, hacker: PlayerSource)
final case class FlagCaptured(flag: CaptureFlag)
+ final case class FlagLost(flag: CaptureFlag)
private final case class ProcessCompleteHacks()
diff --git a/src/main/scala/net/psforever/services/vehicle/VehicleService.scala b/src/main/scala/net/psforever/services/vehicle/VehicleService.scala
index 35b377981..72168faa0 100644
--- a/src/main/scala/net/psforever/services/vehicle/VehicleService.scala
+++ b/src/main/scala/net/psforever/services/vehicle/VehicleService.scala
@@ -184,6 +184,10 @@ class VehicleService(zone: Zone) extends Actor {
VehicleResponse.MountVehicle(vehicle_guid, seat)
)
)
+ case VehicleAction.LoseOwnership(owner_guid, vehicle_guid) =>
+ VehicleEvents.publish(
+ VehicleServiceResponse(s"/$forChannel/Vehicle", Service.defaultPlayerGUID, VehicleResponse.LoseOwnership(owner_guid, vehicle_guid))
+ )
case VehicleAction.Ownership(player_guid, vehicle_guid) =>
VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.Ownership(vehicle_guid))
diff --git a/src/main/scala/net/psforever/services/vehicle/VehicleServiceMessage.scala b/src/main/scala/net/psforever/services/vehicle/VehicleServiceMessage.scala
index 979d179f9..07bb29769 100644
--- a/src/main/scala/net/psforever/services/vehicle/VehicleServiceMessage.scala
+++ b/src/main/scala/net/psforever/services/vehicle/VehicleServiceMessage.scala
@@ -93,6 +93,7 @@ object VehicleAction {
) extends Action
final case class MountVehicle(player_guid: PlanetSideGUID, object_guid: PlanetSideGUID, seat: Int) extends Action
final case class ObjectDelete(guid: PlanetSideGUID) extends Action
+ final case class LoseOwnership(owner_guid: PlanetSideGUID, vehicle_guid: PlanetSideGUID) extends Action
final case class Ownership(player_guid: PlanetSideGUID, vehicle_guid: PlanetSideGUID) extends Action
final case class PlanetsideAttribute(
player_guid: PlanetSideGUID,
diff --git a/src/main/scala/net/psforever/services/vehicle/VehicleServiceResponse.scala b/src/main/scala/net/psforever/services/vehicle/VehicleServiceResponse.scala
index e64c6536e..22e2ed94f 100644
--- a/src/main/scala/net/psforever/services/vehicle/VehicleServiceResponse.scala
+++ b/src/main/scala/net/psforever/services/vehicle/VehicleServiceResponse.scala
@@ -74,6 +74,8 @@ object VehicleResponse {
final case class MountVehicle(object_guid: PlanetSideGUID, seat: Int) extends Response
final case class ObjectDelete(guid: PlanetSideGUID) extends Response
final case class Ownership(vehicle_guid: PlanetSideGUID) extends Response
+ final case class LoseOwnership(owner_guid: PlanetSideGUID, vehicle_guid: PlanetSideGUID)
+ extends Response
final case class PlanetsideAttribute(vehicle_guid: PlanetSideGUID, attribute_type: Int, attribute_value: Long)
extends Response
final case class Reload(weapon_guid: PlanetSideGUID) extends Response
diff --git a/src/main/scala/net/psforever/types/OxygenState.scala b/src/main/scala/net/psforever/types/OxygenState.scala
index 56f27166a..c588a2ac7 100644
--- a/src/main/scala/net/psforever/types/OxygenState.scala
+++ b/src/main/scala/net/psforever/types/OxygenState.scala
@@ -1,9 +1,6 @@
package net.psforever.types
import enumeratum.{Enum, EnumEntry}
-import net.psforever.packet.PacketHelpers
-import scodec.Codec
-import scodec.codecs.uint
/**
* The progress state of being a drowning victim.
@@ -22,6 +19,4 @@ object OxygenState extends Enum[OxygenState] {
case object Recovery extends OxygenState
case object Suffocation extends OxygenState
-
- implicit val codec: Codec[OxygenState] = PacketHelpers.createEnumCodec(e = this, uint(bits = 1))
}
diff --git a/src/test/scala/game/objectcreate/CommonFieldDataWithPlacementTest.scala b/src/test/scala/game/objectcreate/CommonFieldDataWithPlacementTest.scala
index dd5a89fde..f3442112e 100644
--- a/src/test/scala/game/objectcreate/CommonFieldDataWithPlacementTest.scala
+++ b/src/test/scala/game/objectcreate/CommonFieldDataWithPlacementTest.scala
@@ -20,7 +20,7 @@ class CommonFieldDataWithPlacementTest extends Specification {
guid mustEqual PlanetSideGUID(3840)
parent.isDefined mustEqual false
data match {
- case CommonFieldDataWithPlacement(pos, com) =>
+ case SmallDeployableData(CommonFieldDataWithPlacement(pos, com)) =>
pos.coord mustEqual Vector3(4704.172f, 5546.4375f, 82.234375f)
pos.orient mustEqual Vector3.z(272.8125f)
com match {
@@ -31,9 +31,9 @@ class CommonFieldDataWithPlacementTest extends Specification {
v1 mustEqual false
v2.isEmpty mustEqual true
v3 mustEqual false
- v4.contains(false) mustEqual true
+ v4.isEmpty mustEqual true
v5.isEmpty mustEqual true
- fguid mustEqual PlanetSideGUID(8290)
+ fguid mustEqual PlanetSideGUID(4145)
case _ =>
ko
}
@@ -46,10 +46,10 @@ class CommonFieldDataWithPlacementTest extends Specification {
}
"encode" in {
- val obj = CommonFieldDataWithPlacement(
+ val obj = SmallDeployableData(CommonFieldDataWithPlacement(
PlacementData(Vector3(4704.172f, 5546.4375f, 82.234375f), Vector3.z(272.8125f)),
- CommonFieldData(PlanetSideEmpire.TR, false, false, false, None, false, Some(false), None, PlanetSideGUID(8290))
- )
+ CommonFieldData(PlanetSideEmpire.TR, bops = false, alternate = false, v1 = false, None, jammered = false, None, None, PlanetSideGUID(4145))
+ ))
val msg = ObjectCreateMessage(ObjectClass.boomer, PlanetSideGUID(3840), obj)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual string_boomer
diff --git a/src/test/scala/objects/ConverterTest.scala b/src/test/scala/objects/ConverterTest.scala
index 691a06e77..e25352925 100644
--- a/src/test/scala/objects/ConverterTest.scala
+++ b/src/test/scala/objects/ConverterTest.scala
@@ -32,9 +32,9 @@ class ConverterTest extends Specification {
PlanetSideEmpire.NEUTRAL,
bops = false,
alternate = false,
- true,
+ v1 = true,
None,
- false,
+ jammered = false,
None,
None,
PlanetSideGUID(0)
@@ -50,9 +50,9 @@ class ConverterTest extends Specification {
PlanetSideEmpire.NEUTRAL,
bops = false,
alternate = false,
- false,
+ v1 = false,
None,
- false,
+ jammered = false,
Some(false),
None,
PlanetSideGUID(0)
@@ -71,7 +71,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual DetailedWeaponData(
- CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
+ CommonFieldData(PlanetSideEmpire.NEUTRAL, bops = false, alternate = false, v1 = true, None, jammered = false, None, None, PlanetSideGUID(0)),
0,
List(InternalSlot(Ammo.shotgun_shell.id, PlanetSideGUID(90), 0, DetailedAmmoBoxData(8, 12)))
)
@@ -81,7 +81,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual WeaponData(
- CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
+ CommonFieldData(PlanetSideEmpire.NEUTRAL, bops = false, alternate = false, v1 = true, None, jammered = false, None, None, PlanetSideGUID(0)),
0,
List(
InternalSlot(
@@ -90,11 +90,11 @@ class ConverterTest extends Specification {
0,
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
- false,
- false,
- false,
+ bops = false,
+ alternate = false,
+ v1 = false,
None,
- false,
+ jammered = false,
Some(false),
None,
PlanetSideGUID(0)
@@ -115,7 +115,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual DetailedWeaponData(
- CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0)),
+ CommonFieldData(PlanetSideEmpire.NEUTRAL, bops = false, alternate = false, v1 = true, None, jammered = false, None, None, PlanetSideGUID(0)),
0,
List(
InternalSlot(Ammo.bullet_9mm.id, PlanetSideGUID(90), 0, DetailedAmmoBoxData(8, 30)),
@@ -132,17 +132,17 @@ class ConverterTest extends Specification {
PlanetSideEmpire.NEUTRAL, //TODO need faction affinity
bops = false,
alternate = false,
- true,
+ v1 = true,
None,
- false,
+ jammered = false,
None,
None,
PlanetSideGUID(0)
),
0,
List(
- InternalSlot(Ammo.bullet_9mm.id, PlanetSideGUID(90), 0, CommonFieldData()(false)),
- InternalSlot(Ammo.rocket.id, PlanetSideGUID(91), 1, CommonFieldData()(false))
+ InternalSlot(Ammo.bullet_9mm.id, PlanetSideGUID(90), 0, CommonFieldData()(flag = false)),
+ InternalSlot(Ammo.rocket.id, PlanetSideGUID(91), 1, CommonFieldData()(flag = false))
)
)
case _ =>
@@ -164,7 +164,7 @@ class ConverterTest extends Specification {
}
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
- pkt mustEqual CommonFieldData()(false)
+ pkt mustEqual CommonFieldData()(flag = false)
case _ =>
ko
}
@@ -177,7 +177,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual DetailedConstructionToolData(
- CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0))
+ CommonFieldData(PlanetSideEmpire.NEUTRAL, bops = false, alternate = false, v1 = true, None, jammered = false, None, None, PlanetSideGUID(0))
)
case _ =>
ko
@@ -188,11 +188,11 @@ class ConverterTest extends Specification {
pkt mustEqual HandheldData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
- false,
- false,
- true,
+ bops = false,
+ alternate = false,
+ v1 = true,
None,
- false,
+ jammered = false,
None,
None,
PlanetSideGUID(0)
@@ -216,11 +216,11 @@ class ConverterTest extends Specification {
pkt mustEqual DetailedREKData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL, //TODO faction affinity
- false,
- false,
- true,
+ bops = false,
+ alternate = false,
+ v1 = true,
None,
- false,
+ jammered = false,
Some(false),
None,
PlanetSideGUID(0)
@@ -234,11 +234,11 @@ class ConverterTest extends Specification {
pkt mustEqual REKData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
- false,
- false,
- true,
+ bops = false,
+ alternate = false,
+ v1 = true,
None,
- false,
+ jammered = false,
Some(false),
None,
PlanetSideGUID(0)
@@ -257,7 +257,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual DetailedConstructionToolData(
- CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, true, None, false, None, None, PlanetSideGUID(0))
+ CommonFieldData(PlanetSideEmpire.NEUTRAL, bops = false, alternate = false, v1 = true, None, jammered = false, None, None, PlanetSideGUID(0))
)
case _ =>
ko
@@ -265,7 +265,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual HandheldData(
- CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, false, None, false, None, None, PlanetSideGUID(0))
+ CommonFieldData(PlanetSideEmpire.NEUTRAL, bops = false, alternate = false, v1 = false, None, jammered = false, None, None, PlanetSideGUID(0))
)
case _ =>
ko
@@ -282,11 +282,11 @@ class ConverterTest extends Specification {
pkt mustEqual HandheldData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
- false,
- false,
- false,
+ bops = false,
+ alternate = false,
+ v1 = false,
None,
- false,
+ jammered = false,
None,
Some(1001),
PlanetSideGUID(0)
@@ -301,11 +301,11 @@ class ConverterTest extends Specification {
pkt mustEqual DetailedConstructionToolData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
- false,
- false,
- true,
+ bops = false,
+ alternate = false,
+ v1 = true,
None,
- false,
+ jammered = false,
None,
Some(1001),
PlanetSideGUID(0)
@@ -333,20 +333,20 @@ class ConverterTest extends Specification {
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
- pkt mustEqual CommonFieldDataWithPlacement(
+ pkt mustEqual SmallDeployableData(CommonFieldDataWithPlacement(
PlacementData(Vector3.Zero, Vector3.Zero),
CommonFieldData(
PlanetSideEmpire.TR,
- false,
- false,
- false,
+ bops = false,
+ alternate = false,
+ v1 = false,
None,
- false,
+ jammered = false,
Some(false),
None,
PlanetSideGUID(0)
)
- )
+ ))
case _ =>
ko
}
@@ -369,12 +369,12 @@ class ConverterTest extends Specification {
PlacementData(Vector3.Zero, Vector3.Zero),
CommonFieldData(
PlanetSideEmpire.TR,
- false,
- false,
- true,
+ bops = false,
+ alternate = false,
+ v1 = true,
+ None,
+ jammered = false,
None,
- false,
- Some(true),
None,
PlanetSideGUID(0)
)
@@ -389,11 +389,11 @@ class ConverterTest extends Specification {
WeaponData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
- false,
- false,
- true,
+ bops = false,
+ alternate = false,
+ v1 = true,
None,
- false,
+ jammered = false,
None,
None,
PlanetSideGUID(0)
@@ -406,11 +406,11 @@ class ConverterTest extends Specification {
0,
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
- false,
- false,
- false,
+ bops = false,
+ alternate = false,
+ v1 = false,
None,
- false,
+ jammered = false,
Some(false),
None,
PlanetSideGUID(0)
@@ -444,11 +444,11 @@ class ConverterTest extends Specification {
PlacementData(Vector3.Zero, Vector3.Zero),
CommonFieldData(
PlanetSideEmpire.TR,
- false,
- false,
- true,
+ bops = false,
+ alternate = false,
+ v1 = true,
None,
- false,
+ jammered = false,
Some(false),
None,
PlanetSideGUID(0)
@@ -464,11 +464,11 @@ class ConverterTest extends Specification {
WeaponData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
- false,
- false,
- true,
+ bops = false,
+ alternate = false,
+ v1 = true,
None,
- false,
+ jammered = false,
None,
None,
PlanetSideGUID(0)
@@ -481,11 +481,11 @@ class ConverterTest extends Specification {
0,
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
- false,
- false,
- false,
+ bops = false,
+ alternate = false,
+ v1 = false,
None,
- false,
+ jammered = false,
Some(false),
None,
PlanetSideGUID(0)
@@ -519,10 +519,10 @@ class ConverterTest extends Specification {
PlanetSideEmpire.TR,
bops = false,
alternate = false,
- true,
+ v1 = true,
+ None,
+ jammered = false,
None,
- false,
- Some(true),
None,
PlanetSideGUID(0)
)
@@ -584,9 +584,9 @@ class ConverterTest extends Specification {
PlanetSideEmpire.TR,
bops = false,
alternate = false,
- true,
+ v1 = true,
None,
- false,
+ jammered = false,
None,
Some(1001),
PlanetSideGUID(5001)
@@ -616,9 +616,9 @@ class ConverterTest extends Specification {
PlanetSideEmpire.TR,
bops = false,
alternate = true,
- true,
+ v1 = true,
None,
- false,
+ jammered = false,
None,
Some(1001),
PlanetSideGUID(0)
@@ -751,7 +751,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) =>
pkt mustEqual DetailedLockerContainerData(
- CommonFieldData(PlanetSideEmpire.NEUTRAL, false, false, false, None, false, None, None, PlanetSideGUID(0)),
+ CommonFieldData(PlanetSideEmpire.NEUTRAL, bops = false, alternate = false, v1 = false, None, jammered = false, None, None, PlanetSideGUID(0)),
None
)
case _ =>
@@ -783,11 +783,11 @@ class ConverterTest extends Specification {
DetailedREKData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
- false,
- false,
- true,
+ bops = false,
+ alternate = false,
+ v1 = true,
None,
- false,
+ jammered = false,
Some(false),
None,
PlanetSideGUID(0)
@@ -809,11 +809,11 @@ class ConverterTest extends Specification {
REKData(
CommonFieldData(
PlanetSideEmpire.NEUTRAL,
- false,
- false,
- true,
+ bops = false,
+ alternate = false,
+ v1 = true,
None,
- false,
+ jammered = false,
Some(false),
None,
PlanetSideGUID(0)
@@ -841,7 +841,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
- pkt mustEqual CommonFieldData(PlanetSideEmpire.NEUTRAL)(false)
+ pkt mustEqual CommonFieldData(PlanetSideEmpire.NEUTRAL)(flag = false)
case _ =>
ko
}
@@ -861,7 +861,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) =>
- pkt mustEqual CommonFieldData(PlanetSideEmpire.NEUTRAL)(false)
+ pkt mustEqual CommonFieldData(PlanetSideEmpire.NEUTRAL)(flag = false)
case _ =>
ko
}
diff --git a/src/test/scala/objects/DefaultTest.scala b/src/test/scala/objects/DefaultTest.scala
index cbf5ca7a5..3e1569aa0 100644
--- a/src/test/scala/objects/DefaultTest.scala
+++ b/src/test/scala/objects/DefaultTest.scala
@@ -21,26 +21,26 @@ class DefaultTest extends Specification {
}
}
-class DefaultActorStartedTest extends ActorTest {
- "Default.Actor" should {
- "send messages to deadLetters" in {
- //after being started
- val probe = new TestProbe(system)
- system.eventStream.subscribe(probe.ref, classOf[DeadLetter])
- Default.Actor ! "hello world"
- val msg1 = probe.receiveOne(5000 milliseconds)
- assert(msg1.isInstanceOf[DeadLetter])
- assert(msg1.asInstanceOf[DeadLetter].message equals "hello world")
-
- //if it was stopped
- system.stop(Default.Actor)
- Default.Actor ! "hello world"
- val msg2 = probe.receiveOne(5000 milliseconds)
- assert(msg2.isInstanceOf[DeadLetter])
- assert(msg2.asInstanceOf[DeadLetter].message equals "hello world")
- }
- }
-}
+//class DefaultActorStartedTest extends ActorTest {
+// "Default.Actor" should {
+// "send messages to deadLetters" in {
+// //after being started
+// val probe = new TestProbe(system)
+// system.eventStream.subscribe(probe.ref, classOf[DeadLetter])
+// Default.Actor ! "hello world"
+// val msg1 = probe.receiveOne(5000 milliseconds)
+// assert(msg1.isInstanceOf[DeadLetter])
+// assert(msg1.asInstanceOf[DeadLetter].message equals "hello world")
+//
+// //if it was stopped
+// system.stop(Default.Actor)
+// Default.Actor ! "hello world"
+// val msg2 = probe.receiveOne(5000 milliseconds)
+// assert(msg2.isInstanceOf[DeadLetter])
+// assert(msg2.asInstanceOf[DeadLetter].message equals "hello world")
+// }
+// }
+//}
object DefaultActorTest {
//due to being a singleton, the original original value of the Default.Actor is cached here
diff --git a/src/test/scala/objects/DeployableBehaviorTest.scala b/src/test/scala/objects/DeployableBehaviorTest.scala
index 0de9023c5..e9ac7df88 100644
--- a/src/test/scala/objects/DeployableBehaviorTest.scala
+++ b/src/test/scala/objects/DeployableBehaviorTest.scala
@@ -48,21 +48,14 @@ class DeployableBehaviorSetupTest extends ActorTest {
assert(deployableList.isEmpty, "self-setup test - deployable list is not empty")
zone.Deployables ! Zone.Deployable.Build(jmine)
- val eventsMsgs = eventsProbe.receiveN(3, 10.seconds)
+ val eventsMsgs = eventsProbe.receiveN(2, 10.seconds)
eventsMsgs.head match {
- case LocalServiceMessage(
- "test",
- LocalAction.TriggerEffectLocation(PlanetSideGUID(0), "spawn_object_effect", Vector3(1,2,3), Vector3(4,5,6))
- ) => ;
- case _ => assert(false, "self-setup test - no spawn fx")
- }
- eventsMsgs(1) match {
case LocalServiceMessage("test", LocalAction.DeployItem(obj)) =>
assert(obj eq jmine, "self-setup test - not same mine")
case _ =>
assert( false, "self-setup test - wrong deploy message")
}
- eventsMsgs(2) match {
+ eventsMsgs(1) match {
case LocalServiceMessage(
"TR",
LocalAction.DeployableMapIcon(
@@ -251,13 +244,13 @@ class DeployableBehaviorDeconstructTest extends ActorTest {
"DeployableBehavior" should {
"deconstruct, by self" in {
zone.Deployables ! Zone.Deployable.Build(jmine)
- eventsProbe.receiveN(3, 10.seconds) //all of the messages from the construction (see other testing)
+ eventsProbe.receiveN(2, 10.seconds) //all of the messages from the construction (see other testing)
assert(deployableList.contains(jmine), "deconstruct test - deployable not appended to list")
jmine.Actor ! Deployable.Deconstruct()
val eventsMsgs = eventsProbe.receiveN(2, 10.seconds)
eventsMsgs.head match {
- case LocalServiceMessage("test", LocalAction.EliminateDeployable(`jmine`, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
+ case LocalServiceMessage("test", LocalAction.EliminateDeployable(_, PlanetSideGUID(1), Vector3(1,2,3), 2)) => ;
case _ => assert(false, "deconstruct test - not eliminating deployable")
}
eventsMsgs(1) match {
diff --git a/src/test/scala/objects/DeployableTest.scala b/src/test/scala/objects/DeployableTest.scala
index 1708ff74a..b02c1ed21 100644
--- a/src/test/scala/objects/DeployableTest.scala
+++ b/src/test/scala/objects/DeployableTest.scala
@@ -314,7 +314,7 @@ class ExplosiveDeployableJammerTest extends ActorTest {
val guid = new NumberPoolHub(new MaxNumberSource(10))
val eventsProbe = new TestProbe(system)
- val j_mine = Deployables.Make(DeployedItem.jammer_mine)().asInstanceOf[ExplosiveDeployable] //guid=1
+ val mine = Deployables.Make(DeployedItem.he_mine)().asInstanceOf[ExplosiveDeployable] //guid=1
val avatar1 = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
val player1 = Player(avatar1) //guid=3
val avatar2 = Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterSex.Male, 0, CharacterVoice.Mute)
@@ -335,17 +335,17 @@ class ExplosiveDeployableJammerTest extends ActorTest {
}
player1.Spawn()
player2.Spawn()
- guid.register(j_mine, 1)
+ guid.register(mine, 1)
guid.register(player1, 3)
guid.register(player2, 4)
guid.register(weapon, 5)
- j_mine.Zone = zone
- j_mine.OwnerGuid = player2
+ mine.Zone = zone
+ mine.OwnerGuid = player2
//j_mine.OwnerName = player2.Name
- j_mine.Faction = PlanetSideEmpire.NC
- j_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], j_mine), "j-mine-control")
+ mine.Faction = PlanetSideEmpire.NC
+ mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], mine), "j-mine-control")
- val jMineSource = SourceEntry(j_mine)
+ val jMineSource = SourceEntry(mine)
val pSource = PlayerSource(player1)
val projectile = weapon.Projectile
val resolved = DamageInteraction(
@@ -353,7 +353,7 @@ class ExplosiveDeployableJammerTest extends ActorTest {
ProjectileReason(
DamageResolution.Hit,
Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
- j_mine.DamageModel
+ mine.DamageModel
),
Vector3(1, 0, 0)
)
@@ -361,26 +361,53 @@ class ExplosiveDeployableJammerTest extends ActorTest {
"ExplosiveDeployable" should {
"handle being jammered appropriately (no detonation)" in {
- assert(!j_mine.Destroyed)
+ assert(!mine.Destroyed)
- j_mine.Actor ! Vitality.Damage(applyDamageToJ)
- val eventMsgs = eventsProbe.receiveN(2, 200 milliseconds)
+ mine.Actor ! Vitality.Damage(applyDamageToJ)
+ val eventMsgs = eventsProbe.receiveN(4, 200 milliseconds)
eventMsgs.head match {
+ case Zone.HotSpot.Conflict(mineSrc, playerSrc, Vector3(1.0,0.0,0.0)) => ;
+ case _ => assert(false, "")
+ }
+// eventMsgs.head match {
+// case LocalServiceMessage(
+// "NC",
+// LocalAction.DeployableMapIcon(
+// ValidPlanetSideGUID(0),
+// DeploymentAction.Dismiss,
+// DeployableInfo(ValidPlanetSideGUID(1), DeployableIcon.HEMine, Vector3.Zero, ValidPlanetSideGUID(0))
+// )
+// ) => ;
+// case _ => assert(false, "")
+// }
+ eventMsgs(1) match {
+ case LocalServiceMessage("test", LocalAction.Detonate(PlanetSideGUID(1), _)) => ;
+ case _ => assert(false, "")
+ }
+ eventMsgs(2) match {
case LocalServiceMessage(
"NC",
LocalAction.DeployableMapIcon(
ValidPlanetSideGUID(0),
DeploymentAction.Dismiss,
- DeployableInfo(ValidPlanetSideGUID(1), DeployableIcon.DisruptorMine, Vector3.Zero, ValidPlanetSideGUID(0))
+ DeployableInfo(ValidPlanetSideGUID(1), DeployableIcon.HEMine, Vector3.Zero, ValidPlanetSideGUID(0))
)
) => ;
case _ => assert(false, "")
}
- eventMsgs(1) match {
- case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(1), _, Service.defaultPlayerGUID, _)) => ;
+ eventMsgs(3) match {
+ case AvatarServiceMessage(
+ "test",
+ AvatarAction.Destroy(
+ ValidPlanetSideGUID(1),
+ ValidPlanetSideGUID(3),
+ ValidPlanetSideGUID(0),
+ Vector3.Zero
+ )
+ ) => ;
case _ => assert(false, "")
}
- assert(j_mine.Destroyed)
+ assert(mine.Destroyed)
}
}
}
diff --git a/src/test/scala/objects/DeploymentTest.scala b/src/test/scala/objects/DeploymentTest.scala
index ca83ddc18..0b86da3f9 100644
--- a/src/test/scala/objects/DeploymentTest.scala
+++ b/src/test/scala/objects/DeploymentTest.scala
@@ -65,57 +65,59 @@ class DeploymentBehavior2Test extends ActorTest {
assert(obj.DeploymentState == DriveState.Mobile)
//to Deploying
obj.Actor.tell(Deployment.TryDeploymentChange(DriveState.Deploying), probe.ref)
- val reply1a = probe.receiveOne(Duration.create(500, "ms"))
- assert(reply1a match {
- case Deployment.CanDeploy(_, DriveState.Deploying) => true
- case _ => false
- })
- val reply1b = eventsProbe.receiveOne(Duration.create(500, "ms"))
- assert(reply1b match {
+ val reply1 = probe.receiveN(2, Duration.create(2000, "ms"))
+ val reply2 = eventsProbe.receiveN(2, Duration.create(2000, "ms"))
+ reply1.head match {
+ case Deployment.CanDeploy(_, DriveState.Deploying) => ()
+ case _ => assert(false, "")
+ }
+ reply2.head match {
case VehicleServiceMessage(
- "test",
- VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deploying, 0, false, Vector3.Zero)
- ) =>
- true
- case _ => false
- })
+ "test",
+ VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deploying, 0, false, Vector3.Zero)
+ ) => ()
+ case _ => assert(false, "")
+ }
//to Deployed
- val reply2 = eventsProbe.receiveOne(Duration.create(500, "ms"))
- assert(reply2 match {
+ reply1(1) match {
+ case Deployment.CanDeploy(_, DriveState.Deployed) => ()
+ case _ => assert(false, "")
+ }
+ reply2(1) match {
case VehicleServiceMessage(
- "test",
- VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deployed, 0, false, Vector3.Zero)
- ) =>
- true
- case _ => false
- })
+ "test",
+ VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deployed, 0, false, Vector3.Zero)
+ ) => ()
+ case _ => assert(false, "")
+ }
assert(obj.DeploymentState == DriveState.Deployed)
//to Undeploying
obj.Actor.tell(Deployment.TryDeploymentChange(DriveState.Undeploying), probe.ref)
- val reply3a = probe.receiveOne(Duration.create(500, "ms"))
- assert(reply3a match {
- case Deployment.CanUndeploy(_, DriveState.Undeploying) => true
- case _ => false
- })
- val reply3b = eventsProbe.receiveOne(Duration.create(500, "ms"))
- assert(reply3b match {
+ val reply3 = probe.receiveN(2, Duration.create(2000, "ms"))
+ val reply4 = eventsProbe.receiveN(2, Duration.create(2000, "ms"))
+ reply3.head match {
+ case Deployment.CanUndeploy(_, DriveState.Undeploying) => ()
+ case _ => assert(false, "")
+ }
+ reply4.head match {
case VehicleServiceMessage(
- "test",
- VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Undeploying, 0, false, Vector3.Zero)
- ) =>
- true
- case _ => false
- })
+ "test",
+ VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Undeploying, 0, false, Vector3.Zero)
+ ) => ()
+ case _ => assert(false, "")
+ }
//to Mobile
- val reply4 = eventsProbe.receiveOne(Duration.create(500, "ms"))
- assert(reply4 match {
+ reply3(1) match {
+ case Deployment.CanUndeploy(_, DriveState.Mobile) => ()
+ case _ => assert(false, "")
+ }
+ reply4(1) match {
case VehicleServiceMessage(
- "test",
- VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Mobile, 0, false, Vector3.Zero)
- ) =>
- true
- case _ => false
- })
+ "test",
+ VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Mobile, 0, false, Vector3.Zero)
+ ) => ()
+ case _ => assert(false, "")
+ }
assert(obj.DeploymentState == DriveState.Mobile)
}
}
@@ -131,57 +133,59 @@ class DeploymentBehavior3Test extends ActorTest {
assert(obj.DeploymentState == DriveState.Mobile)
//to Deploying
obj.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), probe.ref)
- val reply1a = probe.receiveOne(Duration.create(500, "ms"))
- assert(reply1a match {
- case Deployment.CanDeploy(_, DriveState.Deploying) => true
- case _ => false
- })
- val reply1b = eventsProbe.receiveOne(Duration.create(500, "ms"))
- assert(reply1b match {
+ val reply1 = probe.receiveN(2, Duration.create(2000, "ms"))
+ val reply2 = eventsProbe.receiveN(2, Duration.create(2000, "ms"))
+ reply1.head match {
+ case Deployment.CanDeploy(_, DriveState.Deploying) => ()
+ case _ => assert(false, "")
+ }
+ reply2.head match {
case VehicleServiceMessage(
- "test",
- VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deploying, 0, false, Vector3.Zero)
- ) =>
- true
- case _ => false
- })
+ "test",
+ VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deploying, 0, false, Vector3.Zero)
+ ) => ()
+ case _ => assert(false, "")
+ }
//to Deployed
- val reply2 = eventsProbe.receiveOne(Duration.create(500, "ms"))
- assert(reply2 match {
+ reply1(1) match {
+ case Deployment.CanDeploy(_, DriveState.Deployed) => ()
+ case _ => assert(false, "")
+ }
+ reply2(1) match {
case VehicleServiceMessage(
- "test",
- VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deployed, 0, false, Vector3.Zero)
- ) =>
- true
- case _ => false
- })
+ "test",
+ VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deployed, 0, false, Vector3.Zero)
+ ) => ()
+ case _ => assert(false, "")
+ }
assert(obj.DeploymentState == DriveState.Deployed)
//to Undeploying
obj.Actor.tell(Deployment.TryUndeploy(DriveState.Undeploying), probe.ref)
- val reply3a = probe.receiveOne(Duration.create(500, "ms"))
- assert(reply3a match {
- case Deployment.CanUndeploy(_, DriveState.Undeploying) => true
- case _ => false
- })
- val reply3b = eventsProbe.receiveOne(Duration.create(500, "ms"))
- assert(reply3b match {
+ val reply3 = probe.receiveN(2, Duration.create(2000, "ms"))
+ val reply4 = eventsProbe.receiveN(2, Duration.create(2000, "ms"))
+ reply3.head match {
+ case Deployment.CanUndeploy(_, DriveState.Undeploying) => ()
+ case _ => assert(false, "")
+ }
+ reply4.head match {
case VehicleServiceMessage(
- "test",
- VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Undeploying, 0, false, Vector3.Zero)
- ) =>
- true
- case _ => false
- })
+ "test",
+ VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Undeploying, 0, false, Vector3.Zero)
+ ) => ()
+ case _ => assert(false, "")
+ }
//to Mobile
- val reply4 = eventsProbe.receiveOne(Duration.create(500, "ms"))
- assert(reply4 match {
+ reply3(1) match {
+ case Deployment.CanUndeploy(_, DriveState.Mobile) => ()
+ case _ => assert(false, "")
+ }
+ reply4(1) match {
case VehicleServiceMessage(
- "test",
- VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Mobile, 0, false, Vector3.Zero)
- ) =>
- true
- case _ => false
- })
+ "test",
+ VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Mobile, 0, false, Vector3.Zero)
+ ) => ()
+ case _ => assert(false, "")
+ }
assert(obj.DeploymentState == DriveState.Mobile)
}
}
@@ -195,16 +199,18 @@ class DeploymentBehavior4Test extends ActorTest {
obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deployed)
val reply1 = receiveOne(Duration.create(100, "ms"))
- assert(reply1.isInstanceOf[Deployment.CanNotChangeDeployment])
- assert(reply1.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj)
- assert(reply1.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Deployed)
+ reply1 match {
+ case Deployment.CanNotChangeDeployment(_, DriveState.Deployed, _) => ()
+ case _ => assert(false, "")
+ }
assert(obj.DeploymentState == DriveState.Mobile)
obj.Actor ! Deployment.TryDeploy(DriveState.Deployed)
val reply2 = receiveOne(Duration.create(100, "ms"))
- assert(reply2.isInstanceOf[Deployment.CanNotChangeDeployment])
- assert(reply2.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj)
- assert(reply2.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Deployed)
+ reply2 match {
+ case Deployment.CanNotChangeDeployment(_, DriveState.Deployed, _) => ()
+ case _ => assert(false, "")
+ }
assert(obj.DeploymentState == DriveState.Mobile)
}
}
@@ -215,18 +221,22 @@ class DeploymentBehavior5Test extends ActorTest {
"not deploy to an undeploy state" in {
val obj = DeploymentTest.SetUpAgent
assert(obj.DeploymentState == DriveState.Mobile)
- obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deploying)
- receiveOne(Duration.create(100, "ms")) //consume
- obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deployed)
- receiveOne(Duration.create(100, "ms")) //consume
- assert(obj.DeploymentState == DriveState.Deployed)
+
+ obj.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
+ val reply1 = receiveOne(Duration.create(100, "ms"))
+ reply1 match {
+ case Deployment.CanNotChangeDeployment(_, DriveState.Undeploying, _) => ()
+ case _ => assert(false, "")
+ }
+ assert(obj.DeploymentState == DriveState.Mobile)
obj.Actor ! Deployment.TryDeploy(DriveState.Undeploying)
- val reply = receiveOne(Duration.create(100, "ms"))
- assert(reply.isInstanceOf[Deployment.CanNotChangeDeployment])
- assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj)
- assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Undeploying)
- assert(obj.DeploymentState == DriveState.Deployed)
+ val reply2 = receiveOne(Duration.create(100, "ms"))
+ reply2 match {
+ case Deployment.CanNotChangeDeployment(_, DriveState.Undeploying, _) => ()
+ case _ => assert(false, "")
+ }
+ assert(obj.DeploymentState == DriveState.Mobile)
}
}
}
@@ -235,14 +245,24 @@ class DeploymentBehavior6Test extends ActorTest {
"Deployment" should {
"not undeploy to a deploy state" in {
val obj = DeploymentTest.SetUpAgent
- assert(obj.DeploymentState == DriveState.Mobile)
+ obj.DeploymentState = DriveState.Deployed
+ assert(obj.DeploymentState == DriveState.Deployed)
+
+ obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deploying)
+ val reply1 = receiveOne(Duration.create(100, "ms"))
+ reply1 match {
+ case Deployment.CanNotChangeDeployment(_, DriveState.Deploying, _) => ()
+ case _ => assert(false, "")
+ }
+ assert(obj.DeploymentState == DriveState.Deployed)
obj.Actor ! Deployment.TryUndeploy(DriveState.Deploying)
- val reply = receiveOne(Duration.create(100, "ms"))
- assert(reply.isInstanceOf[Deployment.CanNotChangeDeployment])
- assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj)
- assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Deploying)
- assert(obj.DeploymentState == DriveState.Mobile)
+ val reply2 = receiveOne(Duration.create(100, "ms"))
+ reply2 match {
+ case Deployment.CanNotChangeDeployment(_, DriveState.Deploying, _) => ()
+ case _ => assert(false, "")
+ }
+ assert(obj.DeploymentState == DriveState.Deployed)
}
}
}
diff --git a/src/test/scala/objects/InteractsWithZoneEnvironmentTest.scala b/src/test/scala/objects/InteractsWithZoneEnvironmentTest.scala
index 23d2765e1..19bd9eeba 100644
--- a/src/test/scala/objects/InteractsWithZoneEnvironmentTest.scala
+++ b/src/test/scala/objects/InteractsWithZoneEnvironmentTest.scala
@@ -16,9 +16,9 @@ import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire, Plan
import scala.concurrent.duration._
class InteractsWithZoneEnvironmentTest extends ActorTest {
- val pool1: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(5, 10, 10, 0, 0))
- val pool2: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(5, 10, 15, 5, 10))
- val pool3: Pool = Pool(EnvironmentAttribute.Lava, DeepSquare(5, 15, 10, 10, 5))
+ val pool1: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(3, 2, 2, 0, 0))
+ val pool2: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(3, 4, 2, 2, 0))
+ val pool3: Pool = Pool(EnvironmentAttribute.Lava, DeepSquare(3, 2, 4, 0, 2))
val zoneEvents: TestProbe = TestProbe()
val testZone: Zone = {
val testMap = new ZoneMap(name = "test-map") {
@@ -38,27 +38,26 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
val obj = InteractsWithZoneEnvironmentTest.testObject()
obj.Zone = testZone
obj.Actor = testProbe.ref
- obj.Position = Vector3(0,0,50)
+ obj.Position = Vector3(10,10,0)
obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds)
}
- "acknowledge interaction when moved into the critical region of a registered environment object (just once)" in {
+ "acknowledge interaction when moved into the critical region of a registered environment object" in {
val testProbe = TestProbe()
val obj = InteractsWithZoneEnvironmentTest.testObject()
obj.Zone = testZone
obj.Actor = testProbe.ref
- obj.Position = Vector3(1,1,2)
+ obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions()
- val msg = testProbe.receiveOne(max = 250 milliseconds)
- assert(
- msg match {
- case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
- case _ => false
- }
- )
+ val msg = testProbe.receiveOne(4.seconds)
+ msg match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, s"$msg")
+ }
+
obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds)
}
@@ -69,54 +68,48 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Zone = testZone
obj.Actor = testProbe.ref
- obj.Position = Vector3(1,1,2)
+ obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions()
- val msg1 = testProbe.receiveOne(max = 250 milliseconds)
- assert(
- msg1 match {
- case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
- case _ => false
- }
- )
-
- obj.Position = Vector3(1,1,50)
+ val msg1 = testProbe.receiveOne(4.seconds)
+ msg1 match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
+
+ obj.Position = Vector3(1,1,5)
obj.zoneInteractions()
- val msg2 = testProbe.receiveOne(max = 250 milliseconds)
- assert(
- msg2 match {
- case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
- case _ => false
- }
- )
+ val msg2 = testProbe.receiveOne(4.seconds)
+ msg2 match {
+ case RespondsToZoneEnvironment.StopTimer(EnvironmentAttribute.Water) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
+
obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds)
}
- "transition between two different critical regions when the regions that the same attribute" in {
+ "transition between two different critical regions when the regions have the same attribute" in {
val testProbe = TestProbe()
val obj = InteractsWithZoneEnvironmentTest.testObject()
obj.Zone = testZone
obj.Actor = testProbe.ref
- obj.Position = Vector3(7,7,2)
+ obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions()
- val msg1 = testProbe.receiveOne(max = 250 milliseconds)
- assert(
- msg1 match {
- case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
- case _ => false
- }
- )
-
- obj.Position = Vector3(12,7,2)
+ val msg1 = testProbe.receiveOne(4.seconds)
+ msg1 match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
+
+ obj.Position = Vector3(1, 3, 2.7f)
obj.zoneInteractions()
- val msg2 = testProbe.receiveOne(max = 250 milliseconds)
- assert(
- msg2 match {
- case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
- case _ => false
- }
- )
+ val msg2 = testProbe.receiveOne(4.seconds)
+ msg2 match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ => assert(false, "")
+ }
+ testProbe.expectNoMessage()
}
"transition between two different critical regions when the regions have different attributes" in {
@@ -125,37 +118,33 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Zone = testZone
obj.Actor = testProbe.ref
- obj.Position = Vector3(7,7,2)
+ obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions()
- val msg1 = testProbe.receiveOne(max = 250 milliseconds)
- assert(
- msg1 match {
- case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
- case _ => false
- }
- )
-
- obj.Position = Vector3(7,12,2)
+ val msg1 = testProbe.receiveOne(4.seconds)
+ msg1 match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
+
+ obj.Position = Vector3(3, 1, 2.7f)
obj.zoneInteractions()
- val msgs = testProbe.receiveN(3, max = 250 milliseconds)
- assert(
- msgs.head match {
- case Vitality.Damage(_) => true
- case _ => false
- }
- )
- assert(
- msgs(1) match {
- case AuraEffectBehavior.StartEffect(Aura.Fire, _) => true
- case _ => false
- }
- )
- assert(
- msgs(2) match {
- case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Lava, _, _, _) => true
- case _ => false
- }
- )
+ val msgs = testProbe.receiveN(4, 4.seconds)
+ msgs.head match {
+ case RespondsToZoneEnvironment.StopTimer(EnvironmentAttribute.Water) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
+ msgs(1) match {
+ case Vitality.Damage(_) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
+ msgs(2) match {
+ case AuraEffectBehavior.StartEffect(Aura.Fire, _) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
+ msgs(3) match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Lava, _, _, _) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
}
}
@@ -165,24 +154,21 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Zone = testZone
obj.Actor = testProbe.ref
- obj.Position = Vector3(1,1,2)
+ obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions()
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
- assert(
- msg1 match {
- case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
- case _ => false
- }
- )
+ msg1 match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
obj.allowInteraction = false
val msg2 = testProbe.receiveOne(max = 250 milliseconds)
- assert(
- msg2 match {
- case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
- case _ => false
- }
- )
+ msg2 match {
+ case RespondsToZoneEnvironment.StopTimer(EnvironmentAttribute.Water) => true
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
+
obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds)
}
@@ -194,22 +180,22 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Actor = testProbe.ref
obj.allowInteraction = false
- obj.Position = Vector3(1,1,2)
+ obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds)
obj.allowInteraction = true
val msg1 = testProbe.receiveOne(max = 250 milliseconds)
- assert(
- msg1 match {
- case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
- case _ => false
- }
- )
+ msg1 match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
}
}
object InteractsWithZoneEnvironmentTest {
+ val fail: Boolean = false
+
def testObject(): PlanetSideServerObject with InteractsWithZone = {
val p = new Player(Avatar(1, "test", PlanetSideEmpire.VS, CharacterSex.Male, 1, CharacterVoice.Mute))
p.GUID = PlanetSideGUID(1)
diff --git a/src/test/scala/objects/ResourceSiloTest.scala b/src/test/scala/objects/ResourceSiloTest.scala
index 401bdc7ff..aa56739af 100644
--- a/src/test/scala/objects/ResourceSiloTest.scala
+++ b/src/test/scala/objects/ResourceSiloTest.scala
@@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever
package objects
-import akka.actor.Props
+import akka.actor.{ActorRef, Props}
import akka.testkit.TestProbe
import base.{ActorTest, FreedContextActorTest}
import net.psforever.actors.zone.BuildingActor
@@ -63,7 +63,7 @@ class ResourceSiloTest extends Specification {
PlanetSideGUID(0),
PlanetSideGUID(2),
0L,
- false,
+ unk3 = false,
Vector3(0f, 0f, 0f),
Vector3(0f, 0f, 0f),
0,
@@ -77,11 +77,11 @@ class ResourceSiloTest extends Specification {
}
class ResourceSiloControlStartupTest extends ActorTest {
- val obj = ResourceSilo()
+ val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
- val buildingEvents = TestProbe("test-building-events")
+ val buildingEvents: TestProbe = TestProbe("test-building-events")
obj.Owner =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) {
Actor = buildingEvents.ref
@@ -97,11 +97,11 @@ class ResourceSiloControlStartupTest extends ActorTest {
}
class ResourceSiloControlStartupMessageNoneTest extends ActorTest {
- val obj = ResourceSilo()
+ val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
- val buildingEvents = TestProbe("test-building-events")
+ val buildingEvents: TestProbe = TestProbe("test-building-events")
obj.Owner =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) {
Actor = buildingEvents.ref
@@ -123,11 +123,11 @@ class ResourceSiloControlStartupMessageNoneTest extends ActorTest {
}
class ResourceSiloControlStartupMessageSomeTest extends ActorTest {
- val obj = ResourceSilo()
+ val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
- val buildingEvents = TestProbe("test-building-events")
+ val buildingEvents: TestProbe = TestProbe("test-building-events")
obj.Owner =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) {
Actor = buildingEvents.ref
@@ -154,19 +154,19 @@ class ResourceSiloControlUseTest extends FreedContextActorTest {
expectNoMessage(1000 milliseconds)
var buildingMap = new TrieMap[Int, Building]()
val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
- val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
- val ant = Vehicle(GlobalDefinitions.ant)
+ val player: Player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
+ val ant: Vehicle = Vehicle(GlobalDefinitions.ant)
val silo = new ResourceSilo()
- val catchall = new TestProbe(system).ref
- val zone = new Zone("test", new ZoneMap("test-map"), 0) {
- override def SetupNumberPools() = {}
+ val catchall: ActorRef = new TestProbe(system).ref
+ val zone: Zone = new Zone("test", new ZoneMap("test-map"), 0) {
+ override def SetupNumberPools(): Unit = {}
GUID(guid)
- override def AvatarEvents = catchall
- override def LocalEvents = catchall
- override def VehicleEvents = catchall
- override def Activity = catchall
- override def Vehicles = List(ant)
- override def Buildings = { buildingMap.toMap }
+ override def AvatarEvents: ActorRef = catchall
+ override def LocalEvents: ActorRef = catchall
+ override def VehicleEvents: ActorRef = catchall
+ override def Activity: ActorRef = catchall
+ override def Vehicles: List[Vehicle] = List(ant)
+ override def Buildings: Map[Int, Building] = { buildingMap.toMap }
}
val building = new Building(
name = "integ-fac-test-building",
@@ -186,7 +186,7 @@ class ResourceSiloControlUseTest extends FreedContextActorTest {
guid.register(silo, number = 5)
guid.register(building, number = 6)
- val maxNtuCap = ant.Definition.MaxNtuCapacitor
+ val maxNtuCap: Float = ant.Definition.MaxNtuCapacitor
player.Spawn()
ant.NtuCapacitor = maxNtuCap
val probe = new TestProbe(system)
@@ -201,19 +201,18 @@ class ResourceSiloControlUseTest extends FreedContextActorTest {
"Resource silo" should {
"respond when being used" in {
expectNoMessage(1 seconds)
- silo.Actor ! CommonMessages.Use(ResourceSiloTest.player)
-
- val reply = probe.receiveOne(2000 milliseconds)
- assert(reply match {
- case TransferBehavior.Discharging(Ntu.Nanites) => true
- case _ => false
- })
+ silo.Actor ! CommonMessages.Use(ResourceSiloTest.player, Some(ant))
+ val reply = probe.receiveOne(3000 milliseconds)
+ reply match {
+ case TransferBehavior.Discharging(Ntu.Nanites) => ()
+ case _ => assert(ResourceSiloTest.fail, "")
+ }
}
}
}
class ResourceSiloControlNtuWarningTest extends ActorTest {
- val obj = ResourceSilo()
+ val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
@@ -223,7 +222,7 @@ class ResourceSiloControlNtuWarningTest extends ActorTest {
}
obj.Owner.GUID = PlanetSideGUID(6)
- val zoneEvents = TestProbe("zone-events")
+ val zoneEvents: TestProbe = TestProbe("zone-events")
zone.AvatarEvents = zoneEvents.ref
obj.Actor ! Service.Startup()
obj.Actor ! ResourceSilo.UpdateChargeLevel(-obj.NtuCapacitor)
@@ -237,7 +236,7 @@ class ResourceSiloControlNtuWarningTest extends ActorTest {
val reply = zoneEvents.receiveOne(5000 milliseconds)
reply match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => ;
- case _ => assert(false, s"$reply is wrong")
+ case _ => assert(ResourceSiloTest.fail, s"$reply is wrong")
}
assert(!obj.LowNtuWarningOn)
}
@@ -245,7 +244,7 @@ class ResourceSiloControlNtuWarningTest extends ActorTest {
}
class ResourceSiloControlUpdate1Test extends ActorTest {
- val obj = ResourceSilo()
+ val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
@@ -253,8 +252,8 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
bldg.GUID = PlanetSideGUID(6)
obj.Owner = bldg
- val zoneEvents = TestProbe("zone-events")
- val buildingEvents = TestProbe("building-events")
+ val zoneEvents: TestProbe = TestProbe("zone-events")
+ val buildingEvents: TestProbe = TestProbe("building-events")
zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref
obj.Actor ! Service.Startup()
@@ -276,12 +275,12 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
assert(obj.CapacitorDisplay == 3)
reply1.head match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(1), 45, 3)) => ;
- case _ => assert(false, s"$reply1 is wrong")
+ case _ => assert(ResourceSiloTest.fail, s"$reply1 is wrong")
}
assert(reply2.isInstanceOf[BuildingActor.MapUpdate], s"$reply2 is wrong")
reply1(1) match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => ;
- case _ => assert(false, s"${reply1(1)} is wrong")
+ case _ => assert(ResourceSiloTest.fail, s"${reply1(1)} is wrong")
}
assert(!obj.LowNtuWarningOn)
}
@@ -289,7 +288,7 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
}
class ResourceSiloControlUpdate2Test extends ActorTest {
- val obj = ResourceSilo()
+ val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
@@ -297,8 +296,8 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
bldg.GUID = PlanetSideGUID(6)
obj.Owner = bldg
- val zoneEvents = TestProbe("zone-events")
- val buildingEvents = TestProbe("building-events")
+ val zoneEvents: TestProbe = TestProbe("zone-events")
+ val buildingEvents: TestProbe = TestProbe("building-events")
zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref
obj.Actor ! Service.Startup()
@@ -320,12 +319,12 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
assert(obj.CapacitorDisplay == 2)
reply1.head match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(1), 45, 2)) => ;
- case _ => assert(false, s"$reply1 is wrong")
+ case _ => assert(ResourceSiloTest.fail, s"$reply1 is wrong")
}
assert(reply2.isInstanceOf[BuildingActor.MapUpdate])
reply1(1) match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => ;
- case _ => assert(false, s"${reply1(1)} is wrong")
+ case _ => assert(ResourceSiloTest.fail, s"${reply1(1)} is wrong")
}
assert(!obj.LowNtuWarningOn)
}
@@ -333,7 +332,7 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
}
class ResourceSiloControlNoUpdateTest extends ActorTest {
- val obj = ResourceSilo()
+ val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
@@ -341,8 +340,8 @@ class ResourceSiloControlNoUpdateTest extends ActorTest {
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
bldg.GUID = PlanetSideGUID(6)
obj.Owner = bldg
- val zoneEvents = TestProbe("zone-events")
- val buildingEvents = TestProbe("building-events")
+ val zoneEvents: TestProbe = TestProbe("zone-events")
+ val buildingEvents: TestProbe = TestProbe("building-events")
zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref
obj.Actor ! Service.Startup()
@@ -371,7 +370,9 @@ class ResourceSiloControlNoUpdateTest extends ActorTest {
}
object ResourceSiloTest {
- val player = Player(
+ val player: Player = Player(
Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute).copy(stamina = 0)
)
+
+ val fail: Boolean = false
}
diff --git a/src/test/scala/objects/TelepadRouterTest.scala b/src/test/scala/objects/TelepadRouterTest.scala
index 8c52c5ae0..7eff27c37 100644
--- a/src/test/scala/objects/TelepadRouterTest.scala
+++ b/src/test/scala/objects/TelepadRouterTest.scala
@@ -15,7 +15,6 @@ import net.psforever.objects.vehicles.{Utility, UtilityType}
import net.psforever.objects.zones.{Zone, ZoneDeployableActor, ZoneMap}
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import net.psforever.packet.game._
-import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.types.{DriveState, PlanetSideGUID, Vector3}
@@ -253,7 +252,7 @@ class TelepadDeployableResponseFromRouterTest extends FreedContextActorTest {
val deploymentProbe = new TestProbe(system)
router.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), deploymentProbe.ref)
eventsProbe.receiveN(10, 10.seconds) //flush all messages related to deployment
- deploymentProbe.receiveOne(2.seconds) //CanDeploy
+ deploymentProbe.receiveN(2, 10.seconds) //CanDeploy
deploymentProbe.expectNoMessage(2.seconds) //intentional delay
assert(internal.Active, "link to router test - router internals not active when expected")
assert(!telepad.Active, "link to router test - telepad active earlier than intended (2)")
diff --git a/src/test/scala/objects/VehicleControlTest.scala b/src/test/scala/objects/VehicleControlTest.scala
index b756d25e0..9b9b7ca79 100644
--- a/src/test/scala/objects/VehicleControlTest.scala
+++ b/src/test/scala/objects/VehicleControlTest.scala
@@ -14,17 +14,17 @@ 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, RespondsToZoneEnvironment}
+import net.psforever.objects.serverobject.environment.interaction.{InteractingWithEnvironment, RespondsToZoneEnvironment}
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.sourcing.VehicleSource
import net.psforever.objects.vehicles.VehicleLockState
import net.psforever.objects.vehicles.control.VehicleControl
+import net.psforever.objects.vehicles.interaction.WithWater
import net.psforever.objects.vital.{ShieldCharge, SpawningActivity, Vitality}
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game._
import net.psforever.services.ServiceManager
-import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types._
@@ -174,15 +174,17 @@ class VehicleControlPrepareForDeletionPassengerTest extends ActorTest {
//}
class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActorTest {
- val vehicleProbe = new TestProbe(system)
- val catchall = new TestProbe(system)
+ val eventsProbe = new TestProbe(system)
+ val cargoProbe = new TestProbe(system)
val guid = new NumberPoolHub(new MaxNumberSource(10))
ServiceManager.boot
val zone = new Zone("test", new ZoneMap("test"), 0) {
GUID(guid)
override def SetupNumberPools(): Unit = {}
- override def VehicleEvents = vehicleProbe.ref
+ override def AvatarEvents = eventsProbe.ref
+ override def LocalEvents = eventsProbe.ref
+ override def VehicleEvents = eventsProbe.ref
}
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
// crappy workaround but without it the zone doesn't get initialized in time
@@ -191,7 +193,6 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.Faction = PlanetSideEmpire.TR
vehicle.Zone = zone
- val cargoProbe = new TestProbe(system)
vehicle.Actor = cargoProbe.ref
val lodestar = Vehicle(GlobalDefinitions.lodestar)
lodestar.Faction = PlanetSideEmpire.TR
@@ -220,39 +221,45 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
"if with mounted cargo, eject it when marked for deconstruction" in {
lodestar.Actor ! Vehicle.Deconstruct()
- val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds)
- vehicle_msg.head match {
- case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(4), 4, true, PlanetSideGUID(2))) => ;
+ val vehicleMsgs = eventsProbe.receiveN(6, 10.seconds)
+ val cargoMsgs = cargoProbe.receiveN(1, 1.seconds)
+ vehicleMsgs.head match {
+ case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(4), 4, true, PlanetSideGUID(2))) => ()
case _ =>
- assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-1: ${vehicle_msg(5)}")
+ assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-1: ${vehicleMsgs.head}")
}
assert(player2.VehicleSeated.isEmpty)
assert(lodestar.Seats(0).occupant.isEmpty)
//cargo dismounting messages
- vehicle_msg(1) match {
- case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ;
+ vehicleMsgs(1) match {
+ case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ()
case _ =>
- assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-2: ${vehicle_msg.head}")
+ assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-2: ${vehicleMsgs(1)}")
}
- vehicle_msg(2) match {
- case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ;
+ vehicleMsgs(2) match {
+ case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ()
case _ =>
- assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-3: ${vehicle_msg(1)}")
+ assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-3: ${vehicleMsgs(2)}")
}
- vehicle_msg(3) match {
+ vehicleMsgs(3) match {
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0))) => ;
case _ =>
- assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-4: ${vehicle_msg(2)}")
+ assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-4: ${vehicleMsgs(3)}")
+ }
+ vehicleMsgs(4) match {
+ case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ()
+ case _ =>
+ assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-5: ${vehicleMsgs(4)}")
}
- vehicle_msg(4) match {
- case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ;
+ vehicleMsgs(5) match {
+ case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ()
case _ =>
- assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-5: ${vehicle_msg(3)}")
+ assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-6: ${vehicleMsgs(5)}")
}
- vehicle_msg(5) match {
- case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ;
+ cargoMsgs.head match {
+ case Vehicle.Deconstruct(_) => ()
case _ =>
- assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-6: ${vehicle_msg(4)}")
+ assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-7: ${cargoMsgs.head}")
}
}
}
@@ -393,7 +400,7 @@ class VehicleControlMountingOwnedLockedDriverSeatTest extends ActorTest {
val player1 = Player(VehicleTest.avatar1)
player1.GUID = PlanetSideGUID(1)
- val player2 = Player(VehicleTest.avatar1)
+ val player2 = Player(VehicleTest.avatar1.copy(basic = VehicleTest.avatar1.basic.copy(faction = PlanetSideEmpire.NC)))
player2.GUID = PlanetSideGUID(2)
"Vehicle Control" should {
@@ -578,13 +585,13 @@ class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest {
// }
//}
-class VehicleControlInteractWithWaterPartialTest extends ActorTest {
- val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
- val player1 =
- Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
+class VehicleControlInteractWithWaterWadingTest extends ActorTest {
val playerProbe = TestProbe()
+ val vehicleProbe = TestProbe()
+ val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
+ val player1 = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val guid = new NumberPoolHub(new MaxNumberSource(15))
- val pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
+ val pool = Pool(EnvironmentAttribute.Water, DeepSquare(5, 2, 2, 0, 0))
val zone = new Zone(
id = "test-zone",
new ZoneMap(name = "test-map") {
@@ -609,36 +616,35 @@ class VehicleControlInteractWithWaterPartialTest extends ActorTest {
vehicle.Seats(0).mount(player1)
player1.VehicleSeated = vehicle.GUID
player1.Actor = playerProbe.ref
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control")
+ vehicle.Actor = vehicleProbe.ref
"VehicleControl" should {
- "causes disability when the vehicle drives too deep in water (check driver messaging)" in {
- vehicle.Position = Vector3(5,5,-3) //right in the pool
- vehicle.zoneInteractions() //trigger
+ "report when the vehicle starts treading water" in {
+ vehicle.Position = Vector3(1, 1, 6)
+ vehicle.zoneInteractions()
+ vehicleProbe.expectNoMessage(2.seconds)
+ playerProbe.expectNoMessage()
- val msg_drown = playerProbe.receiveOne(250 milliseconds)
- assert(
- msg_drown match {
- case InteractingWithEnvironment(
- p2,
- Some(OxygenStateTarget(PlanetSideGUID(2), _, OxygenState.Suffocation, 100f))
- ) => (p2 eq pool)
- case _ => false
- }
- )
+ vehicle.Position = Vector3(1, 1, 4f)
+ vehicle.zoneInteractions()
+ val vehicleMsgs = vehicleProbe.receiveN(1, 5.seconds)
+ vehicleMsgs.head match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ =>
+ assert(false, "")
+ }
+ playerProbe.expectNoMessage()
}
}
}
-class VehicleControlInteractWithWaterTest extends ActorTest {
- val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
- val player1 =
- Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
- val avatarProbe = TestProbe()
+class VehicleControlInteractWithWaterStartDrowningTest extends ActorTest {
val playerProbe = TestProbe()
val vehicleProbe = TestProbe()
+ val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
+ val player1 = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val guid = new NumberPoolHub(new MaxNumberSource(15))
- val pool = Pool(EnvironmentAttribute.Water, DeepSquare(10, 10, 10, 0, 0))
+ val pool = Pool(EnvironmentAttribute.Water, DeepSquare(5, 2, 2, 0, 0))
val zone = new Zone(
id = "test-zone",
new ZoneMap(name = "test-map") {
@@ -650,53 +656,155 @@ class VehicleControlInteractWithWaterTest extends ActorTest {
GUID(guid)
override def LivePlayers = List(player1)
override def Vehicles = List(vehicle)
- override def AvatarEvents = avatarProbe.ref
- override def VehicleEvents = avatarProbe.ref
-
- this.actor = new TestProbe(system).ref.toTyped[ZoneActor.Command]
}
zone.blockMap.addTo(vehicle)
zone.blockMap.addTo(pool)
guid.register(player1, 1)
guid.register(vehicle, 2)
- guid.register(player1.avatar.locker, 5)
player1.Zone = zone
player1.Spawn()
vehicle.Zone = zone
vehicle.Faction = PlanetSideEmpire.TR
vehicle.Seats(0).mount(player1)
player1.VehicleSeated = vehicle.GUID
- val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
player1.Actor = playerProbe.ref
vehicle.Actor = vehicleProbe.ref
"VehicleControl" should {
- "causes disability when the vehicle drives too deep in water" in {
- vehicle.Position = Vector3(5,5,3) //right in the pool
- vehicle.zoneInteractions() //trigger
+ "report when the vehicle starts drowning" in {
+ vehicle.Position = Vector3(1, 1, 6)
+ vehicle.zoneInteractions()
+ vehicleProbe.expectNoMessage(2.seconds)
+ playerProbe.expectNoMessage()
- val msg_drown = playerProbe.receiveOne(250 milliseconds)
- assert(msg_drown match {
- case InteractingWithEnvironment(body, _) => body eq pool
- case _ => false
- })
- val msg_disable = vehicleProbe.receiveOne(10 seconds)
- assert(msg_disable match {
- case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, VehicleControl.Disable(true)) => true
- case _ => false
- })
+ vehicle.Position = Vector3(1, 1, 0f)
+ vehicle.zoneInteractions()
+ val vehicleMsgs = vehicleProbe.receiveN(3, 5.seconds)
+ val playerMsgs = playerProbe.receiveN(1, 1.seconds)
+ vehicleMsgs.head match {
+ case RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction) => ()
+ case _ =>
+ assert(false, "")
+ }
+ vehicleMsgs(1) match {
+ case RespondsToZoneEnvironment.Timer(WithWater.WaterAction, _, _, _) => ()
+ case _ =>
+ assert(false, "")
+ }
+ vehicleMsgs(2) match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ =>
+ assert(false, "")
+ }
+ playerMsgs.head match {
+ case InteractingWithEnvironment(somePool, Some(OxygenStateTarget(ValidPlanetSideGUID(2), _, OxygenState.Suffocation, 100.0f)))
+ if somePool eq pool => ()
+ case _ =>
+ assert(false, "")
+ }
}
}
}
-class VehicleControlStopInteractWithWaterTest extends ActorTest {
- val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
- val player1 =
- Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
+//class VehicleControlInteractWithWaterStopDrowningTest extends ActorTest {
+// val playerProbe = TestProbe()
+// val vehicleProbe = TestProbe()
+// val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
+// val player1 = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
+// val guid = new NumberPoolHub(new MaxNumberSource(15))
+// val pool = Pool(EnvironmentAttribute.Water, DeepSquare(5, 2, 2, 0, 0))
+// val zone = new Zone(
+// id = "test-zone",
+// new ZoneMap(name = "test-map") {
+// environment = List(pool)
+// },
+// zoneNumber = 0
+// ) {
+// override def SetupNumberPools() = {}
+// GUID(guid)
+// override def LivePlayers = List(player1)
+// override def Vehicles = List(vehicle)
+// }
+// zone.blockMap.addTo(vehicle)
+// zone.blockMap.addTo(pool)
+//
+// guid.register(player1, 1)
+// guid.register(vehicle, 2)
+// player1.Zone = zone
+// player1.Spawn()
+// vehicle.Zone = zone
+// vehicle.Faction = PlanetSideEmpire.TR
+// vehicle.Seats(0).mount(player1)
+// player1.VehicleSeated = vehicle.GUID
+// player1.Actor = playerProbe.ref
+// vehicle.Actor = vehicleProbe.ref
+//
+// "VehicleControl" should {
+// "report when the vehicle stops drowning" in {
+// vehicle.Position = Vector3(1, 1, 6)
+// vehicle.zoneInteractions()
+// vehicleProbe.expectNoMessage(2.seconds)
+// playerProbe.expectNoMessage()
+//
+// vehicle.Position = Vector3(1, 1, 0f)
+// vehicle.zoneInteractions()
+// val vehicleMsgs = vehicleProbe.receiveN(3, 5.seconds)
+// val playerMsgs = playerProbe.receiveN(1, 1.seconds)
+// vehicleMsgs.head match {
+// case RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction) => ()
+// case _ =>
+// assert(false, "")
+// }
+// vehicleMsgs(1) match {
+// case RespondsToZoneEnvironment.Timer(WithWater.WaterAction, _, _, _) => ()
+// case _ =>
+// assert(false, "")
+// }
+// vehicleMsgs(2) match {
+// case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+// case _ =>
+// assert(false, "")
+// }
+// playerMsgs.head match {
+// case InteractingWithEnvironment(somePool, Some(OxygenStateTarget(ValidPlanetSideGUID(2), _, OxygenState.Suffocation, 100.0f)))
+// if somePool eq pool => ()
+// case _ =>
+// assert(false, "")
+// }
+//
+// //escape drowning
+// vehicle.Position = Vector3(1, 1, 4.7f)
+// vehicle.zoneInteractions()
+// val vehicleMsgs2 = vehicleProbe.receiveN(2, 5.seconds)
+// val playerMsgs2 = playerProbe.receiveN(1, 1.seconds)
+// vehicleMsgs2.head match {
+// case RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction) => ()
+// case _ =>
+// assert(false, "")
+// }
+// vehicleMsgs2(1) match {
+// case RespondsToZoneEnvironment.Timer(WithWater.WaterAction, _, _, _) => ()
+// case _ =>
+// assert(false, "")
+// }
+// playerMsgs2.head match {
+// case EscapeFromEnvironment(somePool, Some(OxygenStateTarget(ValidPlanetSideGUID(2), _, OxygenState.Recovery, _)))
+// if somePool eq pool => ()
+// case _ =>
+// assert(false, "")
+// }
+// }
+// }
+//}
+
+class VehicleControlInteractWithWaterStopWadingTest extends ActorTest {
val playerProbe = TestProbe()
+ val vehicleProbe = TestProbe()
+ val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
+ val player1 = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
val guid = new NumberPoolHub(new MaxNumberSource(15))
- val pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 0, 0))
+ val pool = Pool(EnvironmentAttribute.Water, DeepSquare(5, 2, 2, 0, 0))
val zone = new Zone(
id = "test-zone",
new ZoneMap(name = "test-map") {
@@ -721,39 +829,130 @@ class VehicleControlStopInteractWithWaterTest extends ActorTest {
vehicle.Seats(0).mount(player1)
player1.VehicleSeated = vehicle.GUID
player1.Actor = playerProbe.ref
- vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control")
+ vehicle.Actor = vehicleProbe.ref
"VehicleControl" should {
- "stop becoming disabled if the vehicle drives out of the water" in {
- vehicle.Position = Vector3(5,5,-3) //right in the pool
- vehicle.zoneInteractions() //trigger
- val msg_drown = playerProbe.receiveOne(250 milliseconds)
- assert(
- msg_drown match {
- case InteractingWithEnvironment(
- p2,
- Some(OxygenStateTarget(PlanetSideGUID(2), _, OxygenState.Suffocation, 100f))
- ) => (p2 eq pool)
- case _ => false
- }
- )
+ "report when the vehicle stops wading" in {
+ vehicle.Position = Vector3(1, 1, 6)
+ vehicle.zoneInteractions()
+ vehicleProbe.expectNoMessage(2.seconds)
+ playerProbe.expectNoMessage()
- vehicle.Position = Vector3.Zero //that's enough of that
+ vehicle.Position = Vector3(1, 1, 4f)
vehicle.zoneInteractions()
- val msg_recover = playerProbe.receiveOne(250 milliseconds)
- assert(
- msg_recover match {
- case EscapeFromEnvironment(
- p2,
- Some(OxygenStateTarget(PlanetSideGUID(2), _, OxygenState.Recovery, _))
- ) => (p2 eq pool)
- case _ => false
- }
- )
+ val vehicleMsgs = vehicleProbe.receiveN(1, 5.seconds)
+ playerProbe.expectNoMessage()
+ vehicleMsgs.head match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ =>
+ assert(false, "")
+ }
+
+ //stop wading
+ vehicle.Position = Vector3(1, 1, 6f)
+ vehicle.zoneInteractions()
+ val vehicleMsgs2 = vehicleProbe.receiveN(1, 5.seconds)
+ playerProbe.expectNoMessage()
+ vehicleMsgs2.head match {
+ case RespondsToZoneEnvironment.StopTimer(EnvironmentAttribute.Water) => ()
+ case _ =>
+ assert(false, "")
+ }
}
}
}
+//class VehicleControlInteractWithWaterFullStopTest extends ActorTest {
+// val playerProbe = TestProbe()
+// val vehicleProbe = TestProbe()
+// val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
+// val player1 = Player(Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) //guid=1
+// val guid = new NumberPoolHub(new MaxNumberSource(15))
+// val pool = Pool(EnvironmentAttribute.Water, DeepSquare(5, 2, 2, 0, 0))
+// val zone = new Zone(
+// id = "test-zone",
+// new ZoneMap(name = "test-map") {
+// environment = List(pool)
+// },
+// zoneNumber = 0
+// ) {
+// override def SetupNumberPools() = {}
+// GUID(guid)
+// override def LivePlayers = List(player1)
+// override def Vehicles = List(vehicle)
+// }
+// zone.blockMap.addTo(vehicle)
+// zone.blockMap.addTo(pool)
+//
+// guid.register(player1, 1)
+// guid.register(vehicle, 2)
+// player1.Zone = zone
+// player1.Spawn()
+// vehicle.Zone = zone
+// vehicle.Faction = PlanetSideEmpire.TR
+// vehicle.Seats(0).mount(player1)
+// player1.VehicleSeated = vehicle.GUID
+// player1.Actor = playerProbe.ref
+// vehicle.Actor = vehicleProbe.ref
+//
+// "VehicleControl" should {
+// "report when the vehicle stops interacting with water altogether" in {
+// vehicle.Position = Vector3(1, 1, 6)
+// vehicle.zoneInteractions()
+// vehicleProbe.expectNoMessage(2.seconds)
+// playerProbe.expectNoMessage()
+// //wading and drowning
+// vehicle.Position = Vector3(1, 1, 0f)
+// vehicle.zoneInteractions()
+// val vehicleMsgs = vehicleProbe.receiveN(3, 5.seconds)
+// val playerMsgs = playerProbe.receiveN(1, 1.seconds)
+// vehicleMsgs.head match {
+// case RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction) => ()
+// case _ =>
+// assert(false, "")
+// }
+// vehicleMsgs(1) match {
+// case RespondsToZoneEnvironment.Timer(WithWater.WaterAction, _, _, _) => ()
+// case _ =>
+// assert(false, "")
+// }
+// vehicleMsgs(2) match {
+// case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+// case _ =>
+// assert(false, "")
+// }
+// playerMsgs.head match {
+// case InteractingWithEnvironment(somePool, Some(OxygenStateTarget(ValidPlanetSideGUID(2), _, OxygenState.Suffocation, 100.0f)))
+// if somePool eq pool => ()
+// case _ =>
+// assert(false, "")
+// }
+//
+// //escape drowning and wading
+// vehicle.Position = Vector3(1, 1, 6f)
+// vehicle.zoneInteractions()
+// val vehicleMsgs2 = vehicleProbe.receiveN(2, 5.seconds)
+// val playerMsgs2 = playerProbe.receiveN(1, 1.seconds)
+// vehicleMsgs2.head match {
+// case RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction) => ()
+// case _ =>
+// assert(false, "")
+// }
+// vehicleMsgs2(1) match {
+// case RespondsToZoneEnvironment.StopTimer(EnvironmentAttribute.Water) => ()
+// case _ =>
+// assert(false, "")
+// }
+// playerMsgs2.head match {
+// case EscapeFromEnvironment(somePool, Some(OxygenStateTarget(ValidPlanetSideGUID(2), _, OxygenState.Recovery, _)))
+// if somePool eq pool => ()
+// case _ =>
+// assert(false, "")
+// }
+// }
+// }
+//}
+
class VehicleControlInteractWithLavaTest extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
val player1 =