Repeating the Obvious (#1233)

* adding chat and event messages that have been neglected since; these are easy additions

* completely retooled water interactions for players and vehicles to handle a wading status, before drowning; when player with llu or vehicle with player with llu come into contact with water in this wading state, the llu is lost and the facility hack ends; adding messages to support this and differentiate from an llu facility capture timeout

* periodic message while the llu is active, whether or not there is a carrier, until the hack is finished

* message when inventory is full (assumes player inventory destination, but item winds up in hand due to lack of space, but it works)

* when changing exo-suits, complain if equipment has to be deleted; dropped equipment is limited to special equipment; report a player pointlessly

* announce ams decay to players who have just spawned on an ams, when they spawn on that ams, and for the last round of spawnees when the vehicle finally deconstructs; due to use of the word 'bound' this may be an incorrect application, but matrixing doesn't work anyway

* deconstruction message for would-be driver when vehicle is abandoned on spawn pad; tempo on message delivery is different than usual

* message when player is kicked from orbital shuttle gantry; message when player attempts to bail in a warp gate; message when player attempts to bail from a droppod; stop player from succeeding in bailing from a droppod

* vehicle pad deconstruction complete message; message when nc max shield deactivates when out of capacitor power or when max opens fire; message when ams can not deploy because it is blocked by other entities; message when facility capture requires ntu

* message for deployables being blocked; cleaning up dev test section

* Yellow Ownership (#1226)

* just some tinkering and clean-up

* converted DeployItem from AvatarService to LocalService; attempt at resolving missing overwhip yellow ring is complicated; vehicle ownership packet wqorks on deployables that are mountable, but is less successful on normal simple deployables

* restoration of yellow ring of ownership around deployables; changes to variant of CommonFieldData transcorder used on certain deployable transcoders; static values are assigned parameter names and public variables are given types for completion

* initial packet for GenericObjectAction2Message and tests; repaired transcoders and tests for TRAP and small turrets

* force redraw of the whole boomer to assert reassignment of ownership; it's heavy-handed but it works

* deployable ownership should be asserted during both re-zoning and revival; refactoring of code in ZoningOperations

* message when inventory is full (assumes player inventory destination, but item winds up in hand due to lack of space, but it works)

* vehicle deployment messages added in, then deployment was fixed to accommodate an explicit caller, and that changed a whole lot of the deployment loop for messages; environmental activity was modified to maointain a more responsible start/stop transition; many many test changes (still an issue with a lot of them)

* moving around conditions for losing the llu

* the question of one extra bit for small deployables; also, all tests pass successfully, tho resource silo use remains coin flip; what sorcery is this

* duplicate capture lost message eliminated; flag lost violently due to environment in general rather than just water interaction; flag lost violently due to warp gate envelope interaction; flag lost due to be dropped in an unsafe position

* vary the testing depth for water interaction

* darn tests

* fixed vehicle interference; added missing ArmorShieldOff message; clearing countdown after vehicle spawn should stop countdown from appearing in subsequent vehicle spawns

* the router needs 20m of clearance

* removing the shared group interference information from routers
This commit is contained in:
Fate-JH 2024-10-01 16:01:00 -04:00 committed by GitHub
parent 4b7d41db2f
commit 6a349d0fe1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
63 changed files with 2245 additions and 998 deletions

View file

@ -3,6 +3,7 @@ package net.psforever.actors.session.normal
import akka.actor.{ActorContext, typed}
import net.psforever.actors.session.support.AvatarHandlerFunctions
import net.psforever.objects.serverobject.containable.ContainableBehavior
import net.psforever.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction}
import net.psforever.types.ImplantType
@ -295,6 +296,17 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0))
}
//functionally delete
if (delete.size > 1 || delete.nonEmpty && !delete.exists {
case (e: Tool, _) => GlobalDefinitions.isMaxArms(e.Definition)
case _ => false
}) {
/*
if going x -> max, you will have enough space in max inventory for any displaced holster equipment
for max -> max, don't care about the max weapon arm being deleted (allow for 1)
for any other x -> x, any deleted equipment will raise this comment
*/
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@ItemsDeconstructed"))
}
delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) }
//redraw
if (maxhand) {
@ -330,13 +342,17 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
}
DropLeftovers(player)(drop)
case AvatarResponse.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, _, delete) =>
case AvatarResponse.ChangeExosuit(target, armor, exosuit, subtype, slot, _, oldHolsters, holsters, _, _, drop, delete) =>
sendResponse(ArmorChangedMessage(target, exosuit, subtype))
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
//happening to some other player
sendResponse(ObjectHeldMessage(target, slot, unk1 = false))
//cleanup
(oldHolsters ++ delete).foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) }
val dropPred = ContainableBehavior.DropPredicate(player)
val deleteFromDrop = drop.filterNot(dropPred)
(oldHolsters ++ delete ++ deleteFromDrop.map(f =>(f.obj, f.GUID)))
.distinctBy(_._2)
.foreach { case (_, guid) => sendResponse(ObjectDeleteMessage(guid, unk1=0)) }
//draw holsters
holsters.foreach {
case InventoryItem(obj, index) =>

View file

@ -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"))
}
}

