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 akka.actor.{ActorContext, typed}
import net.psforever.actors.session.support.AvatarHandlerFunctions 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.packet.game.{AvatarImplantMessage, CreateShortcutMessage, ImplantAction}
import net.psforever.types.ImplantType 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)) case (_, dguid) => sendResponse(ObjectDeleteMessage(dguid, unk1=0))
} }
//functionally delete //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)) } delete.foreach { case (obj, _) => TaskWorkflow.execute(GUIDTask.unregisterEquipment(continent.GUID, obj)) }
//redraw //redraw
if (maxhand) { if (maxhand) {
@ -330,13 +342,17 @@ class AvatarHandlerLogic(val ops: SessionAvatarHandlers, implicit val context: A
} }
DropLeftovers(player)(drop) 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(ArmorChangedMessage(target, exosuit, subtype))
sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor)) sendResponse(PlanetsideAttributeMessage(target, attribute_type=4, armor))
//happening to some other player //happening to some other player
sendResponse(ObjectHeldMessage(target, slot, unk1 = false)) sendResponse(ObjectHeldMessage(target, slot, unk1 = false))
//cleanup //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 //draw holsters
holsters.foreach { holsters.foreach {
case InventoryItem(obj, index) => 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 => case (CMT_KICK, _, contents) if gmCommandAllowed =>
ops.commandKick(session, message, contents) ops.commandKick(session, message, contents)
case (CMT_REPORTUSER, _, contents) =>
ops.commandReportUser(session, message, contents)
case _ => 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 => () case None => ()
} }
//llu destruction check
if (player.Carrying.contains(SpecialCarry.CaptureFlag)) {
CaptureFlagManager.ReasonToLoseFlagViolently(continent, sessionLogic.general.specialItemSlotGuid, player)
}
//
val eagleEye: Boolean = ops.canSeeReallyFar val eagleEye: Boolean = ops.canSeeReallyFar
val isNotVisible: Boolean = sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing || val isNotVisible: Boolean = sessionLogic.zoning.zoningStatus == Zoning.Status.Deconstructing ||
(player.isAlive && sessionLogic.zoning.spawn.deadState == DeadState.RespawnTime) (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.environment.interaction.ResetAllEnvironmentInteractions
import net.psforever.objects.serverobject.hackable.GenericHackables import net.psforever.objects.serverobject.hackable.GenericHackables
import net.psforever.objects.serverobject.mount.Mountable 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.terminals.implant.ImplantTerminalMech
import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret} import net.psforever.objects.serverobject.turret.{FacilityTurret, WeaponTurret}
import net.psforever.objects.vehicles.{AccessPermissionGroup, CargoBehavior} 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.Service
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} 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._ 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) obj.Actor ! Mountable.TryDismount(player, seat_num, bailType)
//short-circuit the temporary channel for transferring between zones, the player is no longer doing that //short-circuit the temporary channel for transferring between zones, the player is no longer doing that
sessionLogic.zoning.interstellarFerry = None 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 => case None =>
dError(s"DismountVehicleMsg: can not find where player ${player.Name}_guid is seated in mountable ${player.VehicleSeated}", player) 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) DismountAction(tplayer, obj, seatNum)
obj.Actor ! Vehicle.Deconstruct() 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, _) case Mountable.CanDismount(obj: Vehicle, seatNum, _)
if tplayer.GUID == player.GUID => 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) DismountVehicleAction(tplayer, obj, seatNum)
case Mountable.CanDismount(obj: Vehicle, seat_num, _) => 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) 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}") log.info(s"${tplayer.Name} dismounts a ${obj.Definition.asInstanceOf[ObjectDefinition].Name}")
DismountAction(tplayer, obj, seatNum) DismountAction(tplayer, obj, seatNum)
@ -407,7 +397,38 @@ class MountHandlerLogic(val ops: SessionMountHandlers, implicit val context: Act
case Mountable.CanNotMount(obj: Mountable, seatNumber) => case Mountable.CanNotMount(obj: Mountable, seatNumber) =>
log.warn(s"MountVehicleMsg: ${tplayer.Name} attempted to mount $obj's seat $seatNumber, but was not allowed") 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") 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 obj the mountable object
* @param seatNum the mount out of which which the player is disembarking * @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) DismountAction(tplayer, obj, seatNum)
//until vehicles maintain synchronized momentum without a driver //until vehicles maintain synchronized momentum without a driver
obj match { obj match {

View file

@ -4,6 +4,7 @@ package net.psforever.actors.session.normal
import akka.actor.{ActorContext, ActorRef, typed} import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.actors.session.AvatarActor import net.psforever.actors.session.AvatarActor
import net.psforever.actors.session.support.{SessionData, SessionVehicleHandlers, VehicleHandlerFunctions} 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.{GlobalDefinitions, Player, Tool, Vehicle, Vehicles}
import net.psforever.objects.equipment.{Equipment, JammableMountedWeapons, JammableUnit} import net.psforever.objects.equipment.{Equipment, JammableMountedWeapons, JammableUnit}
import net.psforever.objects.guid.{GUIDTask, TaskWorkflow} 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.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.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.Service
import net.psforever.services.local.support.CaptureFlagManager
import net.psforever.services.vehicle.{VehicleResponse, VehicleServiceResponse} import net.psforever.services.vehicle.{VehicleResponse, VehicleServiceResponse}
import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3} import net.psforever.types.{BailType, ChatMessageType, PlanetSideGUID, Vector3}
@ -62,6 +64,14 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
player.Orientation = orient player.Orientation = orient
player.Velocity = vel player.Velocity = vel
sessionLogic.updateLocalBlockMap(pos) 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( case VehicleResponse.VehicleState(
vehicleGuid, vehicleGuid,
@ -199,6 +209,9 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid)) avatarActor ! AvatarActor.SetVehicle(Some(vehicleGuid))
sendResponse(PlanetsideAttributeMessage(resolvedPlayerGuid, attribute_type=21, 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 => case VehicleResponse.PlanetsideAttribute(vehicleGuid, attributeType, attributeValue) if isNotSameTarget =>
sendResponse(PlanetsideAttributeMessage(vehicleGuid, attributeType, attributeValue)) sendResponse(PlanetsideAttributeMessage(vehicleGuid, attributeType, attributeValue))
@ -217,6 +230,16 @@ class VehicleHandlerLogic(val ops: SessionVehicleHandlers, implicit val context:
case VehicleResponse.UnloadVehicle(_, vehicleGuid) => case VehicleResponse.UnloadVehicle(_, vehicleGuid) =>
sendResponse(ObjectDeleteMessage(vehicleGuid, unk1=0)) 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 => case VehicleResponse.UnstowEquipment(itemGuid) if isNotSameTarget =>
//TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? //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) sessionLogic.vehicles.ServerVehicleOverrideStop(vehicle)
case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) => case VehicleResponse.PeriodicReminder(VehicleSpawnPad.Reminders.Blocked, data) =>
sendResponse(ChatMsg( val str = s"${data.getOrElse("The vehicle spawn pad where you placed your order is blocked.")}"
ChatMessageType.CMT_OPEN, val msg = if (str.contains("@")) {
wideContents=true, ChatMsg(ChatMessageType.UNK_229, str)
recipient="", } else {
s"The vehicle spawn where you placed your order is blocked. ${data.getOrElse("")}", ChatMsg(ChatMessageType.CMT_OPEN, wideContents = true, recipient = "", str, note = None)
note=None }
)) sendResponse(msg)
case VehicleResponse.PeriodicReminder(_, data) => case VehicleResponse.PeriodicReminder(_, data) =>
val (isType, flag, msg): (ChatMessageType, Boolean, String) = data match { 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.serverobject.mount.Mountable
import net.psforever.objects.vehicles.control.BfrFlight import net.psforever.objects.vehicles.control.BfrFlight
import net.psforever.objects.zones.Zone 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.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types.{DriveState, Vector3} import net.psforever.types.{ChatMessageType, DriveState, Vector3}
object VehicleLogic { object VehicleLogic {
def apply(ops: VehicleOperations): VehicleLogic = { def apply(ops: VehicleOperations): VehicleLogic = {
@ -70,6 +70,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
obj.Velocity = None obj.Velocity = None
obj.Flying = None obj.Flying = None
} }
//
continent.VehicleEvents ! VehicleServiceMessage( continent.VehicleEvents ! VehicleServiceMessage(
continent.id, continent.id,
VehicleAction.VehicleState( VehicleAction.VehicleState(
@ -303,6 +304,7 @@ class VehicleLogic(val ops: VehicleOperations, implicit val context: ActorContex
log.trace(s"DeployRequest: $obj transitioning to deploy state") log.trace(s"DeployRequest: $obj transitioning to deploy state")
} else if (state == DriveState.Deployed) { } else if (state == DriveState.Deployed) {
log.trace(s"DeployRequest: $obj has been Deployed") log.trace(s"DeployRequest: $obj has been Deployed")
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@DeployingMessage"))
} else { } else {
CanNotChangeDeployment(obj, state, "incorrect deploy state") 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") log.trace(s"DeployRequest: $obj transitioning to undeploy state")
} else if (state == DriveState.Mobile) { } else if (state == DriveState.Mobile) {
log.trace(s"DeployRequest: $obj is Mobile") log.trace(s"DeployRequest: $obj is Mobile")
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@UndeployingMessage"))
} else { } else {
CanNotChangeDeployment(obj, state, "incorrect undeploy state") 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) => case (CMT_WARP, _, contents) =>
ops.commandWarp(session, message, 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, _) => case Mountable.CanDismount(obj: PlanetSideGameObject with PlanetSideGameObject with Mountable with FactionAffinity with InGameHistory, seatNum, _) =>
DismountAction(tplayer, obj, 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() obj.Actor ! Vehicle.Deconstruct()
case _ => () 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 handleCanDeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = { /* intentionally blank */ }
def handleCanUndeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = { def handleCanUndeploy(obj: Deployment.DeploymentObject, state: DriveState.Value): Unit = { /* intentionally blank */ }
if (state != DriveState.Undeploying && state != DriveState.Mobile) {
CanNotChangeDeployment(obj, state, "incorrect undeploy state")
}
}
def handleCanNotChangeDeployment(obj: Deployment.DeploymentObject, state: DriveState.Value, reason: String): Unit = { def handleCanNotChangeDeployment(obj: Deployment.DeploymentObject, state: DriveState.Value, reason: String): Unit = {
if (Deployment.CheckForDeployState(state) && !Deployment.AngleCheck(obj)) { if (Deployment.CheckForDeployState(state) && !Deployment.AngleCheck(obj)) {

View file

@ -91,11 +91,13 @@ class ChatOperations(
} }
def commandWatermark(contents: String): Unit = { def commandWatermark(contents: String): Unit = {
val connectionState = val connectionState = {
if (contents.contains("40 80")) 100 if (contents.contains("40 80")) 100
else if (contents.contains("120 200")) 25 else if (contents.contains("120 200")) 25
else 50 else 50
}
context.self ! SessionActor.SetConnectionState(connectionState) context.self ! SessionActor.SetConnectionState(connectionState)
context.self ! SessionActor.SendResponse(ChatMsg(ChatMessageType.UNK_227, "@CMT_CULLWATERMARK_success"))
} }
def commandSpeed(message: ChatMsg, contents: String): Unit = { 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 = { def commandIncomingSendAllIfOnline(session: Session, message: ChatMsg): Unit = {
if (AvatarActor.onlineIfNotIgnored(session.avatar, message.recipient)) { if (AvatarActor.onlineIfNotIgnored(session.avatar, message.recipient)) {
sendResponse(message) sendResponse(message)

View file

@ -220,8 +220,12 @@ class GeneralOperations(
) )
} }
} }
continent.LocalEvents ! CaptureFlagManager.DropFlag(llu) if (!CaptureFlagManager.ReasonToLoseFlagViolently(continent, Some(guid), player)) {
case Some((llu, Some(carrier: Player))) if carrier.GUID == player.GUID => 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) continent.LocalEvents ! CaptureFlagManager.DropFlag(llu)
case Some((_, Some(carrier: Player))) => case Some((_, Some(carrier: Player))) =>
log.warn(s"${player.toString} tried to drop LLU, but it is currently held by ${carrier.toString}") log.warn(s"${player.toString} tried to drop LLU, but it is currently held by ${carrier.toString}")
@ -669,6 +673,9 @@ class GeneralOperations(
case _ => () case _ => ()
} }
} else { } else {
if (player.Capacitor < 1f && player.UsingSpecial == SpecialExoSuitDefinition.Mode.Shielded) {
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@ArmorShieldOff"))
}
player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal player.UsingSpecial = SpecialExoSuitDefinition.Mode.Normal
continent.AvatarEvents ! AvatarServiceMessage( continent.AvatarEvents ! AvatarServiceMessage(
continent.id, continent.id,

View file

@ -3,8 +3,10 @@ package net.psforever.actors.session.support
import akka.actor.{ActorContext, ActorRef, typed} import akka.actor.{ActorContext, ActorRef, typed}
import net.psforever.actors.session.AvatarActor 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.services.vehicle.VehicleResponse
import net.psforever.types.PlanetSideGUID import net.psforever.types.{ChatMessageType, DriveState, PlanetSideGUID}
trait VehicleHandlerFunctions extends CommonSessionInterfacingFunctionality { trait VehicleHandlerFunctions extends CommonSessionInterfacingFunctionality {
def ops: SessionVehicleHandlers def ops: SessionVehicleHandlers
@ -17,4 +19,17 @@ class SessionVehicleHandlers(
val avatarActor: typed.ActorRef[AvatarActor.Command], val avatarActor: typed.ActorRef[AvatarActor.Command],
val galaxyService: ActorRef, val galaxyService: ActorRef,
implicit val context: ActorContext 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 package net.psforever.actors.session.support
import akka.actor.{ActorContext, typed} import akka.actor.{ActorContext, typed}
import net.psforever.objects.definition.SpecialExoSuitDefinition
import net.psforever.objects.zones.Zoning import net.psforever.objects.zones.Zoning
import net.psforever.objects.serverobject.turret.VanuSentry import net.psforever.objects.serverobject.turret.VanuSentry
import net.psforever.objects.zones.exp.ToDatabase import net.psforever.objects.zones.exp.ToDatabase
import net.psforever.types.ChatMessageType
import scala.collection.mutable import scala.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -83,9 +85,10 @@ class WeaponAndProjectileOperations(
if (player.ZoningRequest != Zoning.Method.None) { if (player.ZoningRequest != Zoning.Method.None) {
sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_fire") sessionLogic.zoning.CancelZoningProcessWithDescriptiveReason("cancel_fire")
} }
if (player.isShielded) { if (player.UsingSpecial == SpecialExoSuitDefinition.Mode.Shielded) {
// Cancel NC MAX shield if it's active // Cancel NC MAX shield if it's active
sessionLogic.general.toggleMaxSpecialState(enable = false) sessionLogic.general.toggleMaxSpecialState(enable = false)
sendResponse(ChatMsg(ChatMessageType.UNK_227, "@ArmorShieldOverride"))
} }
val (o, tools) = FindContainedWeapon val (o, tools) = FindContainedWeapon
val (_, enabledTools) = FindEnabledWeaponsToHandleWeaponFireAccountability(o, tools) val (_, enabledTools) = FindEnabledWeaponsToHandleWeaponFireAccountability(o, tools)

View file

@ -1792,6 +1792,7 @@ class ZoningOperations(
private[session] var setupAvatarFunc: () => Unit = AvatarCreate private[session] var setupAvatarFunc: () => Unit = AvatarCreate
private[session] var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally private[session] var setCurrentAvatarFunc: Player => Unit = SetCurrentAvatarNormally
private[session] var nextSpawnPoint: Option[SpawnPoint] = None private[session] var nextSpawnPoint: Option[SpawnPoint] = None
private[session] var prevSpawnPoint: Option[SpawnPoint] = None
private[session] var interimUngunnedVehicle: Option[PlanetSideGUID] = None private[session] var interimUngunnedVehicle: Option[PlanetSideGUID] = None
private[session] var interimUngunnedVehicleSeat: Option[Int] = None private[session] var interimUngunnedVehicleSeat: Option[Int] = None
/** Upstream message counter<br> /** Upstream message counter<br>
@ -2811,6 +2812,7 @@ class ZoningOperations(
) )
) )
nextSpawnPoint = physSpawnPoint nextSpawnPoint = physSpawnPoint
prevSpawnPoint = physSpawnPoint
shiftPosition = Some(pos) shiftPosition = Some(pos)
shiftOrientation = Some(ori) shiftOrientation = Some(ori)
val toZoneNumber = if (continent.id.equals(zoneId)) { 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 Zones.zones.find { _.id.equals(zoneId) }.orElse(Some(Zone.Nowhere)).get.Number
} }
val toSide = physSpawnPoint.map(_.Owner) match { val toSide = physSpawnPoint.map(_.Owner) match {
case Some(_: WarpGate) => Sidedness.OutsideOf case Some(_: WarpGate) =>
case Some(_: Building) => Sidedness.InsideOf Sidedness.OutsideOf
case Some(v: Vehicle) => v.WhichSide //though usually OutsideOf case Some(_: Building) =>
case _ => Sidedness.StrictlyBetweenSides //todo needs better determination 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) } val toSpawnPoint = physSpawnPoint.collect { case o: PlanetSideGameObject with FactionAffinity => SourceEntry(o) }
respawnTimer = context.system.scheduler.scheduleOnce(respawnTime) { respawnTimer = context.system.scheduler.scheduleOnce(respawnTime) {
@ -3124,11 +3130,17 @@ class ZoningOperations(
Config.app.game.savedMsg.short.fixed, Config.app.game.savedMsg.short.fixed,
Config.app.game.savedMsg.short.variable Config.app.game.savedMsg.short.variable
) )
val effortBy = nextSpawnPoint val effortBy = prevSpawnPoint
.collect { case sp: SpawnTube => (sp, continent.GUID(sp.Owner.GUID)) } .collect { case sp: SpawnTube => (sp, continent.GUID(sp.Owner.GUID)) }
.collect { .collect {
case (_, Some(v: Vehicle)) => continent.GUID(v.OwnerGuid) case (_, Some(v: Vehicle)) =>
case (sp, Some(_: Building)) => Some(sp) 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)) } .collect { case Some(thing: PlanetSideGameObject with FactionAffinity) => Some(SourceEntry(thing)) }
.flatten .flatten

View file

@ -65,7 +65,7 @@ object WorldSession {
Zone.Ground.DropItem(localItem, localContainer.Position, Vector3.z(localContainer.Orientation.z)), Zone.Ground.DropItem(localItem, localContainer.Position, Vector3.z(localContainer.Orientation.z)),
localContainer.Actor localContainer.Actor
) )
case _ => ; case _ => ()
} }
result result
} }
@ -163,7 +163,7 @@ object WorldSession {
result.onComplete { result.onComplete {
case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) => case Failure(_) | Success(_: Containable.CanNotPutItemInSlot) =>
TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem)) TaskWorkflow.execute(GUIDTask.unregisterEquipment(localContainer.Zone.GUID, localItem))
case _ => ; case _ => ()
} }
result result
} }
@ -361,17 +361,21 @@ object WorldSession {
val future = ask(localZone.Ground, Zone.Ground.PickupItem(item.GUID)) val future = ask(localZone.Ground, Zone.Ground.PickupItem(item.GUID))
future.onComplete { future.onComplete {
case Success(Zone.Ground.ItemInHand(_)) => 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, _)) => case Success(Zone.Ground.CanNotPickupItem(_, item_guid, _)) =>
localZone.GUID(item_guid) match { localZone.GUID(item_guid) match {
case Some(_) => ; case Some(_) => ()
case None => //acting on old data? case None => //acting on old data?
localZone.AvatarEvents ! AvatarServiceMessage( localZone.AvatarEvents ! AvatarServiceMessage(
localZone.id, localZone.id,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item_guid) AvatarAction.ObjectDelete(Service.defaultPlayerGUID, item_guid)
) )
} }
case _ => ; case _ => ()
} }
future future
} }
@ -407,7 +411,7 @@ object WorldSession {
.DropItem(localItem, localPos.getOrElse(localContainer.Position), Vector3.z(localContainer.Orientation.z)), .DropItem(localItem, localPos.getOrElse(localContainer.Position), Vector3.z(localContainer.Orientation.z)),
localContainer.Actor localContainer.Actor
) )
case _ => ; case _ => ()
} }
result result
} }
@ -584,7 +588,7 @@ object WorldSession {
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) => case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
//swapItem is not registered right now, we can not drop the item without re-registering it //swapItem is not registered right now, we can not drop the item without re-registering it
TaskWorkflow.execute(PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot)) TaskWorkflow.execute(PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot))
case _ => ; case _ => ()
} }
override def description(): String = s"unregistering $localItem before stowing in $localDestination" override def description(): String = s"unregistering $localItem before stowing in $localDestination"
@ -597,7 +601,7 @@ object WorldSession {
localChannel, localChannel,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid) AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid)
) )
case None => ; case None => ()
} }
val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot))) val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot)))
moveResult.onComplete(localMoveOnComplete) moveResult.onComplete(localMoveOnComplete)
@ -610,7 +614,7 @@ object WorldSession {
moveItemTaskFunc(fromSlot), moveItemTaskFunc(fromSlot),
GUIDTask.unregisterEquipment(fromSource.Zone.GUID, itemToMove) GUIDTask.unregisterEquipment(fromSource.Zone.GUID, itemToMove)
)) ))
case _ => ; case _ => ()
} }
val result = ask(source.Actor, Containable.RemoveItemFromSlot(item)) val result = ask(source.Actor, Containable.RemoveItemFromSlot(item))
result.onComplete(resultOnComplete) result.onComplete(resultOnComplete)
@ -689,7 +693,7 @@ object WorldSession {
case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) => case Success(Containable.ItemPutInSlot(_, _, _, Some(swapItem))) =>
//swapItem is not registered right now, we can not drop the item without re-registering it //swapItem is not registered right now, we can not drop the item without re-registering it
TaskWorkflow.execute(PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot)) TaskWorkflow.execute(PutNewEquipmentInInventorySlot(localSource)(swapItem, localSrcSlot))
case _ => ; case _ => ()
} }
override def description(): String = s"registering $localItem in ${localDestination.Zone.id} before removing from $localSource" override def description(): String = s"registering $localItem in ${localDestination.Zone.id} before removing from $localSource"
@ -702,7 +706,7 @@ object WorldSession {
localChannel, localChannel,
AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid) AvatarAction.ObjectDelete(Service.defaultPlayerGUID, guid)
) )
case None => ; case None => ()
} }
val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot))) val moveResult = ask(localDestination.Actor, Containable.PutItemInSlotOrAway(localItem, Some(localDestSlot)))
moveResult.onComplete(localMoveOnComplete) moveResult.onComplete(localMoveOnComplete)
@ -715,7 +719,7 @@ object WorldSession {
moveItemTaskFunc(fromSlot), moveItemTaskFunc(fromSlot),
GUIDTask.registerEquipment(fromSource.Zone.GUID, itemToMove) GUIDTask.registerEquipment(fromSource.Zone.GUID, itemToMove)
)) ))
case _ => ; case _ => ()
} }
val result = ask(source.Actor, Containable.RemoveItemFromSlot(item)) val result = ask(source.Actor, Containable.RemoveItemFromSlot(item))
result.onComplete(resultOnComplete) result.onComplete(resultOnComplete)
@ -847,17 +851,17 @@ object WorldSession {
case Some(e) => case Some(e) =>
log.info(s"${tplayer.Name} has dropped ${tplayer.Sex.possessive} ${e.Definition.Name}") log.info(s"${tplayer.Name} has dropped ${tplayer.Sex.possessive} ${e.Definition.Name}")
PutEquipmentInInventoryOrDrop(tplayer)(e) PutEquipmentInInventoryOrDrop(tplayer)(e)
case _ => ; case _ => ()
} }
//restore previously-held-up equipment //restore previously-held-up equipment
itemInPreviouslyDrawnSlotToDrop match { itemInPreviouslyDrawnSlotToDrop match {
case Some(e) => PutEquipmentInInventorySlot(tplayer)(e, previouslyDrawnSlot) case Some(e) => PutEquipmentInInventorySlot(tplayer)(e, previouslyDrawnSlot)
case _ => ; case _ => ()
} }
log.info(s"${tplayer.Name} has quickly drawn a ${grenade.Definition.Name}") log.info(s"${tplayer.Name} has quickly drawn a ${grenade.Definition.Name}")
case _ => ; case _ => ()
} }
case None => ; case None => ()
} }
optGrenadeInSlot.nonEmpty optGrenadeInSlot.nonEmpty
} else { } 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.serverobject.structures.WarpGate
import net.psforever.objects.vehicles._ import net.psforever.objects.vehicles._
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{HackMessage, HackState, HackState1, HackState7, TriggeredSound} import net.psforever.packet.game.{ChatMsg, HackMessage, HackState, HackState1, HackState7, TriggeredSound}
import net.psforever.types.{DriveState, PlanetSideEmpire, PlanetSideGUID, Vector3} import net.psforever.types.{ChatMessageType, DriveState, PlanetSideEmpire, PlanetSideGUID, Vector3}
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
@ -46,7 +46,7 @@ object Vehicles {
vehicle.Zone.id, vehicle.Zone.id,
VehicleAction.Ownership(tplayer.GUID, vehicle.GUID) VehicleAction.Ownership(tplayer.GUID, vehicle.GUID)
) )
Vehicles.ReloadAccessPermissions(vehicle, tplayer.Name) Vehicles.ReloadAccessPermissions(vehicle, tplayer.Faction.toString)
Some(vehicle) Some(vehicle)
case None => case None =>
None None
@ -61,31 +61,37 @@ object Vehicles {
* @return the vehicle, if it had a previous owner; * @return the vehicle, if it had a previous owner;
* `None`, otherwise * `None`, otherwise
*/ */
def Disown(guid: PlanetSideGUID, vehicle: Vehicle): Option[Vehicle] = def Disown(guid: PlanetSideGUID, vehicle: Vehicle): Option[Vehicle] = {
vehicle.Zone.GUID(vehicle.OwnerGuid) match { val zone = vehicle.Zone
case Some(player: Player) => val ownerGuid = vehicle.OwnerGuid
if (player.avatar.vehicle.contains(guid)) { val ownerName = vehicle.OwnerName
player.avatar.vehicle = None vehicle.AssignOwnership(None)
// vehicle.Zone.VehicleEvents ! VehicleServiceMessage( val result = zone
// player.Name, .GUID(ownerGuid)
// VehicleAction.Ownership(player.GUID, PlanetSideGUID(0)) .collect {
// ) case player: Player => player.avatar
} }
vehicle.AssignOwnership(None) .orElse {
val empire = VehicleLockState.Empire.id zone
//val factionChannel = s"${vehicle.Faction}" .Players
(0 to 2).foreach(group => { .collectFirst {
vehicle.PermissionGroup(group, empire) case avatar if ownerName.contains(avatar.name) => avatar
/*vehicle.Zone.VehicleEvents ! VehicleServiceMessage( }
factionChannel, }
VehicleAction.SeatPermissions(Service.defaultPlayerGUID, guid, group, empire) .collect {
)*/ case avatar if avatar.vehicle.contains(guid) =>
}) avatar.vehicle = None
ReloadAccessPermissions(vehicle, player.Name) vehicle
Some(vehicle) }
case _ => val empire = VehicleLockState.Empire.id
None (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. * Disassociate a player from a vehicle that he owns.
@ -140,7 +146,7 @@ object Vehicles {
VehicleAction.SeatPermissions(pguid, vguid, group, empire) VehicleAction.SeatPermissions(pguid, vguid, group, empire)
)*/ )*/
}) })
ReloadAccessPermissions(vehicle, player.Name) ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
Some(vehicle) Some(vehicle)
} else { } else {
None None
@ -239,6 +245,8 @@ object Vehicles {
val zone = target.Zone val zone = target.Zone
val zoneid = zone.id val zoneid = zone.id
val vehicleEvents = zone.VehicleEvents val vehicleEvents = zone.VehicleEvents
val localEvents = zone.LocalEvents
val previousOwnerName = target.OwnerName.getOrElse("")
vehicleEvents ! VehicleServiceMessage( vehicleEvents ! VehicleServiceMessage(
zoneid, zoneid,
VehicleAction.SendResponse( VehicleAction.SendResponse(
@ -292,10 +300,20 @@ object Vehicles {
AvatarAction.SetEmpire(Service.defaultPlayerGUID, tGuid, hFaction) AvatarAction.SetEmpire(Service.defaultPlayerGUID, tGuid, hFaction)
) )
} }
zone.LocalEvents ! LocalServiceMessage( localEvents ! LocalServiceMessage(
zoneid, zoneid,
LocalAction.TriggerSound(hGuid, TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f) 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 // Clean up after specific vehicles, e.g. remove router telepads
// If AMS is deployed, swap it to the new faction // If AMS is deployed, swap it to the new faction
target.Definition match { 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) => 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") 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) => case Player.BuildDeployable(obj: TelepadDeployable, tool: Telepad) =>
obj.Router = tool.Router //necessary; forwards link to the router that produced the 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) { if (player.DrawnSlot != Player.HandsDownSlot) {
player.DrawnSlot = Player.HandsDownSlot player.DrawnSlot = Player.HandsDownSlot
} }
val dropPred = ContainableBehavior.DropPredicate(player)
val (toDelete, toDrop, afterHolsters, afterInventory) = if (originalSuit == ExoSuitType.MAX) { val (toDelete, toDrop, afterHolsters, afterInventory) = if (originalSuit == ExoSuitType.MAX) {
//was max //was max
val (delete, insert) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.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 //changing to a vanilla exo-suit
val (newHolsters, unplacedHolsters) = Players.fillEmptyHolsters(player.Holsters().iterator, insert ++ beforeInventory) val (newHolsters, unplacedHolsters) = Players.fillEmptyHolsters(player.Holsters().iterator, insert ++ beforeInventory)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(unplacedHolsters, player.Inventory) 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) { } else if (willBecomeMax) {
//will be max, drop everything but melee slot //will be max, drop everything but melee slot
val (melee, other) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Melee) val (melee, other) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Melee)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(beforeInventory ++ other, player.Inventory) 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 { } else {
//was not a max nor will become a max; vanilla exo-suit to a vanilla-exo-suit //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 (insert, unplacedHolsters) = Players.fillEmptyHolsters(player.Holsters().iterator, beforeHolsters ++ beforeInventory)
val (inventory, unplacedInventory) = GridInventory.recoverInventory(unplacedHolsters, player.Inventory) 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 //insert
afterHolsters.foreach(elem => player.Slot(elem.start).Equipment = elem.obj) afterHolsters.foreach(elem => player.Slot(elem.start).Equipment = elem.obj)

View file

@ -2,14 +2,15 @@
package net.psforever.objects.avatar.interaction package net.psforever.objects.avatar.interaction
import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment} 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.environment.{EnvironmentAttribute, EnvironmentTrait, GantryDenialField, PieceOfEnvironment, interaction}
import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad import net.psforever.objects.serverobject.shuttle.OrbitalShuttlePad
import net.psforever.objects.zones.InteractsWithZone 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.Service
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.hart.ShuttleState import net.psforever.services.hart.ShuttleState
import net.psforever.types.ChatMessageType
import scala.annotation.unused import scala.annotation.unused
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -28,17 +29,26 @@ class WithGantry(val channel: String)
(zone.GUID(field.obbasemesh) match { (zone.GUID(field.obbasemesh) match {
case Some(pad : OrbitalShuttlePad) => zone.GUID(pad.shuttle) case Some(pad : OrbitalShuttlePad) => zone.GUID(pad.shuttle)
case _ => None case _ => None
}) match { }, obj) match {
case Some(shuttle: Vehicle) case (Some(shuttle: Vehicle), player: Player)
if shuttle.Flying.contains(ShuttleState.State11.id) || shuttle.Faction != obj.Faction => if (shuttle.Flying.contains(ShuttleState.State11.id) || shuttle.Faction != player.Faction) &&
player.VehicleSeated.isEmpty =>
val (pos, ang) = Vehicles.dismountShuttle(shuttle, field.mountPoint) val (pos, ang) = Vehicles.dismountShuttle(shuttle, field.mountPoint)
shuttle.Zone.AvatarEvents ! AvatarServiceMessage( val events = shuttle.Zone.AvatarEvents
events ! AvatarServiceMessage(
channel, channel,
AvatarAction.SendResponse( AvatarAction.SendResponse(
Service.defaultPlayerGUID, Service.defaultPlayerGUID,
PlayerStateShiftMessage(ShiftState(0, pos, ang, None))) 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( obj.Actor ! RespondsToZoneEnvironment.Timer(
attribute, attribute,
delay = 250 milliseconds, delay = 250 milliseconds,

View file

@ -1,11 +1,11 @@
// Copyright (c) 2024 PSForever // Copyright (c) 2024 PSForever
package net.psforever.objects.avatar.interaction 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.{InteractionWith, RespondsToZoneEnvironment}
import net.psforever.objects.serverobject.environment.interaction.common.Watery 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.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.objects.zones.InteractsWithZone
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.types.OxygenState import net.psforever.types.OxygenState
@ -15,9 +15,11 @@ import scala.concurrent.duration._
class WithWater(val channel: String) class WithWater(val channel: String)
extends InteractionWith extends InteractionWith
with Watery { 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. * Water is wet.
* When they (finally) drown, they will die.
* @param obj the target * @param obj the target
* @param body the environment * @param body the environment
*/ */
@ -26,22 +28,84 @@ class WithWater(val channel: String)
body: PieceOfEnvironment, body: PieceOfEnvironment,
data: Option[Any] data: Option[Any]
): Unit = { ): Unit = {
val extra = data.collect { if (getExtra(data).nonEmpty) {
case t: OxygenStateTarget => Some(t) inheritAndPushExtraData(obj, body, data)
case w: Watery => w.Condition
}.flatten
if (extra.isDefined) {
//inform the player that their mounted vehicle is in trouble (that they are in trouble (but not from drowning (yet)))
stopInteractingWith(obj, body, data)
} else { } else {
val (effect, time, percentage) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime) depth = math.max(0f, body.collision.altitude - obj.Position.z)
if (effect) { doInteractingWithBehavior(obj, body, data)
val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage) obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = 500 milliseconds, obj.Actor, interaction.InteractingWithEnvironment(body, Some("wading")))
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)) * 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 obj the target
* @param body the environment * @param body the environment
*/ */
override def stopInteractingWith( private def recoverFromDrowning(
obj: InteractsWithZone, obj: InteractsWithZone,
body: PieceOfEnvironment, body: PieceOfEnvironment,
data: Option[Any] data: Option[Any]
): Unit = { ): Unit = {
val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime) val state = condition.map(_.state)
if (percentage > 99f) { if (state.contains(OxygenState.Suffocation)) {
recoverFromInteracting(obj) //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 { } 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 time current time until completion of the next effect
* @param percentage value to display in the drowning UI progress bar * @param percentage value to display in the drowning UI progress bar
*/ */
private def stopInteractingAction( private def recoverFromDrowning(
obj: InteractsWithZone, obj: InteractsWithZone,
body: PieceOfEnvironment, body: PieceOfEnvironment,
data: Option[Any], data: Option[Any],
effect: Boolean, effect: Boolean,
time: Long, time: Long,
percentage: Float percentage: Float
): Unit = { ): Unit = {
val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage) val cond = OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage)
val extra = data.collect { val extra = data.collect {
case t: OxygenStateTarget => Some(t) case t: OxygenStateTarget => Some(t)
case w: Watery => w.Condition case w: Watery => w.Condition
}.flatten }.flatten
if (effect) { if (effect) {
condition = Some(cond) condition = Some(cond)
waterInteractionTime = System.currentTimeMillis() + time 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 //inform the player
obj.Zone.AvatarEvents ! AvatarServiceMessage(channel, AvatarAction.OxygenState(cond, extra)) displayOxygenState(obj, cond, extra)
} else if (extra.isDefined) { } else if (extra.isDefined) {
//inform the player //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 = { override def recoverFromInteracting(obj: InteractsWithZone): Unit = {
super.recoverFromInteracting(obj) super.recoverFromInteracting(obj)
if (condition.exists(_.state == OxygenState.Suffocation)) { val cond = condition.map(_.state)
val (effect, time, percentage) = Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime) //whether or not we were suffocating or recovering, we need to undo the visuals for that
stopInteractingAction(obj, condition.map(_.body).get, None, effect, time, percentage) 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 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} import scala.util.{Failure, Success, Try}
class SmallDeployableConverter extends ObjectCreateConverter[Deployable]() { class SmallDeployableConverter extends ObjectCreateConverter[Deployable]() {
override def ConstructorData(obj: Deployable): Try[CommonFieldDataWithPlacement] = { override def ConstructorData(obj: Deployable): Try[SmallDeployableData] = {
Success( Success(
CommonFieldDataWithPlacement( SmallDeployableData(CommonFieldDataWithPlacement(
PlacementData(obj.Position, obj.Orientation), PlacementData(obj.Position, obj.Orientation),
CommonFieldData( CommonFieldData(
obj.Faction, obj.Faction,
bops = false, bops = false,
alternate = obj.Destroyed, alternate = obj.Destroyed,
false, v1 = false,
None, None,
jammered = obj match { jammered = obj match {
case o: JammableUnit => o.Jammed case o: JammableUnit => o.Jammed
@ -30,10 +30,10 @@ class SmallDeployableConverter extends ObjectCreateConverter[Deployable]() {
case None => PlanetSideGUID(0) 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")) Failure(new Exception("converter should not be used to generate detailed small deployable data"))
} }

View file

@ -801,7 +801,7 @@ object GlobalDefinitionsMiscellaneous {
AutoRanges( AutoRanges(
detection = 125f, detection = 125f,
trigger = 100f, trigger = 100f,
escape = 200f escape = 150f
), ),
AutoChecks( AutoChecks(
validation = List( validation = List(
@ -813,7 +813,7 @@ object GlobalDefinitionsMiscellaneous {
), ),
retaliatoryDelay = 4000L, //8000L retaliatoryDelay = 4000L, //8000L
cylindrical = true, cylindrical = true,
cylindricalExtraHeight = 50f, cylindricalExtraHeight = 25f,
detectionSweepTime = 2.seconds, detectionSweepTime = 2.seconds,
refireTime = 362.milliseconds //312.milliseconds refireTime = 362.milliseconds //312.milliseconds
) )

View file

@ -971,7 +971,7 @@ object GlobalDefinitionsVehicle {
ams.DeployTime = 2000 ams.DeployTime = 2000
ams.UndeployTime = 2000 ams.UndeployTime = 2000
ams.interference = InterferenceRange(main = 125f, sharedGroupId = 3, shared = 30f) ams.interference = InterferenceRange(main = 125f, sharedGroupId = 3, shared = 30f)
ams.DeconstructionTime = Some(20 minutes) ams.DeconstructionTime = Some(15 minutes)
ams.AutoPilotSpeeds = (18, 6) ams.AutoPilotSpeeds = (18, 6)
ams.Packet = utilityConverter ams.Packet = utilityConverter
ams.DestroyedModel = Some(DestroyedVehicle.Ams) ams.DestroyedModel = Some(DestroyedVehicle.Ams)
@ -1014,6 +1014,7 @@ object GlobalDefinitionsVehicle {
router.Deployment = true router.Deployment = true
router.DeployTime = 2000 router.DeployTime = 2000
router.UndeployTime = 2000 router.UndeployTime = 2000
router.interference = InterferenceRange(main = 20f)
router.DeconstructionTime = Duration(20, "minutes") router.DeconstructionTime = Duration(20, "minutes")
router.AutoPilotSpeeds = (16, 6) router.AutoPilotSpeeds = (16, 6)
router.Packet = variantConverter router.Packet = variantConverter

View file

@ -676,6 +676,7 @@ object ContainableBehavior {
entry => { entry => {
val objDef = entry.obj.Definition val objDef = entry.obj.Definition
val faction = GlobalDefinitions.isFactionEquipment(objDef) val faction = GlobalDefinitions.isFactionEquipment(objDef)
GlobalDefinitions.isCavernEquipment(objDef) ||
objDef == GlobalDefinitions.router_telepad || objDef == GlobalDefinitions.router_telepad ||
entry.obj.isInstanceOf[BoomerTrigger] || entry.obj.isInstanceOf[BoomerTrigger] ||
(faction != tplayer.Faction && faction != PlanetSideEmpire.NEUTRAL) (faction != tplayer.Faction && faction != PlanetSideEmpire.NEUTRAL)

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.deploy 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.objects.Default
import net.psforever.types.{DriveState, Vector3} import net.psforever.types.{DriveState, Vector3}
import net.psforever.services.Service import net.psforever.services.Service
@ -33,24 +33,24 @@ trait DeploymentBehavior {
val deployBehavior: Receive = { val deployBehavior: Receive = {
case Deployment.TryDeploymentChange(state) => case Deployment.TryDeploymentChange(state) =>
sender() ! TryDeploymentStateChange(state) sender() ! TryDeploymentStateChange(state, sender())
case Deployment.TryDeploy(state) => case Deployment.TryDeploy(state) =>
sender() ! TryDeployStateChange(state) sender() ! TryDeployStateChange(state, sender())
case Deployment.TryUndeploy(state) => 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 obj = DeploymentObject
val prevState = obj.DeploymentState val prevState = obj.DeploymentState
if (TryDeploymentChange(obj, state)) { if (TryDeploymentChange(obj, state)) {
if (Deployment.CheckForDeployState(state)) { if (Deployment.CheckForDeployState(state)) {
DeploymentAction(obj, state, prevState) DeploymentAction(obj, state, prevState, replyTo)
Deployment.CanDeploy(obj, state) Deployment.CanDeploy(obj, state)
} else { } else {
UndeploymentAction(obj, state, prevState) UndeploymentAction(obj, state, prevState, replyTo)
Deployment.CanUndeploy(obj, state) Deployment.CanUndeploy(obj, state)
} }
} else { } 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 obj = DeploymentObject
val prevState = obj.DeploymentState val prevState = obj.DeploymentState
if (Deployment.CheckForDeployState(state) && TryDeploymentChange(obj, state)) { if (Deployment.CheckForDeployState(state) && TryDeploymentChange(obj, state)) {
DeploymentAction(obj, state, prevState) DeploymentAction(obj, state, prevState, replyTo)
Deployment.CanDeploy(obj, state) Deployment.CanDeploy(obj, state)
} else { } else {
Deployment.CanNotChangeDeployment(obj, state, "incorrect deploy transition state") 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 obj = DeploymentObject
val prevState = obj.DeploymentState val prevState = obj.DeploymentState
if (Deployment.CheckForUndeployState(state) && TryUndeploymentChange(obj, state)) { if (Deployment.CheckForUndeployState(state) && TryUndeploymentChange(obj, state)) {
UndeploymentAction(obj, state, prevState) UndeploymentAction(obj, state, prevState, replyTo)
Deployment.CanUndeploy(obj, state) Deployment.CanUndeploy(obj, state)
} else { } else {
Deployment.CanNotChangeDeployment(obj, state, "incorrect undeploy transition state") Deployment.CanNotChangeDeployment(obj, state, "incorrect undeploy transition state")
@ -91,7 +91,8 @@ trait DeploymentBehavior {
def DeploymentAction( def DeploymentAction(
obj: Deployment.DeploymentObject, obj: Deployment.DeploymentObject,
state: DriveState.Value, state: DriveState.Value,
prevState: DriveState.Value prevState: DriveState.Value,
replyTo: ActorRef
): DriveState.Value = { ): DriveState.Value = {
val guid = obj.GUID val guid = obj.GUID
val zone = obj.Zone val zone = obj.Zone
@ -108,11 +109,9 @@ trait DeploymentBehavior {
VehicleAction.DeployRequest(GUID0, guid, state, 0, unk2=false, Vector3.Zero) VehicleAction.DeployRequest(GUID0, guid, state, 0, unk2=false, Vector3.Zero)
) )
deploymentTimer.cancel() deploymentTimer.cancel()
deploymentTimer = context.system.scheduler.scheduleOnce( deploymentTimer = context.system.scheduler.scheduleOnce(obj.DeployTime.milliseconds)({
obj.DeployTime milliseconds, obj.Actor.tell(Deployment.TryDeploy(DriveState.Deployed), replyTo)
obj.Actor, })
Deployment.TryDeploy(DriveState.Deployed)
)
state state
} else if (state == DriveState.Deployed) { } else if (state == DriveState.Deployed) {
obj.Velocity = Some(Vector3.Zero) //no velocity obj.Velocity = Some(Vector3.Zero) //no velocity
@ -129,7 +128,8 @@ trait DeploymentBehavior {
def UndeploymentAction( def UndeploymentAction(
obj: Deployment.DeploymentObject, obj: Deployment.DeploymentObject,
state: DriveState.Value, state: DriveState.Value,
prevState: DriveState.Value prevState: DriveState.Value,
replyTo: ActorRef
): DriveState.Value = { ): DriveState.Value = {
val guid = obj.GUID val guid = obj.GUID
val zone = obj.Zone val zone = obj.Zone
@ -142,11 +142,9 @@ trait DeploymentBehavior {
) )
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
deploymentTimer.cancel() deploymentTimer.cancel()
deploymentTimer = context.system.scheduler.scheduleOnce( deploymentTimer = context.system.scheduler.scheduleOnce(obj.UndeployTime.milliseconds)({
obj.UndeployTime milliseconds, obj.Actor.tell(Deployment.TryUndeploy(DriveState.Mobile), replyTo)
obj.Actor, })
Deployment.TryUndeploy(DriveState.Mobile)
)
state state
} else if (state == DriveState.Mobile) { } else if (state == DriveState.Mobile) {
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(

View file

@ -46,11 +46,11 @@ object Interference {
* @param zone game world in which this test will be conducted; * @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 * 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 * @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) 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 { abstract class EnvironmentTrait {
def canInteractWith(obj: PlanetSideGameObject): Boolean def canInteractWith(obj: PlanetSideGameObject): Boolean
def testingDepth(obj: PlanetSideGameObject): Float
} }
object EnvironmentAttribute { object EnvironmentAttribute {
@ -25,16 +27,33 @@ object EnvironmentAttribute {
case _ => false 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 { case object Lava extends EnvironmentTrait {
/** lava can only interact with anything capable of registering damage */ /** lava can only interact with anything capable of registering damage */
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithDamagingFields(obj) def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithDamagingFields(obj)
def testingDepth(obj: _root_.net.psforever.objects.PlanetSideGameObject): Float = 0f
} }
case object Death extends EnvironmentTrait { case object Death extends EnvironmentTrait {
/** death can only interact with anything capable of registering damage */ /** death can only interact with anything capable of registering damage */
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithDamagingFields(obj) def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithDamagingFields(obj)
def testingDepth(obj: _root_.net.psforever.objects.PlanetSideGameObject): Float = 0f
} }
case object GantryDenialField case object GantryDenialField
@ -46,18 +65,24 @@ object EnvironmentAttribute {
case _ => false case _ => false
} }
} }
def testingDepth(obj: _root_.net.psforever.objects.PlanetSideGameObject): Float = 0f
} }
case object MovementFieldTrigger case object MovementFieldTrigger
extends EnvironmentTrait { extends EnvironmentTrait {
/** only interact with living player characters or vehicles */ /** only interact with living player characters or vehicles */
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithPlayersAndVehicles(obj) def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithPlayersAndVehicles(obj)
def testingDepth(obj: _root_.net.psforever.objects.PlanetSideGameObject): Float = 0f
} }
case object InteriorField case object InteriorField
extends EnvironmentTrait { extends EnvironmentTrait {
/** only interact with living player characters or vehicles */ /** only interact with living player characters or vehicles */
def canInteractWith(obj: PlanetSideGameObject): Boolean = canInteractWithPlayersAndVehicles(obj) 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 // Copyright (c) 2021 PSForever
package net.psforever.objects.serverobject.environment.interaction package net.psforever.objects.serverobject.environment.interaction
import net.psforever.objects.GlobalDefinitions
import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment} import net.psforever.objects.serverobject.environment.{EnvironmentTrait, PieceOfEnvironment}
import net.psforever.objects.zones._ import net.psforever.objects.zones._
@ -98,6 +97,8 @@ class InteractWithEnvironment()
.foreach(_.stopInteractingWith(obj, body, None)) .foreach(_.stopInteractingWith(obj, body, None))
} }
} }
def OngoingInteractions: Set[EnvironmentTrait] = interactWith.map(_.attribute)
} }
object InteractWithEnvironment { object InteractWithEnvironment {
@ -117,9 +118,8 @@ object InteractWithEnvironment {
obj: PlanetSideServerObject, obj: PlanetSideServerObject,
sector: SectorPopulation sector: SectorPopulation
): Set[PieceOfEnvironment] = { ): Set[PieceOfEnvironment] = {
val depth = GlobalDefinitions.MaxDepth(obj)
sector.environmentList 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) .distinctBy(_.attribute)
.toSet .toSet
} }
@ -136,7 +136,7 @@ object InteractWithEnvironment {
body: PieceOfEnvironment, body: PieceOfEnvironment,
obj: PlanetSideServerObject obj: PlanetSideServerObject
): Option[PieceOfEnvironment] = { ): 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) Some(body)
} else { } else {
None None
@ -185,12 +185,12 @@ case class OnStableEnvironment() extends InteractionBehavior {
): Set[PieceOfEnvironment] = { ): Set[PieceOfEnvironment] = {
if (allow) { if (allow) {
val interactions = obj.interaction().collectFirst { case inter: InteractWithEnvironment => inter.Interactions } val interactions = obj.interaction().collectFirst { case inter: InteractWithEnvironment => inter.Interactions }
val env = InteractWithEnvironment.checkAllEnvironmentInteractions(obj, sector) val bodies = InteractWithEnvironment.checkAllEnvironmentInteractions(obj, sector)
env.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.doInteractingWith(obj, body, None))) bodies.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.doInteractingWith(obj, body, None)))
if (env.nonEmpty) { if (bodies.nonEmpty) {
nextstep = AwaitOngoingInteraction(obj.Zone) nextstep = AwaitOngoingInteraction(obj.Zone)
} }
env bodies
} else { } else {
nextstep = BlockedFromInteracting() nextstep = BlockedFromInteracting()
Set() Set()
@ -225,17 +225,22 @@ final case class AwaitOngoingInteraction(zone: Zone) extends InteractionBehavior
): Set[PieceOfEnvironment] = { ): Set[PieceOfEnvironment] = {
val interactions = obj.interaction().collectFirst { case inter: InteractWithEnvironment => inter.Interactions } val interactions = obj.interaction().collectFirst { case inter: InteractWithEnvironment => inter.Interactions }
if (allow) { 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) 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))) val inAttrs = bodies.map(_.attribute)
out.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.stopInteractingWith(obj, body, None))) out
if (env.isEmpty) { .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 n = OnStableEnvironment()
val out = n.perform(obj, sector, Set(), allow) val out = n.perform(obj, sector, Set(), allow)
nextstep = n.next nextstep = n.next
out out
} else { } else {
env bodies
} }
} else { } else {
existing.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.stopInteractingWith(obj, body, None))) existing.foreach(body => interactions.flatMap(_.get(body.attribute)).foreach(_.stopInteractingWith(obj, body, None)))

View file

@ -1,20 +1,26 @@
// Copyright (c) 2024 PSForever // Copyright (c) 2024 PSForever
package net.psforever.objects.serverobject.environment.interaction.common package net.psforever.objects.serverobject.environment.interaction.common
import net.psforever.objects.PlanetSideGameObject
import net.psforever.objects.serverobject.PlanetSideServerObject 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.interaction.common.Watery.OxygenStateTarget
import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment} import net.psforever.objects.serverobject.environment.{EnvironmentAttribute, EnvironmentTrait, PieceOfEnvironment}
import net.psforever.objects.zones.InteractsWithZone
import net.psforever.types.{OxygenState, PlanetSideGUID} import net.psforever.types.{OxygenState, PlanetSideGUID}
trait Watery { trait Watery {
val attribute: EnvironmentTrait = EnvironmentAttribute.Water val attribute: EnvironmentTrait = EnvironmentAttribute.Water
/** how long the current interaction has been progressing in the current way */ /** how long the current interaction has been progressing in the current way */
protected var waterInteractionTime: Long = 0 protected var waterInteractionTime: Long = 0
/** information regarding the drowning state */ /** information regarding the drowning state */
protected var condition: Option[OxygenStateTarget] = None protected var condition: Option[OxygenStateTarget] = None
/** information regarding the drowning state */ /** information regarding the drowning state */
def Condition: Option[OxygenStateTarget] = condition 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 { object Watery {
@ -33,6 +39,27 @@ object Watery {
progress: Float 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. * Calculate the effect of being exposed to a watery environment beyond an entity's critical region.
* @param obj the target * @param obj the target
@ -122,10 +149,10 @@ object Watery {
//switching from suffocation to recovery //switching from suffocation to recovery
val oldDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Suffocation) val oldDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Suffocation)
val newDuration: Long = obj.Definition.UnderwaterLifespan(OxygenState.Recovery) 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 oldTimeRatio: Float = oldTimeRemaining / oldDuration.toFloat
val percentage: Float = oldTimeRatio * 100 val percentage: Float = oldTimeRatio * 100
val recoveryTime: Long = newDuration - (newDuration * oldTimeRatio).toLong val recoveryTime: Long = newDuration * (1f - oldTimeRatio).toLong
(true, recoveryTime, percentage) (true, recoveryTime, percentage)
case Some(OxygenState.Recovery) => case Some(OxygenState.Recovery) =>
//interrupted while recovering, calculate the progress and keep recovering //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 faction: PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
private var carrier: Option[Player] = None private var carrier: Option[Player] = None
private var lastTimeCollected: Long = System.currentTimeMillis() private var lastTimeCollected: Long = System.currentTimeMillis()
private val spawnedTime: Long = lastTimeCollected
def Target: Building = target def Target: Building = target
def Target_=(newTarget: Building): Building = { 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. * 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 * @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) { override def Position: Vector3 = {
carrier.get.Position carrier match {
} else { case Some(player) => player.Position
super.Position case None => super.Position
}
} }
def Carrier: Option[Player] = carrier 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 LastCollectionTime: Long = carrier.map { _ => lastTimeCollected }.getOrElse { System.currentTimeMillis() }
def InitialSpawnTime: Long = spawnedTime
} }
object CaptureFlag { object CaptureFlag {

View file

@ -146,5 +146,5 @@ object Mountable {
* @param obj the `Mountable` object * @param obj the `Mountable` object
* @param seat_num the seat index * @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 { 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 */ /** how to process either the first order or every subsequent order */
private var handleOrderFunc: VehicleSpawnPad.VehicleOrder => Unit = NewTasking private var handleOrderFunc: VehicleSpawnPad.VehicleOrder => Unit = NewTasking
/** ... */
private var reminderSeq: Seq[Int] = Seq()
def LogId = "" def LogId = ""
/** /**
@ -77,7 +80,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
} }
override def postStop() : Unit = { override def postStop() : Unit = {
periodicReminder.cancel() discontinueCurrentReminder()
queueManagement.cancel() queueManagement.cancel()
} }
@ -113,36 +116,11 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
case VehicleSpawnControl.ProcessControl.QueueManagement => case VehicleSpawnControl.ProcessControl.QueueManagement =>
queueManagementTask() 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 => case VehicleSpawnControl.ProcessControl.Reminder =>
trackedOrder evaluateBlockedReminder()
.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
}
case VehicleSpawnControl.ProcessControl.Flush => case VehicleSpawnControl.ProcessControl.Flush =>
periodicReminder.cancel() discontinueCurrentReminder()
orders.foreach { CancelOrder(_, Some("@SVCP_RemovedFromVehicleQueue_Generic")) } orders.foreach { CancelOrder(_, Some("@SVCP_RemovedFromVehicleQueue_Generic")) }
orders = Nil orders = Nil
trackedOrder.foreach { trackedOrder.foreach {
@ -259,7 +237,7 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
* `None`, if no order found or submitted * `None`, if no order found or submitted
*/ */
private def ProcessOrder(order: Option[VehicleSpawnPad.VehicleOrder]): Unit = { private def ProcessOrder(order: Option[VehicleSpawnPad.VehicleOrder]): Unit = {
periodicReminder.cancel() discontinueCurrentReminder()
order.collect { order.collect {
case VehicleSpawnPad.VehicleOrder(driver, vehicle, terminal) => case VehicleSpawnPad.VehicleOrder(driver, vehicle, terminal) =>
val size = orders.size + 1 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. * Cancel this vehicle order and inform the person who made it, if possible.
* @param entry the order being cancelled * @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( @tailrec private final def recursiveBlockedReminder(
iter: Iterator[VehicleSpawnPad.VehicleOrder], iter: Iterator[VehicleSpawnPad.VehicleOrder],
cause: Option[Any] cause: Option[Any]
): Unit = { ): Unit = {
if (iter.hasNext) { if (iter.hasNext) {
val recipient = iter.next() standaloneBlockedReminder(iter.next(), cause)
pad.Zone.VehicleEvents ! VehicleSpawnPad.PeriodicReminder(
recipient.player.Name,
VehicleSpawnPad.Reminders.Blocked,
cause
)
recursiveBlockedReminder(iter, 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( @tailrec private final def recursiveOrderReminder(
iter: Iterator[VehicleSpawnPad.VehicleOrder], iter: Iterator[VehicleSpawnPad.VehicleOrder],
size: Int, size: Int,
@ -414,19 +476,16 @@ class VehicleSpawnControl(pad: VehicleSpawnPad)
} }
object VehicleSpawnControl { object VehicleSpawnControl {
private final val periodicReminderTestDelay: FiniteDuration = 10 seconds
/** /**
* Control messages for the vehicle spawn process. * Control messages for the vehicle spawn process.
*/ */
sealed trait ProcessControlOperation
object ProcessControl { object ProcessControl {
sealed trait ProcessControl case object Flush extends ProcessControlOperation
case object OrderCancelled extends ProcessControlOperation
case object Flush extends ProcessControl case object GetNewOrder extends ProcessControlOperation
case object OrderCancelled extends ProcessControl case object Reminder extends ProcessControlOperation
case object GetNewOrder extends ProcessControl case object QueueManagement extends ProcessControlOperation
case object Reminder extends ProcessControl
case object QueueManagement extends ProcessControl
} }
/** /**

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 // 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 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 // 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 // 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) // 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` */ * @see `net.psforever.objects.serverobject.pad.process.VehicleSpawnControlRailJack` */
var killBox: (VehicleSpawnPad, Boolean)=>(PlanetSideGameObject, PlanetSideGameObject, Float)=> Boolean = var killBox: (VehicleSpawnPad, Boolean)=>(PlanetSideGameObject, PlanetSideGameObject, Float)=> Boolean =
VehicleSpawnPadDefinition.prepareKillBox(forwardLimit = 0, backLimit = 0, sideLimit = 0, aboveLimit = 0) 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 { object VehicleSpawnPadDefinition {

View file

@ -50,26 +50,35 @@ class VehicleSpawnControlFinalClearance(pad: VehicleSpawnPad) extends VehicleSpa
self ! VehicleSpawnControlFinalClearance.Test(order) self ! VehicleSpawnControlFinalClearance.Test(order)
case test @ VehicleSpawnControlFinalClearance.Test(entry) => 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 //once mounted, it will complain to the current driver that it is blocking the spawn pad
//no time limit exists for that state //no time limit exists for that state
val vehicle = entry.vehicle val vehicle = entry.vehicle
if (Vector3.DistanceSquared(vehicle.Position, pad.Position) > 144) { //12m away from pad val distanceTest = Vector3.DistanceSquared(vehicle.Position, pad.Position) > 144 //12m away from pad
trace("pad cleared") if (vehicle.Destroyed) {
pad.Zone.VehicleEvents ! VehicleSpawnPad.ResetSpawnPad(pad) trace("pad cleared of vehicle wreckage")
context.parent ! VehicleSpawnControl.ProcessControl.GetNewOrder val delay = if (distanceTest) { 2000 } else { 5000 }
} else if (vehicle.Destroyed) {
trace("clearing the pad of vehicle wreckage")
VehicleSpawnControl.DisposeVehicle(vehicle, pad.Zone) 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 { } 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 { 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 // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.pad.process 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.{Default, Vehicle}
import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad}
@ -25,7 +25,7 @@ import scala.concurrent.duration._
class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) { class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnControlBase(pad) {
def LogId = "-usher" def LogId = "-usher"
val vehicleOverride = context.actorOf( val vehicleOverride: ActorRef = context.actorOf(
Props(classOf[VehicleSpawnControlServerVehicleOverride], pad), Props(classOf[VehicleSpawnControlServerVehicleOverride], pad),
s"${context.parent.path.name}-override" s"${context.parent.path.name}-override"
) )
@ -43,7 +43,7 @@ class VehicleSpawnControlSeatDriver(pad: VehicleSpawnPad) extends VehicleSpawnCo
val driver = entry.driver val driver = entry.driver
val vehicle = entry.vehicle val vehicle = entry.vehicle
//avoid unattended vehicle blocking the pad; user should mount (and does so normally) to reset decon timer //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) { if (VehicleSpawnControl.validateOrderCredentials(pad, driver, vehicle).isEmpty) {
trace("driver to be made seated in vehicle") trace("driver to be made seated in vehicle")
pad.Zone.VehicleEvents ! VehicleSpawnPad.StartPlayerSeatedInVehicle(driver.Name, vehicle, pad) 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) => case msg @ (VehicleSpawnControl.ProcessControl.Reminder | VehicleSpawnControl.ProcessControl.GetNewOrder) =>
context.parent ! msg 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.sourcing.{PlayerSource, UniquePlayer}
import net.psforever.objects.zones.{HotSpotInfo, ZoneHotSpotProjector} import net.psforever.objects.zones.{HotSpotInfo, ZoneHotSpotProjector}
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage} 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 net.psforever.util.Config
import akka.pattern.ask import akka.pattern.ask
import akka.util.Timeout import akka.util.Timeout
import net.psforever.objects.Player
import net.psforever.objects.avatar.scoring.Kill import net.psforever.objects.avatar.scoring.Kill
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.zones.exp.ToDatabase 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.collection.mutable
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -31,6 +35,20 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
updateHotSpotInfoOverTime() updateHotSpotInfoOverTime()
updateTime(now) 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 lastInfoRequest = now
} }
@ -287,8 +305,8 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
if (isResecured) { if (isResecured) {
hackerScore hackerScore
} else { } else {
val flagCarrierScore = flagCarrier.map (p => List((p.CharId, 0L, "llu"))).getOrElse(Nil) val flagCarrierScore = flagCarrier.map(p => List((p.CharId, 0L, "llu"))).getOrElse(Nil)
if (playersInSoi.exists(_.CharId == hackerId) && !flagCarrierScore.exists { case (charId, _,_) => charId == hackerId }) { if (playersInSoi.exists(_.CharId == hackerId) && !flagCarrierScore.exists { case (charId, _, _) => charId == hackerId }) {
hackerScore ++ flagCarrierScore hackerScore ++ flagCarrierScore
} else { } else {
flagCarrierScore flagCarrierScore
@ -326,3 +344,61 @@ final case class MajorFacilityHackParticipation(building: Building) extends Faci
.getOrElse(list) .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 // Copyright (c) 2021 PSForever
package net.psforever.objects.vehicles.control package net.psforever.objects.vehicles.control
import akka.actor.ActorRef
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject import net.psforever.objects.serverobject.deploy.Deployment.DeploymentObject
import net.psforever.objects.serverobject.deploy.{Deployment, DeploymentBehavior} 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. * Even when disabled, the vehicle can be made to undeploy.
*/ */
override def PrepareForDisabled(kickPassengers: Boolean) : Unit = { override def PrepareForDisabled(kickPassengers: Boolean) : Unit = {
TryUndeployStateChange(DriveState.Undeploying) TryUndeployStateChange(DriveState.Undeploying, self)
super.PrepareForDisabled(kickPassengers) super.PrepareForDisabled(kickPassengers)
} }
@ -77,9 +78,10 @@ class DeployingVehicleControl(vehicle: Vehicle)
override def DeploymentAction( override def DeploymentAction(
obj: DeploymentObject, obj: DeploymentObject,
state: DriveState.Value, state: DriveState.Value,
prevState: DriveState.Value prevState: DriveState.Value,
replyTo: ActorRef
): DriveState.Value = { ): DriveState.Value = {
val out = super.DeploymentAction(obj, state, prevState) val out = super.DeploymentAction(obj, state, prevState, replyTo)
Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString) Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
specificResponseToDeployment(state) specificResponseToDeployment(state)
out out
@ -90,9 +92,10 @@ class DeployingVehicleControl(vehicle: Vehicle)
override def UndeploymentAction( override def UndeploymentAction(
obj: DeploymentObject, obj: DeploymentObject,
state: DriveState.Value, state: DriveState.Value,
prevState: DriveState.Value prevState: DriveState.Value,
replyTo: ActorRef
): DriveState.Value = { ): 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) Vehicles.ReloadAccessPermissions(vehicle, vehicle.Faction.toString)
specificResponseToUndeployment(state) specificResponseToUndeployment(state)
out out

View file

@ -117,7 +117,8 @@ class VehicleControl(vehicle: Vehicle)
case Vehicle.Ownership(Some(player)) => case Vehicle.Ownership(Some(player)) =>
GainOwnership(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)) sender() ! Mountable.MountMessages(user, Mountable.CanNotMount(vehicle, mountPoint))
case msg @ Mountable.TryMount(player, mount_point) => case msg @ Mountable.TryMount(player, mount_point) =>
@ -125,19 +126,23 @@ class VehicleControl(vehicle: Vehicle)
mountCleanup(mount_point, player) mountCleanup(mount_point, player)
// Issue 1133. Todo: There may be a better way to address the issue? // 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 { (vehicle.History.find { entry => entry.isInstanceOf[SpawningActivity] } match {
case Some(entry) if System.currentTimeMillis() - entry.time < 3000L => true case Some(entry) if System.currentTimeMillis() - entry.time < 3000L => true
case _ => false 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 { (vehicle.History.find { entry => entry.isInstanceOf[SpawningActivity] } match {
case Some(entry) if System.currentTimeMillis() - entry.time < 8500L => true case Some(entry) if System.currentTimeMillis() - entry.time < 8500L => true
case _ => false 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, _) => case msg @ Mountable.TryDismount(player, seat_num, _) =>
dismountBehavior.apply(msg) dismountBehavior.apply(msg)
@ -601,9 +606,9 @@ class VehicleControl(vehicle: Vehicle)
c 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) => case watery: WithWater if watery.Condition.map(_.state).contains(OxygenState.Recovery) =>
watery.stopInteractingWithTargets( WithWater.stopInteractingWithTargets(
player, player,
Watery.recoveringFromWater(vehicle, watery)._3, Watery.recoveringFromWater(vehicle, watery)._3,
watery.Condition.map(_.body).get, watery.Condition.map(_.body).get,

View file

@ -1,37 +1,71 @@
// Copyright (c) 2024 PSForever // Copyright (c) 2024 PSForever
package net.psforever.objects.vehicles.interaction 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.PlanetSideServerObject
import net.psforever.objects.serverobject.environment.interaction.{InteractionWith, RespondsToZoneEnvironment} 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
import net.psforever.objects.serverobject.environment.interaction.common.Watery.OxygenStateTarget 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.vehicles.control.VehicleControl
import net.psforever.objects.zones.InteractsWithZone import net.psforever.objects.zones.InteractsWithZone
import net.psforever.types.OxygenState import net.psforever.types.OxygenState
import scala.annotation.unused
import scala.concurrent.duration._ import scala.concurrent.duration._
class WithWater() class WithWater()
extends InteractionWith extends InteractionWith
with Watery { 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. * Water is wet.
* Flying vehicles do not display progress towards being waterlogged. * @param obj the target
* They just disable outright. * @param body the environment
* @param obj the target */
* @param body the environment
* @param data additional interaction information, if applicable
*/
def doInteractingWith( def doInteractingWith(
obj: InteractsWithZone, obj: InteractsWithZone,
body: PieceOfEnvironment, body: PieceOfEnvironment,
data: Option[Any] data: Option[Any]
): Unit = { ): 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 { obj match {
case vehicle: Vehicle => 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) val (a, b, c) = Watery.drowningInWateryConditions(obj, condition.map(_.state), waterInteractionTime)
if (a && GlobalDefinitions.isFlightVehicle(vehicle.Definition)) { if (a && GlobalDefinitions.isFlightVehicle(vehicle.Definition)) {
(true, 0L, 0f) //no progress bar (true, 0L, 0f) //no progress bar
@ -42,8 +76,94 @@ class WithWater()
if (effect) { if (effect) {
condition = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage)) condition = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage))
waterInteractionTime = System.currentTimeMillis() + time waterInteractionTime = System.currentTimeMillis() + time
obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, VehicleControl.Disable(true)) obj.Actor ! RespondsToZoneEnvironment.StopTimer(WithWater.WaterAction)
doInteractingWithTargets( 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, obj,
percentage, percentage,
body, body,
@ -55,34 +175,52 @@ class WithWater()
} }
/** /**
* When out of water, the vehicle no longer risks becoming disabled. * The recovery period is much faster than the waterlogging process.
* It does have to endure a recovery period to get back to full dehydration * Check for when the vehicle fully recovers,
* Flying vehicles are exempt from this process due to the abrupt disability they experience. * and that the vehicle does not regress back to waterlogging.
* @param obj the target * @param obj the target
* @param body the environment * @param body the environment
* @param data additional interaction information, if applicable */
*/ 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( override def stopInteractingWith(
obj: InteractsWithZone, obj: InteractsWithZone,
body: PieceOfEnvironment, body: PieceOfEnvironment,
data: Option[Any] data: Option[Any]
): Unit = { ): Unit = {
obj match { val cond = condition.map(_.state)
case vehicle: Vehicle => if (cond.contains(OxygenState.Suffocation)) {
val (effect: Boolean, time: Long, percentage: Float) = //go from suffocating to recovery
Watery.recoveringFromWateryConditions(obj, condition.map(_.state), waterInteractionTime) recoverFromDrowning(obj, body, data)
if (effect) { } else if (cond.isEmpty) {
condition = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage)) //neither suffocating nor recovering, so just reset everything
waterInteractionTime = System.currentTimeMillis() + time recoverFromInteracting(obj)
obj.Actor ! RespondsToZoneEnvironment.Timer(attribute, delay = time milliseconds, obj.Actor, interaction.RecoveredFromEnvironmentInteraction(attribute)) obj.Actor ! RespondsToZoneEnvironment.StopTimer(attribute)
stopInteractingWithTargets( waterInteractionTime = 0L
obj, depth = 0f
percentage, condition = None
body, doInteractingWithBehavior = wadingBeforeDrowning
vehicle.Seats.values.flatMap(_.occupants).filter(p => p.isAlive && (p.Zone eq obj.Zone))
)
}
case _ => ()
} }
} }
@ -91,46 +229,53 @@ class WithWater()
if (condition.exists(_.state == OxygenState.Suffocation)) { if (condition.exists(_.state == OxygenState.Suffocation)) {
stopInteractingWith(obj, condition.map(_.body).get, None) stopInteractingWith(obj, condition.map(_.body).get, None)
} }
waterInteractionTime = 0L
condition = None 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. * Tell the given targets that water causes vehicles to become disabled if they dive off too far, too deep.
* @see `InteractingWithEnvironment` * @see `InteractingWithEnvironment`
* @see `OxygenState` * @see `OxygenState`
* @see `OxygenStateTarget` * @see `OxygenStateTarget`
* @param obj the target * @param obj the target
* @param percentage the progress bar completion state * @param percentage the progress bar completion state
* @param body the environment * @param body the environment
* @param targets recipients of the information * @param targets recipients of the information
*/ */
def doInteractingWithTargets( def doInteractingWithTargets(
obj: PlanetSideServerObject, obj: PlanetSideServerObject,
percentage: Float, percentage: Float,
body: PieceOfEnvironment, body: PieceOfEnvironment,
targets: Iterable[PlanetSideServerObject] targets: Iterable[PlanetSideServerObject]
): Unit = { ): Unit = {
val state = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage)) val state = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Suffocation, percentage))
targets.foreach(_.Actor ! interaction.InteractingWithEnvironment(body, state)) targets.foreach(_.Actor ! interaction.InteractingWithEnvironment(body, state))
} }
/** /**
* Tell the given targets that, when out of water, the vehicle no longer risks becoming disabled. * Tell the given targets that, when out of water, the vehicle no longer risks becoming disabled.
* @see `EscapeFromEnvironment` * @see `EscapeFromEnvironment`
* @see `OxygenState` * @see `OxygenState`
* @see `OxygenStateTarget` * @see `OxygenStateTarget`
* @param obj the target * @param obj the target
* @param percentage the progress bar completion state * @param percentage the progress bar completion state
* @param body the environment * @param body the environment
* @param targets recipients of the information * @param targets recipients of the information
*/ */
def stopInteractingWithTargets( def stopInteractingWithTargets(
obj: PlanetSideServerObject, obj: PlanetSideServerObject,
percentage: Float, percentage: Float,
body: PieceOfEnvironment, body: PieceOfEnvironment,
targets: Iterable[PlanetSideServerObject] targets: Iterable[PlanetSideServerObject]
): Unit = { ): Unit = {
val state = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage)) val state = Some(OxygenStateTarget(obj.GUID, body, OxygenState.Recovery, percentage))
targets.foreach(_.Actor ! interaction.EscapeFromEnvironment(body, state)) 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 * @return synchronized reference to the globally unique identifier system
*/ */
def GUID(hub: NumberPoolHub): Boolean = { 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.Color.RED
import org.fusesource.jansi.Ansi.ansi import org.fusesource.jansi.Ansi.ansi
println( println(

View file

@ -9,6 +9,9 @@ import net.psforever.objects.serverobject.deploy.Interference
import net.psforever.objects.sourcing.ObjectSource import net.psforever.objects.sourcing.ObjectSource
import net.psforever.objects.vehicles.MountedWeapons import net.psforever.objects.vehicles.MountedWeapons
import net.psforever.objects.vital.SpawningActivity 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.annotation.tailrec
import scala.collection.mutable import scala.collection.mutable
@ -103,7 +106,13 @@ object ZoneDeployableActor {
): Boolean = { ): Boolean = {
val position = obj.Position val position = obj.Position
deployableList.find(_ eq obj) match { 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 deployableList += obj
zone.actor ! ZoneActor.AddToBlockMap(obj, position) zone.actor ! ZoneActor.AddToBlockMap(obj, position)
true true

View file

@ -3,11 +3,14 @@ package net.psforever.objects.zones
import akka.actor.Actor import akka.actor.Actor
import net.psforever.actors.zone.ZoneActor 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.serverobject.deploy.{Deployment, Interference}
import net.psforever.objects.vital.InGameHistory import net.psforever.objects.vital.InGameHistory
import net.psforever.objects.{Default, Vehicle} 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.annotation.tailrec
import scala.collection.mutable import scala.collection.mutable
@ -66,15 +69,18 @@ class ZoneVehicleActor(
sender() ! Zone.Vehicle.CanNotDespawn(zone, vehicle, "can not find") sender() ! Zone.Vehicle.CanNotDespawn(zone, vehicle, "can not find")
} }
case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState) case Zone.Vehicle.TryDeploymentChange(vehicle, DriveState.Deploying) =>
if toDeployState == DriveState.Deploying && if (ZoneVehicleActor.ReportOnInterferenceResults(
(ZoneVehicleActor.temporaryInterferenceTest(vehicle, temporaryInterference) || Interference.Test(zone, vehicle).nonEmpty) => zone,
sender() ! Zone.Vehicle.CanNotDeploy(zone, vehicle, toDeployState, "blocked by a nearby entity") vehicle,
ZoneVehicleActor.temporaryInterferenceTest(vehicle, temporaryInterference) ++
case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState) Interference.Test(zone, vehicle).map(_.Definition)
if toDeployState == DriveState.Deploying => )) {
tryAddToInterferenceField(vehicle.Position, vehicle.Faction, vehicle.Definition) sender() ! Zone.Vehicle.CanNotDeploy(zone, vehicle, DriveState.Deploying, "blocked by a nearby entity")
vehicle.Actor.tell(Deployment.TryDeploymentChange(toDeployState), sender()) } else {
tryAddToInterferenceField(vehicle.Position, vehicle.Faction, vehicle.Definition)
vehicle.Actor.tell(Deployment.TryDeploymentChange(DriveState.Deploying), sender())
}
case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState) => case Zone.Vehicle.TryDeploymentChange(vehicle, toDeployState) =>
vehicle.Actor.tell(Deployment.TryDeploymentChange(toDeployState), sender()) vehicle.Actor.tell(Deployment.TryDeploymentChange(toDeployState), sender())
@ -83,7 +89,19 @@ class ZoneVehicleActor(
case Zone.Vehicle.CanNotDespawn(_, _, _) => () 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 pos = vehicle.Position
val driverMoniker = vehicle.Seats.headOption.flatMap(_._2.occupant).map(_.Name).getOrElse("Driver") 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") 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( private def temporaryInterferenceTest(
vehicle: Vehicle, vehicle: Vehicle,
existingInterferences: Seq[(Vector3, PlanetSideEmpire.Value, VehicleDefinition)] existingInterferences: Seq[(Vector3, PlanetSideEmpire.Value, VehicleDefinition)]
): Boolean = { ): Seq[VehicleDefinition] = {
val vPosition = vehicle.Position val vPosition = vehicle.Position
val vFaction = vehicle.Faction val vFaction = vehicle.Faction
val vDefinition = vehicle.Definition val vDefinition = vehicle.Definition
if (vDefinition.interference eq Interference.AllowAll) { if (vDefinition.interference eq Interference.AllowAll) {
false Nil
} else { } else {
existingInterferences existingInterferences
.collect { case (p, faction, d) if faction == vFaction => (p, d) } .collect { case (p, faction, d) if faction == vFaction => (p, d) }
.exists { case (position, definition) => .filter { case (position, definition) =>
val interference = definition.interference val interference = definition.interference
(interference ne Interference.AllowAll) && { (interference ne Interference.AllowAll) && {
lazy val distanceSq = Vector3.DistanceSquared(position, vPosition) lazy val distanceSq = Vector3.DistanceSquared(position, vPosition)
definition == vDefinition && distanceSq < interference.main * interference.main 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, 204.8f,
11 11
) :: //hackish: 2^11 == 2047, so it should be 204.7; but, 204.8 allows decode == encode ) :: //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] ).as[DrowningTarget]
implicit val codec: Codec[OxygenStateMessage] = ( implicit val codec: Codec[OxygenStateMessage] = (

View file

@ -1216,11 +1216,11 @@ object ObjectClass {
case ObjectClass.advanced_ace => DroppedItemData(HandheldData.codec, "advanced ace") case ObjectClass.advanced_ace => DroppedItemData(HandheldData.codec, "advanced ace")
case ObjectClass.router_telepad => DroppedItemData(HandheldData.codec, "router telepad") case ObjectClass.router_telepad => DroppedItemData(HandheldData.codec, "router telepad")
case ObjectClass.boomer_trigger => DroppedItemData(HandheldData.codec, "boomer trigger") case ObjectClass.boomer_trigger => DroppedItemData(HandheldData.codec, "boomer trigger")
case ObjectClass.boomer => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable") case ObjectClass.boomer => ConstructorData(SmallDeployableData.codec, "ace deployable")
case ObjectClass.he_mine => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable") case ObjectClass.he_mine => ConstructorData(SmallDeployableData.codec, "ace deployable")
case ObjectClass.jammer_mine => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable") case ObjectClass.jammer_mine => ConstructorData(SmallDeployableData.codec, "ace deployable")
case ObjectClass.motionalarmsensor => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable") case ObjectClass.motionalarmsensor => ConstructorData(SmallDeployableData.codec, "ace deployable")
case ObjectClass.sensor_shield => ConstructorData(CommonFieldDataWithPlacement.codec, "ace deployable") case ObjectClass.sensor_shield => ConstructorData(SmallDeployableData.codec, "ace deployable")
case ObjectClass.spitfire_aa => ConstructorData(SmallTurretData.codec, "small turret") case ObjectClass.spitfire_aa => ConstructorData(SmallTurretData.codec, "small turret")
case ObjectClass.spitfire_cloaked => ConstructorData(SmallTurretData.codec, "small turret") case ObjectClass.spitfire_cloaked => ConstructorData(SmallTurretData.codec, "small turret")
case ObjectClass.spitfire_turret => 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) hackCapturer ! HackCaptureActor.StartCaptureTerminalHack(target, zone, 0, 8L)
case LocalAction.LluCaptured(llu) => case LocalAction.LluCaptured(llu) =>
hackCapturer ! HackCaptureActor.FlagCaptured(llu) hackCapturer ! HackCaptureActor.FlagCaptured(llu)
case LocalAction.LluLost(llu) =>
hackCapturer ! HackCaptureActor.FlagLost(llu)
case LocalAction.LluSpawned(player_guid, llu) => case LocalAction.LluSpawned(player_guid, llu) =>
// Forward to all clients to create object locally // 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 ResecureCaptureTerminal(target: CaptureTerminal, hacker: PlayerSource) extends Action
final case class StartCaptureTerminalHack(target: CaptureTerminal) extends Action final case class StartCaptureTerminalHack(target: CaptureTerminal) extends Action
final case class LluCaptured(llu: CaptureFlag) 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 LluSpawned(player_guid: PlanetSideGUID, llu: CaptureFlag) extends Action
final case class LluDespawned(player_guid: PlanetSideGUID, guid: PlanetSideGUID, position: Vector3) 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 akka.actor.{Actor, ActorRef, Cancellable}
import net.psforever.login.WorldSession 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.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.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.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.packet.game._
import net.psforever.services.{Service, ServiceManager} import net.psforever.services.{Service, ServiceManager}
import net.psforever.services.ServiceManager.{Lookup, LookupResult} import net.psforever.services.ServiceManager.{Lookup, LookupResult}
@ -22,12 +24,13 @@ import scala.concurrent.duration.DurationInt
* Responsible for handling capture flag related lifecycles * Responsible for handling capture flag related lifecycles
*/ */
class CaptureFlagManager(zone: Zone) extends Actor { class CaptureFlagManager(zone: Zone) extends Actor {
import CaptureFlagManager.CaptureFlagEntry
private[this] val log = org.log4s.getLogger(self.path.name) private[this] val log = org.log4s.getLogger(self.path.name)
private var galaxyService: ActorRef = ActorRef.noSender private var galaxyService: ActorRef = ActorRef.noSender
private var mapUpdateTick: Cancellable = Default.Cancellable private var mapUpdateTick: Cancellable = Default.Cancellable
/** An internally tracked list of cached flags, to avoid querying the amenity owner each second for flag lookups. */ /** 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") ServiceManager.serviceManager ! Lookup("galaxy")
@ -39,7 +42,6 @@ class CaptureFlagManager(zone: Zone) extends Actor {
DoMapUpdate() DoMapUpdate()
case CaptureFlagManager.SpawnCaptureFlag(capture_terminal, target, hackingFaction) => case CaptureFlagManager.SpawnCaptureFlag(capture_terminal, target, hackingFaction) =>
val zone = capture_terminal.Zone
val socket = capture_terminal.Owner.asInstanceOf[Building].GetFlagSocket.get val socket = capture_terminal.Owner.asInstanceOf[Building].GetFlagSocket.get
// Override CC message when looked at // Override CC message when looked at
zone.LocalEvents ! LocalServiceMessage( zone.LocalEvents ! LocalServiceMessage(
@ -62,16 +64,16 @@ class CaptureFlagManager(zone: Zone) extends Actor {
socket.captureFlag = flag socket.captureFlag = flag
TrackFlag(flag) TrackFlag(flag)
TaskWorkflow.execute(WorldSession.CallBackForTask( TaskWorkflow.execute(WorldSession.CallBackForTask(
GUIDTask.registerObject(socket.Zone.GUID, flag), GUIDTask.registerObject(zone.GUID, flag),
socket.Zone.LocalEvents, zone.LocalEvents,
LocalServiceMessage( LocalServiceMessage(
socket.Zone.id, zone.id,
LocalAction.LluSpawned(Service.defaultPlayerGUID, flag) LocalAction.LluSpawned(Service.defaultPlayerGUID, flag)
) )
)) ))
// Broadcast chat message for LLU spawn // Broadcast chat message for LLU spawn
val owner = flag.Owner.asInstanceOf[Building] 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) => case CaptureFlagManager.Captured(flag: CaptureFlag) =>
val name = flag.Carrier match { val name = flag.Carrier match {
@ -79,36 +81,46 @@ class CaptureFlagManager(zone: Zone) extends Actor {
case None => "A soldier" case None => "A soldier"
} }
// Trigger Install sound // 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 // 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 // Despawn flag
HandleFlagDespawn(flag) HandleFlagDespawn(flag)
case CaptureFlagManager.Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) => case CaptureFlagManager.Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) =>
reason match { reason match {
case CaptureFlagLostReasonEnum.Resecured => case CaptureFlagLostReasonEnum.Resecured =>
ChatBroadcast( CaptureFlagManager.ChatBroadcast(
flag.Zone, zone,
CaptureFlagChatMessageStrings.CTF_Failed_SourceResecured(flag.Owner.asInstanceOf[Building].Name, flag.Faction) CaptureFlagChatMessageStrings.CTF_Failed_SourceResecured(flag.Owner.asInstanceOf[Building].Name, flag.Faction)
) )
case CaptureFlagLostReasonEnum.TimedOut => case CaptureFlagLostReasonEnum.TimedOut =>
val building = flag.Owner.asInstanceOf[Building] val building = flag.Owner.asInstanceOf[Building]
ChatBroadcast( CaptureFlagManager.ChatBroadcast(
flag.Zone, zone,
CaptureFlagChatMessageStrings.CTF_Failed_TimedOut(building.Name, flag.Target.Name, flag.Faction) 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 => case CaptureFlagLostReasonEnum.Ended =>
() ()
} }
HandleFlagDespawn(flag) HandleFlagDespawn(flag)
case CaptureFlagManager.PickupFlag(flag: CaptureFlag, player: Player) => case CaptureFlagManager.PickupFlag(flag: CaptureFlag, player: Player) =>
val continent = flag.Zone
flag.Carrier = Some(player) flag.Carrier = Some(player)
continent.LocalEvents ! LocalServiceMessage(continent.id, LocalAction.SendPacket(ObjectAttachMessage(player.GUID, flag.GUID, 252))) zone.LocalEvents ! LocalServiceMessage(zone.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)) zone.LocalEvents ! LocalServiceMessage(zone.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) CaptureFlagManager.ChatBroadcast(
zone,
CaptureFlagChatMessageStrings.CTF_FlagPickedUp(player.Name, player.Faction, flag.Owner.asInstanceOf[Building].Name),
fanfare = false
)
case CaptureFlagManager.DropFlag(flag: CaptureFlag) => case CaptureFlagManager.DropFlag(flag: CaptureFlag) =>
flag.Carrier match { flag.Carrier match {
@ -119,9 +131,13 @@ class CaptureFlagManager(zone: Zone) extends Actor {
// Remove attached player from flag // Remove attached player from flag
flag.Carrier = None flag.Carrier = None
// Send drop packet // 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 // 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) HandleFlagDespawn(flag)
// Register LLU object create task and callback to create on clients // Register LLU object create task and callback to create on clients
val replacementLlu = CaptureFlag.Constructor( val replacementLlu = CaptureFlag.Constructor(
@ -136,10 +152,10 @@ class CaptureFlagManager(zone: Zone) extends Actor {
socket.captureFlag = replacementLlu socket.captureFlag = replacementLlu
TrackFlag(replacementLlu) TrackFlag(replacementLlu)
TaskWorkflow.execute(WorldSession.CallBackForTask( TaskWorkflow.execute(WorldSession.CallBackForTask(
GUIDTask.registerObject(socket.Zone.GUID, replacementLlu), GUIDTask.registerObject(zone.GUID, replacementLlu),
socket.Zone.LocalEvents, zone.LocalEvents,
LocalServiceMessage( LocalServiceMessage(
socket.Zone.id, zone.id,
LocalAction.LluSpawned(Service.defaultPlayerGUID, replacementLlu) LocalAction.LluSpawned(Service.defaultPlayerGUID, replacementLlu)
) )
)) ))
@ -152,33 +168,42 @@ class CaptureFlagManager(zone: Zone) extends Actor {
} }
private def DoMapUpdate(): Unit = { 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( FlagInfo(
u1 = 0, u1 = 0,
owner_map_id = flag.Owner.asInstanceOf[Building].MapId, owner_map_id = owner.MapId,
target_map_id = flag.Target.MapId, target_map_id = flag.Target.MapId,
x = flag.Position.x, x = pos.x,
y = flag.Position.y, y = pos.y,
hack_time_remaining = flag.Owner.asInstanceOf[Building].infoUpdateMessage().hack_time_remaining, hack_time_remaining = hackTimeRemaining,
is_monolith_unit = false is_monolith_unit = false
) )
) }
galaxyService ! GalaxyServiceMessage(GalaxyAction.FlagMapUpdate(CaptureFlagUpdateMessage(zone.Number, flagInfo))) galaxyService ! GalaxyServiceMessage(GalaxyAction.FlagMapUpdate(CaptureFlagUpdateMessage(zone.Number, flagInfo)))
} }
private def TrackFlag(flag: CaptureFlag): Unit = { private def TrackFlag(flag: CaptureFlag): Unit = {
flag.Owner.Amenities = flag flag.Owner.Amenities = flag
flags = flags :+ flag flags = flags :+ CaptureFlagEntry(flag)
if (mapUpdateTick.isCancelled) { if (mapUpdateTick.isCancelled) {
// Start sending map updates periodically // Start sending map updates periodically
import scala.concurrent.ExecutionContext.Implicits.global 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 = { private def UntrackFlag(flag: CaptureFlag): Unit = {
flag.Owner.RemoveAmenity(flag) flag.Owner.RemoveAmenity(flag)
flags = flags.filterNot(x => x eq flag) flags = flags.filterNot(x => x.flag eq flag)
if (flags.isEmpty) { if (flags.isEmpty) {
mapUpdateTick.cancel() mapUpdateTick.cancel()
DoMapUpdate() DoMapUpdate()
@ -186,7 +211,6 @@ class CaptureFlagManager(zone: Zone) extends Actor {
} }
private def HandleFlagDespawn(flag: CaptureFlag): Unit = { private def HandleFlagDespawn(flag: CaptureFlag): Unit = {
val zone = flag.Zone
// Remove the flag as an amenity // Remove the flag as an amenity
flag.Owner.asInstanceOf[Building].GetFlagSocket.get.captureFlag = None flag.Owner.asInstanceOf[Building].GetFlagSocket.get.captureFlag = None
UntrackFlag(flag) UntrackFlag(flag)
@ -195,21 +219,6 @@ class CaptureFlagManager(zone: Zone) extends Actor {
// Then unregister it from the GUID pool // Then unregister it from the GUID pool
TaskWorkflow.execute(GUIDTask.unregisterObject(zone.GUID, flag)) 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 { object CaptureFlagManager {
@ -221,18 +230,98 @@ object CaptureFlagManager {
final case class Captured(flag: CaptureFlag) extends Command final case class Captured(flag: CaptureFlag) extends Command
final case class Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) extends Command final case class Lost(flag: CaptureFlag, reason: CaptureFlagLostReasonEnum) extends Command
final case class MapUpdate() 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 { 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! // @CTF_Success=%1 captured %2's LLU for the %3!
/** {player.Name} captured {ownerName}'s LLU for the {player.Faction}! */ /** {player.Name} captured {ownerName}'s LLU for the {player.Faction}! */
private[support] def CTF_Success(playerName:String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String = 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 = private[support] def CTF_Failed_TimedOut(ownerName: String, name: String, faction: PlanetSideEmpire.Value): String =
s"@CTF_Failed_TimedOut^@${GetFactionString(faction)}~^@$ownerName~^@$name~" 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! // @CTF_Failed_SourceResecured=The %1 resecured %2!\nThe LLU was lost!
/** The {faction} resecured {name}!\nThe LLU was lost! */ /** The {faction} resecured {name}!\nThe LLU was lost! */
private[support] def CTF_Failed_SourceResecured(name: String, faction: PlanetSideEmpire.Value): String = 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! // @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! */ /** {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 // @CTF_FlagPickedUp=%1 of the %2 picked up %3's LLU
/** {player.Name} of the {player.Faction} picked up {ownerName}'s LLU */ /** {player.Name} of the {player.Faction} picked up {ownerName}'s LLU */
def CTF_FlagPickedUp(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String = private[support] def CTF_FlagPickedUp(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
s"@CTF_FlagPickedUp^$playerName~^@${CaptureFlagChatMessageStrings.GetFactionString(playerFaction)}~^@$ownerName~" s"@CTF_FlagPickedUp^$playerName~^@${GetFactionString(playerFaction)}~^@$ownerName~"
// @CTF_FlagDropped=%1 of the %2 dropped %3's LLU // @CTF_FlagDropped=%1 of the %2 dropped %3's LLU
/** {playerName} of the {faction} dropped {facilityName}'s LLU */ /** {playerName} of the {faction} dropped {facilityName}'s LLU */
def CTF_FlagDropped(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String = private[support] def CTF_FlagDropped(playerName: String, playerFaction: PlanetSideEmpire.Value, ownerName: String): String =
s"@CTF_FlagDropped^$playerName~^@${CaptureFlagChatMessageStrings.GetFactionString(playerFaction)}~^@$ownerName~" 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 = { private def GetFactionString: PlanetSideEmpire.Value=>String = {
case PlanetSideEmpire.TR => "TerranRepublic" case PlanetSideEmpire.TR => "TerranRepublic"
@ -277,4 +420,5 @@ object CaptureFlagLostReasonEnum {
final case object Resecured extends CaptureFlagLostReasonEnum final case object Resecured extends CaptureFlagLostReasonEnum
final case object TimedOut extends CaptureFlagLostReasonEnum final case object TimedOut extends CaptureFlagLostReasonEnum
final case object Ended 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.CommonMessages
import net.psforever.objects.serverobject.hackable.Hackable import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.llu.CaptureFlag 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.serverobject.terminals.capture.CaptureTerminal
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.objects.Default 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.objects.sourcing.PlayerSource
import net.psforever.services.Service import net.psforever.services.Service
import net.psforever.services.local.support.HackCaptureActor.GetHackingFaction import net.psforever.services.local.support.HackCaptureActor.GetHackingFaction
import net.psforever.services.local.{LocalAction, LocalServiceMessage} 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.concurrent.duration.{FiniteDuration, _}
import scala.util.Random 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. // 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] val building = terminal.Owner.asInstanceOf[Building]
building.GetFlag match { 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) => case Some(llu) =>
// LLU was not delivered in time. Send resecured notifications // LLU was not delivered in time. Send resecured notifications
terminal.Zone.LocalEvents ! CaptureFlagManager.Lost(llu, CaptureFlagLostReasonEnum.TimedOut) 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") 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 _ => () case _ => ()
} }
@ -209,6 +237,18 @@ class HackCaptureActor extends Actor {
val owner = terminal.Owner.asInstanceOf[Building] val owner = terminal.Owner.asInstanceOf[Building]
// Notify parent building that state has changed // Notify parent building that state has changed
owner.Actor ! BuildingActor.AmenityStateChange(terminal, Some(isResecured)) 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 // Push map update to clients
owner.Zone.actor ! ZoneActor.ZoneMapUpdate() owner.Zone.actor ! ZoneActor.ZoneMapUpdate()
} }
@ -256,6 +296,7 @@ object HackCaptureActor {
final case class ResecureCaptureTerminal(target: CaptureTerminal, zone: Zone, hacker: PlayerSource) final case class ResecureCaptureTerminal(target: CaptureTerminal, zone: Zone, hacker: PlayerSource)
final case class FlagCaptured(flag: CaptureFlag) final case class FlagCaptured(flag: CaptureFlag)
final case class FlagLost(flag: CaptureFlag)
private final case class ProcessCompleteHacks() private final case class ProcessCompleteHacks()

View file

@ -184,6 +184,10 @@ class VehicleService(zone: Zone) extends Actor {
VehicleResponse.MountVehicle(vehicle_guid, seat) 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) => case VehicleAction.Ownership(player_guid, vehicle_guid) =>
VehicleEvents.publish( VehicleEvents.publish(
VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.Ownership(vehicle_guid)) VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.Ownership(vehicle_guid))

View file

@ -93,6 +93,7 @@ object VehicleAction {
) extends Action ) extends Action
final case class MountVehicle(player_guid: PlanetSideGUID, object_guid: PlanetSideGUID, seat: Int) 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 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 Ownership(player_guid: PlanetSideGUID, vehicle_guid: PlanetSideGUID) extends Action
final case class PlanetsideAttribute( final case class PlanetsideAttribute(
player_guid: PlanetSideGUID, 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 MountVehicle(object_guid: PlanetSideGUID, seat: Int) extends Response
final case class ObjectDelete(guid: PlanetSideGUID) extends Response final case class ObjectDelete(guid: PlanetSideGUID) extends Response
final case class Ownership(vehicle_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) final case class PlanetsideAttribute(vehicle_guid: PlanetSideGUID, attribute_type: Int, attribute_value: Long)
extends Response extends Response
final case class Reload(weapon_guid: PlanetSideGUID) extends Response final case class Reload(weapon_guid: PlanetSideGUID) extends Response

View file

@ -1,9 +1,6 @@
package net.psforever.types package net.psforever.types
import enumeratum.{Enum, EnumEntry} import enumeratum.{Enum, EnumEntry}
import net.psforever.packet.PacketHelpers
import scodec.Codec
import scodec.codecs.uint
/** /**
* The progress state of being a drowning victim. * The progress state of being a drowning victim.
@ -22,6 +19,4 @@ object OxygenState extends Enum[OxygenState] {
case object Recovery extends OxygenState case object Recovery extends OxygenState
case object Suffocation 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) guid mustEqual PlanetSideGUID(3840)
parent.isDefined mustEqual false parent.isDefined mustEqual false
data match { data match {
case CommonFieldDataWithPlacement(pos, com) => case SmallDeployableData(CommonFieldDataWithPlacement(pos, com)) =>
pos.coord mustEqual Vector3(4704.172f, 5546.4375f, 82.234375f) pos.coord mustEqual Vector3(4704.172f, 5546.4375f, 82.234375f)
pos.orient mustEqual Vector3.z(272.8125f) pos.orient mustEqual Vector3.z(272.8125f)
com match { com match {
@ -31,9 +31,9 @@ class CommonFieldDataWithPlacementTest extends Specification {
v1 mustEqual false v1 mustEqual false
v2.isEmpty mustEqual true v2.isEmpty mustEqual true
v3 mustEqual false v3 mustEqual false
v4.contains(false) mustEqual true v4.isEmpty mustEqual true
v5.isEmpty mustEqual true v5.isEmpty mustEqual true
fguid mustEqual PlanetSideGUID(8290) fguid mustEqual PlanetSideGUID(4145)
case _ => case _ =>
ko ko
} }
@ -46,10 +46,10 @@ class CommonFieldDataWithPlacementTest extends Specification {
} }
"encode" in { "encode" in {
val obj = CommonFieldDataWithPlacement( val obj = SmallDeployableData(CommonFieldDataWithPlacement(
PlacementData(Vector3(4704.172f, 5546.4375f, 82.234375f), Vector3.z(272.8125f)), 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 msg = ObjectCreateMessage(ObjectClass.boomer, PlanetSideGUID(3840), obj)
val pkt = PacketCoding.encodePacket(msg).require.toByteVector val pkt = PacketCoding.encodePacket(msg).require.toByteVector
pkt mustEqual string_boomer pkt mustEqual string_boomer

View file

@ -32,9 +32,9 @@ class ConverterTest extends Specification {
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
bops = false, bops = false,
alternate = false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
None, None,
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -50,9 +50,9 @@ class ConverterTest extends Specification {
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
bops = false, bops = false,
alternate = false, alternate = false,
false, v1 = false,
None, None,
false, jammered = false,
Some(false), Some(false),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -71,7 +71,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.DetailedConstructorData(obj) match { obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual DetailedWeaponData( 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, 0,
List(InternalSlot(Ammo.shotgun_shell.id, PlanetSideGUID(90), 0, DetailedAmmoBoxData(8, 12))) 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 { obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual WeaponData( 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, 0,
List( List(
InternalSlot( InternalSlot(
@ -90,11 +90,11 @@ class ConverterTest extends Specification {
0, 0,
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
false, bops = false,
false, alternate = false,
false, v1 = false,
None, None,
false, jammered = false,
Some(false), Some(false),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -115,7 +115,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.DetailedConstructorData(obj) match { obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual DetailedWeaponData( 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, 0,
List( List(
InternalSlot(Ammo.bullet_9mm.id, PlanetSideGUID(90), 0, DetailedAmmoBoxData(8, 30)), 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 PlanetSideEmpire.NEUTRAL, //TODO need faction affinity
bops = false, bops = false,
alternate = false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
None, None,
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
), ),
0, 0,
List( List(
InternalSlot(Ammo.bullet_9mm.id, PlanetSideGUID(90), 0, CommonFieldData()(false)), InternalSlot(Ammo.bullet_9mm.id, PlanetSideGUID(90), 0, CommonFieldData()(flag = false)),
InternalSlot(Ammo.rocket.id, PlanetSideGUID(91), 1, CommonFieldData()(false)) InternalSlot(Ammo.rocket.id, PlanetSideGUID(91), 1, CommonFieldData()(flag = false))
) )
) )
case _ => case _ =>
@ -164,7 +164,7 @@ class ConverterTest extends Specification {
} }
obj.Definition.Packet.ConstructorData(obj) match { obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual CommonFieldData()(false) pkt mustEqual CommonFieldData()(flag = false)
case _ => case _ =>
ko ko
} }
@ -177,7 +177,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.DetailedConstructorData(obj) match { obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual DetailedConstructionToolData( 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 _ => case _ =>
ko ko
@ -188,11 +188,11 @@ class ConverterTest extends Specification {
pkt mustEqual HandheldData( pkt mustEqual HandheldData(
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
false, bops = false,
false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
None, None,
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -216,11 +216,11 @@ class ConverterTest extends Specification {
pkt mustEqual DetailedREKData( pkt mustEqual DetailedREKData(
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, //TODO faction affinity PlanetSideEmpire.NEUTRAL, //TODO faction affinity
false, bops = false,
false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
Some(false), Some(false),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -234,11 +234,11 @@ class ConverterTest extends Specification {
pkt mustEqual REKData( pkt mustEqual REKData(
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
false, bops = false,
false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
Some(false), Some(false),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -257,7 +257,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.DetailedConstructorData(obj) match { obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual DetailedConstructionToolData( 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 _ => case _ =>
ko ko
@ -265,7 +265,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.ConstructorData(obj) match { obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual HandheldData( 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 _ => case _ =>
ko ko
@ -282,11 +282,11 @@ class ConverterTest extends Specification {
pkt mustEqual HandheldData( pkt mustEqual HandheldData(
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
false, bops = false,
false, alternate = false,
false, v1 = false,
None, None,
false, jammered = false,
None, None,
Some(1001), Some(1001),
PlanetSideGUID(0) PlanetSideGUID(0)
@ -301,11 +301,11 @@ class ConverterTest extends Specification {
pkt mustEqual DetailedConstructionToolData( pkt mustEqual DetailedConstructionToolData(
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
false, bops = false,
false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
None, None,
Some(1001), Some(1001),
PlanetSideGUID(0) PlanetSideGUID(0)
@ -333,20 +333,20 @@ class ConverterTest extends Specification {
obj.Definition.Packet.ConstructorData(obj) match { obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual CommonFieldDataWithPlacement( pkt mustEqual SmallDeployableData(CommonFieldDataWithPlacement(
PlacementData(Vector3.Zero, Vector3.Zero), PlacementData(Vector3.Zero, Vector3.Zero),
CommonFieldData( CommonFieldData(
PlanetSideEmpire.TR, PlanetSideEmpire.TR,
false, bops = false,
false, alternate = false,
false, v1 = false,
None, None,
false, jammered = false,
Some(false), Some(false),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
) )
) ))
case _ => case _ =>
ko ko
} }
@ -369,12 +369,12 @@ class ConverterTest extends Specification {
PlacementData(Vector3.Zero, Vector3.Zero), PlacementData(Vector3.Zero, Vector3.Zero),
CommonFieldData( CommonFieldData(
PlanetSideEmpire.TR, PlanetSideEmpire.TR,
false, bops = false,
false, alternate = false,
true, v1 = true,
None,
jammered = false,
None, None,
false,
Some(true),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
) )
@ -389,11 +389,11 @@ class ConverterTest extends Specification {
WeaponData( WeaponData(
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
false, bops = false,
false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
None, None,
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -406,11 +406,11 @@ class ConverterTest extends Specification {
0, 0,
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
false, bops = false,
false, alternate = false,
false, v1 = false,
None, None,
false, jammered = false,
Some(false), Some(false),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -444,11 +444,11 @@ class ConverterTest extends Specification {
PlacementData(Vector3.Zero, Vector3.Zero), PlacementData(Vector3.Zero, Vector3.Zero),
CommonFieldData( CommonFieldData(
PlanetSideEmpire.TR, PlanetSideEmpire.TR,
false, bops = false,
false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
Some(false), Some(false),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -464,11 +464,11 @@ class ConverterTest extends Specification {
WeaponData( WeaponData(
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
false, bops = false,
false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
None, None,
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -481,11 +481,11 @@ class ConverterTest extends Specification {
0, 0,
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
false, bops = false,
false, alternate = false,
false, v1 = false,
None, None,
false, jammered = false,
Some(false), Some(false),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -519,10 +519,10 @@ class ConverterTest extends Specification {
PlanetSideEmpire.TR, PlanetSideEmpire.TR,
bops = false, bops = false,
alternate = false, alternate = false,
true, v1 = true,
None,
jammered = false,
None, None,
false,
Some(true),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
) )
@ -584,9 +584,9 @@ class ConverterTest extends Specification {
PlanetSideEmpire.TR, PlanetSideEmpire.TR,
bops = false, bops = false,
alternate = false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
None, None,
Some(1001), Some(1001),
PlanetSideGUID(5001) PlanetSideGUID(5001)
@ -616,9 +616,9 @@ class ConverterTest extends Specification {
PlanetSideEmpire.TR, PlanetSideEmpire.TR,
bops = false, bops = false,
alternate = true, alternate = true,
true, v1 = true,
None, None,
false, jammered = false,
None, None,
Some(1001), Some(1001),
PlanetSideGUID(0) PlanetSideGUID(0)
@ -751,7 +751,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.DetailedConstructorData(obj) match { obj.Definition.Packet.DetailedConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual DetailedLockerContainerData( 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 None
) )
case _ => case _ =>
@ -783,11 +783,11 @@ class ConverterTest extends Specification {
DetailedREKData( DetailedREKData(
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
false, bops = false,
false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
Some(false), Some(false),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -809,11 +809,11 @@ class ConverterTest extends Specification {
REKData( REKData(
CommonFieldData( CommonFieldData(
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
false, bops = false,
false, alternate = false,
true, v1 = true,
None, None,
false, jammered = false,
Some(false), Some(false),
None, None,
PlanetSideGUID(0) PlanetSideGUID(0)
@ -841,7 +841,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.ConstructorData(obj) match { obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual CommonFieldData(PlanetSideEmpire.NEUTRAL)(false) pkt mustEqual CommonFieldData(PlanetSideEmpire.NEUTRAL)(flag = false)
case _ => case _ =>
ko ko
} }
@ -861,7 +861,7 @@ class ConverterTest extends Specification {
obj.Definition.Packet.ConstructorData(obj) match { obj.Definition.Packet.ConstructorData(obj) match {
case Success(pkt) => case Success(pkt) =>
pkt mustEqual CommonFieldData(PlanetSideEmpire.NEUTRAL)(false) pkt mustEqual CommonFieldData(PlanetSideEmpire.NEUTRAL)(flag = false)
case _ => case _ =>
ko ko
} }

View file

@ -21,26 +21,26 @@ class DefaultTest extends Specification {
} }
} }
class DefaultActorStartedTest extends ActorTest { //class DefaultActorStartedTest extends ActorTest {
"Default.Actor" should { // "Default.Actor" should {
"send messages to deadLetters" in { // "send messages to deadLetters" in {
//after being started // //after being started
val probe = new TestProbe(system) // val probe = new TestProbe(system)
system.eventStream.subscribe(probe.ref, classOf[DeadLetter]) // system.eventStream.subscribe(probe.ref, classOf[DeadLetter])
Default.Actor ! "hello world" // Default.Actor ! "hello world"
val msg1 = probe.receiveOne(5000 milliseconds) // val msg1 = probe.receiveOne(5000 milliseconds)
assert(msg1.isInstanceOf[DeadLetter]) // assert(msg1.isInstanceOf[DeadLetter])
assert(msg1.asInstanceOf[DeadLetter].message equals "hello world") // assert(msg1.asInstanceOf[DeadLetter].message equals "hello world")
//
//if it was stopped // //if it was stopped
system.stop(Default.Actor) // system.stop(Default.Actor)
Default.Actor ! "hello world" // Default.Actor ! "hello world"
val msg2 = probe.receiveOne(5000 milliseconds) // val msg2 = probe.receiveOne(5000 milliseconds)
assert(msg2.isInstanceOf[DeadLetter]) // assert(msg2.isInstanceOf[DeadLetter])
assert(msg2.asInstanceOf[DeadLetter].message equals "hello world") // assert(msg2.asInstanceOf[DeadLetter].message equals "hello world")
} // }
} // }
} //}
object DefaultActorTest { object DefaultActorTest {
//due to being a singleton, the original original value of the Default.Actor is cached here //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") assert(deployableList.isEmpty, "self-setup test - deployable list is not empty")
zone.Deployables ! Zone.Deployable.Build(jmine) zone.Deployables ! Zone.Deployable.Build(jmine)
val eventsMsgs = eventsProbe.receiveN(3, 10.seconds) val eventsMsgs = eventsProbe.receiveN(2, 10.seconds)
eventsMsgs.head match { 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)) => case LocalServiceMessage("test", LocalAction.DeployItem(obj)) =>
assert(obj eq jmine, "self-setup test - not same mine") assert(obj eq jmine, "self-setup test - not same mine")
case _ => case _ =>
assert( false, "self-setup test - wrong deploy message") assert( false, "self-setup test - wrong deploy message")
} }
eventsMsgs(2) match { eventsMsgs(1) match {
case LocalServiceMessage( case LocalServiceMessage(
"TR", "TR",
LocalAction.DeployableMapIcon( LocalAction.DeployableMapIcon(
@ -251,13 +244,13 @@ class DeployableBehaviorDeconstructTest extends ActorTest {
"DeployableBehavior" should { "DeployableBehavior" should {
"deconstruct, by self" in { "deconstruct, by self" in {
zone.Deployables ! Zone.Deployable.Build(jmine) 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") assert(deployableList.contains(jmine), "deconstruct test - deployable not appended to list")
jmine.Actor ! Deployable.Deconstruct() jmine.Actor ! Deployable.Deconstruct()
val eventsMsgs = eventsProbe.receiveN(2, 10.seconds) val eventsMsgs = eventsProbe.receiveN(2, 10.seconds)
eventsMsgs.head match { 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") case _ => assert(false, "deconstruct test - not eliminating deployable")
} }
eventsMsgs(1) match { eventsMsgs(1) match {

View file

@ -314,7 +314,7 @@ class ExplosiveDeployableJammerTest extends ActorTest {
val guid = new NumberPoolHub(new MaxNumberSource(10)) val guid = new NumberPoolHub(new MaxNumberSource(10))
val eventsProbe = new TestProbe(system) 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 avatar1 = Avatar(0, "TestCharacter1", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)
val player1 = Player(avatar1) //guid=3 val player1 = Player(avatar1) //guid=3
val avatar2 = Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterSex.Male, 0, CharacterVoice.Mute) val avatar2 = Avatar(0, "TestCharacter2", PlanetSideEmpire.NC, CharacterSex.Male, 0, CharacterVoice.Mute)
@ -335,17 +335,17 @@ class ExplosiveDeployableJammerTest extends ActorTest {
} }
player1.Spawn() player1.Spawn()
player2.Spawn() player2.Spawn()
guid.register(j_mine, 1) guid.register(mine, 1)
guid.register(player1, 3) guid.register(player1, 3)
guid.register(player2, 4) guid.register(player2, 4)
guid.register(weapon, 5) guid.register(weapon, 5)
j_mine.Zone = zone mine.Zone = zone
j_mine.OwnerGuid = player2 mine.OwnerGuid = player2
//j_mine.OwnerName = player2.Name //j_mine.OwnerName = player2.Name
j_mine.Faction = PlanetSideEmpire.NC mine.Faction = PlanetSideEmpire.NC
j_mine.Actor = system.actorOf(Props(classOf[MineDeployableControl], j_mine), "j-mine-control") 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 pSource = PlayerSource(player1)
val projectile = weapon.Projectile val projectile = weapon.Projectile
val resolved = DamageInteraction( val resolved = DamageInteraction(
@ -353,7 +353,7 @@ class ExplosiveDeployableJammerTest extends ActorTest {
ProjectileReason( ProjectileReason(
DamageResolution.Hit, DamageResolution.Hit,
Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero), Projectile(projectile, weapon.Definition, weapon.FireMode, pSource, 0, Vector3.Zero, Vector3.Zero),
j_mine.DamageModel mine.DamageModel
), ),
Vector3(1, 0, 0) Vector3(1, 0, 0)
) )
@ -361,26 +361,53 @@ class ExplosiveDeployableJammerTest extends ActorTest {
"ExplosiveDeployable" should { "ExplosiveDeployable" should {
"handle being jammered appropriately (no detonation)" in { "handle being jammered appropriately (no detonation)" in {
assert(!j_mine.Destroyed) assert(!mine.Destroyed)
j_mine.Actor ! Vitality.Damage(applyDamageToJ) mine.Actor ! Vitality.Damage(applyDamageToJ)
val eventMsgs = eventsProbe.receiveN(2, 200 milliseconds) val eventMsgs = eventsProbe.receiveN(4, 200 milliseconds)
eventMsgs.head match { 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( case LocalServiceMessage(
"NC", "NC",
LocalAction.DeployableMapIcon( LocalAction.DeployableMapIcon(
ValidPlanetSideGUID(0), ValidPlanetSideGUID(0),
DeploymentAction.Dismiss, DeploymentAction.Dismiss,
DeployableInfo(ValidPlanetSideGUID(1), DeployableIcon.DisruptorMine, Vector3.Zero, ValidPlanetSideGUID(0)) DeployableInfo(ValidPlanetSideGUID(1), DeployableIcon.HEMine, Vector3.Zero, ValidPlanetSideGUID(0))
) )
) => ; ) => ;
case _ => assert(false, "") case _ => assert(false, "")
} }
eventMsgs(1) match { eventMsgs(3) match {
case AvatarServiceMessage("test", AvatarAction.Destroy(PlanetSideGUID(1), _, Service.defaultPlayerGUID, _)) => ; case AvatarServiceMessage(
"test",
AvatarAction.Destroy(
ValidPlanetSideGUID(1),
ValidPlanetSideGUID(3),
ValidPlanetSideGUID(0),
Vector3.Zero
)
) => ;
case _ => assert(false, "") 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) assert(obj.DeploymentState == DriveState.Mobile)
//to Deploying //to Deploying
obj.Actor.tell(Deployment.TryDeploymentChange(DriveState.Deploying), probe.ref) obj.Actor.tell(Deployment.TryDeploymentChange(DriveState.Deploying), probe.ref)
val reply1a = probe.receiveOne(Duration.create(500, "ms")) val reply1 = probe.receiveN(2, Duration.create(2000, "ms"))
assert(reply1a match { val reply2 = eventsProbe.receiveN(2, Duration.create(2000, "ms"))
case Deployment.CanDeploy(_, DriveState.Deploying) => true reply1.head match {
case _ => false case Deployment.CanDeploy(_, DriveState.Deploying) => ()
}) case _ => assert(false, "")
val reply1b = eventsProbe.receiveOne(Duration.create(500, "ms")) }
assert(reply1b match { reply2.head match {
case VehicleServiceMessage( case VehicleServiceMessage(
"test", "test",
VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deploying, 0, false, Vector3.Zero) VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deploying, 0, false, Vector3.Zero)
) => ) => ()
true case _ => assert(false, "")
case _ => false }
})
//to Deployed //to Deployed
val reply2 = eventsProbe.receiveOne(Duration.create(500, "ms")) reply1(1) match {
assert(reply2 match { case Deployment.CanDeploy(_, DriveState.Deployed) => ()
case _ => assert(false, "")
}
reply2(1) match {
case VehicleServiceMessage( case VehicleServiceMessage(
"test", "test",
VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deployed, 0, false, Vector3.Zero) VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deployed, 0, false, Vector3.Zero)
) => ) => ()
true case _ => assert(false, "")
case _ => false }
})
assert(obj.DeploymentState == DriveState.Deployed) assert(obj.DeploymentState == DriveState.Deployed)
//to Undeploying //to Undeploying
obj.Actor.tell(Deployment.TryDeploymentChange(DriveState.Undeploying), probe.ref) obj.Actor.tell(Deployment.TryDeploymentChange(DriveState.Undeploying), probe.ref)
val reply3a = probe.receiveOne(Duration.create(500, "ms")) val reply3 = probe.receiveN(2, Duration.create(2000, "ms"))
assert(reply3a match { val reply4 = eventsProbe.receiveN(2, Duration.create(2000, "ms"))
case Deployment.CanUndeploy(_, DriveState.Undeploying) => true reply3.head match {
case _ => false case Deployment.CanUndeploy(_, DriveState.Undeploying) => ()
}) case _ => assert(false, "")
val reply3b = eventsProbe.receiveOne(Duration.create(500, "ms")) }
assert(reply3b match { reply4.head match {
case VehicleServiceMessage( case VehicleServiceMessage(
"test", "test",
VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Undeploying, 0, false, Vector3.Zero) VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Undeploying, 0, false, Vector3.Zero)
) => ) => ()
true case _ => assert(false, "")
case _ => false }
})
//to Mobile //to Mobile
val reply4 = eventsProbe.receiveOne(Duration.create(500, "ms")) reply3(1) match {
assert(reply4 match { case Deployment.CanUndeploy(_, DriveState.Mobile) => ()
case _ => assert(false, "")
}
reply4(1) match {
case VehicleServiceMessage( case VehicleServiceMessage(
"test", "test",
VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Mobile, 0, false, Vector3.Zero) VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Mobile, 0, false, Vector3.Zero)
) => ) => ()
true case _ => assert(false, "")
case _ => false }
})
assert(obj.DeploymentState == DriveState.Mobile) assert(obj.DeploymentState == DriveState.Mobile)
} }
} }
@ -131,57 +133,59 @@ class DeploymentBehavior3Test extends ActorTest {
assert(obj.DeploymentState == DriveState.Mobile) assert(obj.DeploymentState == DriveState.Mobile)
//to Deploying //to Deploying
obj.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), probe.ref) obj.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), probe.ref)
val reply1a = probe.receiveOne(Duration.create(500, "ms")) val reply1 = probe.receiveN(2, Duration.create(2000, "ms"))
assert(reply1a match { val reply2 = eventsProbe.receiveN(2, Duration.create(2000, "ms"))
case Deployment.CanDeploy(_, DriveState.Deploying) => true reply1.head match {
case _ => false case Deployment.CanDeploy(_, DriveState.Deploying) => ()
}) case _ => assert(false, "")
val reply1b = eventsProbe.receiveOne(Duration.create(500, "ms")) }
assert(reply1b match { reply2.head match {
case VehicleServiceMessage( case VehicleServiceMessage(
"test", "test",
VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deploying, 0, false, Vector3.Zero) VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deploying, 0, false, Vector3.Zero)
) => ) => ()
true case _ => assert(false, "")
case _ => false }
})
//to Deployed //to Deployed
val reply2 = eventsProbe.receiveOne(Duration.create(500, "ms")) reply1(1) match {
assert(reply2 match { case Deployment.CanDeploy(_, DriveState.Deployed) => ()
case _ => assert(false, "")
}
reply2(1) match {
case VehicleServiceMessage( case VehicleServiceMessage(
"test", "test",
VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deployed, 0, false, Vector3.Zero) VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Deployed, 0, false, Vector3.Zero)
) => ) => ()
true case _ => assert(false, "")
case _ => false }
})
assert(obj.DeploymentState == DriveState.Deployed) assert(obj.DeploymentState == DriveState.Deployed)
//to Undeploying //to Undeploying
obj.Actor.tell(Deployment.TryUndeploy(DriveState.Undeploying), probe.ref) obj.Actor.tell(Deployment.TryUndeploy(DriveState.Undeploying), probe.ref)
val reply3a = probe.receiveOne(Duration.create(500, "ms")) val reply3 = probe.receiveN(2, Duration.create(2000, "ms"))
assert(reply3a match { val reply4 = eventsProbe.receiveN(2, Duration.create(2000, "ms"))
case Deployment.CanUndeploy(_, DriveState.Undeploying) => true reply3.head match {
case _ => false case Deployment.CanUndeploy(_, DriveState.Undeploying) => ()
}) case _ => assert(false, "")
val reply3b = eventsProbe.receiveOne(Duration.create(500, "ms")) }
assert(reply3b match { reply4.head match {
case VehicleServiceMessage( case VehicleServiceMessage(
"test", "test",
VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Undeploying, 0, false, Vector3.Zero) VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Undeploying, 0, false, Vector3.Zero)
) => ) => ()
true case _ => assert(false, "")
case _ => false }
})
//to Mobile //to Mobile
val reply4 = eventsProbe.receiveOne(Duration.create(500, "ms")) reply3(1) match {
assert(reply4 match { case Deployment.CanUndeploy(_, DriveState.Mobile) => ()
case _ => assert(false, "")
}
reply4(1) match {
case VehicleServiceMessage( case VehicleServiceMessage(
"test", "test",
VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Mobile, 0, false, Vector3.Zero) VehicleAction.DeployRequest(_, PlanetSideGUID(1), DriveState.Mobile, 0, false, Vector3.Zero)
) => ) => ()
true case _ => assert(false, "")
case _ => false }
})
assert(obj.DeploymentState == DriveState.Mobile) assert(obj.DeploymentState == DriveState.Mobile)
} }
} }
@ -195,16 +199,18 @@ class DeploymentBehavior4Test extends ActorTest {
obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deployed) obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deployed)
val reply1 = receiveOne(Duration.create(100, "ms")) val reply1 = receiveOne(Duration.create(100, "ms"))
assert(reply1.isInstanceOf[Deployment.CanNotChangeDeployment]) reply1 match {
assert(reply1.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj) case Deployment.CanNotChangeDeployment(_, DriveState.Deployed, _) => ()
assert(reply1.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Deployed) case _ => assert(false, "")
}
assert(obj.DeploymentState == DriveState.Mobile) assert(obj.DeploymentState == DriveState.Mobile)
obj.Actor ! Deployment.TryDeploy(DriveState.Deployed) obj.Actor ! Deployment.TryDeploy(DriveState.Deployed)
val reply2 = receiveOne(Duration.create(100, "ms")) val reply2 = receiveOne(Duration.create(100, "ms"))
assert(reply2.isInstanceOf[Deployment.CanNotChangeDeployment]) reply2 match {
assert(reply2.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj) case Deployment.CanNotChangeDeployment(_, DriveState.Deployed, _) => ()
assert(reply2.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Deployed) case _ => assert(false, "")
}
assert(obj.DeploymentState == DriveState.Mobile) assert(obj.DeploymentState == DriveState.Mobile)
} }
} }
@ -215,18 +221,22 @@ class DeploymentBehavior5Test extends ActorTest {
"not deploy to an undeploy state" in { "not deploy to an undeploy state" in {
val obj = DeploymentTest.SetUpAgent val obj = DeploymentTest.SetUpAgent
assert(obj.DeploymentState == DriveState.Mobile) assert(obj.DeploymentState == DriveState.Mobile)
obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deploying)
receiveOne(Duration.create(100, "ms")) //consume obj.Actor ! Deployment.TryDeploymentChange(DriveState.Undeploying)
obj.Actor ! Deployment.TryDeploymentChange(DriveState.Deployed) val reply1 = receiveOne(Duration.create(100, "ms"))
receiveOne(Duration.create(100, "ms")) //consume reply1 match {
assert(obj.DeploymentState == DriveState.Deployed) case Deployment.CanNotChangeDeployment(_, DriveState.Undeploying, _) => ()
case _ => assert(false, "")
}
assert(obj.DeploymentState == DriveState.Mobile)
obj.Actor ! Deployment.TryDeploy(DriveState.Undeploying) obj.Actor ! Deployment.TryDeploy(DriveState.Undeploying)
val reply = receiveOne(Duration.create(100, "ms")) val reply2 = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[Deployment.CanNotChangeDeployment]) reply2 match {
assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj) case Deployment.CanNotChangeDeployment(_, DriveState.Undeploying, _) => ()
assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Undeploying) case _ => assert(false, "")
assert(obj.DeploymentState == DriveState.Deployed) }
assert(obj.DeploymentState == DriveState.Mobile)
} }
} }
} }
@ -235,14 +245,24 @@ class DeploymentBehavior6Test extends ActorTest {
"Deployment" should { "Deployment" should {
"not undeploy to a deploy state" in { "not undeploy to a deploy state" in {
val obj = DeploymentTest.SetUpAgent 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) obj.Actor ! Deployment.TryUndeploy(DriveState.Deploying)
val reply = receiveOne(Duration.create(100, "ms")) val reply2 = receiveOne(Duration.create(100, "ms"))
assert(reply.isInstanceOf[Deployment.CanNotChangeDeployment]) reply2 match {
assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].obj == obj) case Deployment.CanNotChangeDeployment(_, DriveState.Deploying, _) => ()
assert(reply.asInstanceOf[Deployment.CanNotChangeDeployment].to_state == DriveState.Deploying) case _ => assert(false, "")
assert(obj.DeploymentState == DriveState.Mobile) }
assert(obj.DeploymentState == DriveState.Deployed)
} }
} }
} }

View file

@ -16,9 +16,9 @@ import net.psforever.types.{CharacterSex, CharacterVoice, PlanetSideEmpire, Plan
import scala.concurrent.duration._ import scala.concurrent.duration._
class InteractsWithZoneEnvironmentTest extends ActorTest { class InteractsWithZoneEnvironmentTest extends ActorTest {
val pool1: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(5, 10, 10, 0, 0)) val pool1: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(3, 2, 2, 0, 0))
val pool2: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(5, 10, 15, 5, 10)) val pool2: Pool = Pool(EnvironmentAttribute.Water, DeepSquare(3, 4, 2, 2, 0))
val pool3: Pool = Pool(EnvironmentAttribute.Lava, DeepSquare(5, 15, 10, 10, 5)) val pool3: Pool = Pool(EnvironmentAttribute.Lava, DeepSquare(3, 2, 4, 0, 2))
val zoneEvents: TestProbe = TestProbe() val zoneEvents: TestProbe = TestProbe()
val testZone: Zone = { val testZone: Zone = {
val testMap = new ZoneMap(name = "test-map") { val testMap = new ZoneMap(name = "test-map") {
@ -38,27 +38,26 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
val obj = InteractsWithZoneEnvironmentTest.testObject() val obj = InteractsWithZoneEnvironmentTest.testObject()
obj.Zone = testZone obj.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(0,0,50) obj.Position = Vector3(10,10,0)
obj.zoneInteractions() obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds) 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 testProbe = TestProbe()
val obj = InteractsWithZoneEnvironmentTest.testObject() val obj = InteractsWithZoneEnvironmentTest.testObject()
obj.Zone = testZone obj.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(1,1,2) obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions() obj.zoneInteractions()
val msg = testProbe.receiveOne(max = 250 milliseconds) val msg = testProbe.receiveOne(4.seconds)
assert( msg match {
msg match { case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true case _ => assert(InteractsWithZoneEnvironmentTest.fail, s"$msg")
case _ => false }
}
)
obj.zoneInteractions() obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds) testProbe.expectNoMessage(max = 500 milliseconds)
} }
@ -69,54 +68,48 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Zone = testZone obj.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(1,1,2) obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions() obj.zoneInteractions()
val msg1 = testProbe.receiveOne(max = 250 milliseconds) val msg1 = testProbe.receiveOne(4.seconds)
assert( msg1 match {
msg1 match { case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
case _ => false }
}
)
obj.Position = Vector3(1,1,50) obj.Position = Vector3(1,1,5)
obj.zoneInteractions() obj.zoneInteractions()
val msg2 = testProbe.receiveOne(max = 250 milliseconds) val msg2 = testProbe.receiveOne(4.seconds)
assert( msg2 match {
msg2 match { case RespondsToZoneEnvironment.StopTimer(EnvironmentAttribute.Water) => ()
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
case _ => false }
}
)
obj.zoneInteractions() obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds) 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 testProbe = TestProbe()
val obj = InteractsWithZoneEnvironmentTest.testObject() val obj = InteractsWithZoneEnvironmentTest.testObject()
obj.Zone = testZone obj.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(7,7,2) obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions() obj.zoneInteractions()
val msg1 = testProbe.receiveOne(max = 250 milliseconds) val msg1 = testProbe.receiveOne(4.seconds)
assert( msg1 match {
msg1 match { case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
case _ => false }
}
)
obj.Position = Vector3(12,7,2) obj.Position = Vector3(1, 3, 2.7f)
obj.zoneInteractions() obj.zoneInteractions()
val msg2 = testProbe.receiveOne(max = 250 milliseconds) val msg2 = testProbe.receiveOne(4.seconds)
assert( msg2 match {
msg2 match { case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true case _ => assert(false, "")
case _ => false }
} testProbe.expectNoMessage()
)
} }
"transition between two different critical regions when the regions have different attributes" in { "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.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(7,7,2) obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions() obj.zoneInteractions()
val msg1 = testProbe.receiveOne(max = 250 milliseconds) val msg1 = testProbe.receiveOne(4.seconds)
assert( msg1 match {
msg1 match { case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
case _ => false }
}
)
obj.Position = Vector3(7,12,2) obj.Position = Vector3(3, 1, 2.7f)
obj.zoneInteractions() obj.zoneInteractions()
val msgs = testProbe.receiveN(3, max = 250 milliseconds) val msgs = testProbe.receiveN(4, 4.seconds)
assert( msgs.head match {
msgs.head match { case RespondsToZoneEnvironment.StopTimer(EnvironmentAttribute.Water) => ()
case Vitality.Damage(_) => true case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
case _ => false }
} msgs(1) match {
) case Vitality.Damage(_) => ()
assert( case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
msgs(1) match { }
case AuraEffectBehavior.StartEffect(Aura.Fire, _) => true msgs(2) match {
case _ => false case AuraEffectBehavior.StartEffect(Aura.Fire, _) => ()
} case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
) }
assert( msgs(3) match {
msgs(2) match { case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Lava, _, _, _) => ()
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Lava, _, _, _) => true case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
case _ => false }
}
)
} }
} }
@ -165,24 +154,21 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Zone = testZone obj.Zone = testZone
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.Position = Vector3(1,1,2) obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions() obj.zoneInteractions()
val msg1 = testProbe.receiveOne(max = 250 milliseconds) val msg1 = testProbe.receiveOne(max = 250 milliseconds)
assert( msg1 match {
msg1 match { case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
case _ => false }
}
)
obj.allowInteraction = false obj.allowInteraction = false
val msg2 = testProbe.receiveOne(max = 250 milliseconds) val msg2 = testProbe.receiveOne(max = 250 milliseconds)
assert( msg2 match {
msg2 match { case RespondsToZoneEnvironment.StopTimer(EnvironmentAttribute.Water) => true
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
case _ => false }
}
)
obj.zoneInteractions() obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds) testProbe.expectNoMessage(max = 500 milliseconds)
} }
@ -194,22 +180,22 @@ class InteractsWithZoneEnvironmentTest extends ActorTest {
obj.Actor = testProbe.ref obj.Actor = testProbe.ref
obj.allowInteraction = false obj.allowInteraction = false
obj.Position = Vector3(1,1,2) obj.Position = Vector3(1, 1, 2.7f)
obj.zoneInteractions() obj.zoneInteractions()
testProbe.expectNoMessage(max = 500 milliseconds) testProbe.expectNoMessage(max = 500 milliseconds)
obj.allowInteraction = true obj.allowInteraction = true
val msg1 = testProbe.receiveOne(max = 250 milliseconds) val msg1 = testProbe.receiveOne(max = 250 milliseconds)
assert( msg1 match {
msg1 match { case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => true case _ => assert(InteractsWithZoneEnvironmentTest.fail, "")
case _ => false }
}
)
} }
} }
object InteractsWithZoneEnvironmentTest { object InteractsWithZoneEnvironmentTest {
val fail: Boolean = false
def testObject(): PlanetSideServerObject with InteractsWithZone = { def testObject(): PlanetSideServerObject with InteractsWithZone = {
val p = new Player(Avatar(1, "test", PlanetSideEmpire.VS, CharacterSex.Male, 1, CharacterVoice.Mute)) val p = new Player(Avatar(1, "test", PlanetSideEmpire.VS, CharacterSex.Male, 1, CharacterVoice.Mute))
p.GUID = PlanetSideGUID(1) p.GUID = PlanetSideGUID(1)

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package objects package objects
import akka.actor.Props import akka.actor.{ActorRef, Props}
import akka.testkit.TestProbe import akka.testkit.TestProbe
import base.{ActorTest, FreedContextActorTest} import base.{ActorTest, FreedContextActorTest}
import net.psforever.actors.zone.BuildingActor import net.psforever.actors.zone.BuildingActor
@ -63,7 +63,7 @@ class ResourceSiloTest extends Specification {
PlanetSideGUID(0), PlanetSideGUID(0),
PlanetSideGUID(2), PlanetSideGUID(2),
0L, 0L,
false, unk3 = false,
Vector3(0f, 0f, 0f), Vector3(0f, 0f, 0f),
Vector3(0f, 0f, 0f), Vector3(0f, 0f, 0f),
0, 0,
@ -77,11 +77,11 @@ class ResourceSiloTest extends Specification {
} }
class ResourceSiloControlStartupTest extends ActorTest { class ResourceSiloControlStartupTest extends ActorTest {
val obj = ResourceSilo() val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1) obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0) 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 = obj.Owner =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) { new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) {
Actor = buildingEvents.ref Actor = buildingEvents.ref
@ -97,11 +97,11 @@ class ResourceSiloControlStartupTest extends ActorTest {
} }
class ResourceSiloControlStartupMessageNoneTest extends ActorTest { class ResourceSiloControlStartupMessageNoneTest extends ActorTest {
val obj = ResourceSilo() val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1) obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0) 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 = obj.Owner =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) { new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) {
Actor = buildingEvents.ref Actor = buildingEvents.ref
@ -123,11 +123,11 @@ class ResourceSiloControlStartupMessageNoneTest extends ActorTest {
} }
class ResourceSiloControlStartupMessageSomeTest extends ActorTest { class ResourceSiloControlStartupMessageSomeTest extends ActorTest {
val obj = ResourceSilo() val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1) obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0) 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 = obj.Owner =
new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) { new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building) {
Actor = buildingEvents.ref Actor = buildingEvents.ref
@ -154,19 +154,19 @@ class ResourceSiloControlUseTest extends FreedContextActorTest {
expectNoMessage(1000 milliseconds) expectNoMessage(1000 milliseconds)
var buildingMap = new TrieMap[Int, Building]() var buildingMap = new TrieMap[Int, Building]()
val guid = new NumberPoolHub(new MaxNumberSource(max = 10)) val guid = new NumberPoolHub(new MaxNumberSource(max = 10))
val player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute)) val player: Player = Player(Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute))
val ant = Vehicle(GlobalDefinitions.ant) val ant: Vehicle = Vehicle(GlobalDefinitions.ant)
val silo = new ResourceSilo() val silo = new ResourceSilo()
val catchall = new TestProbe(system).ref val catchall: ActorRef = new TestProbe(system).ref
val zone = new Zone("test", new ZoneMap("test-map"), 0) { val zone: Zone = new Zone("test", new ZoneMap("test-map"), 0) {
override def SetupNumberPools() = {} override def SetupNumberPools(): Unit = {}
GUID(guid) GUID(guid)
override def AvatarEvents = catchall override def AvatarEvents: ActorRef = catchall
override def LocalEvents = catchall override def LocalEvents: ActorRef = catchall
override def VehicleEvents = catchall override def VehicleEvents: ActorRef = catchall
override def Activity = catchall override def Activity: ActorRef = catchall
override def Vehicles = List(ant) override def Vehicles: List[Vehicle] = List(ant)
override def Buildings = { buildingMap.toMap } override def Buildings: Map[Int, Building] = { buildingMap.toMap }
} }
val building = new Building( val building = new Building(
name = "integ-fac-test-building", name = "integ-fac-test-building",
@ -186,7 +186,7 @@ class ResourceSiloControlUseTest extends FreedContextActorTest {
guid.register(silo, number = 5) guid.register(silo, number = 5)
guid.register(building, number = 6) guid.register(building, number = 6)
val maxNtuCap = ant.Definition.MaxNtuCapacitor val maxNtuCap: Float = ant.Definition.MaxNtuCapacitor
player.Spawn() player.Spawn()
ant.NtuCapacitor = maxNtuCap ant.NtuCapacitor = maxNtuCap
val probe = new TestProbe(system) val probe = new TestProbe(system)
@ -201,19 +201,18 @@ class ResourceSiloControlUseTest extends FreedContextActorTest {
"Resource silo" should { "Resource silo" should {
"respond when being used" in { "respond when being used" in {
expectNoMessage(1 seconds) expectNoMessage(1 seconds)
silo.Actor ! CommonMessages.Use(ResourceSiloTest.player) silo.Actor ! CommonMessages.Use(ResourceSiloTest.player, Some(ant))
val reply = probe.receiveOne(3000 milliseconds)
val reply = probe.receiveOne(2000 milliseconds) reply match {
assert(reply match { case TransferBehavior.Discharging(Ntu.Nanites) => ()
case TransferBehavior.Discharging(Ntu.Nanites) => true case _ => assert(ResourceSiloTest.fail, "")
case _ => false }
})
} }
} }
} }
class ResourceSiloControlNtuWarningTest extends ActorTest { class ResourceSiloControlNtuWarningTest extends ActorTest {
val obj = ResourceSilo() val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1) obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0) val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0)
@ -223,7 +222,7 @@ class ResourceSiloControlNtuWarningTest extends ActorTest {
} }
obj.Owner.GUID = PlanetSideGUID(6) obj.Owner.GUID = PlanetSideGUID(6)
val zoneEvents = TestProbe("zone-events") val zoneEvents: TestProbe = TestProbe("zone-events")
zone.AvatarEvents = zoneEvents.ref zone.AvatarEvents = zoneEvents.ref
obj.Actor ! Service.Startup() obj.Actor ! Service.Startup()
obj.Actor ! ResourceSilo.UpdateChargeLevel(-obj.NtuCapacitor) obj.Actor ! ResourceSilo.UpdateChargeLevel(-obj.NtuCapacitor)
@ -237,7 +236,7 @@ class ResourceSiloControlNtuWarningTest extends ActorTest {
val reply = zoneEvents.receiveOne(5000 milliseconds) val reply = zoneEvents.receiveOne(5000 milliseconds)
reply match { reply match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => ; 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) assert(!obj.LowNtuWarningOn)
} }
@ -245,7 +244,7 @@ class ResourceSiloControlNtuWarningTest extends ActorTest {
} }
class ResourceSiloControlUpdate1Test extends ActorTest { class ResourceSiloControlUpdate1Test extends ActorTest {
val obj = ResourceSilo() val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1) obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0) 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) new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
bldg.GUID = PlanetSideGUID(6) bldg.GUID = PlanetSideGUID(6)
obj.Owner = bldg obj.Owner = bldg
val zoneEvents = TestProbe("zone-events") val zoneEvents: TestProbe = TestProbe("zone-events")
val buildingEvents = TestProbe("building-events") val buildingEvents: TestProbe = TestProbe("building-events")
zone.AvatarEvents = zoneEvents.ref zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref bldg.Actor = buildingEvents.ref
obj.Actor ! Service.Startup() obj.Actor ! Service.Startup()
@ -276,12 +275,12 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
assert(obj.CapacitorDisplay == 3) assert(obj.CapacitorDisplay == 3)
reply1.head match { reply1.head match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(1), 45, 3)) => ; 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") assert(reply2.isInstanceOf[BuildingActor.MapUpdate], s"$reply2 is wrong")
reply1(1) match { reply1(1) match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => ; 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) assert(!obj.LowNtuWarningOn)
} }
@ -289,7 +288,7 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
} }
class ResourceSiloControlUpdate2Test extends ActorTest { class ResourceSiloControlUpdate2Test extends ActorTest {
val obj = ResourceSilo() val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1) obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0) 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) new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
bldg.GUID = PlanetSideGUID(6) bldg.GUID = PlanetSideGUID(6)
obj.Owner = bldg obj.Owner = bldg
val zoneEvents = TestProbe("zone-events") val zoneEvents: TestProbe = TestProbe("zone-events")
val buildingEvents = TestProbe("building-events") val buildingEvents: TestProbe = TestProbe("building-events")
zone.AvatarEvents = zoneEvents.ref zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref bldg.Actor = buildingEvents.ref
obj.Actor ! Service.Startup() obj.Actor ! Service.Startup()
@ -320,12 +319,12 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
assert(obj.CapacitorDisplay == 2) assert(obj.CapacitorDisplay == 2)
reply1.head match { reply1.head match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(1), 45, 2)) => ; 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]) assert(reply2.isInstanceOf[BuildingActor.MapUpdate])
reply1(1) match { reply1(1) match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => ; 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) assert(!obj.LowNtuWarningOn)
} }
@ -333,7 +332,7 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
} }
class ResourceSiloControlNoUpdateTest extends ActorTest { class ResourceSiloControlNoUpdateTest extends ActorTest {
val obj = ResourceSilo() val obj: ResourceSilo = ResourceSilo()
obj.GUID = PlanetSideGUID(1) obj.GUID = PlanetSideGUID(1)
obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo") obj.Actor = system.actorOf(Props(classOf[ResourceSiloControl], obj), "test-silo")
val zone = new Zone("nowhere", new ZoneMap("nowhere-map"), 0) 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) new Building("Building", building_guid = 6, map_id = 0, zone, StructureType.Building, GlobalDefinitions.building)
bldg.GUID = PlanetSideGUID(6) bldg.GUID = PlanetSideGUID(6)
obj.Owner = bldg obj.Owner = bldg
val zoneEvents = TestProbe("zone-events") val zoneEvents: TestProbe = TestProbe("zone-events")
val buildingEvents = TestProbe("building-events") val buildingEvents: TestProbe = TestProbe("building-events")
zone.AvatarEvents = zoneEvents.ref zone.AvatarEvents = zoneEvents.ref
bldg.Actor = buildingEvents.ref bldg.Actor = buildingEvents.ref
obj.Actor ! Service.Startup() obj.Actor ! Service.Startup()
@ -371,7 +370,9 @@ class ResourceSiloControlNoUpdateTest extends ActorTest {
} }
object ResourceSiloTest { object ResourceSiloTest {
val player = Player( val player: Player = Player(
Avatar(0, "TestCharacter", PlanetSideEmpire.TR, CharacterSex.Male, 0, CharacterVoice.Mute).copy(stamina = 0) 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.objects.zones.{Zone, ZoneDeployableActor, ZoneMap}
import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent import net.psforever.packet.game.objectcreate.ObjectCreateMessageParent
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.local.{LocalAction, LocalServiceMessage} import net.psforever.services.local.{LocalAction, LocalServiceMessage}
import net.psforever.types.{DriveState, PlanetSideGUID, Vector3} import net.psforever.types.{DriveState, PlanetSideGUID, Vector3}
@ -253,7 +252,7 @@ class TelepadDeployableResponseFromRouterTest extends FreedContextActorTest {
val deploymentProbe = new TestProbe(system) val deploymentProbe = new TestProbe(system)
router.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), deploymentProbe.ref) router.Actor.tell(Deployment.TryDeploy(DriveState.Deploying), deploymentProbe.ref)
eventsProbe.receiveN(10, 10.seconds) //flush all messages related to deployment 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 deploymentProbe.expectNoMessage(2.seconds) //intentional delay
assert(internal.Active, "link to router test - router internals not active when expected") 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)") 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.guid.source.MaxNumberSource
import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.environment._ 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.environment.interaction.common.Watery.OxygenStateTarget
import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.sourcing.VehicleSource import net.psforever.objects.sourcing.VehicleSource
import net.psforever.objects.vehicles.VehicleLockState import net.psforever.objects.vehicles.VehicleLockState
import net.psforever.objects.vehicles.control.VehicleControl 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.vital.{ShieldCharge, SpawningActivity, Vitality}
import net.psforever.objects.zones.{Zone, ZoneMap} import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game._ import net.psforever.packet.game._
import net.psforever.services.ServiceManager import net.psforever.services.ServiceManager
import net.psforever.services.avatar.{AvatarAction, AvatarServiceMessage}
import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage} import net.psforever.services.vehicle.{VehicleAction, VehicleServiceMessage}
import net.psforever.types._ import net.psforever.types._
@ -174,15 +174,17 @@ class VehicleControlPrepareForDeletionPassengerTest extends ActorTest {
//} //}
class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActorTest { class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActorTest {
val vehicleProbe = new TestProbe(system) val eventsProbe = new TestProbe(system)
val catchall = new TestProbe(system) val cargoProbe = new TestProbe(system)
val guid = new NumberPoolHub(new MaxNumberSource(10)) val guid = new NumberPoolHub(new MaxNumberSource(10))
ServiceManager.boot ServiceManager.boot
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone = new Zone("test", new ZoneMap("test"), 0) {
GUID(guid) GUID(guid)
override def SetupNumberPools(): Unit = {} 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") zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
// crappy workaround but without it the zone doesn't get initialized in time // 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) val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.Faction = PlanetSideEmpire.TR vehicle.Faction = PlanetSideEmpire.TR
vehicle.Zone = zone vehicle.Zone = zone
val cargoProbe = new TestProbe(system)
vehicle.Actor = cargoProbe.ref vehicle.Actor = cargoProbe.ref
val lodestar = Vehicle(GlobalDefinitions.lodestar) val lodestar = Vehicle(GlobalDefinitions.lodestar)
lodestar.Faction = PlanetSideEmpire.TR lodestar.Faction = PlanetSideEmpire.TR
@ -220,39 +221,45 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
"if with mounted cargo, eject it when marked for deconstruction" in { "if with mounted cargo, eject it when marked for deconstruction" in {
lodestar.Actor ! Vehicle.Deconstruct() lodestar.Actor ! Vehicle.Deconstruct()
val vehicle_msg = vehicleProbe.receiveN(6, 500 milliseconds) val vehicleMsgs = eventsProbe.receiveN(6, 10.seconds)
vehicle_msg.head match { val cargoMsgs = cargoProbe.receiveN(1, 1.seconds)
case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(4), 4, true, PlanetSideGUID(2))) => ; vehicleMsgs.head match {
case VehicleServiceMessage("test", VehicleAction.KickPassenger(PlanetSideGUID(4), 4, true, PlanetSideGUID(2))) => ()
case _ => case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-1: ${vehicle_msg(5)}") assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-1: ${vehicleMsgs.head}")
} }
assert(player2.VehicleSeated.isEmpty) assert(player2.VehicleSeated.isEmpty)
assert(lodestar.Seats(0).occupant.isEmpty) assert(lodestar.Seats(0).occupant.isEmpty)
//cargo dismounting messages //cargo dismounting messages
vehicle_msg(1) match { vehicleMsgs(1) match {
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ; case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 0, _))) => ()
case _ => case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-2: ${vehicle_msg.head}") assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-2: ${vehicleMsgs(1)}")
} }
vehicle_msg(2) match { vehicleMsgs(2) match {
case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ; case VehicleServiceMessage(_, VehicleAction.SendResponse(_, PlanetsideAttributeMessage(PlanetSideGUID(1), 68, _))) => ()
case _ => 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 VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, PlanetSideGUID(1), _, 1, CargoStatus.InProgress, 0))) => ;
case _ => case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-4: ${vehicle_msg(2)}") assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-4: ${vehicleMsgs(3)}")
} }
vehicle_msg(4) match { vehicleMsgs(4) match {
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ; case VehicleServiceMessage("test", VehicleAction.SendResponse(_, ObjectDetachMessage(PlanetSideGUID(2), PlanetSideGUID(1), _, _, _, _))) => ()
case _ => case _ =>
assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-5: ${vehicle_msg(3)}") assert(false, s"VehicleControlPrepareForDeletionMountedCargoTest-5: ${vehicleMsgs(4)}")
} }
vehicle_msg(5) match { vehicleMsgs(5) match {
case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ; case VehicleServiceMessage("test", VehicleAction.SendResponse(_, CargoMountPointStatusMessage(PlanetSideGUID(2), _, _, PlanetSideGUID(1), 1, CargoStatus.Empty, 0))) => ()
case _ => 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) val player1 = Player(VehicleTest.avatar1)
player1.GUID = PlanetSideGUID(1) 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) player2.GUID = PlanetSideGUID(2)
"Vehicle Control" should { "Vehicle Control" should {
@ -578,67 +585,13 @@ class VehicleControlShieldsNotChargingTooEarlyTest extends ActorTest {
// } // }
//} //}
class VehicleControlInteractWithWaterPartialTest extends ActorTest { class VehicleControlInteractWithWaterWadingTest 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()
val playerProbe = TestProbe() val playerProbe = TestProbe()
val vehicleProbe = 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 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( val zone = new Zone(
id = "test-zone", id = "test-zone",
new ZoneMap(name = "test-map") { new ZoneMap(name = "test-map") {
@ -650,53 +603,48 @@ class VehicleControlInteractWithWaterTest extends ActorTest {
GUID(guid) GUID(guid)
override def LivePlayers = List(player1) override def LivePlayers = List(player1)
override def Vehicles = List(vehicle) 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(vehicle)
zone.blockMap.addTo(pool) zone.blockMap.addTo(pool)
guid.register(player1, 1) guid.register(player1, 1)
guid.register(vehicle, 2) guid.register(vehicle, 2)
guid.register(player1.avatar.locker, 5)
player1.Zone = zone player1.Zone = zone
player1.Spawn() player1.Spawn()
vehicle.Zone = zone vehicle.Zone = zone
vehicle.Faction = PlanetSideEmpire.TR vehicle.Faction = PlanetSideEmpire.TR
vehicle.Seats(0).mount(player1) vehicle.Seats(0).mount(player1)
player1.VehicleSeated = vehicle.GUID player1.VehicleSeated = vehicle.GUID
val (probe, avatarActor) = PlayerControlTest.DummyAvatar(system)
player1.Actor = playerProbe.ref player1.Actor = playerProbe.ref
vehicle.Actor = vehicleProbe.ref vehicle.Actor = vehicleProbe.ref
"VehicleControl" should { "VehicleControl" should {
"causes disability when the vehicle drives too deep in water" in { "report when the vehicle starts treading water" in {
vehicle.Position = Vector3(5,5,3) //right in the pool vehicle.Position = Vector3(1, 1, 6)
vehicle.zoneInteractions() //trigger vehicle.zoneInteractions()
vehicleProbe.expectNoMessage(2.seconds)
playerProbe.expectNoMessage()
val msg_drown = playerProbe.receiveOne(250 milliseconds) vehicle.Position = Vector3(1, 1, 4f)
assert(msg_drown match { vehicle.zoneInteractions()
case InteractingWithEnvironment(body, _) => body eq pool val vehicleMsgs = vehicleProbe.receiveN(1, 5.seconds)
case _ => false vehicleMsgs.head match {
}) case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, _) => ()
val msg_disable = vehicleProbe.receiveOne(10 seconds) case _ =>
assert(msg_disable match { assert(false, "")
case RespondsToZoneEnvironment.Timer(EnvironmentAttribute.Water, _, _, VehicleControl.Disable(true)) => true }
case _ => false playerProbe.expectNoMessage()
})
} }
} }
} }
class VehicleControlStopInteractWithWaterTest extends ActorTest { class VehicleControlInteractWithWaterStartDrowningTest 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 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 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( val zone = new Zone(
id = "test-zone", id = "test-zone",
new ZoneMap(name = "test-map") { new ZoneMap(name = "test-map") {
@ -721,39 +669,290 @@ class VehicleControlStopInteractWithWaterTest extends ActorTest {
vehicle.Seats(0).mount(player1) vehicle.Seats(0).mount(player1)
player1.VehicleSeated = vehicle.GUID player1.VehicleSeated = vehicle.GUID
player1.Actor = playerProbe.ref player1.Actor = playerProbe.ref
vehicle.Actor = system.actorOf(Props(classOf[VehicleControl], vehicle), "vehicle-control") vehicle.Actor = vehicleProbe.ref
"VehicleControl" should { "VehicleControl" should {
"stop becoming disabled if the vehicle drives out of the water" in { "report when the vehicle starts drowning" in {
vehicle.Position = Vector3(5,5,-3) //right in the pool vehicle.Position = Vector3(1, 1, 6)
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
vehicle.zoneInteractions() vehicle.zoneInteractions()
val msg_recover = playerProbe.receiveOne(250 milliseconds) vehicleProbe.expectNoMessage(2.seconds)
assert( playerProbe.expectNoMessage()
msg_recover match {
case EscapeFromEnvironment( vehicle.Position = Vector3(1, 1, 0f)
p2, vehicle.zoneInteractions()
Some(OxygenStateTarget(PlanetSideGUID(2), _, OxygenState.Recovery, _)) val vehicleMsgs = vehicleProbe.receiveN(3, 5.seconds)
) => (p2 eq pool) val playerMsgs = playerProbe.receiveN(1, 1.seconds)
case _ => false 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 { class VehicleControlInteractWithLavaTest extends ActorTest {
val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2 val vehicle = Vehicle(GlobalDefinitions.fury) //guid=2
val player1 = val player1 =