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