View file

@ -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)

View file

@ -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 {

View file

@ -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 {

View file

@ -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")
}

View file

@ -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"))
}
}

View file

@ -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 _ => ()

View file

@ -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)) {

View file

@ -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)

View file

@ -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,

View file

@ -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))
}
}
}

View file

@ -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)

View file

@ -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<br>
@ -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

View file

@ -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 {

View file

@ -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 {

View file

@ -522,6 +522,12 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
case Zone.Ground.CanNotPickupItem(_, item_guid, reason) =>
log.warn(s"${player.Name} failed to pick up an item ($item_guid) from the ground because $reason")
if (reason.startsWith("@")) {
player.Zone.AvatarEvents ! AvatarServiceMessage(
player.Name,
AvatarAction.SendResponse(Service.defaultPlayerGUID, ChatMsg(ChatMessageType.UNK_227, reason))
)
}
case Player.BuildDeployable(obj: TelepadDeployable, tool: Telepad) =>
obj.Router = tool.Router //necessary; forwards link to the router that produced the telepad
@ -631,6 +637,7 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
if (player.DrawnSlot != Player.HandsDownSlot) {
player.DrawnSlot = Player.HandsDownSlot
}
val dropPred = ContainableBehavior.DropPredicate(player)
val (toDelete, toDrop, afterHolsters, afterInventory) = if (originalSuit == ExoSuitType.MAX) {
//was max
val (delete, insert) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max)
@ -641,18 +648,21 @@ class PlayerControl(player: Player, avatarActor: typed.ActorRef[AvatarActor.Comm
//changing to a vanilla exo-suit
val (newHolsters, unplacedHolsters) = Players.fillEmptyHolsters(player.Holsters().iterator, insert ++ beforeInventory)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(unplacedHolsters, player.Inventory)
(delete, unplacedInventory.map(InventoryItem(_, -1)), newHolsters, inventory)
val (dropFromUnplaced, deleteFromUnplaced) = unplacedInventory.map(InventoryItem(_, -1)).partition(dropPred)
(delete ++ deleteFromUnplaced, dropFromUnplaced, newHolsters, inventory)
}
} else if (willBecomeMax) {
//will be max, drop everything but melee slot
val (melee, other) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Melee)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(beforeInventory ++ other, player.Inventory)
(Nil, unplacedInventory.map(InventoryItem(_, -1)), melee, inventory)
val (dropFromUnplaced, deleteFromUnplaced) = unplacedInventory.map(InventoryItem(_, -1)).partition(dropPred)
(deleteFromUnplaced, dropFromUnplaced, melee, inventory)
} else {
//was not a max nor will become a max; vanilla exo-suit to a vanilla-exo-suit
val (insert, unplacedHolsters) = Players.fillEmptyHolsters(player.Holsters().iterator, beforeHolsters ++ beforeInventory)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(unplacedHolsters, player.Inventory)
(Nil, unplacedInventory.map(InventoryItem(_, -1)), insert, inventory)
val (dropFromUnplaced, deleteFromUnplaced) = unplacedInventory.map(InventoryItem(_, -1)).partition(dropPred)
(deleteFromUnplaced, dropFromUnplaced, insert, inventory)
}
//insert
afterHolsters.foreach(elem => player.Slot(elem.start).Equipment = elem.obj)

View file

@ -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,

View file

@ -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
}
}

View file

@ -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"))
}

View file

@ -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
)

View file

@ -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

View file

@ -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)

View file

