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 bdda54da..890cd27b 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,9 +296,21 @@ 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) {
+ sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong))
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
0
@@ -330,13 +343,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) =>
@@ -365,7 +382,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
drops
) if resolvedPlayerGuid == target =>
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
- sendResponse(PlanetsideAttributeMessage(target, attribute_type = 4, armor))
+ sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
//happening to this player
sendResponse(ObjectHeldMessage(target, Player.HandsDownSlot, unk1=true))
//cleanup
@@ -377,6 +394,7 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
drops.foreach(item => sendResponse(ObjectDeleteMessage(item.obj.GUID, unk1=0)))
//redraw
if (maxhand) {
+ sendResponse(PlanetsideAttributeMessage(target, attribute_type=7, player.Capacitor.toLong))
TaskWorkflow.execute(HoldNewEquipmentUp(player)(
Tool(GlobalDefinitions.MAXArms(subtype, player.Faction)),
slot = 0
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 dc599e01..e701c0a5 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 1eb457f9..ab40f622 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 211cc48e..c2f1a896 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 6d8f9c1a..55d01d2a 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 6b293ed7..5b8ab62d 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/normal/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala
index 43144a11..6d202dc8 100644
--- a/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/normal/WeaponAndProjectileLogic.scala
@@ -554,7 +554,7 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
}
.orElse {
//occasionally, something that is not technically a turret's natural target may be attacked
- sessionLogic.validObject(targetGuid, decorator = "AIDamage/Target")
+ continent.GUID(targetGuid) //AIDamage/Attacker
.collect {
case target: PlanetSideServerObject with FactionAffinity with Vitality =>
sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker")
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 3b7d10ed..a7e5884e 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 f6c3a4a4..2cf441e0 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 10fdb8ed..7ba9cca0 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/spectator/WeaponAndProjectileLogic.scala b/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala
index 7f63dc73..1ebcc1fd 100644
--- a/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala
+++ b/src/main/scala/net/psforever/actors/session/spectator/WeaponAndProjectileLogic.scala
@@ -1,138 +1,28 @@
// Copyright (c) 2024 PSForever
package net.psforever.actors.session.spectator
-import akka.actor.{ActorContext, typed}
-import net.psforever.actors.session.AvatarActor
+import akka.actor.ActorContext
import net.psforever.actors.session.support.{SessionData, WeaponAndProjectileFunctions, WeaponAndProjectileOperations}
import net.psforever.login.WorldSession.{CountGrenades, FindEquipmentStock, FindToolThatUses, RemoveOldEquipmentFromInventory}
-import net.psforever.objects.ballistics.{Projectile, ProjectileQuality}
-import net.psforever.objects.definition.ProjectileDefinition
-import net.psforever.objects.equipment.{ChargeFireModeDefinition, EquipmentSize}
+import net.psforever.objects.ballistics.Projectile
+import net.psforever.objects.equipment.ChargeFireModeDefinition
import net.psforever.objects.inventory.Container
-import net.psforever.objects.serverobject.affinity.FactionAffinity
-import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject}
-import net.psforever.objects.serverobject.doors.InteriorDoorPassage
-import net.psforever.objects.{AmmoBox, BoomerDeployable, BoomerTrigger, DummyExplodingEntity, GlobalDefinitions, OwnableByPlayer, PlanetSideGameObject, SpecialEmp, Tool}
-import net.psforever.objects.serverobject.interior.Sidedness
-import net.psforever.objects.serverobject.mount.Mountable
-import net.psforever.objects.serverobject.turret.auto.{AutomatedTurret, AutomatedTurretBehavior}
-import net.psforever.objects.sourcing.SourceEntry
-import net.psforever.objects.vital.Vitality
-import net.psforever.objects.vital.base.{DamageResolution, DamageType}
-import net.psforever.objects.vital.etc.OicwLilBuddyReason
-import net.psforever.objects.vital.interaction.DamageInteraction
-import net.psforever.objects.vital.projectile.ProjectileReason
-import net.psforever.objects.zones.{Zone, ZoneProjectile}
-import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChainLashMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, InventoryStateMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, UplinkRequestType, UplinkResponse, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
+import net.psforever.objects.serverobject.CommonMessages
+import net.psforever.objects.{AmmoBox, BoomerDeployable, BoomerTrigger, GlobalDefinitions, PlanetSideGameObject, Tool}
+import net.psforever.packet.game.{AIDamage, AvatarGrenadeStateMessage, ChangeAmmoMessage, ChangeFireModeMessage, ChangeFireStateMessage_Start, ChangeFireStateMessage_Stop, HitMessage, InventoryStateMessage, LashMessage, LongRangeProjectileInfoMessage, ProjectileStateMessage, QuantityUpdateMessage, ReloadMessage, SplashHitMessage, UplinkRequest, UplinkRequestType, UplinkResponse, WeaponDelayFireMessage, WeaponDryFireMessage, WeaponFireMessage, WeaponLazeTargetPositionMessage}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
-import net.psforever.types.{PlanetSideGUID, Vector3}
-import net.psforever.util.Config
-
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.concurrent.duration._
+import net.psforever.types.PlanetSideGUID
object WeaponAndProjectileLogic {
def apply(ops: WeaponAndProjectileOperations): WeaponAndProjectileLogic = {
new WeaponAndProjectileLogic(ops, ops.context)
}
-
- /**
- * Does a line segment line intersect with a sphere?
- * This most likely belongs in `Geometry` or `GeometryForm` or somehow in association with the `\objects\geometry\` package.
- * @param start first point of the line segment
- * @param end second point of the line segment
- * @param center center of the sphere
- * @param radius radius of the sphere
- * @return list of all points of intersection, if any
- * @see `Vector3.DistanceSquared`
- * @see `Vector3.MagnitudeSquared`
- */
- private def quickLineSphereIntersectionPoints(
- start: Vector3,
- end: Vector3,
- center: Vector3,
- radius: Float
- ): Iterable[Vector3] = {
- /*
- Algorithm adapted from code found on https://paulbourke.net/geometry/circlesphere/index.html#linesphere,
- because I kept messing up proper substitution of the line formula and the circle formula into the quadratic equation.
- */
- val Vector3(cx, cy, cz) = center
- val Vector3(sx, sy, sz) = start
- val vector = end - start
- //speed our way through a quadratic equation
- val (a, b) = {
- val Vector3(dx, dy, dz) = vector
- (
- dx * dx + dy * dy + dz * dz,
- 2f * (dx * (sx - cx) + dy * (sy - cy) + dz * (sz - cz))
- )
- }
- val c = Vector3.MagnitudeSquared(center) + Vector3.MagnitudeSquared(start) - 2f * (cx * sx + cy * sy + cz * sz) - radius * radius
- val result = b * b - 4 * a * c
- if (result < 0f) {
- //negative, no intersection
- Seq()
- } else if (result < 0.00001f) {
- //zero-ish, one intersection point
- Seq(start - vector * (b / (2f * a)))
- } else {
- //positive, two intersection points
- val sqrt = math.sqrt(result).toFloat
- val endStart = vector / (2f * a)
- Seq(start + endStart * (sqrt - b), start + endStart * (b + sqrt) * -1f)
- }.filter(p => Vector3.DistanceSquared(start, p) <= a)
- }
- /**
- * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles.
- * The main difference from "normal" server-side explosion
- * is that the owner of the projectile must be clarified explicitly.
- * @see `Zone::serverSideDamage`
- * @param zone where the explosion is taking place
- * (`source` contains the coordinate location)
- * @param source a game object that represents the source of the explosion
- * @param owner who or what to accredit damage from the explosion to;
- * clarifies a normal `SourceEntry(source)` accreditation
- */
- private def detonateLittleBuddy(
- zone: Zone,
- source: PlanetSideGameObject with FactionAffinity with Vitality,
- proxy: Projectile,
- owner: SourceEntry
- )(): Unit = {
- Zone.serverSideDamage(zone, source, littleBuddyExplosionDamage(owner, proxy.id, source.Position))
- }
-
- /**
- * Preparation for explosion damage that utilizes the Scorpion's little buddy sub-projectiles.
- * The main difference from "normal" server-side explosion
- * is that the owner of the projectile must be clarified explicitly.
- * The sub-projectiles will be the product of a normal projectile rather than a standard game object
- * so a custom `source` entity must wrap around it and fulfill the requirements of the field.
- * @see `Zone::explosionDamage`
- * @param owner who or what to accredit damage from the explosion to
- * @param explosionPosition where the explosion will be positioned in the game world
- * @param source a game object that represents the source of the explosion
- * @param target a game object that is affected by the explosion
- * @return a `DamageInteraction` object
- */
- private def littleBuddyExplosionDamage(
- owner: SourceEntry,
- projectileId: Long,
- explosionPosition: Vector3
- )
- (
- source: PlanetSideGameObject with FactionAffinity with Vitality,
- target: PlanetSideGameObject with FactionAffinity with Vitality
- ): DamageInteraction = {
- DamageInteraction(SourceEntry(target), OicwLilBuddyReason(owner, projectileId, target.DamageModel), explosionPosition)
- }
}
class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit val context: ActorContext) extends WeaponAndProjectileFunctions {
def sessionLogic: SessionData = ops.sessionLogic
- private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
+ //private val avatarActor: typed.ActorRef[AvatarActor.Command] = ops.avatarActor
/* packets */
@@ -227,126 +117,11 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
def handleDirectHit(pkt: HitMessage): Unit = { /* intentionally blank */ }
- def handleSplashHit(pkt: SplashHitMessage): Unit = {
- val SplashHitMessage(
- _,
- projectile_guid,
- explosion_pos,
- direct_victim_uid,
- _,
- projectile_vel,
- _,
- targets
- ) = pkt
- ops.FindProjectileEntry(projectile_guid) match {
- case Some(projectile) =>
- val profile = projectile.profile
- projectile.Velocity = projectile_vel
- val (resolution1, resolution2) = profile.Aggravated match {
- case Some(_) if profile.ProjectileDamageTypes.contains(DamageType.Aggravated) =>
- (DamageResolution.AggravatedDirect, DamageResolution.AggravatedSplash)
- case _ =>
- (DamageResolution.Splash, DamageResolution.Splash)
- }
- //direct_victim_uid
- sessionLogic.validObject(direct_victim_uid, decorator = "SplashHit/direct_victim") match {
- case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
- CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
- ResolveProjectileInteraction(projectile, resolution1, target, target.Position).collect { resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- case _ => ()
- }
- //other victims
- targets.foreach(elem => {
- sessionLogic.validObject(elem.uid, decorator = "SplashHit/other_victims") match {
- case Some(target: PlanetSideGameObject with FactionAffinity with Vitality) =>
- CheckForHitPositionDiscrepancy(projectile_guid, explosion_pos, target)
- ResolveProjectileInteraction(projectile, resolution2, target, explosion_pos).collect { resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- case _ => ()
- }
- })
- //...
- HandleDamageProxy(projectile, projectile_guid, explosion_pos)
- if (
- projectile.profile.HasJammedEffectDuration ||
- projectile.profile.JammerProjectile ||
- projectile.profile.SympatheticExplosion
- ) {
- //can also substitute 'projectile.profile' for 'SpecialEmp.emp'
- Zone.serverSideDamage(
- continent,
- player,
- SpecialEmp.emp,
- SpecialEmp.createEmpInteraction(SpecialEmp.emp, explosion_pos),
- SpecialEmp.prepareDistanceCheck(player, explosion_pos, player.Faction),
- SpecialEmp.findAllBoomers(profile.DamageRadius)
- )
- }
- if (profile.ExistsOnRemoteClients && projectile.HasGUID) {
- //cleanup
- if (projectile.HasGUID) {
- continent.Projectile ! ZoneProjectile.Remove(projectile.GUID)
- }
- }
- case None => ()
- }
- }
+ def handleSplashHit(pkt: SplashHitMessage): Unit = { /* intentionally blank */ }
def handleLashHit(pkt: LashMessage): Unit = { /* intentionally blank */ }
- def handleAIDamage(pkt: AIDamage): Unit = {
- val AIDamage(targetGuid, attackerGuid, projectileTypeId, _, _) = pkt
- (continent.GUID(player.VehicleSeated) match {
- case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality with OwnableByPlayer)
- if tobj.GUID == targetGuid &&
- tobj.OwnerGuid.contains(player.GUID) =>
- //deployable turrets
- Some(tobj)
- case Some(tobj: PlanetSideServerObject with FactionAffinity with Vitality with Mountable)
- if tobj.GUID == targetGuid &&
- tobj.Seats.values.flatMap(_.occupants.map(_.GUID)).toSeq.contains(player.GUID) =>
- //facility turrets, etc.
- Some(tobj)
- case _
- if player.GUID == targetGuid =>
- //player avatars
- Some(player)
- case _ =>
- None
- }).collect {
- case target: AutomatedTurret.Target =>
- sessionLogic.validObject(attackerGuid, decorator = "AIDamage/AutomatedTurret")
- .collect {
- case turret: AutomatedTurret if turret.Target.isEmpty =>
- turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
- Some(target)
-
- case turret: AutomatedTurret =>
- turret.Actor ! AutomatedTurretBehavior.ConfirmShot(target)
- HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, turret.TurretOwner, projectileTypeId))
- Some(target)
- }
- }
- .orElse {
- //occasionally, something that is not technically a turret's natural target may be attacked
- sessionLogic.validObject(targetGuid, decorator = "AIDamage/Target")
- .collect {
- case target: PlanetSideServerObject with FactionAffinity with Vitality =>
- sessionLogic.validObject(attackerGuid, decorator = "AIDamage/Attacker")
- .collect {
- case turret: AutomatedTurret if turret.Target.nonEmpty =>
- //the turret must be shooting at something (else) first
- HandleAIDamage(target, CompileAutomatedTurretDamageData(turret, turret.TurretOwner, projectileTypeId))
- }
- Some(target)
- }
- }
- }
+ def handleAIDamage(pkt: AIDamage): Unit = { /* intentionally blank */ }
/* support code */
@@ -402,167 +177,6 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
sendResponse(InventoryStateMessage(box.GUID, obj.GUID, capacity))
}
- private def CheckForHitPositionDiscrepancy(
- projectile_guid: PlanetSideGUID,
- hitPos: Vector3,
- target: PlanetSideGameObject with FactionAffinity with Vitality
- ): Unit = {
- val hitPositionDiscrepancy = Vector3.DistanceSquared(hitPos, target.Position)
- if (hitPositionDiscrepancy > Config.app.antiCheat.hitPositionDiscrepancyThreshold) {
- // If the target position on the server does not match the position where the projectile landed within reason there may be foul play
- log.warn(
- s"${player.Name}'s shot #${projectile_guid.guid} has hit discrepancy with target. Target: ${target.Position}, Reported: $hitPos, Distance: $hitPositionDiscrepancy / ${math.sqrt(hitPositionDiscrepancy).toFloat}; suspect"
- )
- }
- }
-
- /**
- * na
- * @param projectile the projectile object
- * @param resolution the resolution status to promote the projectile
- * @return a copy of the projectile
- */
- private def ResolveProjectileInteraction(
- projectile: Projectile,
- resolution: DamageResolution.Value,
- target: PlanetSideGameObject with FactionAffinity with Vitality,
- pos: Vector3
- ): Option[DamageInteraction] = {
- if (projectile.isMiss) {
- log.warn("expected projectile was already counted as a missed shot; can not resolve any further")
- None
- } else {
- val outProjectile = ProjectileQuality.modifiers(projectile, resolution, target, pos, Some(player))
- if (projectile.tool_def.Size == EquipmentSize.Melee && outProjectile.quality == ProjectileQuality.Modified(25)) {
- avatarActor ! AvatarActor.ConsumeStamina(10)
- }
- Some(DamageInteraction(SourceEntry(target), ProjectileReason(resolution, outProjectile, target.DamageModel), pos))
- }
- }
-
- /**
- * Take a projectile that was introduced into the game world and
- * determine if it generates a secondary damage projectile or
- * an method of damage causation that requires additional management.
- * @param projectile the projectile
- * @param pguid the client-local projectile identifier
- * @param hitPos the game world position where the projectile is being recorded
- * @return a for all affected targets, a combination of projectiles, projectile location, and the target's location;
- * nothing if no targets were affected
- */
- private def HandleDamageProxy(
- projectile: Projectile,
- pguid: PlanetSideGUID,
- hitPos: Vector3
- ): List[(PlanetSideGameObject with FactionAffinity with Vitality, Projectile, Vector3, Vector3)] = {
- GlobalDefinitions.getDamageProxy(projectile, hitPos) match {
- case Nil =>
- Nil
- case list if list.isEmpty =>
- Nil
- case list =>
- HandleDamageProxySetupLittleBuddy(list, hitPos)
- UpdateProjectileSidednessAfterHit(projectile, hitPos)
- val projectileSide = projectile.WhichSide
- list.flatMap { proxy =>
- if (proxy.profile.ExistsOnRemoteClients) {
- proxy.Position = hitPos
- proxy.WhichSide = projectileSide
- continent.Projectile ! ZoneProjectile.Add(player.GUID, proxy)
- Nil
- } else if (proxy.tool_def == GlobalDefinitions.maelstrom) {
- //server-side maelstrom grenade target selection
- val radius = proxy.profile.LashRadius * proxy.profile.LashRadius
- val targets = Zone.findAllTargets(continent, hitPos, proxy.profile.LashRadius, { _.livePlayerList })
- .filter { target =>
- Vector3.DistanceSquared(target.Position, hitPos) <= radius
- }
- //chainlash is separated from the actual damage application for convenience
- continent.AvatarEvents ! AvatarServiceMessage(
- continent.id,
- AvatarAction.SendResponse(
- PlanetSideGUID(0),
- ChainLashMessage(
- hitPos,
- projectile.profile.ObjectId,
- targets.map { _.GUID }
- )
- )
- )
- targets.map { target =>
- CheckForHitPositionDiscrepancy(pguid, hitPos, target)
- (target, proxy, hitPos, target.Position)
- }
- } else {
- Nil
- }
- }
- }
- }
-
- private def HandleDamageProxySetupLittleBuddy(listOfProjectiles: List[Projectile], detonationPosition: Vector3): Boolean = {
- val listOfLittleBuddies: List[Projectile] = listOfProjectiles.filter { _.tool_def == GlobalDefinitions.oicw }
- val size: Int = listOfLittleBuddies.size
- if (size > 0) {
- val desiredDownwardsProjectiles: Int = 2
- val firstHalf: Int = math.min(size, desiredDownwardsProjectiles) //number that fly straight down
- val secondHalf: Int = math.max(size - firstHalf, 0) //number that are flared out
- val z: Float = player.Orientation.z //player's standing direction
- val north: Vector3 = Vector3(0,1,0) //map North
- val speed: Float = 144f //speed (packet discovered)
- val dist: Float = 25 //distance (client defined)
- val downwardsAngle: Float = -85f
- val flaredAngle: Float = -70f
- //angle of separation for downwards, degrees from vertical for flared out
- val (smallStep, smallAngle): (Float, Float) = if (firstHalf > 1) {
- (360f / firstHalf, downwardsAngle)
- } else {
- (0f, 0f)
- }
- val (largeStep, largeAngle): (Float, Float) = if (secondHalf > 1) {
- (360f / secondHalf, flaredAngle)
- } else {
- (0f, 0f)
- }
- val smallRotOffset: Float = z + 90f
- val largeRotOffset: Float = z + math.random().toFloat * 45f
- val verticalCorrection = Vector3.z(dist - dist * math.sin(math.toRadians(90 - smallAngle + largeAngle)).toFloat)
- //downwards projectiles
- var i: Int = 0
- listOfLittleBuddies.take(firstHalf).foreach { proxy =>
- val facing = (smallRotOffset + smallStep * i.toFloat) % 360
- val dir = north.Rx(smallAngle).Rz(facing)
- proxy.Position = detonationPosition + dir.xy + verticalCorrection
- proxy.Velocity = dir * speed
- proxy.Orientation = Vector3(0, (360f + smallAngle) % 360, facing)
- HandleDamageProxyLittleBuddyExplosion(proxy, dir, dist)
- i += 1
- }
- //flared out projectiles
- i = 0
- listOfLittleBuddies.drop(firstHalf).foreach { proxy =>
- val facing = (largeRotOffset + largeStep * i.toFloat) % 360
- val dir = north.Rx(largeAngle).Rz(facing)
- proxy.Position = detonationPosition + dir
- proxy.Velocity = dir * speed
- proxy.Orientation = Vector3(0, (360f + largeAngle) % 360, facing)
- HandleDamageProxyLittleBuddyExplosion(proxy, dir, dist)
- i += 1
- }
- true
- } else {
- false
- }
- }
-
- private def HandleDamageProxyLittleBuddyExplosion(proxy: Projectile, orientation: Vector3, distance: Float): Unit = {
- //explosion
- val obj = new DummyExplodingEntity(proxy, proxy.owner.Faction)
- obj.Position = obj.Position + orientation * distance
- val explosionFunc: ()=>Unit = WeaponAndProjectileLogic.detonateLittleBuddy(continent, obj, proxy, proxy.owner)
- context.system.scheduler.scheduleOnce(500.milliseconds) { explosionFunc() }
- }
-
private def fireStateStartPlayerMessages(itemGuid: PlanetSideGUID): Unit = {
continent.AvatarEvents ! AvatarServiceMessage(
continent.id,
@@ -601,81 +215,4 @@ class WeaponAndProjectileLogic(val ops: WeaponAndProjectileOperations, implicit
fireStateStopUpdateChargeAndCleanup(tool)
ops.fireStateStopMountedMessages(itemGuid)
}
-
- //noinspection SameParameterValue
- private def addShotsLanded(weaponId: Int, shots: Int): Unit = {
- ops.addShotsToMap(ops.shotsLanded, weaponId, shots)
- }
-
- private def CompileAutomatedTurretDamageData(
- turret: AutomatedTurret,
- owner: SourceEntry,
- projectileTypeId: Long
- ): Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)] = {
- turret.Weapons
- .values
- .flatMap { _.Equipment }
- .collect { case weapon: Tool => (turret, weapon, owner, weapon.Projectile) }
- .find { case (_, _, _, p) => p.ObjectId == projectileTypeId }
- }
-
- private def HandleAIDamage(
- target: PlanetSideServerObject with FactionAffinity with Vitality,
- results: Option[(AutomatedTurret, Tool, SourceEntry, ProjectileDefinition)]
- ): Unit = {
- results.collect {
- case (obj, tool, owner, projectileInfo) =>
- val angle = Vector3.Unit(target.Position - obj.Position)
- val proj = new Projectile(
- projectileInfo,
- tool.Definition,
- tool.FireMode,
- None,
- owner,
- obj.Definition.ObjectId,
- obj.Position + Vector3.z(value = 1f),
- angle,
- Some(angle * projectileInfo.FinalVelocity)
- )
- val hitPos = target.Position + Vector3.z(value = 1f)
- ResolveProjectileInteraction(proj, DamageResolution.Hit, target, hitPos).collect { resprojectile =>
- addShotsLanded(resprojectile.cause.attribution, shots = 1)
- sessionLogic.handleDealingDamage(target, resprojectile)
- }
- }
- }
-
- private def UpdateProjectileSidednessAfterHit(projectile: Projectile, hitPosition: Vector3): Unit = {
- val origin = projectile.Position
- val distance = Vector3.Magnitude(hitPosition - origin)
- continent.blockMap
- .sector(hitPosition, distance)
- .environmentList
- .collect { case o: InteriorDoorPassage =>
- val door = o.door
- val intersectTest = WeaponAndProjectileLogic.quickLineSphereIntersectionPoints(
- origin,
- hitPosition,
- door.Position,
- door.Definition.UseRadius + 0.1f
- )
- (door, intersectTest)
- }
- .collect { case (door, intersectionTest) if intersectionTest.nonEmpty =>
- (door, Vector3.Magnitude(hitPosition - door.Position), intersectionTest)
- }
- .minByOption { case (_, dist, _) => dist }
- .foreach { case (door, _, intersects) =>
- val strictly = if (Vector3.DotProduct(Vector3.Unit(hitPosition - door.Position), door.Outwards) > 0f) {
- Sidedness.OutsideOf
- } else {
- Sidedness.InsideOf
- }
- projectile.WhichSide = if (intersects.size == 1) {
- Sidedness.InBetweenSides(door, strictly)
- } else {
- strictly
- }
- }
- }
}
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 412a4c34..02efbc7e 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 deea975f..67bf09d4 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/SessionData.scala b/src/main/scala/net/psforever/actors/session/support/SessionData.scala
index 5c40651f..8821f6cd 100644
--- a/src/main/scala/net/psforever/actors/session/support/SessionData.scala
+++ b/src/main/scala/net/psforever/actors/session/support/SessionData.scala
@@ -238,8 +238,9 @@ class SessionData(
case Some(_: LocalLockerItem) =>
player.avatar.locker.Inventory.hasItem(guid) match {
- case out @ Some(_) =>
+ case out @ Some(thing) =>
contextSafeEntity = guid
+ oldRefsMap.put(guid, thing.Definition.Name)
out
case None if contextSafeEntity == guid =>
//safeguard
@@ -263,9 +264,10 @@ class SessionData(
None
case out @ Some(obj) if obj.HasGUID =>
+ oldRefsMap.put(guid, obj.Definition.Name)
out
- case None if !id.contains(PlanetSideGUID(0)) =>
+ case None if guid != PlanetSideGUID(0) && guid != player.GUID && !player.VehicleSeated.contains(guid) =>
//delete stale entity reference from client
//deleting guid=0 will cause BAD things to happen
log.error(s"$elevatedDecorator: ${player.Name} has an invalid reference to $hint with GUID $guid in zone ${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 5389b6bb..b493b1ff 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 7741fa86..55753dff 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 ebeb512f..4af23102 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 ae02c6f6..a83a9df0 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 086d7d04..6a66afe3 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 1d0746b5..f0b36413 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,28 +637,36 @@ 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)
if (willBecomeMax) {
- //changing to a different kind(?) of max
+ if (originalSubtype != subtype) {
+ //changing to a different kind of max
+ player.Capacitor = 0
+ }
(delete, Nil, insert, beforeInventory)
} else {
//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
+ player.Capacitor = 0
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 5f637e00..35f08405 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 333ae6cb..674b6dbf 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,22 +28,84 @@ class WithWater(val channel: String)
body: PieceOfEnvironment,
data: Option[Any]
): Unit = {
- val extra = data.collect {
- case t: OxygenStateTarget => Some(t)
- case w: Watery => w.Condition
- }.flatten
- if (extra.isDefined) {
- //inform the player that their mounted vehicle is in trouble (that they are in trouble (but not from drowning (yet)))
- stopInteractingWith(obj, body, data)
+ if (getExtra(data).nonEmpty) {
+ inheritAndPushExtraData(obj, body, data)
} else {
- val (effect, time, percentage) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime)
- if (effect) {
- val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage)
- waterInteractionTime = System.currentTimeMillis() + time
- condition = Some(cond)
- obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, Player.Die())
- //inform the player that they are in trouble
- obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra))
+ depth = math.max(0f, body.collision.altitude - obj.Position.z)
+ doInteractingWithBehavior(obj, body, data)
+ obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = 500 milliseconds, obj.Actor, interaction.InteractingWithEnvironment(body, Some("wading")))
+ }
+ }
+
+ /**
+ * Wading only happens while the player's head is above the water.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def wadingBeforeDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ //we're already "wading", let's see if we're drowning
+ if (depth >= GlobalDefinitions.MaxDepth(obj)) {
+ //drowning
+ beginDrowning(obj, body, data)
+ } else {
+ //inform the player that their mounted vehicle is in trouble (that they are in trouble (but not from drowning (yet)))
+ val extra = getExtra(data)
+ if (extra.nonEmpty) {
+ displayOxygenState(
+ obj,
+ condition.getOrElse(OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, 95f)),
+ extra
+ )
+ }
+ }
+ }
+
+ /**
+ * Too much water causes players to slowly suffocate.
+ * When they (finally) drown, they will die.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def beginDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ val (effect, time, percentage) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime)
+ if (effect) {
+ val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage)
+ waterInteractionTime = System.currentTimeMillis() + time
+ condition = Some(cond)
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
+ obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, Player.Die())
+ //inform the player that they are in trouble
+ displayOxygenState(obj, cond, getExtra(data))
+ doInteractingWithBehavior = drowning
+ }
+ }
+
+ /**
+ * Too much water causes players to slowly suffocate.
+ * When they (finally) drown, they will die.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def drowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ //test if player ever gets head above the water level
+ if (depth < GlobalDefinitions.MaxDepth(obj)) {
+ val (_, _, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
+ //switch to recovery
+ if (percentage > 0) {
+ recoverFromDrowning(obj, body, data)
+ doInteractingWithBehavior = recoverFromDrowning
}
}
}
@@ -52,16 +116,22 @@ class WithWater(val channel: String)
* @param obj the target
* @param body the environment
*/
- override def stopInteractingWith(
- obj: InteractsWithZone,
- body: PieceOfEnvironment,
- data: Option[Any]
- ): Unit = {
- val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
- if (percentage > 99f) {
- recoverFromInteracting(obj)
+ private def recoverFromDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ val state = condition.map(_.state)
+ if (state.contains(OxygenState.Suffocation)) {
+ //set up for recovery
+ val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
+ if (percentage < 99f) {
+ //we're not too far gone
+ recoverFromDrowning(obj, body, data, effect, time, percentage)
+ }
+ doInteractingWithBehavior = recovering
} else {
- stopInteractingAction(obj, body, data, effect, time, percentage)
+ doInteractingWithBehavior = wadingBeforeDrowning
}
}
@@ -74,38 +144,169 @@ class WithWater(val channel: String)
* @param time current time until completion of the next effect
* @param percentage value to display in the drowning UI progress bar
*/
- private def stopInteractingAction(
- obj: InteractsWithZone,
- body: PieceOfEnvironment,
- data: Option[Any],
- effect: Boolean,
- time: Long,
- percentage: Float
- ): Unit = {
+ private def recoverFromDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any],
+ effect: Boolean,
+ time: Long,
+ percentage: Float
+ ): Unit = {
val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage)
val extra = data.collect {
case t: OxygenStateTarget => Some(t)
case w: Watery => w.Condition
}.flatten
- if (effect) {
+ if (effect) {
condition = Some(cond)
waterInteractionTime = System.currentTimeMillis() + time
- obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
+ obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
//inform the player
- obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra))
+ displayOxygenState(obj, cond, extra)
} else if (extra.isDefined) {
//inform the player
- obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra))
+ displayOxygenState(obj, cond, extra)
+ }
+ }
+
+ /**
+ * The recovery period is much faster than the drowning process.
+ * Check for when the player fully recovers,
+ * and that the player does not regress back to drowning.
+ * @param obj the target
+ * @param body the environment
+ */
+ def recovering(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ lazy val state = condition.map(_.state)
+ if (depth >= GlobalDefinitions.MaxDepth(obj)) {
+ //go back to drowning
+ beginDrowning(obj, body, data)
+ } else if (state.contains(OxygenState.Recovery)) {
+ //check recovery conditions
+ val (_, _, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
+ if (percentage < 1f) {
+ doInteractingWithBehavior = wadingBeforeDrowning
+ }
+ }
+ }
+
+ /**
+ * When out of water, the player is no longer suffocating.
+ * He's even stopped wading.
+ * The only thing we should let complete now is recovery.
+ * @param obj the target
+ * @param body the environment
+ */
+ override def stopInteractingWith(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ if (getExtra(data).nonEmpty) {
+ inheritAndPushExtraData(obj, body, data)
+ } else {
+ stopInteractingWithAction(obj, body, data)
+ }
+ }
+
+ /**
+ * When out of water, the player is no longer suffocating.
+ * He's even stopped wading.
+ * The only thing we should let complete now is recovery.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def stopInteractingWithAction(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ val cond = condition.map(_.state)
+ if (cond.contains(OxygenState.Suffocation)) {
+ //go from suffocating to recovery
+ recoverFromDrowning(obj, body, data)
+ } else if (cond.isEmpty) {
+ //neither suffocating nor recovering, so just reset everything
+ recoverFromInteracting(obj)
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(attribute)
+ waterInteractionTime = 0L
+ depth = 0f
+ condition = None
+ doInteractingWithBehavior = wadingBeforeDrowning
}
}
override def recoverFromInteracting(obj: InteractsWithZone): Unit = {
super.recoverFromInteracting(obj)
- if (condition.exists(_.state == OxygenState.Suffocation)) {
- val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
- stopInteractingAction(obj, condition.map(_.body).get, None, effect, time, percentage)
+ val cond = condition.map(_.state)
+ //whether or not we were suffocating or recovering, we need to undo the visuals for that
+ if (cond.nonEmpty) {
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
+ displayOxygenState(
+ obj,
+ OxygenStateTarget(obj.GUID, condition.map(_.body).get, OxygenState.Recovery, 100f),
+ None
+ )
}
- waterInteractionTime = 0L
condition = None
}
+
+ /**
+ * From the "condition" of someone else's drowning status,
+ * extract target information and progress.
+ * @param data any information
+ * @return target information and drowning progress
+ */
+ private def getExtra(data: Option[Any]): Option[OxygenStateTarget] = {
+ data.collect {
+ case t: OxygenStateTarget => Some(t)
+ case w: Watery => w.Condition
+ }.flatten
+ }
+
+ /**
+ * Send the message regarding drowning and recovery
+ * that includes additional information about a related target that is drowning or recovering.
+ * @param obj the target
+ * @param body the environment
+ * @param data essential information about someone else's interaction with water
+ */
+ private def inheritAndPushExtraData(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ val state = condition.map(_.state).getOrElse(OxygenState.Recovery)
+ val Some((_, _, percentage)) = state match {
+ case OxygenState.Suffocation => Some(Watery.drowningInWateryConditions(obj, Some(state), waterInteractionTime))
+ case OxygenState.Recovery => Some(Watery.recoveringFromWateryConditions(obj, Some(state), waterInteractionTime))
+ }
+ displayOxygenState(obj, OxygenStateTarget(obj.GUID, body, state, percentage), getExtra(data))
+ }
+
+ /**
+ * Send the message regarding drowning and recovery.
+ * @param obj the target
+ * @param cond the environment
+ */
+ private def displayOxygenState(
+ obj: InteractsWithZone,
+ cond: OxygenStateTarget,
+ data: Option[OxygenStateTarget]
+ ): Unit = {
+ obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, data))
+ }
+}
+
+object WithWater {
+ /** special environmental trait to queue actions independent from the primary wading test */
+ case object WaterAction extends EnvironmentTrait {
+ override def canInteractWith(obj: PlanetSideGameObject): Boolean = false
+ override def testingDepth(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 b7b43073..8791b639 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 11059fba..3c465013 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/GlobalDefinitionsProjectile.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala
index 7b3e4f5d..c16487ae 100644
--- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala
+++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsProjectile.scala
@@ -1783,7 +1783,7 @@ object GlobalDefinitionsProjectile {
spitfire_aa_ammo_projectile.Lifespan = 5f
ProjectileDefinition.CalculateDerivedFields(spitfire_aa_ammo_projectile)
spitfire_aa_ammo_projectile.Modifiers = List(
- //FlakHit,
+ CerberusTurretWrongTarget,
FlakBurst,
MaxDistanceCutoff
)
diff --git a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala
index c3dd0c9d..5e7ae6da 100644
--- a/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala
+++ b/src/main/scala/net/psforever/objects/global/GlobalDefinitionsVehicle.scala
@@ -606,7 +606,7 @@ object GlobalDefinitionsVehicle {
apc_tr.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_tr.Geometry = apcForm
apc_tr.MaxCapacitor = 300
- apc_tr.CapacitorRecharge = 1
+ apc_tr.CapacitorRecharge = 10
apc_tr.collision.avatarCollisionDamageMax = 300
apc_tr.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 40), (0.75f, 70), (1f, 110)))
apc_tr.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000)))
@@ -676,7 +676,7 @@ object GlobalDefinitionsVehicle {
apc_nc.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_nc.Geometry = apcForm
apc_nc.MaxCapacitor = 300
- apc_nc.CapacitorRecharge = 1
+ apc_nc.CapacitorRecharge = 10
apc_nc.collision.avatarCollisionDamageMax = 300
apc_nc.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 40), (0.75f, 70), (1f, 110)))
apc_nc.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000)))
@@ -746,7 +746,7 @@ object GlobalDefinitionsVehicle {
apc_vs.UnderwaterLifespan(suffocation = 15000L, recovery = 7500L)
apc_vs.Geometry = apcForm
apc_vs.MaxCapacitor = 300
- apc_vs.CapacitorRecharge = 1
+ apc_vs.CapacitorRecharge = 10
apc_vs.collision.avatarCollisionDamageMax = 300
apc_vs.collision.xy = CollisionXYData(Array((0.1f, 1), (0.25f, 10), (0.5f, 40), (0.75f, 70), (1f, 110)))
apc_vs.collision.z = CollisionZData(Array((2f, 1), (6f, 50), (10f, 300), (12f, 1000), (13f, 3000)))
@@ -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 76be594b..d3680de9 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 664facf5..0eb8cf71 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 8b0cae70..da5fba1a 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 2ad7d1e8..23292395 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 6edf96b1..e3b6e687 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._
@@ -99,6 +98,8 @@ class InteractWithEnvironment()
.foreach(_.stopInteractingWith(obj, body, None))
}
}
+
+ def OngoingInteractions: Set[EnvironmentTrait] = interactWith.map(_.attribute)
}
object InteractWithEnvironment {
@@ -118,9 +119,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
}
@@ -137,7 +137,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
@@ -186,12 +186,12 @@ case class OnStableEnvironment() extends InteractionBehavior {
): Set[PieceOfEnvironment] = {
if (obj.Position != Vector3.Zero && 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()
@@ -226,17 +226,22 @@ final case class AwaitOngoingInteraction(zone: Zone) extends InteractionBehavior
): Set[PieceOfEnvironment] = {
val interactions = obj.interaction().collectFirst { case inter: InteractWithEnvironment => inter.Interactions }
if (obj.Position != Vector3.Zero && 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 799c166c..e08cd8b2 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 d13627e9..bb02a697 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 3d858c4d..c797371a 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 1c60938c..aca66da0 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 0fa40b9e..29bd0f51 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 51c4c4a3..86b072f1 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 516b904b..67c8d6e6 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 bf14a8cb..520d0c1e 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 05a01682..6b7b3f95 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 14faf3d9..85d56739 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 8a5d48a2..66b6f270 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 39fd899b..01208856 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,8 +76,94 @@ class WithWater()
if (effect) {
condition = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage))
waterInteractionTime = System.currentTimeMillis() + time
- obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, VehicleControl.Disable(true))
- doInteractingWithTargets(
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
+ obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, VehicleControl.Disable(true))
+ WithWater.doInteractingWithTargets(
+ obj,
+ percentage,
+ body,
+ vehicle.Seats.values.flatMap(_.occupants).filter(p => p.isAlive && (p.Zone eq obj.Zone))
+ )
+ doInteractingWithBehavior = drowning
+ }
+ case _ => ()
+ }
+ }
+
+ /**
+ * Too much water causes vehicles to slowly disable.
+ * When fully waterlogged, the vehicle is completely immobile.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def drowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ //test if player ever gets head above the water level
+ if (depth < GlobalDefinitions.MaxDepth(obj)) {
+ val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
+ //switch to recovery
+ if (percentage > 0) {
+ recoverFromDrowning(obj, body, data, effect, time, percentage)
+ doInteractingWithBehavior = recovering
+ }
+ }
+ }
+
+ /**
+ * When out of water, the vehicle is no longer being waterlogged.
+ * It does have to endure a recovery period to get back to normal, though.
+ * @param obj the target
+ * @param body the environment
+ */
+ private def recoverFromDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ val state = condition.map(_.state)
+ if (state.contains(OxygenState.Suffocation)) {
+ //set up for recovery
+ val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
+ if (percentage < 99f) {
+ //we're not too far gone
+ recoverFromDrowning(obj, body, data, effect, time, percentage)
+ }
+ doInteractingWithBehavior = recovering
+ } else {
+ doInteractingWithBehavior = wadingBeforeDrowning
+ }
+ }
+
+ /**
+ * When out of water, the vehicle is no longer being waterlogged.
+ * It does have to endure a recovery period to get back to normal, though.
+ * @param obj the target
+ * @param body the environment
+ * @param effect na
+ * @param time current time until completion of the next effect
+ * @param percentage value to display in the drowning UI progress bar
+ */
+ private def recoverFromDrowning(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ @unused data: Option[Any],
+ effect: Boolean,
+ time: Long,
+ percentage: Float
+ ): Unit = {
+ obj match {
+ case vehicle: Vehicle =>
+ val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage)
+ if (effect) {
+ condition = Some(cond)
+ waterInteractionTime = System.currentTimeMillis() + time
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
+ obj.Actor ! RespondsToZoneEnvironment.Timer(WithWater.WaterAction, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
+ //inform the players
+ WithWater.stopInteractingWithTargets(
obj,
percentage,
body,
@@ -55,34 +175,52 @@ class WithWater()
}
/**
- * When out of water, the vehicle no longer risks becoming disabled.
- * It does have to endure a recovery period to get back to full dehydration
- * Flying vehicles are exempt from this process due to the abrupt disability they experience.
- * @param obj the target
- * @param body the environment
- * @param data additional interaction information, if applicable
- */
+ * The recovery period is much faster than the waterlogging process.
+ * Check for when the vehicle fully recovers,
+ * and that the vehicle does not regress back to waterlogging.
+ * @param obj the target
+ * @param body the environment
+ */
+ def recovering(
+ obj: InteractsWithZone,
+ body: PieceOfEnvironment,
+ data: Option[Any]
+ ): Unit = {
+ lazy val state = condition.map(_.state)
+ if (depth >= GlobalDefinitions.MaxDepth(obj)) {
+ //go back to drowning
+ beginDrowning(obj, body, data)
+ } else if (state.contains(OxygenState.Recovery)) {
+ //check recovery conditions
+ val (_, _, percentage) = Watery.recoveringFromWateryConditions(obj, state, waterInteractionTime)
+ if (percentage < 1f) {
+ doInteractingWithBehavior = wadingBeforeDrowning
+ }
+ }
+ }/**
+ * When out of water, the vehicle no longer risks becoming disabled.
+ * It does have to endure a recovery period to get back to full dehydration
+ * Flying vehicles are exempt from this process due to the abrupt disability they experience.
+ * @param obj the target
+ * @param body the environment
+ */
override def stopInteractingWith(
obj: InteractsWithZone,
body: PieceOfEnvironment,
data: Option[Any]
): Unit = {
- obj match {
- case vehicle: Vehicle =>
- val (effect: Boolean, time: Long, percentage: Float) =
- Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime)
- if (effect) {
- condition = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage))
- waterInteractionTime = System.currentTimeMillis() + time
- obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute))
- stopInteractingWithTargets(
- obj,
- percentage,
- body,
- vehicle.Seats.values.flatMap(_.occupants).filter(p => p.isAlive && (p.Zone eq obj.Zone))
- )
- }
- case _ => ()
+ val cond = condition.map(_.state)
+ if (cond.contains(OxygenState.Suffocation)) {
+ //go from suffocating to recovery
+ recoverFromDrowning(obj, body, data)
+ } else if (cond.isEmpty) {
+ //neither suffocating nor recovering, so just reset everything
+ recoverFromInteracting(obj)
+ obj.Actor ! RespondsToZoneEnvironment.StopTimer(attribute)
+ waterInteractionTime = 0L
+ depth = 0f
+ condition = None
+ doInteractingWithBehavior = wadingBeforeDrowning
}
}
@@ -91,46 +229,53 @@ class WithWater()
if (condition.exists(_.state == OxygenState.Suffocation)) {
stopInteractingWith(obj, condition.map(_.body).get, None)
}
- waterInteractionTime = 0L
condition = None
}
+}
+
+object WithWater {
+ /** special environmental trait to queue actions independent from the primary wading test */
+ case object WaterAction extends EnvironmentTrait {
+ override def canInteractWith(obj: PlanetSideGameObject): Boolean = false
+ override def testingDepth(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/vital/projectile/ProjectileDamageModifierFunctions.scala b/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifierFunctions.scala
index e9fcaa11..8d154140 100644
--- a/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifierFunctions.scala
+++ b/src/main/scala/net/psforever/objects/vital/projectile/ProjectileDamageModifierFunctions.scala
@@ -1,9 +1,10 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.vital.projectile
+import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.ballistics._
import net.psforever.objects.equipment.ChargeFireModeDefinition
-import net.psforever.objects.sourcing.PlayerSource
+import net.psforever.objects.sourcing.{PlayerSource, VehicleSource}
import net.psforever.objects.vital.base._
import net.psforever.objects.vital.damage.DamageModifierFunctions
import net.psforever.objects.vital.interaction.DamageInteraction
@@ -354,6 +355,19 @@ case object ShieldAgainstRadiation extends ProjectileDamageModifiers.Mod {
}
}
+/** The Cerberus turret can not target any entities besides flying vehicles.
+ * An exception to this rule, however, happens when retaliating against something that damaged it first. */
+case object CerberusTurretWrongTarget extends ProjectileDamageModifiers.Mod {
+ def calculate(damage: Int, data: DamageInteraction, cause: ProjectileReason): Int = {
+ data.target match {
+ case v: VehicleSource if GlobalDefinitions.isFlightVehicle(v.Definition) =>
+ damage
+ case _ =>
+ damage - (math.random() * 3d).toInt - 1
+ }
+ }
+}
+
/* Functions */
object ProjectileDamageModifierFunctions {
/**
diff --git a/src/main/scala/net/psforever/objects/zones/Zone.scala b/src/main/scala/net/psforever/objects/zones/Zone.scala
index 07c9ed58..4a44e36d 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 3c4a92a5..f5c0197b 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 c1b7780b..4e05d85d 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 d80f16d7..0a7629a7 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 b4b2939e..1be70996 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 00000000..fb08485b
--- /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 72aa05aa..91d900d3 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 e6d70f3a..de4545cc 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 91bf2499..df0d5f12 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,21 +219,6 @@ class CaptureFlagManager(zone: Zone) extends Actor {
// Then unregister it from the GUID pool
TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, flag))
}
-
- private def ChatBroadcast(zone: Zone, message: String, fanfare: Boolean = true): Unit = {
- val messageType: ChatMessageType = if (fanfare) {
- ChatMessageType.UNK_223
- } else {
- ChatMessageType.UNK_229
- }
- zone.LocalEvents ! LocalServiceMessage(
- zone.id,
- LocalAction.SendChatMsg(
- PlanetSideGUID(-1),
- ChatMsg(messageType, wideContents = false, "", message, None)
- )
- )
- }
}
object CaptureFlagManager {
@@ -221,18 +230,98 @@ object CaptureFlagManager {
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 {
+ ChatMessageType.UNK_229
+ }
+ zone.LocalEvents ! LocalServiceMessage(
+ zone.id,
+ LocalAction.SendChatMsg(
+ PlanetSideGUID(-1),
+ ChatMsg(messageType, wideContents = true, "", message, None)
+ )
+ )
+ }
+
+ 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)
+ }
+ }
+
+ /**
+ * 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 b9a4f470..1a88b0e0 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 35b37798..72168faa 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 979d179f..07bb2976 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 e64c6536..22e2ed94 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 56f27166..c588a2ac 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 dd5a89fd..f3442112 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 691a06e7..e2535292 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 cbf5ca7a..3e1569aa 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 0de9023c..e9ac7df8 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 1708ff74..b02c1ed2 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 ca83ddc1..0b86da3f 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 23d2765e..19bd9eeb 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
- }
- )
+ val msg1 = testProbe.receiveOne(4.seconds)
+ msg1 match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
- obj.Position = Vector3(1,1,50)
+ 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
- }
- )
+ val msg1 = testProbe.receiveOne(4.seconds)
+ msg1 match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
- obj.Position = Vector3(12,7,2)
+ 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
- }
- )
+ val msg1 = testProbe.receiveOne(4.seconds)
+ msg1 match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
+ }
- obj.Position = Vector3(7,12,2)
+ 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 401bdc7f..aa56739a 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 8c52c5ae..7eff27c3 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 b756d25e..9b9b7ca7 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)}")
}
- vehicle_msg(4) match {
- case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ;
+ vehicleMsgs(4) match {
+ case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ()
case _ =>
- assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-5: ${vehicle_msg(3)}")
+ assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-5: ${vehicleMsgs(4)}")
}
- vehicle_msg(5) match {
- case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ;
+ vehicleMsgs(5) match {
+ case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ()
case _ =>
- assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-6: ${vehicle_msg(4)}")
+ assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-6: ${vehicleMsgs(5)}")
+ }
+ cargoMsgs.head match {
+ case Vehicle.Deconstruct(_) => ()
+ case _ =>
+ 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,67 +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
- val playerProbe = TestProbe()
- val guid = new NumberPoolHub(new MaxNumberSource(15))
- val pool = Pool(EnvironmentAttribute.Water, DeepSquare(-1, 10, 10, 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 = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control")
-
- "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
-
- 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
- }
- )
- }
- }
-}
-
-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 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(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 +603,48 @@ 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 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(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, 4f)
+ vehicle.zoneInteractions()
+ val vehicleMsgs = vehicleProbe.receiveN(1, 5.seconds)
+ vehicleMsgs.head match {
+ case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
+ case _ =>
+ assert(false, "")
+ }
+ playerProbe.expectNoMessage()
}
}
}
-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 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(-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 +669,290 @@ 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
- }
- )
-
- vehicle.Position = Vector3.Zero //that's enough of that
+ "report when the vehicle starts drowning" in {
+ vehicle.Position = Vector3(1, 1, 6)
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
- }
- )
+ 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, "")
+ }
}
}
}
+//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(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 wading" in {
+ vehicle.Position = Vector3(1, 1, 6)
+ vehicle.zoneInteractions()
+ vehicleProbe.expectNoMessage(2.seconds)
+ playerProbe.expectNoMessage()
+
+ vehicle.Position = Vector3(1, 1, 4f)
+ vehicle.zoneInteractions()
+ 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 =