@ -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(

View file

@ -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)
}
/**

View file

@ -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
}
/**

View file

@ -1,7 +1,6 @@
// Copyright (c) 2021 PSForever
package net.psforever.objects.serverobject.environment.interaction
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment}
import net.psforever.objects.zones._
@ -98,6 +97,8 @@ class InteractWithEnvironment()
.foreach(_.stopInteractingWith(obj, body, None))
}
}
def OngoingInteractions: Set[EnvironmentTrait] = interactWith.map(_.attribute)
}
object InteractWithEnvironment {
@ -117,9 +118,8 @@ object InteractWithEnvironment {
obj: PlanetSideServerObject,
sector: SectorPopulation
): Set[PieceOfEnvironment] = {
val depth = GlobalDefinitions.MaxDepth(obj)
sector.environmentList
.filter(body => body.attribute.canInteractWith(obj) && body.testInteraction(obj, depth))
.filter(body => body.attribute.canInteractWith(obj) && body.testInteraction(obj, body.attribute.testingDepth(obj)))
.distinctBy(_.attribute)
.toSet
}
@ -136,7 +136,7 @@ object InteractWithEnvironment {
body: PieceOfEnvironment,
obj: PlanetSideServerObject
): Option[PieceOfEnvironment] = {
if ((obj.Zone eq zone) && body.testInteraction(obj, GlobalDefinitions.MaxDepth(obj))) {
if ((obj.Zone eq zone) && body.testInteraction(obj, body.attribute.testingDepth(obj))) {
Some(body)
} else {
None
@ -185,12 +185,12 @@ case class OnStableEnvironment() extends InteractionBehavior {
): Set[PieceOfEnvironment] = {
if (allow) {
val interactions = obj.interaction().collectFirst { case inter: InteractWithEnvironment => inter.Interactions }
val env = InteractWithEnvironment.checkAllEnvironmentInteractions(obj, sector)
env.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.doInteractingWith(obj, body, None)))
if (env.nonEmpty) {
val bodies = InteractWithEnvironment.checkAllEnvironmentInteractions(obj, sector)
bodies.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.doInteractingWith(obj, body, None)))
if (bodies.nonEmpty) {
nextstep = AwaitOngoingInteraction(obj.Zone)
}
env
bodies
} else {
nextstep = BlockedFromInteracting()
Set()
@ -225,17 +225,22 @@ final case class AwaitOngoingInteraction(zone: Zone) extends InteractionBehavior
): Set[PieceOfEnvironment] = {
val interactions = obj.interaction().collectFirst { case inter: InteractWithEnvironment => inter.Interactions }
if (allow) {
val env = InteractWithEnvironment.checkAllEnvironmentInteractions(obj, sector)
val bodies = InteractWithEnvironment.checkAllEnvironmentInteractions(obj, sector)
val (in, out) = existing.partition(body => InteractWithEnvironment.checkSpecificEnvironmentInteraction(zone, body, obj).nonEmpty)
env.diff(in).foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.doInteractingWith(obj, body, None)))
out.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.stopInteractingWith(obj, body, None)))
if (env.isEmpty) {
val inAttrs = bodies.map(_.attribute)
out
.filterNot(e => inAttrs.contains(e.attribute))
.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.stopInteractingWith(obj, body, None)))
bodies
.diff(in)
.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.doInteractingWith(obj, body, None)))
if (bodies.isEmpty) {
val n = OnStableEnvironment()
val out = n.perform(obj, sector, Set(), allow)
nextstep = n.next
out
} else {
env
bodies
}
} else {
existing.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.stopInteractingWith(obj, body, None)))

View file

@ -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

View file

@ -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 {

View file

@ -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
}

View file

@ -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))
}
}

View file

@ -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.<br>
* 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
}
/**

View file

@ -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 {

View file

@ -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
}

View file

@ -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 _ => ()
}
}

View file

@ -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)
}
}
}

View file

@ -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

View file

@ -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,

View file

@ -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))
}

View file

@ -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(

View file

@ -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

View file

@ -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
}
}
}

View file

@ -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] = (

View file

@ -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")

View file

@ -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)
}
)
}

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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()

View file

@ -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))

View file

@ -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,

View file

@ -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

View file

@ -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))
}

View file

@ -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

View file

@ -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
}

View file

@ -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

View file

@ -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 {

View file

@ -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)
}
}
}

View file

@ -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)
}
}
}

View file

@ -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)

View file

@ -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
}

View file

@ -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)")

View file

@ -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 =