From a23643b240e46ddfc99b6827d347206f4925d305 Mon Sep 17 00:00:00 2001 From: Fate-JH Date: Thu, 16 Apr 2020 21:21:33 -0400 Subject: [PATCH] Instant Action / Recall to Sanctuary (#348) * refactored ZoneActor for external calls; earliest code for calculating Instant Action placement * created a building definition so that SOI is no longer indeterminate; gave hot spots projector a longer-lasting backup for purposes of activity retention; instant action ramp-up works * filled out instant action messages; refactored main method * packet and initial tests for DroppodFreefallingMessage; drop pod definition, packet converter, and consideration in WSA and InterstellarCluster instant action functionality; droppods now work * duplicated soi information; modified priority of instant action; assigned cavern status; added reset for instant action failure; implant interrupt condition; wrote comments * no instant action droppods; added messages for cancelling instant action when certain conditions occur; wilderness instant action request * made generic the entire instant action process to shoehorn the whole of the sanctuary recall process into it; I hope you're happy * test fix; vehicle hacking fix; no more artificial NTU drain * escape case for zoning last chance; descriptive mesages condense similar calls * something of a merge repair --- .../psforever/objects/GlobalDefinitions.scala | 139 +-- .../net/psforever/objects/Vehicles.scala | 5 +- .../converter/DroppodConverter.scala | 66 ++ .../resourcesilo/ResourceSiloControl.scala | 2 +- .../serverobject/structures/Building.scala | 8 +- .../structures/BuildingDefinition.scala | 12 + .../serverobject/structures/WarpGate.scala | 11 +- .../psforever/objects/zones/HotSpotInfo.scala | 17 +- .../objects/zones/InterstellarCluster.scala | 105 +- .../zones/SphereOfInfluenceActor.scala | 2 +- .../net/psforever/objects/zones/Zone.scala | 71 +- .../psforever/objects/zones/ZoneActor.scala | 137 +-- .../objects/zones/ZoneHotSpotProjector.scala | 141 ++- .../net/psforever/objects/zones/ZoneMap.scala | 8 + .../net/psforever/objects/zones/Zoning.scala | 53 + .../psforever/packet/GamePacketOpcode.scala | 2 +- .../game/DroppodFreefallingMessage.scala | 44 + .../scala/net/psforever/types/Vector3.scala | 4 +- .../game/DroppodFreefallingMessageTest.scala | 40 + .../objects/ServerObjectBuilderTest.scala | 2 +- .../src/main/scala/WorldSessionActor.scala | 927 +++++++++++++----- pslogin/src/main/scala/zonemaps/Ugd01.scala | 1 + pslogin/src/main/scala/zonemaps/Ugd02.scala | 1 + pslogin/src/main/scala/zonemaps/Ugd03.scala | 1 + pslogin/src/main/scala/zonemaps/Ugd04.scala | 1 + pslogin/src/main/scala/zonemaps/Ugd05.scala | 1 + pslogin/src/main/scala/zonemaps/Ugd06.scala | 1 + 27 files changed, 1342 insertions(+), 460 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/definition/converter/DroppodConverter.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingDefinition.scala create mode 100644 common/src/main/scala/net/psforever/objects/zones/Zoning.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/DroppodFreefallingMessage.scala create mode 100644 common/src/test/scala/game/DroppodFreefallingMessageTest.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 275245c4..c8541002 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -17,7 +17,7 @@ import net.psforever.objects.serverobject.painbox.PainboxDefinition import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.serverobject.tube.SpawnTubeDefinition import net.psforever.objects.serverobject.resourcesilo.ResourceSiloDefinition -import net.psforever.objects.serverobject.structures.SphereOfInfluence +import net.psforever.objects.serverobject.structures.{BuildingDefinition, WarpGateDefinition} import net.psforever.objects.serverobject.turret.{FacilityTurretDefinition, TurretUpgrade} import net.psforever.objects.vehicles.{DestroyedVehicle, InternalTelepadDefinition, SeatArmorRestriction, UtilityType} import net.psforever.objects.vital.{DamageType, StandardMaxDamage, StandardResolutions} @@ -897,6 +897,8 @@ object GlobalDefinitions { val lodestar = VehicleDefinition(ObjectClass.lodestar) val phantasm = VehicleDefinition(ObjectClass.phantasm) + + val droppod = VehicleDefinition(ObjectClass.droppod) init_vehicles() /* @@ -1055,41 +1057,42 @@ object GlobalDefinitions { /* Buildings */ - val building : ObjectDefinition = new ObjectDefinition(474) { Name = "building" } //borrows object id of entity mainbase1 - val amp_station : ObjectDefinition = new ObjectDefinition(45) with SphereOfInfluence { Name = "amp_station"; SOIRadius = 300 } - val comm_station : ObjectDefinition = new ObjectDefinition(211) with SphereOfInfluence { Name = "comm_station"; SOIRadius = 300 } - val comm_station_dsp : ObjectDefinition = new ObjectDefinition(212) with SphereOfInfluence { Name = "comm_station_dsp"; SOIRadius = 300 } - val cryo_facility : ObjectDefinition = new ObjectDefinition(215) with SphereOfInfluence { Name = "cryo_facility"; SOIRadius = 300 } + val building = new BuildingDefinition(474) { Name = "building" } //borrows object id of entity mainbase1 + val amp_station = new BuildingDefinition(45) { Name = "amp_station"; SOIRadius = 300 } + val comm_station = new BuildingDefinition(211) { Name = "comm_station"; SOIRadius = 300 } + val comm_station_dsp = new BuildingDefinition(212) { Name = "comm_station_dsp"; SOIRadius = 300 } + val cryo_facility = new BuildingDefinition(215) { Name = "cryo_facility"; SOIRadius = 300 } - val vanu_core : ObjectDefinition = new ObjectDefinition(932) { Name = "vanu_core" } + val vanu_core = new BuildingDefinition(932) { Name = "vanu_core" } - val ground_bldg_a : ObjectDefinition = new ObjectDefinition(474) { Name = "ground_bldg_a" } //borrows object id of entity mainbase1 - val ground_bldg_b : ObjectDefinition = new ObjectDefinition(474) { Name = "ground_bldg_b" } //borrows object id of entity mainbase1 - val ground_bldg_c : ObjectDefinition = new ObjectDefinition(474) { Name = "ground_bldg_c" } //borrows object id of entity mainbase1 - val ground_bldg_d : ObjectDefinition = new ObjectDefinition(474) { Name = "ground_bldg_d" } //borrows object id of entity mainbase1 - val ground_bldg_e : ObjectDefinition = new ObjectDefinition(474) { Name = "ground_bldg_e" } //borrows object id of entity mainbase1 - val ground_bldg_f : ObjectDefinition = new ObjectDefinition(474) { Name = "ground_bldg_f" } //borrows object id of entity mainbase1 - val ground_bldg_g : ObjectDefinition = new ObjectDefinition(474) { Name = "ground_bldg_g" } //borrows object id of entity mainbase1 - val ground_bldg_h : ObjectDefinition = new ObjectDefinition(474) { Name = "ground_bldg_h" } //borrows object id of entity mainbase1 - val ground_bldg_i : ObjectDefinition = new ObjectDefinition(474) { Name = "ground_bldg_i" } //borrows object id of entity mainbase1 - val ground_bldg_j : ObjectDefinition = new ObjectDefinition(474) { Name = "ground_bldg_j" } //borrows object id of entity mainbase1 - val ground_bldg_z : ObjectDefinition = new ObjectDefinition(474) { Name = "ground_bldg_z" } //borrows object id of entity mainbase1 + val ground_bldg_a = new BuildingDefinition(474) { Name = "ground_bldg_a" } //borrows object id of entity mainbase1 + val ground_bldg_b = new BuildingDefinition(474) { Name = "ground_bldg_b" } //borrows object id of entity mainbase1 + val ground_bldg_c = new BuildingDefinition(474) { Name = "ground_bldg_c" } //borrows object id of entity mainbase1 + val ground_bldg_d = new BuildingDefinition(474) { Name = "ground_bldg_d" } //borrows object id of entity mainbase1 + val ground_bldg_e = new BuildingDefinition(474) { Name = "ground_bldg_e" } //borrows object id of entity mainbase1 + val ground_bldg_f = new BuildingDefinition(474) { Name = "ground_bldg_f" } //borrows object id of entity mainbase1 + val ground_bldg_g = new BuildingDefinition(474) { Name = "ground_bldg_g" } //borrows object id of entity mainbase1 + val ground_bldg_h = new BuildingDefinition(474) { Name = "ground_bldg_h" } //borrows object id of entity mainbase1 + val ground_bldg_i = new BuildingDefinition(474) { Name = "ground_bldg_i" } //borrows object id of entity mainbase1 + val ground_bldg_j = new BuildingDefinition(474) { Name = "ground_bldg_j" } //borrows object id of entity mainbase1 + val ground_bldg_z = new BuildingDefinition(474) { Name = "ground_bldg_z" } //borrows object id of entity mainbase1 - val ceiling_bldg_a : ObjectDefinition = new ObjectDefinition(474) { Name = "ceiling_bldg_a" } //borrows object id of entity mainbase1 - val ceiling_bldg_b : ObjectDefinition = new ObjectDefinition(474) { Name = "ceiling_bldg_b" } //borrows object id of entity mainbase1 - val ceiling_bldg_c : ObjectDefinition = new ObjectDefinition(474) { Name = "ceiling_bldg_c" } //borrows object id of entity mainbase1 - val ceiling_bldg_d : ObjectDefinition = new ObjectDefinition(474) { Name = "ceiling_bldg_d" } //borrows object id of entity mainbase1 - val ceiling_bldg_e : ObjectDefinition = new ObjectDefinition(474) { Name = "ceiling_bldg_e" } //borrows object id of entity mainbase1 - val ceiling_bldg_f : ObjectDefinition = new ObjectDefinition(474) { Name = "ceiling_bldg_f" } //borrows object id of entity mainbase1 - val ceiling_bldg_g : ObjectDefinition = new ObjectDefinition(474) { Name = "ceiling_bldg_g" } //borrows object id of entity mainbase1 - val ceiling_bldg_h : ObjectDefinition = new ObjectDefinition(474) { Name = "ceiling_bldg_h" } //borrows object id of entity mainbase1 - val ceiling_bldg_i : ObjectDefinition = new ObjectDefinition(474) { Name = "ceiling_bldg_i" } //borrows object id of entity mainbase1 - val ceiling_bldg_j : ObjectDefinition = new ObjectDefinition(474) { Name = "ceiling_bldg_j" } //borrows object id of entity mainbase1 - val ceiling_bldg_z : ObjectDefinition = new ObjectDefinition(474) { Name = "ceiling_bldg_z" } //borrows object id of entity mainbase1 + val ceiling_bldg_a = new BuildingDefinition(474) { Name = "ceiling_bldg_a" } //borrows object id of entity mainbase1 + val ceiling_bldg_b = new BuildingDefinition(474) { Name = "ceiling_bldg_b" } //borrows object id of entity mainbase1 + val ceiling_bldg_c = new BuildingDefinition(474) { Name = "ceiling_bldg_c" } //borrows object id of entity mainbase1 + val ceiling_bldg_d = new BuildingDefinition(474) { Name = "ceiling_bldg_d" } //borrows object id of entity mainbase1 + val ceiling_bldg_e = new BuildingDefinition(474) { Name = "ceiling_bldg_e" } //borrows object id of entity mainbase1 + val ceiling_bldg_f = new BuildingDefinition(474) { Name = "ceiling_bldg_f" } //borrows object id of entity mainbase1 + val ceiling_bldg_g = new BuildingDefinition(474) { Name = "ceiling_bldg_g" } //borrows object id of entity mainbase1 + val ceiling_bldg_h = new BuildingDefinition(474) { Name = "ceiling_bldg_h" } //borrows object id of entity mainbase1 + val ceiling_bldg_i = new BuildingDefinition(474) { Name = "ceiling_bldg_i" } //borrows object id of entity mainbase1 + val ceiling_bldg_j = new BuildingDefinition(474) { Name = "ceiling_bldg_j" } //borrows object id of entity mainbase1 + val ceiling_bldg_z = new BuildingDefinition(474) { Name = "ceiling_bldg_z" } //borrows object id of entity mainbase1 - val hst : ObjectDefinition with SpawnPointDefinition = new ObjectDefinition(402) with SpawnPointDefinition + val hst = new WarpGateDefinition(402) hst.Name = "hst" hst.UseRadius = 20.4810f + hst.SOIRadius = 21 hst.VehicleAllowance = true hst.NoWarp += dropship hst.NoWarp += galaxy_gunship @@ -1102,54 +1105,57 @@ object GlobalDefinitions { //hst.NoWarp += peregrine_flight hst.SpecificPointFunc = SpawnPoint.Gate - val mainbase1 : ObjectDefinition = new ObjectDefinition(474) { Name = "mainbase1" } - val mainbase2 : ObjectDefinition = new ObjectDefinition(475) { Name = "mainbase2" } - val mainbase3 : ObjectDefinition = new ObjectDefinition(476) { Name = "mainbase3" } - val meeting_center_nc : ObjectDefinition = new ObjectDefinition(537) { Name = "meeting_center_nc" } - val meeting_center_tr : ObjectDefinition = new ObjectDefinition(538) { Name = "meeting_center_tr" } - val meeting_center_vs : ObjectDefinition = new ObjectDefinition(539) { Name = "meeting_center_vs" } - val minibase1 : ObjectDefinition = new ObjectDefinition(557) { Name = "minibase1" } - val minibase2 : ObjectDefinition = new ObjectDefinition(558) { Name = "minibase2" } - val minibase3 : ObjectDefinition = new ObjectDefinition(559) { Name = "minibase3" } - val redoubt : ObjectDefinition = new ObjectDefinition(726) with SphereOfInfluence { Name = "redoubt"; SOIRadius = 187 } - val tech_plant : ObjectDefinition = new ObjectDefinition(852) with SphereOfInfluence { Name = "tech_plant"; SOIRadius = 300 } - val tower_a : ObjectDefinition = new ObjectDefinition(869) with SphereOfInfluence { Name = "tower_a"; SOIRadius = 50 } - val tower_b : ObjectDefinition = new ObjectDefinition(870) with SphereOfInfluence { Name = "tower_b"; SOIRadius = 50 } - val tower_c : ObjectDefinition = new ObjectDefinition(871) with SphereOfInfluence { Name = "tower_c"; SOIRadius = 50 } - val vanu_control_point : ObjectDefinition = new ObjectDefinition(931) with SphereOfInfluence { Name = "vanu_control_point"; SOIRadius = 187 } - val vanu_vehicle_station : ObjectDefinition = new ObjectDefinition(948) with SphereOfInfluence { Name = "vanu_vehicle_station"; SOIRadius = 187 } + val mainbase1 = new BuildingDefinition(474) { Name = "mainbase1" } + val mainbase2 = new BuildingDefinition(475) { Name = "mainbase2" } + val mainbase3 = new BuildingDefinition(476) { Name = "mainbase3" } + val meeting_center_nc = new BuildingDefinition(537) { Name = "meeting_center_nc" } + val meeting_center_tr = new BuildingDefinition(538) { Name = "meeting_center_tr" } + val meeting_center_vs = new BuildingDefinition(539) { Name = "meeting_center_vs" } + val minibase1 = new BuildingDefinition(557) { Name = "minibase1" } + val minibase2 = new BuildingDefinition(558) { Name = "minibase2" } + val minibase3 = new BuildingDefinition(559) { Name = "minibase3" } + val redoubt = new BuildingDefinition(726) { Name = "redoubt"; SOIRadius = 187 } + val tech_plant = new BuildingDefinition(852) { Name = "tech_plant"; SOIRadius = 300 } + val tower_a = new BuildingDefinition(869) { Name = "tower_a"; SOIRadius = 50 } + val tower_b = new BuildingDefinition(870) { Name = "tower_b"; SOIRadius = 50 } + val tower_c = new BuildingDefinition(871) { Name = "tower_c"; SOIRadius = 50 } + val vanu_control_point = new BuildingDefinition(931) { Name = "vanu_control_point"; SOIRadius = 187 } + val vanu_vehicle_station = new BuildingDefinition(948) { Name = "vanu_vehicle_station"; SOIRadius = 187 } - val warpgate : ObjectDefinition with SpawnPointDefinition = new ObjectDefinition(993) with SpawnPointDefinition + val warpgate = new WarpGateDefinition(993) warpgate.Name = "warpgate" warpgate.UseRadius = 301.8713f + warpgate.SOIRadius = 302 warpgate.VehicleAllowance = true warpgate.SpecificPointFunc = SpawnPoint.Gate - val warpgate_cavern : ObjectDefinition with SpawnPointDefinition = new ObjectDefinition(994) with SpawnPointDefinition + val warpgate_cavern = new WarpGateDefinition(994) warpgate_cavern.Name = "warpgate_cavern" warpgate_cavern.UseRadius = 51.0522f + warpgate_cavern.SOIRadius = 52 warpgate_cavern.VehicleAllowance = true warpgate_cavern.SpecificPointFunc = SpawnPoint.Gate - val warpgate_small : ObjectDefinition with SpawnPointDefinition = new ObjectDefinition(995) with SpawnPointDefinition + val warpgate_small = new WarpGateDefinition(995) warpgate_small.Name = "warpgate_small" warpgate_small.UseRadius = 103f + warpgate_small.SOIRadius = 103 warpgate_small.VehicleAllowance = true warpgate_small.SpecificPointFunc = SpawnPoint.Gate - val bunker_gauntlet : ObjectDefinition = new ObjectDefinition(150) { Name = "bunker_gauntlet" } - val bunker_lg : ObjectDefinition = new ObjectDefinition(151) { Name = "bunker_lg" } - val bunker_sm : ObjectDefinition = new ObjectDefinition(152) { Name = "bunker_sm" } + val bunker_gauntlet = new BuildingDefinition(150) { Name = "bunker_gauntlet" } + val bunker_lg = new BuildingDefinition(151) { Name = "bunker_lg" } + val bunker_sm = new BuildingDefinition(152) { Name = "bunker_sm" } - val orbital_building_nc : ObjectDefinition = new ObjectDefinition(605) { Name = "orbital_building_nc" } - val orbital_building_tr : ObjectDefinition = new ObjectDefinition(606) { Name = "orbital_building_tr" } - val orbital_building_vs : ObjectDefinition = new ObjectDefinition(607) { Name = "orbital_building_vs" } - val VT_building_nc : ObjectDefinition = new ObjectDefinition(978) { Name = "VT_building_nc" } - val VT_building_tr : ObjectDefinition = new ObjectDefinition(979) { Name = "VT_building_tr" } - val VT_building_vs : ObjectDefinition = new ObjectDefinition(980) { Name = "VT_building_vs" } - val vt_dropship : ObjectDefinition = new ObjectDefinition(981) { Name = "vt_dropship" } - val vt_spawn : ObjectDefinition = new ObjectDefinition(984) { Name = "vt_spawn" } - val vt_vehicle : ObjectDefinition = new ObjectDefinition(985) { Name = "vt_vehicle" } + val orbital_building_nc = new BuildingDefinition(605) { Name = "orbital_building_nc" } + val orbital_building_tr = new BuildingDefinition(606) { Name = "orbital_building_tr" } + val orbital_building_vs = new BuildingDefinition(607) { Name = "orbital_building_vs" } + val VT_building_nc = new BuildingDefinition(978) { Name = "VT_building_nc" } + val VT_building_tr = new BuildingDefinition(979) { Name = "VT_building_tr" } + val VT_building_vs = new BuildingDefinition(980) { Name = "VT_building_vs" } + val vt_dropship = new BuildingDefinition(981) { Name = "vt_dropship" } + val vt_spawn = new BuildingDefinition(984) { Name = "vt_spawn" } + val vt_vehicle = new BuildingDefinition(985) { Name = "vt_vehicle" } /** @@ -6083,6 +6089,17 @@ object GlobalDefinitions { phantasm.Packet = variantConverter phantasm.DestroyedModel = None //the adb calls out a phantasm_destroyed but no such asset exists phantasm.JackingDuration = Array(0, 60, 20, 10) + + droppod.Name = "droppod" + droppod.MaxHealth = 20000 + //droppod.Damageable = false + droppod.CanFly = true + droppod.Seats += 0 -> new SeatDefinition + droppod.MountPoints += 1 -> 0 + droppod.TrunkSize = InventoryTile.None + droppod.Packet = new DroppodConverter() + droppod.DeconstructionTime = Some(5 seconds) + droppod.DestroyedModel = None //the adb calls out a droppod; the cyclic nature of this confound me } /** diff --git a/common/src/main/scala/net/psforever/objects/Vehicles.scala b/common/src/main/scala/net/psforever/objects/Vehicles.scala index e4a699aa..8a9ca35e 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicles.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicles.scala @@ -6,7 +6,7 @@ import net.psforever.objects.vehicles.{CargoBehavior, VehicleLockState} import net.psforever.objects.zones.Zone import net.psforever.packet.game.TriggeredSound import net.psforever.types.{DriveState, PlanetSideGUID} -import services.RemoverActor +import services.{RemoverActor, Service} import services.avatar.{AvatarAction, AvatarServiceMessage} import services.local.{LocalAction, LocalServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage} @@ -244,8 +244,7 @@ object Vehicles { Vehicles.Own(target, hacker) //todo: Send HackMessage -> HackCleared to vehicle? can be found in packet captures. Not sure if necessary. // And broadcast the faction change to other clients - zone.AvatarEvents ! AvatarServiceMessage(hacker.Name, AvatarAction.SetEmpire(hacker.GUID, target.GUID, hacker.Faction)) - zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.SetEmpire(hacker.GUID, target.GUID, hacker.Faction)) + zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.SetEmpire(Service.defaultPlayerGUID, target.GUID, hacker.Faction)) } zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.TriggerSound(hacker.GUID, TriggeredSound.HackVehicle, target.Position, 30, 0.49803925f)) // Clean up after specific vehicles, e.g. remove router telepads diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/DroppodConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/DroppodConverter.scala new file mode 100644 index 00000000..783a4c27 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/definition/converter/DroppodConverter.scala @@ -0,0 +1,66 @@ +// Copyright (c) 2020 PSForever +package net.psforever.objects.definition.converter + +import net.psforever.objects.Vehicle +import net.psforever.packet.game.objectcreate._ +import net.psforever.types.PlanetSideGUID + +import scala.util.{Failure, Success, Try} + +class DroppodConverter extends ObjectCreateConverter[Vehicle]() { + override def DetailedConstructorData(obj : Vehicle) : Try[DroppodData] = + Failure(new Exception("DroppodConverter should not be used to generate detailed DroppodData (nothing should)")) + + override def ConstructorData(obj : Vehicle) : Try[DroppodData] = { + val health = StatConverter.Health(obj.Health, obj.MaxHealth) + if(health > 0) { //active + Success( + DroppodData( + CommonFieldDataWithPlacement( + PlacementData(obj.Position, obj.Orientation, obj.Velocity), + CommonFieldData( + obj.Faction, + bops = false, + alternate = false, + v1 = false, + v2 = None, + jammered = obj.Jammed, + v4 = Some(false), + v5 = None, + obj.Owner match { + case Some(owner) => owner + case None => PlanetSideGUID(0) + } + ) + ), + health, + burn = false, + unk = false + ) + ) + } + else { //destroyed + Success( + DroppodData( + CommonFieldDataWithPlacement( + PlacementData(obj.Position, obj.Orientation, obj.Velocity), + CommonFieldData( + obj.Faction, + bops = false, + alternate = false, + v1 = false, + v2 = None, + jammered = false, + v4 = Some(false), + v5 = None, + PlanetSideGUID(0) + ) + ), + 0, + burn = false, + unk = false + ) + ) + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala index 7a624187..57f1c4e0 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/resourcesilo/ResourceSiloControl.scala @@ -22,7 +22,7 @@ class ResourceSiloControl(resourceSilo : ResourceSilo) extends Actor with Factio def receive : Receive = { case "startup" => // todo: This is just a temporary solution to drain NTU over time. When base object destruction is properly implemented NTU should be deducted when base objects repair themselves - context.system.scheduler.schedule(5 second, 5 second, self, ResourceSilo.UpdateChargeLevel(-1)) +// context.system.scheduler.schedule(5 second, 5 second, self, ResourceSilo.UpdateChargeLevel(-1)) context.become(Processing) case _ => ; diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala index f68dc7e1..852d7bbd 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/Building.scala @@ -24,7 +24,7 @@ class Building(private val name: String, private val map_id : Int, private val zone : Zone, private val buildingType : StructureType.Value, - private val buildingDefinition : ObjectDefinition) extends AmenityOwner { + private val buildingDefinition : BuildingDefinition) extends AmenityOwner { /** * The map_id is the identifier number used in BuildingInfoUpdateMessage. This is the index that the building appears in the MPO file starting from index 1 * The GUID is the identifier number used in SetEmpireMessage / Facility hacking / PlanetSideAttributeMessage. @@ -267,7 +267,7 @@ class Building(private val name: String, override def Continent_=(zone : String) : String = Continent //building never leaves zone after being set in constructor - def Definition: ObjectDefinition = buildingDefinition + def Definition: BuildingDefinition = buildingDefinition } object Building { @@ -281,7 +281,7 @@ object Building { new Building(name, guid, map_id, zone, buildingType, GlobalDefinitions.building) } - def Structure(buildingType : StructureType.Value, location : Vector3, definition: ObjectDefinition)(name : String, guid : Int, map_id : Int, zone : Zone, context : ActorContext) : Building = { + def Structure(buildingType : StructureType.Value, location : Vector3, definition: BuildingDefinition)(name : String, guid : Int, map_id : Int, zone : Zone, context : ActorContext) : Building = { import akka.actor.Props val obj = new Building(name, guid, map_id, zone, buildingType, definition) obj.Position = location @@ -305,7 +305,7 @@ object Building { obj } - def Structure(buildingType : StructureType.Value, buildingDefinition : ObjectDefinition, location : Vector3)(name: String, guid: Int, id : Int, zone : Zone, context : ActorContext) : Building = { + def Structure(buildingType : StructureType.Value, buildingDefinition : BuildingDefinition, location : Vector3)(name: String, guid: Int, id : Int, zone : Zone, context : ActorContext) : Building = { import akka.actor.Props val obj = new Building(name, guid, id, zone, buildingType, buildingDefinition) obj.Position = location diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingDefinition.scala new file mode 100644 index 00000000..8584d853 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/BuildingDefinition.scala @@ -0,0 +1,12 @@ +package net.psforever.objects.serverobject.structures + +import net.psforever.objects.SpawnPointDefinition +import net.psforever.objects.definition.ObjectDefinition + +class BuildingDefinition(objectId : Int) extends ObjectDefinition(objectId) + with SphereOfInfluence { + Name = "building" +} + +class WarpGateDefinition(objectId : Int) extends BuildingDefinition(objectId) + with SpawnPointDefinition diff --git a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala index 9fb7e518..402f64e3 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/structures/WarpGate.scala @@ -2,16 +2,15 @@ package net.psforever.objects.serverobject.structures import akka.actor.ActorContext -import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.serverobject.PlanetSideServerObject -import net.psforever.objects.{GlobalDefinitions, SpawnPoint, SpawnPointDefinition} +import net.psforever.objects.{GlobalDefinitions, SpawnPoint} import net.psforever.objects.zones.Zone import net.psforever.packet.game.{Additional1, Additional2, Additional3} import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3} import scala.collection.mutable -class WarpGate(name : String, building_guid : Int, map_id : Int, zone : Zone, buildingDefinition : ObjectDefinition with SpawnPointDefinition) +class WarpGate(name : String, building_guid : Int, map_id : Int, zone : Zone, buildingDefinition : WarpGateDefinition) extends Building(name, building_guid, map_id, zone, StructureType.WarpGate, buildingDefinition) with SpawnPoint { /** can this building be used as an active warp gate */ @@ -155,12 +154,12 @@ class WarpGate(name : String, building_guid : Int, map_id : Int, zone : Zone, bu def Owner : PlanetSideServerObject = this - override def Definition : ObjectDefinition with SpawnPointDefinition = buildingDefinition + override def Definition : WarpGateDefinition = buildingDefinition //TODO stuff later } object WarpGate { - def apply(name : String, guid : Int, map_id : Int, zone : Zone, buildingDefinition : ObjectDefinition with SpawnPointDefinition) : WarpGate = { + def apply(name : String, guid : Int, map_id : Int, zone : Zone, buildingDefinition : WarpGateDefinition) : WarpGate = { new WarpGate(name, guid, map_id, zone, buildingDefinition) } @@ -179,7 +178,7 @@ object WarpGate { obj } - def Structure(location : Vector3, buildingDefinition : ObjectDefinition with SpawnPointDefinition)(name : String, guid : Int, map_id : Int, zone : Zone, context : ActorContext) : WarpGate = { + def Structure(location : Vector3, buildingDefinition : WarpGateDefinition)(name : String, guid : Int, map_id : Int, zone : Zone, context : ActorContext) : WarpGate = { import akka.actor.Props val obj = new WarpGate(name, guid, map_id, zone, buildingDefinition) obj.Position = location diff --git a/common/src/main/scala/net/psforever/objects/zones/HotSpotInfo.scala b/common/src/main/scala/net/psforever/objects/zones/HotSpotInfo.scala index 4a315c25..982c549b 100644 --- a/common/src/main/scala/net/psforever/objects/zones/HotSpotInfo.scala +++ b/common/src/main/scala/net/psforever/objects/zones/HotSpotInfo.scala @@ -77,7 +77,12 @@ class ActivityReport { * As a `Long` value, if there was no previous report, the value will be considered `0L`. * @return the time of the last activity report */ - def LastReport : Long = lastReport match { case Some(t) => t; case _ => 0L } + def LastReport : Long = lastReport.getOrElse(0L) + + def SetLastReport(time : Long) : Long = { + lastReport = Some(time) + LastReport + } /** * The length of time that this (ongoing) activity is relevant. @@ -130,6 +135,16 @@ class ActivityReport { this } + /** + * Submit new activity. + * Do not increase the lifespan of the current report's existence. + * @return the current report + */ + def ReportOld(pow : Int) : ActivityReport = { + RaiseHeat(pow) + this + } + private def RaiseHeat(addHeat : Int) : Int = { if(addHeat < (Integer.MAX_VALUE - heat)) { heat += addHeat diff --git a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala index e47eea1b..dbb1713a 100644 --- a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala +++ b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala @@ -3,8 +3,10 @@ package net.psforever.objects.zones import akka.actor.{Actor, Props} import net.psforever.objects.serverobject.structures.Building +import net.psforever.types.Vector3 import scala.annotation.tailrec +import scala.util.Random /** * The root of the universe of one-continent planets, codified by the game's "Interstellar Map." @@ -23,6 +25,7 @@ import scala.annotation.tailrec */ class InterstellarCluster(zones : List[Zone]) extends Actor { private[this] val log = org.log4s.getLogger + val recallRandom = new Random() log.info("Starting interplanetary cluster ...") /** @@ -53,7 +56,7 @@ class InterstellarCluster(zones : List[Zone]) extends Actor { zones.foreach(zone => { sender ! Zone.ClientInitialization(zone.ClientInitialization()) }) sender ! InterstellarCluster.ClientInitializationComplete() //will be processed after all Zones - case msg @ Zone.Lattice.RequestSpawnPoint(zone_number, _, _) => + case msg @ Zone.Lattice.RequestSpawnPoint(zone_number, _, _, _) => recursiveFindWorldInCluster(zones.iterator, _.Number == zone_number) match { case Some(zone) => zone.Actor forward msg @@ -62,7 +65,7 @@ class InterstellarCluster(zones : List[Zone]) extends Actor { sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None) } - case msg @ Zone.Lattice.RequestSpecificSpawnPoint(zone_number, _, _) => + case msg @ Zone.Lattice.RequestSpecificSpawnPoint(zone_number, _, _, _) => recursiveFindWorldInCluster(zones.iterator, _.Number == zone_number) match { case Some(zone) => zone.Actor forward msg @@ -74,6 +77,74 @@ class InterstellarCluster(zones : List[Zone]) extends Actor { val zone = zones.find(x => x.Number == zone_num).get zone.Buildings.values.foreach(b => b.Actor ! Building.SendMapUpdate(all_clients = true)) + + case Zoning.InstantAction.Request(faction) => + val interests = zones.flatMap { zone => + //TODO zone.Locked.contains(faction) + zone.HotSpotData + .collect { case spot if zone.Players.nonEmpty => (zone, spot) } + } /* ignore zones without existing population */ + if(interests.nonEmpty) { + val (withAllies, onlyEnemies) = interests + .map { case (zone, spot) => + ( + zone, + spot, + ZoneActor.FindLocalSpawnPointsInZone(zone, spot.DisplayLocation, faction, 0).getOrElse(Nil) + ) + } /* pair hotspots and spawn points */ + .filter { case (_, _, spawns) => spawns.nonEmpty } /* faction spawns must exist */ + .sortBy({ case (_, spot, _) => spot.Activity.values.foldLeft(0)(_ + _.Heat) })(Ordering[Int].reverse) /* greatest > least */ + .partition { case (_, spot, _) => spot.ActivityBy().contains(faction) } /* us versus them */ + withAllies.headOption.orElse(onlyEnemies.headOption) match { + case Some((zone, info, List(spawnPoint))) => + //one spawn + val pos = info.DisplayLocation + sender ! Zoning.InstantAction.Located(zone, pos, spawnPoint) + case Some((zone, info, spawns)) => + //multiple spawn options + val pos = info.DisplayLocation + val spawnPoint = spawns.minBy(point => Vector3.DistanceSquared(point.Position, pos)) + sender ! Zoning.InstantAction.Located(zone, pos, spawnPoint) + case None => + //no actionable hot spots + sender ! Zoning.InstantAction.NotLocated() + } + } + else { + //never had any actionable hot spots + sender ! Zoning.InstantAction.NotLocated() + } + + case Zoning.Recall.Request(faction, sanctuary_id) => + recursiveFindWorldInCluster(zones.iterator, _.Id.equals(sanctuary_id)) match { + case Some(zone) => + //TODO zone full + val width = zone.Map.Scale.width + val height = zone.Map.Scale.height + //xy-coordinates indicate sanctuary spawn bias: + val spot = math.abs(scala.util.Random.nextInt() % sender.toString.hashCode % 4) match { + case 0 => Vector3(width, height, 0) //NE + case 1 => Vector3(width, 0, 0) //SE + case 2 => Vector3.Zero //SW + case 3 => Vector3(0, height, 0) //NW + } + ZoneActor.FindLocalSpawnPointsInZone(zone, spot, faction, 7).getOrElse(Nil) match { + case Nil => + //no spawns + sender ! Zoning.Recall.Denied("unavailable") + case List(spawnPoint) => + //one spawn + sender ! Zoning.Recall.Located(zone, spawnPoint) + case spawnPoints => + //multiple spawn options + val spawnPoint = spawnPoints(recallRandom.nextInt(spawnPoints.length)) + sender ! Zoning.Recall.Located(zone, spawnPoint) + } + case None => + sender ! Zoning.Recall.Denied("unavailable") + } + case _ => log.warn(s"InterstellarCluster received unknown message"); } @@ -132,33 +203,5 @@ object InterstellarCluster { * @see `BuildingInfoUpdateMessage` * @param zone_num the zone number to request building map updates for */ - final case class ZoneMapUpdate(zone_num: Int) + final case class ZoneMapUpdate(zone_num : Int) } - -/* -// List[Building] --> List[List[(Amenity, Building)]] --> List[(SpawnTube*, Building)] -zone.LocalLattice.Buildings.values - .filter(_.Faction == player.Faction) - .map(building => { building.Amenities.map { _ -> building } }) - .flatMap( _.filter({ case(amenity, _) => amenity.isInstanceOf[SpawnTube] }) ) - */ - -/* -zone.Buildings.values.filter(building => { - ( - if(spawn_zone == 6) { Set(StructureType.Tower) } - else if(spawn_zone == 7) { Set(StructureType.Facility, StructureType.Building) } - else { Set.empty[StructureType.Value] } - ).contains(building.BuildingType) && - building.Amenities.exists(_.isInstanceOf[SpawnTube]) && - building.Faction == player.Faction && - building.Position != Vector3.Zero -}) - .toSeq - .sortBy(building => { - Vector3.DistanceSquared(player.Position, building.Position) < Vector3.DistanceSquared(player.Position, building.Position) - }) - .map(building => { building.Amenities.map { _ -> building } }) - .flatMap( _.filter({ case(amenity, _) => amenity.isInstanceOf[SpawnTube] }) ) -).headOption - */ diff --git a/common/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala b/common/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala index 05960242..f08082ed 100644 --- a/common/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/SphereOfInfluenceActor.scala @@ -46,7 +46,7 @@ class SphereOfInfluenceActor(zone: Zone) extends Actor { sois = zone.Buildings .values .map { facility => (facility, facility.Definition) } - .collect { case (facility, soi : ObjectDefinition with SphereOfInfluence) if soi.SOIRadius > 0 => + .collect { case (facility, soi) if soi.SOIRadius > 0 => (facility, soi.SOIRadius * soi.SOIRadius) } } diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala index c7b53f06..a70d19cd 100644 --- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -92,6 +92,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { private var projector : ActorRef = ActorRef.noSender /** */ private var hotspots : ListBuffer[HotSpotInfo] = ListBuffer[HotSpotInfo]() + /** */ + private val hotspotHistory : ListBuffer[HotSpotInfo] = ListBuffer[HotSpotInfo]() /** calculate a approximated coordinate from a raw input coordinate */ private var hotspotCoordinateFunc : Vector3=>Vector3 = Zone.HotSpot.Rules.OneToOne /** calculate a duration from a given interaction's participants */ @@ -128,7 +130,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"$Id-deployables") transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"$Id-vehicles") population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players") - projector = context.actorOf(Props(classOf[ZoneHotSpotProjector], this), s"$Id-hotpots") + projector = context.actorOf(Props(classOf[ZoneHotSpotDisplay], this, hotspots, 15 seconds, hotspotHistory, 60 seconds), s"$Id-hotspots") soi = context.actorOf(Props(classOf[SphereOfInfluenceActor], this), s"$Id-soi") avatarEvents = context.actorOf(Props(classOf[AvatarService], this), s"$Id-avatar-events") @@ -504,25 +506,21 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { def Activity : ActorRef = projector - def HotSpots : List[HotSpotInfo] = hotspots toList + def HotSpots : List[HotSpotInfo] = hotSpotListDuplicate(hotspots).toList - def HotSpots_=(spots : Seq[HotSpotInfo]) : List[HotSpotInfo] = { - hotspots.clear - hotspots ++= spots - HotSpots - } + def HotSpotData : List[HotSpotInfo] = hotSpotListDuplicate(hotspotHistory).toList - def TryHotSpot(displayLoc : Vector3) : HotSpotInfo = { - hotspots.find(spot => spot.DisplayLocation == displayLoc) match { - case Some(spot) => - //hotspot already exists - spot - case None => - //insert new hotspot - val spot = new HotSpotInfo(displayLoc) - hotspots += spot - spot + private def hotSpotListDuplicate(data : ListBuffer[HotSpotInfo]) : ListBuffer[HotSpotInfo] = { + val out = data map { info => + val outData = new HotSpotInfo(info.DisplayLocation) + info.Activity.foreach { case (faction, report) => + val doctoredReport = outData.Activity(faction) + doctoredReport.ReportOld(report.Heat) + doctoredReport.SetLastReport(report.LastReport) + } + outData } + out } def HotSpotCoordinateFunction : Vector3=>Vector3 = hotspotCoordinateFunc @@ -678,12 +676,45 @@ object Zone { /** * Message requesting that the current zone determine where a `player` can spawn. * @param zone_number this zone's numeric identifier - * @param player the `Player` object + * @param position the locality that the result should adhere + * @param faction which empire's spawn options should be available * @param spawn_group the category of spawn points the request wants searched */ - final case class RequestSpawnPoint(zone_number : Int, player : Player, spawn_group : Int) + final case class RequestSpawnPoint(zone_number : Int, position : Vector3, faction : PlanetSideEmpire.Value, spawn_group : Int) + + object RequestSpawnPoint { + /** + * Overloaded constructor for `RequestSpawnPoint`. + * @param zone_number this zone's numeric identifier + * @param player the `Player` object + * @param spawn_group the category of spawn points the request wants searched + */ + def apply(zone_number : Int, player : Player, spawn_group : Int) : RequestSpawnPoint = { + RequestSpawnPoint(zone_number, player.Position, player.Faction, spawn_group) + } + } + + /** + * Message requesting a particular spawn point in the current zone. + * @param zone_number this zone's numeric identifier + * @param position the locality that the result should adhere + * @param faction which empire's spawn options should be available + * @param target the identifier of the spawn object + */ + final case class RequestSpecificSpawnPoint(zone_number : Int, position : Vector3, faction : PlanetSideEmpire.Value, target : PlanetSideGUID) + + object RequestSpecificSpawnPoint { + /** + * Overloaded constructor for `RequestSpecificSpawnPoint`. + * @param zone_number this zone's numeric identifier + * @param player the `Player` object + * @param target the identifier of the spawn object + */ + def apply(zone_number : Int, player : Player, target : PlanetSideGUID) : RequestSpecificSpawnPoint = { + RequestSpecificSpawnPoint(zone_number, player.Position, player.Faction, target) + } + } - final case class RequestSpecificSpawnPoint(zone_number : Int, player : Player, target : PlanetSideGUID) /** * Message that returns a discovered spawn point to a request source. * @param zone_id the zone's text identifier diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala index 43b87aff..9bfc8744 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala @@ -81,66 +81,9 @@ class ZoneActor(zone : Zone) extends Actor { zone.Activity forward msg //own - case Zone.Lattice.RequestSpawnPoint(zone_number, player, spawn_group) => + case Zone.Lattice.RequestSpawnPoint(zone_number, position, faction, spawn_group) => if(zone_number == zone.Number) { - val playerPosition = player.Position.xy - ( - if(spawn_group == 2) { - //ams - zone.Vehicles - .filter(veh => - veh.Definition == GlobalDefinitions.ams && - !veh.Destroyed && - veh.DeploymentState == DriveState.Deployed && - veh.Faction == player.Faction - ) - .sortBy(veh => Vector3.DistanceSquared(playerPosition, veh.Position.xy)) - .flatMap(veh => veh.Utilities.values.filter(util => util.UtilType == UtilityType.ams_respawn_tube)) - .headOption match { - case None => - None - case Some(util) => - Some(List(util().asInstanceOf[SpawnTube])) - } - } - else { - //facilities, towers, and buildings - val buildingTypeSet = if(spawn_group == 0) { - Set(StructureType.Facility, StructureType.Tower, StructureType.Building) - } - else if(spawn_group == 6) { - Set(StructureType.Tower) - } - else if(spawn_group == 7) { - Set(StructureType.Facility, StructureType.Building) - } - else if(spawn_group == 12) { - Set(StructureType.WarpGate) - } - else { - Set.empty[StructureType.Value] - } - zone.SpawnGroups() - .filter({ case (building, tubes) => - buildingTypeSet.contains(building.BuildingType) && (building match { - case wg : WarpGate => - building.Faction == player.Faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast - case _ => - building.Faction == player.Faction && !tubes.forall(sp => sp.Offline) - }) - }) - .toSeq - .sortBy({ case (building, _) => - Vector3.DistanceSquared(playerPosition, building.Position.xy) - }) - .headOption match { - case None | Some((_, Nil)) => - None - case Some((_, tubes)) => - Some(tubes) - } - } - ) match { + ZoneActor.FindLocalSpawnPointsInZone(zone, position, faction, spawn_group) match { case Some(List(tube)) => sender ! Zone.Lattice.SpawnPoint(zone.Id, tube) @@ -158,13 +101,13 @@ class ZoneActor(zone : Zone) extends Actor { sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None) } - case Zone.Lattice.RequestSpecificSpawnPoint(zone_number, player, target) => + case Zone.Lattice.RequestSpecificSpawnPoint(zone_number, _, faction, target) => if(zone_number == zone.Number) { //is our spawn point some other privileged vehicle? zone.Vehicles.collectFirst({ - case vehicle : SpawnPoint if vehicle.Faction == player.Faction && vehicle.GUID == target => + case vehicle : SpawnPoint if vehicle.Faction == faction && vehicle.GUID == target => Some(vehicle) //the vehicle itself is the spawn point - case vehicle if vehicle.Faction == player.Faction && vehicle.GUID == target => + case vehicle if vehicle.Faction == faction && vehicle.GUID == target => vehicle.Utilities.values.find { util => util().isInstanceOf[SpawnPoint] @@ -180,9 +123,9 @@ class ZoneActor(zone : Zone) extends Actor { case(building, _) => building match { case wg : WarpGate => - building.Faction == player.Faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast + building.Faction == faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast case _ => - building.Faction == player.Faction + building.Faction == faction } } friendlySpawnGroups.collectFirst({ @@ -273,6 +216,72 @@ class ZoneActor(zone : Zone) extends Actor { } object ZoneActor { + /** + * na + * @param zone na + * @param position na + * @param faction na + * @param spawn_group na + * @return na + */ + def FindLocalSpawnPointsInZone(zone : Zone, position : Vector3, faction : PlanetSideEmpire.Value, spawn_group : Int) : Option[List[SpawnPoint]] = { + val playerPosition = position.xy + if(spawn_group == 2) { + //ams + zone.Vehicles + .filter(veh => + veh.Definition == GlobalDefinitions.ams && + veh.DeploymentState == DriveState.Deployed && + veh.Faction == faction + ) + .sortBy(veh => Vector3.DistanceSquared(playerPosition, veh.Position.xy)) + .flatMap(veh => veh.Utilities.values.filter(util => util.UtilType == UtilityType.ams_respawn_tube)) + .headOption match { + case None => + None + case Some(util) => + Some(List(util().asInstanceOf[SpawnTube])) + } + } + else { + //facilities, towers, and buildings + val buildingTypeSet = if(spawn_group == 0) { + Set(StructureType.Facility, StructureType.Tower, StructureType.Building) + } + else if(spawn_group == 6) { + Set(StructureType.Tower) + } + else if(spawn_group == 7) { + Set(StructureType.Facility, StructureType.Building) + } + else if(spawn_group == 12) { + Set(StructureType.WarpGate) + } + else { + Set.empty[StructureType.Value] + } + zone.SpawnGroups() + .filter({ case (building, _) => + buildingTypeSet.contains(building.BuildingType) && (building match { + case wg : WarpGate => + building.Faction == faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast + case _ => + building.Faction == faction + }) + }) + .toSeq + .sortBy({ case (building, _) => + Vector3.DistanceSquared(playerPosition, building.Position.xy) + }) + .headOption match { + case None | Some((_, Nil)) => + None + case Some((_, tubes)) => + Some(tubes) + } + } + } + /** * Recover an object from a collection and perform any number of validating tests upon it. * If the object fails any tests, log an error. diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneHotSpotProjector.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneHotSpotProjector.scala index ca328fd8..be4fd1a0 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneHotSpotProjector.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneHotSpotProjector.scala @@ -1,26 +1,67 @@ // Copyright (c) 2019 PSForever package net.psforever.objects.zones -import akka.actor.{Actor, ActorRef, Cancellable} +import akka.actor.{Actor, ActorRef, Cancellable, Props} import net.psforever.objects.DefaultCancellable -import net.psforever.types.PlanetSideEmpire +import net.psforever.types.{PlanetSideEmpire, Vector3} import services.ServiceManager +import scala.collection.mutable.ListBuffer import scala.concurrent.duration._ /** * Manage hotspot information for a given zone, * keeping track of aggressive faction interactions, - * and maintaining the visibility state of the hotspots that alert of the location of that activity. - * @param zone the zone + * and maintaining the visibility state of the hotspots that alert of the location of that activity.
+ *
+ * Initializes two internal devices to manage the hotspot activity reported by the zone. + * The first device - "projector" - keeps track of any hotspots that are currently being displayed on the zone map. + * The second device - "backup" - is designed to maintain a much longer record of the same hostpot activity + * that was displayed by the projector. + * Messages sent to this device are sent automatically to each internal device. + * The internal devices do not have to be messaged separately. + * @see `ZoneHotSpotProjector` + * @see `ZoneHotSpotHistory` + * @param zone the zone whose map serves as the "screen" for the hotspot data + * @param outputList an external list used for storing displayed activity hotspots + * @param outputBlanking the period of decay time before hotspot information is forgotten + * @param dataList an external list used for storing activity for prolonged periods of time + * @param dataBlanking the period of decay time before prolonged activity information is forgotten */ -class ZoneHotSpotProjector(zone : Zone) extends Actor { +class ZoneHotSpotDisplay(zone : Zone, + outputList : ListBuffer[HotSpotInfo], + outputBlanking : FiniteDuration, + dataList : ListBuffer[HotSpotInfo], + dataBlanking : FiniteDuration) extends Actor { + val projector = context.actorOf(Props(classOf[ZoneHotSpotProjector], zone, outputList, outputBlanking), s"${zone.Id}-hotspot-projector") + val backup = context.actorOf(Props(classOf[ZoneHotSpotHistory], zone, dataList, dataBlanking), s"${zone.Id}-hotspot-backup") + + def receive : Receive = { + case _ if sender == projector || sender == backup => ; //catch and disrupt cyclic messaging paths + case msg => + projector ! msg + backup ! msg + } +} + +/** + * Manage hotspot information for a given zone, + * keeping track of aggressive faction interactions, + * and maintaining the visibility state of the hotspots that alert of the location of that activity. + * One of the internal devices controlled by the `ZoneHotSpotDisplay`, + * this is the "projector" component that actually displays hotspots onto the zone's map. + * @see `ZoneHotSpotDisplay` + * @param zone the zone + * @param hotspots the data structure of hot spot information that this projector will be leveraging + * @param blankingTime how long to wait in between blanking periods + */ +class ZoneHotSpotProjector(zone : Zone, hotspots : ListBuffer[HotSpotInfo], blankingTime : FiniteDuration) extends Actor { /** a hook for the `GalaxyService` used to broadcast messages */ - private var galaxy : ActorRef = ActorRef.noSender + var galaxy : ActorRef = ActorRef.noSender /** the timer for the blanking process */ - private var blanking : Cancellable = DefaultCancellable.obj + var blanking : Cancellable = DefaultCancellable.obj /** how long to wait in between blanking periods while hotspots decay */ - private val blankingDelay : FiniteDuration = 15 seconds + var blankingDelay : FiniteDuration = blankingTime private[this] val log = org.log4s.getLogger(s"${zone.Id.capitalize}HotSpotProjector") @@ -88,7 +129,7 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { case ZoneHotSpotProjector.UpdateDurationFunction() => blanking.cancel UpdateDurationFunction() - UpdateHotSpots(PlanetSideEmpire.values, zone.HotSpots) + UpdateHotSpots(PlanetSideEmpire.values, hotspots) import scala.concurrent.ExecutionContext.Implicits.global blanking = context.system.scheduler.scheduleOnce(blankingDelay, self, ZoneHotSpotProjector.BlankingPhase()) @@ -97,7 +138,7 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { //this is different from the many individual activity locations that contributed to that `DisplayLocation` blanking.cancel UpdateMappingFunction() - UpdateHotSpots(PlanetSideEmpire.values, zone.HotSpots) + UpdateHotSpots(PlanetSideEmpire.values, hotspots) import scala.concurrent.ExecutionContext.Implicits.global blanking = context.system.scheduler.scheduleOnce(blankingDelay, self, ZoneHotSpotProjector.BlankingPhase()) @@ -105,10 +146,10 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { log.trace(s"received information about activity in ${zone.Id}@$location") val defenderFaction = defender.Faction val attackerFaction = attacker.Faction - val noPriorHotSpots = zone.HotSpots.isEmpty + val noPriorHotSpots = hotspots.isEmpty val duration = zone.HotSpotTimeFunction(defender, attacker) if(duration.toNanos > 0) { - val hotspot = zone.TryHotSpot( zone.HotSpotCoordinateFunction(location) ) + val hotspot = TryHotSpot( zone.HotSpotCoordinateFunction(location) ) log.trace(s"updating activity status for ${zone.Id} hotspot x=${hotspot.DisplayLocation.x} y=${hotspot.DisplayLocation.y}") val noPriorActivity = !(hotspot.ActivityBy(defenderFaction) && hotspot.ActivityBy(attackerFaction)) //update the activity report for these factions @@ -123,7 +164,7 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { } //if the level of activity changed for one of the participants or the number of hotspots was zero if(noPriorActivity || noPriorHotSpots) { - UpdateHotSpots(affectedFactions, zone.HotSpots) + UpdateHotSpots(affectedFactions, hotspots) if(noPriorHotSpots) { import scala.concurrent.ExecutionContext.Implicits.global blanking.cancel @@ -134,13 +175,13 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { case Zone.HotSpot.UpdateNow => log.trace(s"asked to update for zone ${zone.Id} without a blanking period or new activity") - UpdateHotSpots(PlanetSideEmpire.values, zone.HotSpots) + UpdateHotSpots(PlanetSideEmpire.values, hotspots) case ZoneHotSpotProjector.BlankingPhase() | Zone.HotSpot.Cleanup() => blanking.cancel val curr : Long = System.nanoTime //blanking dated activity reports - val changed = zone.HotSpots.flatMap(spot => { + val changed = hotspots.flatMap(spot => { spot.Activity.collect { case (b, a) if a.LastReport + a.Duration.toNanos <= curr => a.Clear() //this faction has no more activity in this sector @@ -148,7 +189,7 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { } }) //collect and re-assign still-relevant hotspots - val spots = zone.HotSpots.filter(spot => { + val spots = hotspots.filter(spot => { spot.Activity .values .collect { @@ -157,9 +198,11 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { } .foldLeft(false)(_ || _) }) - val changesOnMap = zone.HotSpots.size - spots.size - log.trace(s"blanking out $changesOnMap hotspots from zone ${zone.Id}; ${spots.size} remain active") - zone.HotSpots = spots + val newSize = spots.size + val changesOnMap = hotspots.size - newSize + log.trace(s"blanking out $changesOnMap hotspots from zone ${zone.Id}; $newSize remain active") + hotspots.clear + hotspots.insertAll(0, spots) //other hotspots still need to be blanked later if(spots.nonEmpty) { import scala.concurrent.ExecutionContext.Implicits.global @@ -174,18 +217,38 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { case Zone.HotSpot.ClearAll() => log.trace(s"blanking out all hotspots from zone ${zone.Id} immediately") blanking.cancel - zone.HotSpots = Nil + hotspots.clear() UpdateHotSpots(PlanetSideEmpire.values, Nil) case _ => ; } + /** + * Match a hotspot location with a data structure for keeping track of activity information, + * either an existing structure or one that was created in the list of activity data for this location. + * @see `HotSpotInfo` + * @param displayLoc the location for the hotpot that was normalized by the coordinate mapping function + * @return the hotspot data that corresponds to this location + */ + def TryHotSpot(displayLoc : Vector3) : HotSpotInfo = { + hotspots.find(spot => spot.DisplayLocation == displayLoc) match { + case Some(spot) => + //hotspot already exists + spot + case None => + //insert new hotspot + val spot = new HotSpotInfo(displayLoc) + hotspots += spot + spot + } + } + /** * Assign a new functionality for determining how long hotspots remain active. * Recalculate all current hotspot information. */ def UpdateDurationFunction(): Unit = { - zone.HotSpots.foreach { spot => + hotspots.foreach { spot => spot.Activity.values.foreach { report => val heat = report.Heat report.Clear() @@ -193,7 +256,7 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { report.Duration = 0L } } - log.trace(s"new duration remapping function provided; reloading ${zone.HotSpots.size} hotspots for one blanking phase") + log.trace(s"new duration remapping function provided; reloading ${hotspots.size} hotspots for one blanking phase") } /** @@ -201,7 +264,7 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { * Recalculate all current hotspot information. */ def UpdateMappingFunction() : Unit = { - val redoneSpots = zone.HotSpots.map { spot => + val redoneSpots = hotspots.map { spot => val newSpot = new HotSpotInfo( zone.HotSpotCoordinateFunction(spot.DisplayLocation) ) PlanetSideEmpire.values.foreach { faction => if(spot.ActivityBy(faction)) { @@ -211,8 +274,9 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { } newSpot } - log.trace(s"new coordinate remapping function provided; updating ${redoneSpots.size} hotspots") - zone.HotSpots = redoneSpots + log.trace(s"new coordinate remapping function provided; updating $redoneSpots.size hotspots") + hotspots.clear() + hotspots.insertAll(0, redoneSpots) } /** @@ -224,21 +288,38 @@ class ZoneHotSpotProjector(zone : Zone) extends Actor { * if empty or contains no information for a selected group, * that group's hotspots will be eliminated (blanked) as a result */ - def UpdateHotSpots(affectedFactions : Iterable[PlanetSideEmpire.Value], hotSpotInfos : List[HotSpotInfo]) : Unit = { + def UpdateHotSpots(affectedFactions : Iterable[PlanetSideEmpire.Value], hotSpotInfos : Iterable[HotSpotInfo]) : Unit = { val zoneNumber = zone.Number + val hotSpotInfoList = hotSpotInfos.toList affectedFactions.foreach(faction => galaxy ! Zone.HotSpot.Update( faction, zoneNumber, 1, - ZoneHotSpotProjector.SpecificHotSpotInfo(faction, hotSpotInfos) + ZoneHotSpotProjector.SpecificHotSpotInfo(faction, hotSpotInfoList) ) ) } +} - def CreateHotSpotUpdate(faction : PlanetSideEmpire.Value, hotSpotInfos : List[HotSpotInfo]) : List[HotSpotInfo] = { - Nil - } +/** + * Manage hotspot information for a given zone, + * keeping track of aggressive faction interactions, + * and maintaining the visibility state of the hotspots that alert of the location of that activity. + * One of the internal devices controlled by the `ZoneHotSpotDisplay`, + * this is the "backup" component that is intended to retain reported activity for a longer period of time. + * @see `ZoneHotSpotDisplay` + * @see `ZoneHotSpotProjector` + * @param zone the zone + * @param hotspots the data structure of hot spot information that this projector will be leveraging + * @param blankingTime how long to wait in between blanking periods + */ +class ZoneHotSpotHistory(zone : Zone, hotspots : ListBuffer[HotSpotInfo], blankingTime : FiniteDuration) extends ZoneHotSpotProjector(zone, hotspots, blankingTime) { + /* the galaxy service is unnecessary */ + override def preStart() : Unit = { context.become(Established) } + /* this component does not actually the visible hotspots + * a duplicate of the projector device otherwise */ + override def UpdateHotSpots(affectedFactions : Iterable[PlanetSideEmpire.Value], hotSpotInfos : Iterable[HotSpotInfo]) : Unit = { } } object ZoneHotSpotProjector { diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala index fd7e5660..71a47ec2 100644 --- a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala @@ -37,6 +37,7 @@ class ZoneMap(private val name : String) { private var lattice: Set[(String, String)] = Set() private var checksum : Long = 0 private var zipLinePaths : List[ZipLinePath] = List() + private var cavern : Boolean = false def Name : String = name @@ -141,4 +142,11 @@ class ZoneMap(private val name : String) { def LatticeLink(source : String, target: String) : Unit = { lattice = lattice ++ Set((source, target)) } + + def Cavern : Boolean = cavern + + def Cavern_=(cave : Boolean) : Boolean = { + cavern = cave + Cavern + } } diff --git a/common/src/main/scala/net/psforever/objects/zones/Zoning.scala b/common/src/main/scala/net/psforever/objects/zones/Zoning.scala new file mode 100644 index 00000000..b55735fe --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/Zoning.scala @@ -0,0 +1,53 @@ +package net.psforever.objects.zones + +import net.psforever.objects.SpawnPoint +import net.psforever.types.{PlanetSideEmpire, Vector3} + +object Zoning { + object Method extends Enumeration { + type Type = Value + + val + None, + InstantAction, + Recall + = Value + } + + object Status extends Enumeration { + type Type = Value + + val + None, + Request, + Countdown + = Value + } + + object Time { + sealed case class TimeType(id : Int, descriptor : String) + + final val Immediate = TimeType(0, "Immediate") + final val Friendly = TimeType(10, "Friendly") + final val Sanctuary = TimeType(10, "Sanctuary") + final val Neutral = TimeType(20, "Neutral") + final val None = TimeType(20, "None") + final val Enemy = TimeType(30, "Enemy") + } + + object InstantAction { + final case class Request(faction : PlanetSideEmpire.Value) + + final case class Located(zone : Zone, hotspot : Vector3, spawn_point : SpawnPoint) + + final case class NotLocated() + } + + object Recall { + final case class Request(faction : PlanetSideEmpire.Value, sanctuary_id : String) + + final case class Located(zone : Zone, spawn_point : SpawnPoint) + + final case class Denied(reason : String) + } +} diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index 23bb91de..522a20da 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -440,7 +440,7 @@ object GamePacketOpcode extends Enumeration { case 0x66 => game.WeaponJammedMessage.decode case 0x67 => noDecoder(LinkDeadAwarenessMsg) // 0x68 - case 0x68 => noDecoder(DroppodFreefallingMessage) + case 0x68 => game.DroppodFreefallingMessage.decode case 0x69 => game.AvatarFirstTimeEventMessage.decode case 0x6a => noDecoder(AggravatedDamageMessage) case 0x6b => game.TriggerSoundMessage.decode diff --git a/common/src/main/scala/net/psforever/packet/game/DroppodFreefallingMessage.scala b/common/src/main/scala/net/psforever/packet/game/DroppodFreefallingMessage.scala new file mode 100644 index 00000000..2ba2c2b1 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/DroppodFreefallingMessage.scala @@ -0,0 +1,44 @@ +// Copyright (c) 2020 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import net.psforever.types.{Angular, PlanetSideGUID, Vector3} +import scodec.Codec +import scodec.codecs._ +import shapeless.{::, HNil} + +final case class DroppodFreefallingMessage(guid : PlanetSideGUID, + pos : Vector3, + vel : Vector3, + pos2 : Vector3, + orientation1 : Vector3, + orientation2 : Vector3) + extends PlanetSideGamePacket { + type Packet = DroppodFreefallingMessage + def opcode = GamePacketOpcode.DroppodFreefallingMessage + def encode = DroppodFreefallingMessage.encode(this) +} + +object DroppodFreefallingMessage extends Marshallable[DroppodFreefallingMessage] { + implicit val codec : Codec[DroppodFreefallingMessage] = ( + ("guid" | PlanetSideGUID.codec) :: + ("pos" | Vector3.codec_float) :: + ("vel" | Vector3.codec_float) :: + ("pos2" | Vector3.codec_float) :: + ("unkA" | Angular.codec_roll) :: + ("unkB" | Angular.codec_pitch) :: + ("unkC" | Angular.codec_yaw()) :: + ("unkD" | Angular.codec_roll) :: + ("unkE" | Angular.codec_pitch) :: + ("unkF" | Angular.codec_yaw()) + ).xmap[DroppodFreefallingMessage]( + { + case guid :: pos :: vel :: pos2 :: uA :: uB :: uC :: uD :: uE :: uF :: HNil => + DroppodFreefallingMessage(guid, pos, vel, pos2, Vector3(uA, uB, uC), Vector3(uD, uE, uF)) + }, + { + case DroppodFreefallingMessage(guid, pos, vel, pos2, Vector3(uA, uB, uC), Vector3(uD, uE, uF)) => + guid :: pos :: vel :: pos2 :: uA :: uB :: uC :: uD :: uE :: uF :: HNil + } + ) +} diff --git a/common/src/main/scala/net/psforever/types/Vector3.scala b/common/src/main/scala/net/psforever/types/Vector3.scala index 01fc5b8e..710ddf8e 100644 --- a/common/src/main/scala/net/psforever/types/Vector3.scala +++ b/common/src/main/scala/net/psforever/types/Vector3.scala @@ -276,7 +276,7 @@ object Vector3 { } /** - * Perform the x-axis rotation of a `Vector3` element where the angle of rotation is assumed in degrees. + * Perform the y-axis rotation of a `Vector3` element where the angle of rotation is assumed in degrees. * @see `Vector3.Ry(Vector3, Double)` * @param vec a mathematical vector representing direction * @param ang a rotation angle, in degrees @@ -302,7 +302,7 @@ object Vector3 { } /** - * Perform the x-axis rotation of a `Vector3` element where the angle of rotation is assumed in degrees. + * Perform the z-axis rotation of a `Vector3` element where the angle of rotation is assumed in degrees. * @see `Vector3.Rz(Vector3, Double)` * @param vec a mathematical vector representing direction * @param ang a rotation angle, in degrees diff --git a/common/src/test/scala/game/DroppodFreefallingMessageTest.scala b/common/src/test/scala/game/DroppodFreefallingMessageTest.scala new file mode 100644 index 00000000..51ce1637 --- /dev/null +++ b/common/src/test/scala/game/DroppodFreefallingMessageTest.scala @@ -0,0 +1,40 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import net.psforever.types.{PlanetSideGUID, Vector3} +import scodec.bits._ + +class DroppodFreefallingMessageTest extends Specification { + val string = hex"68 220e 00e0b245 00c06145 00a08744 00000000 00000000 ffff79c4 0740b245 22c66145 00608144 00 67 3f 00 00 3f" + + "DroppodFreefallingMessage" should { + "decode" in { + PacketCoding.DecodePacket(string).require match { + case DroppodFreefallingMessage(guid, pos, vel, pos2, orientation1, orientation2) => + guid mustEqual PlanetSideGUID(3618) + pos mustEqual Vector3(5724, 3612, 1085) + vel mustEqual Vector3(0, 0, -999.99994f) + pos2 mustEqual Vector3(5704.0034f, 3612.3833f, 1035.0f) + orientation1 mustEqual Vector3(0, 70.3125f, 272.8125f) + orientation2 mustEqual Vector3(0, 0, 272.8125f) + case _ => + ko + } + } + + "encode" in { + val msg = DroppodFreefallingMessage( + PlanetSideGUID(3618), + Vector3(5724, 3612, 1085), + Vector3(0, 0, -999.99994f), + Vector3(5704.0034f, 3612.3833f, 1035.0f), + Vector3(0, 70.3125f, 272.8125f), Vector3(0, 0, 272.8125f)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } + } +} diff --git a/common/src/test/scala/objects/ServerObjectBuilderTest.scala b/common/src/test/scala/objects/ServerObjectBuilderTest.scala index 18ee2b13..d129cffa 100644 --- a/common/src/test/scala/objects/ServerObjectBuilderTest.scala +++ b/common/src/test/scala/objects/ServerObjectBuilderTest.scala @@ -100,7 +100,7 @@ class ImplantTerminalMechObjectBuilderTest extends ActorTest { "Implant terminal mech object" should { "build" in { val hub = ServerObjectBuilderTest.NumberPoolHub - val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, ImplantTerminalMech.Constructor), hub), "mech") + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, ImplantTerminalMech.Constructor(Vector3.Zero)), hub), "mech") actor ! "!" val reply = receiveOne(Duration.create(1000, "ms")) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index b96fc140..a1d9f30f 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -45,7 +45,7 @@ import net.psforever.objects.serverobject.mount.Mountable import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} import net.psforever.objects.serverobject.painbox.Painbox import net.psforever.objects.serverobject.resourcesilo.ResourceSilo -import net.psforever.objects.serverobject.structures.{Amenity, Building, StructureType, WarpGate} +import net.psforever.objects.serverobject.structures.{Amenity, Building, SphereOfInfluence, StructureType, WarpGate} import net.psforever.objects.serverobject.terminals.{CaptureTerminal, CaptureTerminals, MatrixTerminalDefinition, MedicalTerminalDefinition, ProximityDefinition, ProximityTerminal, ProximityUnit, Terminal} import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.serverobject.tube.SpawnTube @@ -55,7 +55,7 @@ import net.psforever.objects.teamwork.Squad import net.psforever.objects.vehicles.{AccessPermissionGroup, Cargo, CargoBehavior, MountedWeapons, Utility, UtilityType, VehicleLockState} import net.psforever.objects.vehicles.Utility.InternalTelepad import net.psforever.objects.vital.{DamageFromPainbox, HealFromExoSuitChange, HealFromKit, HealFromTerm, PlayerSuicide, RepairFromKit, Vitality, VitalityDefinition} -import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector} +import net.psforever.objects.zones.{InterstellarCluster, Zone, ZoneHotSpotProjector, Zoning} import net.psforever.packet._ import net.psforever.packet.control._ import net.psforever.packet.game._ @@ -77,6 +77,7 @@ import services.vehicle.support.TurretUpgrader class WorldSessionActor extends Actor with MDCContextAware { + import WorldSessionActor._ private[this] val log = org.log4s.getLogger @@ -86,7 +87,7 @@ class WorldSessionActor extends Actor var rightRef : ActorRef = ActorRef.noSender var accountIntermediary : ActorRef = ActorRef.noSender var accountPersistence : ActorRef = ActorRef.noSender - var chatService: ActorRef = ActorRef.noSender + var chatService : ActorRef = ActorRef.noSender var galaxyService : ActorRef = ActorRef.noSender var squadService : ActorRef = ActorRef.noSender var taskResolver : ActorRef = Actor.noSender @@ -130,9 +131,10 @@ class WorldSessionActor extends Actor var recentTeleportAttempt : Long = 0 var lastTerminalOrderFulfillment : Boolean = true var shiftPosition : Option[Vector3] = None - var setupAvatarFunc : ()=>Unit = AvatarCreate - var beginZoningSetCurrentAvatarFunc : (Player)=>Unit = SetCurrentAvatarNormally - var persist : ()=>Unit = NoPersistence + var shiftOrientation : Option[Vector3] = None + var setupAvatarFunc : () => Unit = AvatarCreate + var beginZoningSetCurrentAvatarFunc : (Player) => Unit = SetCurrentAvatarNormally + var persist : () => Unit = NoPersistence /** * used during zone transfers to maintain reference to seated vehicle (which does not yet exist in the new zone) * used during intrazone gate transfers, but not in a way distinct from prior zone transfer procedures @@ -163,15 +165,20 @@ class WorldSessionActor extends Actor var squadSetup : () => Unit = FirstTimeSquadSetup var squadUpdateCounter : Int = 0 val queuedSquadActions : Seq[() => Unit] = Seq(SquadUpdates, NoSquadUpdates, NoSquadUpdates, NoSquadUpdates) - - lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L - var serverTime : Long = 0 - /** Keeps track of the number of PlayerStateMessageUpstream messages received by the client * As they should arrive roughly every 250 milliseconds this allows for a very crude method of scheduling tasks up to four times per second */ private var playerStateMessageUpstreamCount = 0 - + var zoningType : Zoning.Method.Value = Zoning.Method.None + var zoningChatMessageType : ChatMessageType.Value = ChatMessageType.CMT_QUIT + var zoningStatus : Zoning.Status.Value = Zoning.Status.None + var zoningCounter : Int = 0 + var instantActionFallbackDestination : Option[Zoning.InstantAction.Located] = None + var timeDL : Long = 0 + var timeSurge : Long = 0 + lazy val unsignedIntMaxValue : Long = Int.MaxValue.toLong * 2L + 1L + var serverTime : Long = 0 var amsSpawnPoints : List[SpawnPoint] = Nil + var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj var reviveTimer : Cancellable = DefaultCancellable.obj @@ -180,15 +187,19 @@ class WorldSessionActor extends Actor var cargoDismountTimer : Cancellable = DefaultCancellable.obj var antChargingTick : Cancellable = DefaultCancellable.obj var antDischargingTick : Cancellable = DefaultCancellable.obj - + var zoningTimer : Cancellable = DefaultCancellable.obj + var zoningReset : Cancellable = DefaultCancellable.obj /** * Convert a boolean value into an integer value. * Use: `true:Int` or `false:Int` * @param b `true` or `false` (or `null`) * @return 1 for `true`; 0 for `false` */ + import scala.language.implicitConversions - implicit def boolToInt(b : Boolean) : Int = if(b) 1 else 0 + + implicit def boolToInt(b : Boolean) : Int = if(b) 1 + else 0 override def postStop() : Unit = { //normally, the player avatar persists a minute or so after disconnect; we are subject to the SessionReaper @@ -252,7 +263,7 @@ class WorldSessionActor extends Actor def ValidObject(id : PlanetSideGUID) : Option[PlanetSideGameObject] = ValidObject(Some(id)) def ValidObject(id : Option[PlanetSideGUID]) : Option[PlanetSideGameObject] = continent.GUID(id) match { - case out @ Some(obj) if obj.HasGUID => + case out@Some(obj) if obj.HasGUID => out case None if id.nonEmpty => //delete stale entity reference from client @@ -447,18 +458,18 @@ class WorldSessionActor extends Actor val leader = squad.Leader val membershipPositions = positionsToUpdate map squad.Membership.zipWithIndex StartBundlingPackets() - membershipPositions.find({ case(member, _) => member.CharId == avatar.CharId }) match { + membershipPositions.find({ case (member, _) => member.CharId == avatar.CharId }) match { case Some((ourMember, ourIndex)) => //we are joining the squad //load each member's entry (our own too) squad_supplement_id = squad.GUID.guid + 1 - membershipPositions.foreach { case(member, index) => + membershipPositions.foreach { case (member, index) => sendResponse(SquadMemberEvent.Add(squad_supplement_id, member.CharId, index, member.Name, member.ZoneId, unk7 = 0)) squadUI(member.CharId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position) } //repeat our entry sendResponse(SquadMemberEvent.Add(squad_supplement_id, ourMember.CharId, ourIndex, ourMember.Name, ourMember.ZoneId, unk7 = 0)) //repeat of our entry - val playerGuid = player.GUID + val playerGuid = player.GUID //turn lfs off val factionChannel = s"${player.Faction}" if(avatar.LFS) { @@ -479,7 +490,7 @@ class WorldSessionActor extends Actor //other player is joining our squad //load each member's entry GiveSquadColorsInZone( - membershipPositions.map { case(member, index) => + membershipPositions.map { case (member, index) => val charId = member.CharId sendResponse(SquadMemberEvent.Add(squad_supplement_id, charId, index, member.Name, member.ZoneId, unk7 = 0)) squadUI(charId) = SquadUIElement(member.Name, index, member.ZoneId, member.Health, member.Armor, member.Position) @@ -492,17 +503,17 @@ class WorldSessionActor extends Actor sendResponse(SquadState(PlanetSideGUID(squad_supplement_id), membershipPositions .filterNot { case (member, _) => member.CharId == avatar.CharId } - .map{ case (member, _) => SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position, 2,2, false, 429, None,None) } + .map { case (member, _) => SquadStateInfo(member.CharId, member.Health, member.Armor, member.Position, 2, 2, false, 429, None, None) } .toList )) case SquadResponse.Leave(squad, positionsToUpdate) => StartBundlingPackets() - positionsToUpdate.find({ case(member, _) => member == avatar.CharId }) match { + positionsToUpdate.find({ case (member, _) => member == avatar.CharId }) match { case Some((ourMember, ourIndex)) => //we are leaving the squad //remove each member's entry (our own too) - positionsToUpdate.foreach { case(member, index) => + positionsToUpdate.foreach { case (member, index) => sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index)) squadUI.remove(member) } @@ -523,7 +534,7 @@ class WorldSessionActor extends Actor case _ => //remove each member's entry GiveSquadColorsInZone( - positionsToUpdate.map { case(member, index) => + positionsToUpdate.map { case (member, index) => sendResponse(SquadMemberEvent.Remove(squad_supplement_id, member, index)) squadUI.remove(member) member @@ -582,7 +593,7 @@ class WorldSessionActor extends Actor sendResponse( SquadState( PlanetSideGUID(squad_supplement_id), - updatedEntries.map { entry => SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos, 2,2, false, 429, None,None)} + updatedEntries.map { entry => SquadStateInfo(entry.char_id, entry.health, entry.armor, entry.pos, 2, 2, false, 429, None, None) } ) ) } @@ -679,16 +690,17 @@ class WorldSessionActor extends Actor case scala.util.Success(connection) => val accountUserName : String = account.Username connection.inTransaction { - c => c.sendPreparedStatement( - "INSERT INTO characters (name, account_id, faction_id, gender_id, head_id, voice_id) VALUES(?,?,?,?,?,?) RETURNING id", - Array(name, account.AccountId, empire.id, gender.id, head, voice.id) - ) + c => + c.sendPreparedStatement( + "INSERT INTO characters (name, account_id, faction_id, gender_id, head_id, voice_id) VALUES(?,?,?,?,?,?) RETURNING id", + Array(name, account.AccountId, empire.id, gender.id, head, voice.id) + ) }.onComplete { case scala.util.Success(insertResult) => if(connection.isConnected) connection.disconnect insertResult match { - case result: QueryResult => - if (result.rows.nonEmpty) { + case result : QueryResult => + if(result.rows.nonEmpty) { log.info(s"CreateCharacter: successfully created new character for $accountUserName") sendResponse(ActionResultMessage.Pass) self ! ListAccountCharacters() @@ -724,7 +736,6 @@ class WorldSessionActor extends Actor import net.psforever.objects.definition.converter.CharacterSelectConverter val gen : AtomicInteger = new AtomicInteger(1) val converter : CharacterSelectConverter = new CharacterSelectConverter - result.rows foreach { row => log.trace(s"char list : ${row.toString()}") val nowTimeInSeconds = System.currentTimeMillis() / 1000 @@ -798,12 +809,11 @@ class WorldSessionActor extends Actor // StopBundlingPackets() is called on ClientInitializationComplete StartBundlingPackets() - zone.Buildings.foreach({ case (id, building) => initBuilding(continentNumber, building.MapId, building) }) sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) - if (continentNumber == 11) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NC)) // "The NC have captured the NC Sanctuary." - else if (continentNumber == 12) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.TR)) // "The TR have captured the TR Sanctuary." - else if (continentNumber == 13) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." + if(continentNumber == 11) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NC)) // "The NC have captured the NC Sanctuary." + else if(continentNumber == 12) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.TR)) // "The TR have captured the TR Sanctuary." + else if(continentNumber == 13) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.VS)) // "The VS have captured the VS Sanctuary." else sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL)) //CaptureFlagUpdateMessage() //VanuModuleUpdateMessage() @@ -811,7 +821,6 @@ class WorldSessionActor extends Actor sendResponse(ZoneInfoMessage(continentNumber, true, 0)) sendResponse(ZoneLockInfoMessage(continentNumber, false, true)) sendResponse(ZoneForcedCavernConnectionsMessage(continentNumber, 0)) - sendResponse(HotSpotUpdateMessage( continentNumber, 1, @@ -835,6 +844,7 @@ class WorldSessionActor extends Actor log.warn(s"$tplayer is already spawned on zone ${zone.Id}; a clerical error?") case Zone.Lattice.SpawnPoint(zone_id, spawn_tube) => + CancelZoningProcess() var (pos, ori) = spawn_tube.SpecificPoint(continent.GUID(player.VehicleSeated) match { case Some(obj : Vehicle) if !obj.Destroyed => obj @@ -867,10 +877,10 @@ class WorldSessionActor extends Actor RequestSanctuaryZoneSpawn(player, zone_number) } - case msg @ Zone.Vehicle.CanNotSpawn(zone, vehicle, reason) => + case msg@Zone.Vehicle.CanNotSpawn(zone, vehicle, reason) => log.warn(s"$msg") - case msg @ Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) => + case msg@Zone.Vehicle.CanNotDespawn(zone, vehicle, reason) => log.warn(s"$msg") case Zone.Ground.ItemOnGround(item : BoomerTrigger, pos, orient) => @@ -1141,6 +1151,41 @@ class WorldSessionActor extends Actor taskResolver ! RegisterNewAvatar(player) } + case msg@Zoning.InstantAction.Located(zone, _, spawn_point) => + //in between subsequent reply messages, it does not matter if the destination changes + //so long as there is at least one destination at all (including the fallback) + if(ContemplateZoningResponse(Zoning.InstantAction.Request(player.Faction))) { + SpawnThroughZoningProcess(zone, spawn_point.Position, spawn_point) + } + else if(zoningStatus != Zoning.Status.None) { + instantActionFallbackDestination = Some(msg) + } + + case Zoning.InstantAction.NotLocated() => + instantActionFallbackDestination match { + case Some(Zoning.InstantAction.Located(zone, _, spawn_point)) if spawn_point.Owner.Faction == player.Faction && !spawn_point.Offline => + if(ContemplateZoningResponse(Zoning.InstantAction.Request(player.Faction))) { + SpawnThroughZoningProcess(zone, spawn_point.Position, spawn_point) + } + else if(zoningCounter == 0) { + CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable") + } + case _ => + //no instant action available + CancelZoningProcessWithReason("@InstantActionNoHotspotsAvailable") + } + + case Zoning.Recall.Located(zone, spawn_point) => + if(ContemplateZoningResponse(Zoning.Recall.Request(player.Faction, zone.Id))) { + SpawnThroughZoningProcess(zone, spawn_point.Position, spawn_point) + } + + case Zoning.Recall.Denied(reason) => + CancelZoningProcessWithReason(s"@norecall_sanctuary_$reason", Some(ChatMessageType.CMT_QUIT)) + + case ZoningReset() => + CancelZoningProcess() + case NewPlayerLoaded(tplayer) => //new zone log.info(s"Player ${tplayer.Name} has been loaded") @@ -1202,7 +1247,7 @@ class WorldSessionActor extends Actor case scala.util.Success(queryResult) => if(connection.isConnected) connection.disconnect queryResult match { - case row: ArrayRowData => // If we got a row from the database + case row : ArrayRowData => // If we got a row from the database log.info(s"ReceiveAccountData: ready to load character list for ${account.Username}") self ! ListAccountCharacters() case _ => // If the account didn't exist in the database @@ -1292,7 +1337,6 @@ class WorldSessionActor extends Actor InitializeDeployableQuantities(avatar) //set deployables ui elements AwardBattleExperiencePoints(avatar, 20000000L) avatar.CEP = 600000 - avatar.Implants(0).Unlocked = true avatar.Implants(0).Implant = GlobalDefinitions.darklight_vision avatar.Implants(1).Unlocked = true @@ -1301,13 +1345,12 @@ class WorldSessionActor extends Actor avatar.Implants(2).Implant = GlobalDefinitions.targeting player = new Player(avatar) - //xy-coordinates indicate sanctuary spawn bias: player.Position = math.abs(scala.util.Random.nextInt() % avatar.name.hashCode % 4) match { case 0 => Vector3(8192, 8192, 0) //NE - case 1 => Vector3(8192, -8192, 0) //SE - case 2 => Vector3(-8192, -8192, 0) //SW - case 3 => Vector3(-8192, 8192, 0) //NW + case 1 => Vector3(8192, 0, 0) //SE + case 2 => Vector3(0, 0, 0) //SW + case 3 => Vector3(0, 8192, 0) //NW } player.FirstLoad = true LoadClassicDefault(player) @@ -1398,7 +1441,7 @@ class WorldSessionActor extends Actor /** * Do not update this player avatar for persistence. */ - def NoPersistence() : Unit = { } + def NoPersistence() : Unit = {} /** * Common action to perform before starting the transition to client initialization. @@ -1445,6 +1488,209 @@ class WorldSessionActor extends Actor result.future } + /** + * A zoning message was received. + * That doesn't matter. + * In what stage of the zoning determination process is the client, and what is the next stage.
+ *
+ * To perform any actions involving zoning, an initial request must have been dispatched and marked as dispatched. + * When invoked after, the process will switch over to a countdown of time until the zoning actually occurs. + * The origin will be evaluated based on comparison of faction affinity with the client's player + * and from that an initial time and a message will be generated. + * Afterwards, the process will queue another inquiry for another zoning response. + * Each time 5s of the countdown passes, another message will be sent and received; + * and, this is another pass of the countdown.
+ *
+ * Once the countdown reaches 0, the transportation that has been promised by the zoning attempt may begin. + * @param nextStepMsg send this message to the `InterGalacticCluster` for the next step of the zoning process, + * if there will be a next step + * @return `true`, if the zoning transportation process should start; + * `false`, otherwise + */ + def ContemplateZoningResponse(nextStepMsg : Any) : Boolean = { + val descriptor = zoningType.toString.toLowerCase + if(zoningStatus == Zoning.Status.Request) { + DeactivateImplants() + zoningStatus = Zoning.Status.Countdown + val (time, origin) = ZoningStartInitialMessageAndTimer() + zoningCounter = time + sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", s"@${descriptor}_$origin", None)) + import scala.concurrent.ExecutionContext.Implicits.global + zoningReset.cancel + zoningTimer.cancel + zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset()) + zoningTimer = context.system.scheduler.scheduleOnce(5 seconds, cluster, nextStepMsg) + false + } + else if(zoningStatus == Zoning.Status.Countdown) { + zoningCounter -= 5 + zoningReset.cancel + zoningTimer.cancel + if(zoningCounter > 0) { + if(zoningCountdownMessages.contains(zoningCounter)) { + sendResponse(ChatMsg(zoningChatMessageType, false, "", s"@${descriptor}_$zoningCounter", None)) + } + //again + zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset()) + zoningTimer = context.system.scheduler.scheduleOnce(5 seconds, cluster, nextStepMsg) + false + } + else { + //zoning deployment + true + } + } + else { + false + } + } + + /** + * The primary method of determination involves the faction affinity of the most favorable available region subset, + * e.g., in the overlapping sphere of influences of a friendly field tower and an enemy major facility, + * the time representative of the the tower has priority. + * When no spheres of influence are being encroached, one is considered "in the wilderness". + * The messaging is different but the location is normally treated the same as if in a neutral sphere of influence. + * Being anywhere in one's faction's own sanctuary is a special case. + * @return a `Tuple` composed of the initial countdown time and the descriptor for message composition + */ + def ZoningStartInitialMessageAndTimer() : (Int, String) = { + val location = (if(Zones.SanctuaryZoneNumber(player.Faction) == continent.Number) { + Zoning.Time.Sanctuary + } + else { + val playerPosition = player.Position.xy + (continent.Buildings + .values + .filter { building => + val radius = building.Definition.SOIRadius + Vector3.DistanceSquared(building.Position.xy, playerPosition) < radius * radius + }) match { + case Nil => + Zoning.Time.None + case List(building) => + if(building.Faction == player.Faction) Zoning.Time.Friendly + else if(building.Faction == PlanetSideEmpire.NEUTRAL) Zoning.Time.Neutral + else Zoning.Time.Enemy + case buildings => + if(buildings.exists(_.Faction == player.Faction)) Zoning.Time.Friendly + else if(buildings.exists(_.Faction == PlanetSideEmpire.NEUTRAL)) Zoning.Time.Neutral + else Zoning.Time.Enemy + } + }) + (location.id, location.descriptor.toLowerCase) + } + + /** + * Use the zoning process using some spawnable entity in the destination zone. + * @param zone the destination zone + * @param spawnPosition the destination spawn position (may not be related to `spawnPoint`) + * @param spawnPoint a `SpawntPoint` entity that is the target of our spawning in the destination zone + */ + def SpawnThroughZoningProcess(zone : Zone, spawnPosition : Vector3, spawnPoint : SpawnPoint) : Unit = { + CancelZoningProcess() + PlayerActionsToCancel() + CancelAllProximityUnits() + continent.Population ! Zone.Population.Release(avatar) + val respawnTime : Long = if(zone.Number == continent.Number) { + //distract the user while he slips through the cracks of reality + GoToDeploymentMap() + 1L + } + else { + //zone loading will take long enough + 0L + } + LoadZonePhysicalSpawnPoint(zone.Id, spawnPosition, spawnPoint.Orientation, respawnTime) + } + + /** + * You can't instant action to respond to some activity using a droppod! + * You can't. + * You just can't. + * @param zone the destination zone + * @param hotspotPosition where is the hotspot that is being addressed + * @param spawnPosition the destination spawn position (may not be related to a literal `SpawnPoint` entity) + */ + def YouCantInstantActionUsingDroppod(zone : Zone, hotspotPosition : Vector3, spawnPosition : Vector3) : Unit = { + CancelZoningProcess() + PlayerActionsToCancel() + CancelAllProximityUnits() + //find a safe drop point + var targetBuildings = zone.Buildings.values + var whereToDroppod = spawnPosition.xy + while(targetBuildings.nonEmpty) { + (targetBuildings + .filter { building => + val radius = building.Definition.SOIRadius + Vector3.DistanceSquared(building.Position.xy, whereToDroppod) < radius * radius + }) match { + case Nil => + //no soi interference + targetBuildings = Nil + case List(building) => + //blocked by a single soi; find space just outside of this soi and confirm no new overlap + val radius = Vector3(0, building.Definition.SOIRadius + 5, 0) + whereToDroppod = building.Position.xy + Vector3.Rz(radius, math.abs(scala.util.Random.nextInt() % 360)) + case buildings => + //probably blocked by a facility and its tower (maximum overlap potential is 2?); find space outside of largest soi + val largestBuilding = buildings.maxBy(_.Definition.SOIRadius) + val radius = Vector3(0, largestBuilding.Definition.SOIRadius + 5, 0) + whereToDroppod = largestBuilding.Position.xy + Vector3.Rz(radius, math.abs(scala.util.Random.nextInt() % 360)) + targetBuildings = buildings + } + } + //droppod action + val droppod = Vehicle(GlobalDefinitions.droppod) + droppod.Faction = player.Faction + droppod.Position = whereToDroppod.xy + Vector3.z(1024) + droppod.Orientation = Vector3.z(180) //you always seems to land looking south; don't know why + droppod.Seats(0).Occupant = player + droppod.GUID = PlanetSideGUID(0) //droppod is not registered, we must jury-rig this + droppod.Invalidate() //now, we must short-circuit the jury-rig + interstellarFerry = Some(droppod) //leverage vehicle gating + player.Position = droppod.Position + continent.Population ! Zone.Population.Release(avatar) + LoadZonePhysicalSpawnPoint(zone.Id, droppod.Position, Vector3.Zero, 0L) + /* Don't even think about it. */ + } + + /** + * The user no longer expects to perform a zoning event for this reason. + * @param msg the message to the user + */ + def CancelZoningProcessWithDescriptiveReason(msg : String) : Unit = { + CancelZoningProcessWithReason(s"@${zoningType.toString.toLowerCase}_$msg", Some(zoningChatMessageType)) + } + + /** + * The user no longer expects to perform a zoning event for this reason. + * @param msg the message to the user + * @param msgType the type of message, influencing how it is presented to the user; + * normally, this message uses the same value as `zoningChatMessageType`s + * defaults to `None` + */ + def CancelZoningProcessWithReason(msg : String, msgType : Option[ChatMessageType.Value] = None) : Unit = { + if(zoningStatus > Zoning.Status.None) { + sendResponse(ChatMsg(msgType.getOrElse(zoningChatMessageType), false, "", msg, None)) + } + CancelZoningProcess() + } + + /** + * The user no longer expects to perform a zoning event, + * or the process is merely resetting its internal state. + */ + def CancelZoningProcess() : Unit = { + zoningTimer.cancel + zoningReset.cancel + zoningType = Zoning.Method.None + zoningStatus = Zoning.Status.None + zoningCounter = 0 + //instant action exclusive field + instantActionFallbackDestination = None + } + /** * na * @param toChannel na @@ -1527,6 +1773,7 @@ class WorldSessionActor extends Actor player.History(DamageFromPainbox(PlayerSource(player), obj, amount)) case _ => ; } + CancelZoningProcessWithDescriptiveReason("cancel_dmg") player.Health = originalHealth - amount sendResponse(PlanetsideAttributeMessage(target, 0, player.Health)) continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlanetsideAttribute(target, 0, player.Health)) @@ -1567,11 +1814,13 @@ class WorldSessionActor extends Actor case AvatarResponse.HitHint(source_guid) => if(player.isAlive) { sendResponse(HitHint(source_guid, guid)) + CancelZoningProcessWithDescriptiveReason("cancel_dmg") } case AvatarResponse.Killed() => val respawnTimer = 300000 //milliseconds ToggleMaxSpecialState(enable = false) + zoningStatus = Zoning.Status.None deadState = DeadState.Dead continent.GUID(player.VehicleSeated) match { case Some(obj : Vehicle) => @@ -1580,6 +1829,8 @@ class WorldSessionActor extends Actor case _ => ; } PlayerActionsToCancel() + CancelAllProximityUnits() + CancelZoningProcessWithDescriptiveReason("cancel") if(shotsWhileDead > 0) { log.warn(s"KillPlayer/SHOTS_WHILE_DEAD: client of ${avatar.name} fired $shotsWhileDead rounds while character was dead on server") shotsWhileDead = 0 @@ -1616,7 +1867,7 @@ class WorldSessionActor extends Actor sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value)) case AvatarResponse.PlanetsideAttributeSelf(attribute_type, attribute_value) => - if (tplayer_guid == guid) { + if(tplayer_guid == guid) { sendResponse(PlanetsideAttributeMessage(guid, attribute_type, attribute_value)) } @@ -1715,8 +1966,8 @@ class WorldSessionActor extends Actor /** * na * @param tplayer na - * @param msg na - * @param order na + * @param msg na + * @param order na */ def HandleDoorMessage(tplayer : Player, msg : UseItemMessage, order : Door.Exchange) : Unit = { val door_guid = msg.object_guid @@ -1786,7 +2037,8 @@ class WorldSessionActor extends Actor } else { obj.Destroyed = true - DeconstructDeployable(obj, guid, pos, obj.Orientation, if(obj.MountPoints.isEmpty) 2 else 1) + DeconstructDeployable(obj, guid, pos, obj.Orientation, if(obj.MountPoints.isEmpty) 2 + else 1) } case LocalResponse.EliminateDeployable(obj : ExplosiveDeployable, guid, pos) => @@ -1891,7 +2143,8 @@ class WorldSessionActor extends Actor case LocalResponse.UpdateForceDomeStatus(building_guid, activated) => { if(activated) { sendResponse(GenericObjectActionMessage(building_guid, 11)) - } else { + } + else { sendResponse(GenericObjectActionMessage(building_guid, 12)) } } @@ -1899,7 +2152,7 @@ class WorldSessionActor extends Actor case LocalResponse.RechargeVehicleWeapon(vehicle_guid, weapon_guid) => { if(tplayer_guid == guid) { continent.GUID(vehicle_guid) match { - case Some(vehicle: Mountable with MountedWeapons) => + case Some(vehicle : Mountable with MountedWeapons) => vehicle.PassengerInSeat(player) match { case Some(seat_num : Int) => vehicle.WeaponControlledFromSeat(seat_num) match { @@ -1921,34 +2174,34 @@ class WorldSessionActor extends Actor /** * na - * @param toChannel na - * @param avatar_guid na - * @param target na - * @param reply na + * @param toChannel na + * @param avatar_guid na + * @param target na + * @param reply na */ def HandleChatServiceResponse(toChannel : String, avatar_guid : PlanetSideGUID, avatar_name : String, cont : Zone, avatar_pos : Vector3, avatar_faction : PlanetSideEmpire.Value, target : Int, reply : ChatMsg) : Unit = { val tplayer_guid = if(player.HasGUID) player.GUID else PlanetSideGUID(0) target match { case 0 => // for other(s) user(s) - if (player.GUID != avatar_guid) { + if(player.GUID != avatar_guid) { reply.messageType match { case ChatMessageType.CMT_TELL => - if (player.Name == reply.recipient) { + if(player.Name == reply.recipient) { sendResponse(ChatMsg(reply.messageType, reply.wideContents, avatar_name, reply.contents, reply.note)) } case ChatMessageType.CMT_SILENCE => val args = avatar_name.split(" ") var silence_name : String = "" var silence_time : Int = 5 - if (args.length == 1) { + if(args.length == 1) { silence_name = args(0) } - else if (args.length == 2) { + else if(args.length == 2) { silence_name = args(0) silence_time = args(1).toInt } - if (player.Name == args(0)) { + if(player.Name == args(0)) { if(!player.silenced) { sendResponse(ChatMsg(ChatMessageType.UNK_71, reply.wideContents, reply.recipient, "@silence_on", reply.note)) player.silenced = true @@ -1964,24 +2217,24 @@ class WorldSessionActor extends Actor } } case 1 => // for player - if (player.Name == avatar_name) { - if ((reply.contents.length > 1 && (reply.contents.dropRight(reply.contents.length - 1) != "!" || reply.contents.drop(1).dropRight(reply.contents.length - 2) == "!")) || reply.contents.length == 1) { + if(player.Name == avatar_name) { + if((reply.contents.length > 1 && (reply.contents.dropRight(reply.contents.length - 1) != "!" || reply.contents.drop(1).dropRight(reply.contents.length - 2) == "!")) || reply.contents.length == 1) { sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) } } case 2 => // both case - if ((reply.contents.length > 1 && (reply.contents.dropRight(reply.contents.length - 1) != "!" || reply.contents.drop(1).dropRight(reply.contents.length - 2) == "!")) || reply.contents.length == 1) { + if((reply.contents.length > 1 && (reply.contents.dropRight(reply.contents.length - 1) != "!" || reply.contents.drop(1).dropRight(reply.contents.length - 2) == "!")) || reply.contents.length == 1) { reply.messageType match { case ChatMessageType.CMT_OPEN => - if (Vector3.Distance(player.Position, avatar_pos) < 25 && player.Faction == avatar_faction && player.Continent == cont.Id) { + if(Vector3.Distance(player.Position, avatar_pos) < 25 && player.Faction == avatar_faction && player.Continent == cont.Id) { sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) } case ChatMessageType.CMT_SQUAD => - if (player.Faction == avatar_faction) { + if(player.Faction == avatar_faction) { sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) } case ChatMessageType.CMT_VOICE => - if (Vector3.Distance(player.Position, avatar_pos) < 25 && player.Continent == cont.Id) { + if(Vector3.Distance(player.Position, avatar_pos) < 25 && player.Continent == cont.Id) { sendResponse(ChatMsg(reply.messageType, reply.wideContents, reply.recipient, reply.contents, reply.note)) } case _ => @@ -1999,14 +2252,16 @@ class WorldSessionActor extends Actor def HandleMountMessages(tplayer : Player, reply : Mountable.Exchange) : Unit = { reply match { case Mountable.CanMount(obj : ImplantTerminalMech, seat_num) => + CancelZoningProcessWithDescriptiveReason("cancel_use") + CancelAllProximityUnits() MountingAction(tplayer, obj, seat_num) - sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, 1000L)) //health of mech case Mountable.CanMount(obj : Vehicle, seat_num) => + CancelZoningProcessWithDescriptiveReason("cancel_mount") val obj_guid : PlanetSideGUID = obj.GUID val player_guid : PlanetSideGUID = tplayer.GUID log.info(s"MountVehicleMsg: $player_guid mounts $obj_guid @ $seat_num") - PlayerActionsToCancel() + CancelAllProximityUnits() sendResponse(PlanetsideAttributeMessage(obj_guid, 0, obj.Health)) sendResponse(PlanetsideAttributeMessage(obj_guid, 68, obj.Shields)) //shield health if(obj.Definition.MaxNtuCapacitor > 0) { @@ -2017,7 +2272,6 @@ class WorldSessionActor extends Actor val capacitor = scala.math.ceil((obj.Capacitor.toFloat / obj.Definition.MaxCapacitor.toFloat) * 10).toInt sendResponse(PlanetsideAttributeMessage(obj_guid, 113, capacitor)) } - if(seat_num == 0) { continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) //clear timer //simplistic vehicle ownership management @@ -2044,6 +2298,7 @@ class WorldSessionActor extends Actor MountingAction(tplayer, obj, seat_num) case Mountable.CanMount(obj : FacilityTurret, seat_num) => + CancelZoningProcessWithDescriptiveReason("cancel_mount") if(!obj.isUpgrading) { if(obj.Definition == GlobalDefinitions.vanu_sentry_turret) { obj.Zone.LocalEvents ! LocalServiceMessage(obj.Zone.Id, LocalAction.SetEmpire(obj.GUID, player.Faction)) @@ -2057,6 +2312,7 @@ class WorldSessionActor extends Actor } case Mountable.CanMount(obj : PlanetSideGameObject with WeaponTurret, seat_num) => + CancelZoningProcessWithDescriptiveReason("cancel_mount") sendResponse(PlanetsideAttributeMessage(obj.GUID, 0, obj.Health)) UpdateWeaponAtSeatPosition(obj, seat_num) MountingAction(tplayer, obj, seat_num) @@ -2067,6 +2323,12 @@ class WorldSessionActor extends Actor case Mountable.CanDismount(obj : ImplantTerminalMech, seat_num) => DismountAction(tplayer, obj, seat_num) + case Mountable.CanDismount(obj : Vehicle, seat_num) if obj.Definition == GlobalDefinitions.droppod => + UnAccessContents(obj) + DismountAction(tplayer, obj, seat_num) + continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.ClearSpecific(List(obj), continent)) + continent.VehicleEvents ! VehicleServiceMessage.Decon(RemoverActor.AddTask(obj, continent, obj.Definition.DeconstructionTime)) + case Mountable.CanDismount(obj : Vehicle, seat_num) => val player_guid : PlanetSideGUID = tplayer.GUID if(player_guid == player.GUID) { @@ -2111,19 +2373,17 @@ class WorldSessionActor extends Actor //TODO check exo-suit permissions val originalSuit = tplayer.ExoSuit val originalSubtype = Loadout.DetermineSubtype(tplayer) - val lTime = System.currentTimeMillis var changeArmor : Boolean = true - if (lTime - whenUsedLastMAX(subtype) < 300000) { + if(lTime - whenUsedLastMAX(subtype) < 300000) { changeArmor = false } - if (changeArmor && exosuit.id == 2) { - for (i <- 1 to 3) { + if(changeArmor && exosuit.id == 2) { + for(i <- 1 to 3) { sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, whenUsedLastMAXName(i), 300, true)) whenUsedLastMAX(i) = lTime } } - if(originalSuit != exosuit || originalSubtype != subtype && changeArmor) { sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true)) //prepare lists of valid objects @@ -2161,7 +2421,9 @@ class WorldSessionActor extends Actor //sterilize holsters val normalHolsters = if(originalSuit == ExoSuitType.MAX) { val (maxWeapons, normalWeapons) = beforeHolsters.partition(elem => elem.obj.Size == EquipmentSize.Max) - maxWeapons.foreach(entry => { taskResolver ! GUIDTask.UnregisterEquipment(entry.obj)(continent.GUID) }) + maxWeapons.foreach(entry => { + taskResolver ! GUIDTask.UnregisterEquipment(entry.obj)(continent.GUID) + }) normalWeapons } else { @@ -2305,7 +2567,7 @@ class WorldSessionActor extends Actor val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred) val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred) tplayer.FreeHand.Equipment = None //terminal and inventory will close, so prematurely dropping should be fine - val fallbackSuit = ExoSuitType.Standard + val fallbackSuit = ExoSuitType.Standard val fallbackSubtype = 0 //a loadout with a prohibited exo-suit type will result in a fallback exo-suit type val (nextSuit : ExoSuitType.Value, nextSubtype : Int) = @@ -2320,22 +2582,23 @@ class WorldSessionActor extends Actor tplayer.Certifications.intersect(permissions.toSet).nonEmpty }) { val lTime = System.currentTimeMillis - if (lTime - whenUsedLastMAX(subtype) < 300000){ // PTS v3 hack + if(lTime - whenUsedLastMAX(subtype) < 300000) { // PTS v3 hack (originalSuit, subtype) - } else { - if (lTime - whenUsedLastMAX(subtype) > 300000 && subtype != 0) { - for (i <- 1 to 3) { - sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, whenUsedLastMAXName(i), 300, true)) - whenUsedLastMAX(i) = lTime + } + else { + if(lTime - whenUsedLastMAX(subtype) > 300000 && subtype != 0) { + for(i <- 1 to 3) { + sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, whenUsedLastMAXName(i), 300, true)) + whenUsedLastMAX(i) = lTime } } (exosuit, subtype) } - } - else { - log.warn(s"$tplayer no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead") - (fallbackSuit, fallbackSubtype) - } + } + else { + log.warn(s"$tplayer no longer has permission to wear the exo-suit type $exosuit; will wear $fallbackSuit instead") + (fallbackSuit, fallbackSubtype) + } //update suit interally (holsters must be empty before this point) val originalArmor = player.Armor tplayer.ExoSuit = nextSuit @@ -2377,7 +2640,7 @@ class WorldSessionActor extends Actor holsters .filterNot(dropPred) .collect { - case item @ InventoryItem(obj, index) if newSuitDef.Holster(index) == obj.Size => item + case item@InventoryItem(obj, index) if newSuitDef.Holster(index) == obj.Size => item } } val size = newSuitDef.Holsters.size @@ -2405,7 +2668,9 @@ class WorldSessionActor extends Actor sendResponse(ArmorChangedMessage(tplayer.GUID, nextSuit, nextSubtype)) continent.AvatarEvents ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, nextSuit, nextSubtype)) if(nextSuit == ExoSuitType.MAX) { - val (maxWeapons, otherWeapons) = afterHolsters.partition(entry => { entry.obj.Size == EquipmentSize.Max }) + val (maxWeapons, otherWeapons) = afterHolsters.partition(entry => { + entry.obj.Size == EquipmentSize.Max + }) val weapon = maxWeapons.headOption match { case Some(mweapon) => mweapon.obj @@ -2618,7 +2883,7 @@ class WorldSessionActor extends Actor continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match { case Some(pad_guid) => val lTime = System.currentTimeMillis - if (lTime - whenUsedLastItem(vehicle.Definition.ObjectId) > 300000) { + if(lTime - whenUsedLastItem(vehicle.Definition.ObjectId) > 300000) { whenUsedLastItem(vehicle.Definition.ObjectId) = lTime whenUsedLastItemName(vehicle.Definition.ObjectId) = msg.item_name sendResponse(AvatarVehicleTimerMessage(tplayer.GUID, msg.item_name, 300, true)) @@ -2647,7 +2912,7 @@ class WorldSessionActor extends Actor entry.obj.Faction = toFaction vTrunk += entry.start -> entry.obj }) - taskResolver ! RegisterNewVehicle(vehicle, pad) + taskResolver ! RegisterVehicleFromSpawnPad(vehicle, pad) sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true)) } else { @@ -2679,8 +2944,8 @@ class WorldSessionActor extends Actor * @param reply na */ def HandleVehicleServiceResponse(toChannel : String, guid : PlanetSideGUID, reply : VehicleResponse.Response) : Unit = { - val tplayer_guid = if(player.HasGUID) player.GUID else PlanetSideGUID(0) - + val tplayer_guid = if(player.HasGUID) player.GUID + else PlanetSideGUID(0) reply match { case VehicleResponse.AttachToRails(vehicle_guid, pad_guid) => sendResponse(ObjectAttachMessage(pad_guid, vehicle_guid, 3)) @@ -2793,9 +3058,9 @@ class WorldSessionActor extends Actor case VehicleResponse.UnloadVehicle(vehicle, vehicle_guid) => //if(tplayer_guid != guid) { - BeforeUnloadVehicle(vehicle) - sendResponse(ObjectDeleteMessage(vehicle_guid, 0)) - //} + BeforeUnloadVehicle(vehicle) + sendResponse(ObjectDeleteMessage(vehicle_guid, 0)) + //} case VehicleResponse.UnstowEquipment(item_guid) => if(tplayer_guid != guid) { @@ -2831,8 +3096,10 @@ class WorldSessionActor extends Actor case VehicleResponse.KickCargo(vehicle, speed, delay) => if(player.VehicleSeated.nonEmpty && deadState == DeadState.Alive) { if(speed > 0) { - val strafe = if(Vehicles.CargoOrientation(vehicle) == 1) 2 else 1 - val reverseSpeed = if(strafe > 1) 0 else speed + val strafe = if(Vehicles.CargoOrientation(vehicle) == 1) 2 + else 1 + val reverseSpeed = if(strafe > 1) 0 + else speed //strafe or reverse, not both controlled = Some(reverseSpeed) sendResponse(ServerVehicleOverrideMsg(true, true, true, false, 0, strafe, reverseSpeed, Some(0))) @@ -2848,6 +3115,7 @@ class WorldSessionActor extends Actor case VehicleResponse.StartPlayerSeatedInVehicle(vehicle, pad) => val vehicle_guid = vehicle.GUID PlayerActionsToCancel() + CancelAllProximityUnits() if(player.VisibleSlots.contains(player.DrawnSlot)) { player.DrawnSlot = Player.HandsDownSlot sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, true)) @@ -2889,13 +3157,13 @@ class WorldSessionActor extends Actor * Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet only to this client. * @see `CargoMountPointStatusMessage` * @see `ObjectAttachMessage` - * @param carrier the ferrying vehicle - * @param cargo the ferried vehicle + * @param carrier the ferrying vehicle + * @param cargo the ferried vehicle * @param mountPoint the point on the ferryoing vehicle where the ferried vehicle is attached * @return a tuple composed of an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet */ def CargoMountBehaviorForUs(carrier : Vehicle, cargo : Vehicle, mountPoint : Int) : (ObjectAttachMessage, CargoMountPointStatusMessage) = { - val msgs @ (attachMessage, mountPointStatusMessage) = CargoBehavior.CargoMountMessages(carrier, cargo, mountPoint) + val msgs@(attachMessage, mountPointStatusMessage) = CargoBehavior.CargoMountMessages(carrier, cargo, mountPoint) CargoMountMessagesForUs(attachMessage, mountPointStatusMessage) msgs } @@ -2904,7 +3172,7 @@ class WorldSessionActor extends Actor * Dispatch an `ObjectAttachMessage` packet and a `CargoMountPointStatusMessage` packet only to this client. * @see `CargoMountPointStatusMessage` * @see `ObjectAttachMessage` - * @param attachMessage an `ObjectAttachMessage` packet suitable for initializing cargo operations + * @param attachMessage an `ObjectAttachMessage` packet suitable for initializing cargo operations * @param mountPointStatusMessage a `CargoMountPointStatusMessage` packet suitable for initializing cargo operations */ def CargoMountMessagesForUs(attachMessage : ObjectAttachMessage, mountPointStatusMessage : CargoMountPointStatusMessage) : Unit = { @@ -2939,8 +3207,8 @@ class WorldSessionActor extends Actor /** * na - * @param tplayer na - * @param vehicle na + * @param tplayer na + * @param vehicle na * @param silo_guid na */ def HandleNtuDischarging(tplayer : Player, vehicle : Vehicle, silo_guid : PlanetSideGUID) : Unit = { @@ -3014,12 +3282,12 @@ class WorldSessionActor extends Actor * @see `progressBarUpdate` * @see `progressBarValue` * @see `WorldSessionActor.Progress` - * @param delta how much the progress changes each tick + * @param delta how much the progress changes each tick * @param completeAction a custom action performed once the process is completed - * @param tickAction an optional action is is performed for each tick of progress; - * also performs a continuity check to determine if the process has been disrupted + * @param tickAction an optional action is is performed for each tick of progress; + * also performs a continuity check to determine if the process has been disrupted */ - def HandleProgressChange(delta : Float, completionAction : ()=>Unit, tickAction : Float=>Boolean) : Unit = { + def HandleProgressChange(delta : Float, completionAction : () => Unit, tickAction : Float => Boolean) : Unit = { progressBarUpdate.cancel progressBarValue match { case Some(value) => @@ -3073,8 +3341,9 @@ class WorldSessionActor extends Actor sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 75, 0)) sendResponse(SetCurrentAvatarMessage(guid, 0, 0)) sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on //TODO once per respawn? - sendResponse(PlayerStateShiftMessage(ShiftState(1, shiftPosition.getOrElse(tplayer.Position), tplayer.Orientation.z))) + sendResponse(PlayerStateShiftMessage(ShiftState(1, shiftPosition.getOrElse(tplayer.Position), shiftOrientation.getOrElse(tplayer.Orientation).z))) shiftPosition = None + shiftOrientation = None if(spectator) { sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, false, "", "on", None)) } @@ -3153,10 +3422,18 @@ class WorldSessionActor extends Actor case _ => player.VehicleOwned = None } - - //if driver of a vehicle, summon any passengers and cargo vehicles left behind on previous continent GetVehicleAndSeat() match { + //we're falling + case (Some(vehicle), _) if vehicle.Definition == GlobalDefinitions.droppod => + sendResponse(DroppodFreefallingMessage( + vehicle.GUID, + vehicle.Position + Vector3.z(50), + Vector3.z(-999), + vehicle.Position + Vector3.z(25), + Vector3(0, 70.3125f, 90), Vector3(0, 0, 90) + )) case (Some(vehicle), Some(0)) => + //summon any passengers and cargo vehicles left behind on previous continent LoadZoneTransferPassengerMessages( guid, continent.Id, @@ -3165,12 +3442,10 @@ class WorldSessionActor extends Actor case _ => ; } interstellarFerryTopLevelGUID = None - - if (loadConfZone && connectionState == 100) { + if(loadConfZone && connectionState == 100) { configZone(continent) loadConfZone = false } - if(noSpawnPointHere) { RequestSanctuaryZoneSpawn(player, continent.Number) } @@ -3272,7 +3547,7 @@ class WorldSessionActor extends Actor * Allocate the listed squad members in zone and give their nameplates and their marquees the appropriate squad color. * @see `PlanetsideAttributeMessage` * @param members members of the squad to target - * @param value the assignment value + * @param value the assignment value */ def GiveSquadColorsInZone(members : Iterable[Long], value : Long) : Unit = { SquadMembersInZone(members).foreach { @@ -3290,16 +3565,23 @@ class WorldSessionActor extends Actor val players = continent.LivePlayers for { charId <- members - player = players.find { _.CharId == charId } + player = players.find { + _.CharId == charId + } if player.nonEmpty } yield player.get } def handleControlPkt(pkt : PlanetSideControlPacket) = { pkt match { - case sync @ ControlSync(diff, _, _, _, _, _, fa, fb) => + case sync@ControlSync(diff, _, _, _, _, _, fa, fb) => log.trace(s"SYNC: $sync") - val nextDiff = if(diff == 65535) { 0 } else { diff + 1 } + val nextDiff = if(diff == 65535) { + 0 + } + else { + diff + 1 + } val serverTick = ServerTick sendResponse(ControlSyncResp(nextDiff, serverTick, fa, fb, fb, fa)) @@ -3328,18 +3610,14 @@ class WorldSessionActor extends Actor case ConnectToWorldRequestMessage(server, token, majorVersion, minorVersion, revision, buildDate, unk) => val clientVersion = s"Client Version: $majorVersion.$minorVersion.$revision, $buildDate" log.info(s"New world login to $server with Token:$token. $clientVersion") - sendResponse(ChatMsg(ChatMessageType.CMT_CULLWATERMARK, false, "", "", None)) - Thread.sleep(40) - import scala.concurrent.ExecutionContext.Implicits.global clientKeepAlive.cancel clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient()) - accountIntermediary ! RetrieveAccountData(token) - case msg @ MountVehicleCargoMsg(player_guid, cargo_guid, carrier_guid, unk4) => + case msg@MountVehicleCargoMsg(player_guid, cargo_guid, carrier_guid, unk4) => log.info(msg.toString) (continent.GUID(cargo_guid), continent.GUID(carrier_guid)) match { case (Some(cargo : Vehicle), Some(carrier : Vehicle)) => @@ -3354,7 +3632,7 @@ class WorldSessionActor extends Actor case _ => ; } - case msg @ DismountVehicleCargoMsg(player_guid, cargo_guid, bailed, requestedByPassenger, kicked) => + case msg@DismountVehicleCargoMsg(player_guid, cargo_guid, bailed, requestedByPassenger, kicked) => log.info(msg.toString) //when kicked by carrier driver, player_guid will be PlanetSideGUID(0) //when exiting of the cargo vehicle driver's own accord, player_guid will be the cargo vehicle driver @@ -3368,7 +3646,7 @@ class WorldSessionActor extends Actor case _ => ; } - case msg @ CharacterCreateRequestMessage(name, head, voice, gender, empire) => + case msg@CharacterCreateRequestMessage(name, head, voice, gender, empire) => log.info("Handling " + msg) Database.getConnection.connect.onComplete { case scala.util.Success(connection) => @@ -3378,8 +3656,8 @@ class WorldSessionActor extends Actor case scala.util.Success(queryResult) => if(connection.isConnected) connection.disconnect queryResult match { - case row: ArrayRowData => // If we got a row from the database - if (row(0).asInstanceOf[Int] == account.AccountId) { // create char + case row : ArrayRowData => // If we got a row from the database + if(row(0).asInstanceOf[Int] == account.AccountId) { // create char self ! CreateCharacter(name, head, voice, gender, empire) sendResponse(ActionResultMessage.Fail(1)) Thread.sleep(50) @@ -3402,7 +3680,7 @@ class WorldSessionActor extends Actor sendResponse(ActionResultMessage.Fail(5)) } - case msg @ CharacterRequestMessage(charId, action) => + case msg@CharacterRequestMessage(charId, action) => log.info(s"Handling $msg") action match { case CharacterRequestAction.Delete => @@ -3443,11 +3721,10 @@ class WorldSessionActor extends Actor val lVoice : CharacterVoice.Value = CharacterVoice(row(5).asInstanceOf[Int]) log.info(s"CharacterRequest/Select: character $lName found in records") avatar = new Avatar(charId, lName, lFaction, lGender, lHead, lVoice) - var faction : String = lFaction.toString.toLowerCase - whenUsedLastMAXName(2) = faction+"hev_antipersonnel" - whenUsedLastMAXName(3) = faction+"hev_antivehicular" - whenUsedLastMAXName(1) = faction+"hev_antiaircraft" + whenUsedLastMAXName(2) = faction + "hev_antipersonnel" + whenUsedLastMAXName(3) = faction + "hev_antivehicular" + whenUsedLastMAXName(1) = faction + "hev_antiaircraft" accountPersistence ! AccountPersistenceService.Login(lName) case _ => log.error(s"CharacterRequest/Select: no character for $charId found") @@ -3468,7 +3745,7 @@ class WorldSessionActor extends Actor case KeepAliveMessage(code) => sendResponse(KeepAliveMessage()) - case msg @ BeginZoningMessage() => + case msg@BeginZoningMessage() => log.info("Reticulating splines ...") val continentId = continent.Id traveler.zone = continentId @@ -3546,9 +3823,14 @@ class WorldSessionActor extends Actor .filter(obj => obj.Definition.DeployCategory == DeployableCategory.Sensors && !obj.Destroyed && - (obj match { case jObj : JammableUnit => !jObj.Jammed; case _ => true }) + (obj match { + case jObj : JammableUnit => !jObj.Jammed; + case _ => true + }) ) - .foreach(obj => { sendResponse(TriggerEffectMessage(obj.GUID, "on", true, 1000)) }) + .foreach(obj => { + sendResponse(TriggerEffectMessage(obj.GUID, "on", true, 1000)) + }) //update the health of our faction's deployables (if necessary) //draw our faction's deployables on the map continent.DeployableList @@ -3573,7 +3855,9 @@ class WorldSessionActor extends Actor }) //load active players in zone (excepting players who are seated or players who are us) val live = continent.LivePlayers - live.filterNot(tplayer => { tplayer.GUID == player.GUID || tplayer.VehicleSeated.nonEmpty }) + live.filterNot(tplayer => { + tplayer.GUID == player.GUID || tplayer.VehicleSeated.nonEmpty + }) .foreach(char => { val tdefintion = char.Definition sendResponse(ObjectCreateMessage(tdefintion.ObjectId, char.GUID, char.Definition.Packet.ConstructorData(char).get)) @@ -3587,10 +3871,14 @@ class WorldSessionActor extends Actor } //load vehicles in zone (put separate the one we may be using) val (wreckages, (vehicles, usedVehicle)) = { - val (a, b) = continent.Vehicles.partition(vehicle => { vehicle.Destroyed && vehicle.Definition.DestroyedModel.nonEmpty }) + val (a, b) = continent.Vehicles.partition(vehicle => { + vehicle.Destroyed && vehicle.Definition.DestroyedModel.nonEmpty + }) (a, (continent.GUID(player.VehicleSeated) match { case Some(vehicle : Vehicle) if vehicle.PassengerInSeat(player).isDefined => - b.partition { _.GUID != vehicle.GUID } + b.partition { + _.GUID != vehicle.GUID + } case Some(_) => //vehicle, but we're not seated in it player.VehicleSeated = None @@ -3608,8 +3896,8 @@ class WorldSessionActor extends Actor sendResponse(ObjectCreateMessage(vdefinition.ObjectId, vguid, vdefinition.Packet.ConstructorData(vehicle).get)) //occupants other than driver vehicle.Seats - .filter({ case(index, seat) => seat.isOccupied && live.contains(seat.Occupant.get) && index > 0 }) - .foreach({ case(index, seat) => + .filter({ case (index, seat) => seat.isOccupied && live.contains(seat.Occupant.get) && index > 0 }) + .foreach({ case (index, seat) => val tplayer = seat.Occupant.get val tdefintion = tplayer.Definition sendResponse( @@ -3631,8 +3919,8 @@ class WorldSessionActor extends Actor //depict any other passengers already in this zone val vguid = vehicle.GUID vehicle.Seats - .filter({ case(index, seat) => seat.isOccupied && !seat.Occupant.contains(player) && live.contains(seat.Occupant.get) && index > 0 }) - .foreach({ case(index, seat) => + .filter({ case (index, seat) => seat.isOccupied && !seat.Occupant.contains(player) && live.contains(seat.Occupant.get) && index > 0 }) + .foreach({ case (index, seat) => val tplayer = seat.Occupant.get val tdefintion = tplayer.Definition sendResponse( @@ -3668,7 +3956,8 @@ class WorldSessionActor extends Actor vehicles.collect { case vehicle if vehicle.CargoHolds.nonEmpty => vehicle.CargoHolds.collect({ case (index, hold) if hold.isOccupied => { CargoBehavior.CargoMountBehaviorForAll(vehicle, hold.Occupant.get, index) //CargoMountBehaviorForUs can fail to attach the cargo vehicle on some clients - }}) + } + }) } //special deploy states val deployedVehicles = vehicles.filter(_.DeploymentState == DriveState.Deployed) @@ -3719,7 +4008,7 @@ class WorldSessionActor extends Actor //base turrets continent.Map.TurretToWeapon - .map { case((turret_guid, _)) => continent.GUID(turret_guid) } + .map { case ((turret_guid, _)) => continent.GUID(turret_guid) } .collect { case Some(turret : FacilityTurret) => val pguid = turret.GUID //attached weapon @@ -3762,6 +4051,9 @@ class WorldSessionActor extends Actor playerStateMessageUpstreamCount += 1 val isMoving = WorldEntity.isMoving(vel) val isMovingPlus = isMoving || is_jumping || jump_thrust + if(isMovingPlus) { + CancelZoningProcessWithDescriptiveReason("cancel_motion") + } if(deadState == DeadState.Alive && playerStateMessageUpstreamCount % 2 == 0) { // Regen stamina roughly every 500ms if(player.skipStaminaRegenForTurns > 0) { @@ -3772,16 +4064,17 @@ class WorldSessionActor extends Actor player.Stamina += 1 } } - player.Position = pos player.Velocity = vel player.Orientation = Vector3(player.Orientation.x, pitch, yaw) player.FacingYawUpper = yaw_upper player.Crouching = is_crouching player.Jumping = is_jumping + if(is_cloaking && !player.Cloaked) { + CancelZoningProcessWithDescriptiveReason("cancel_cloak") + } player.Cloaked = player.ExoSuit == ExoSuitType.Infiltration && is_cloaking CapacitorTick(jump_thrust) - if(isMovingPlus && usingMedicalTerminal.isDefined) { continent.GUID(usingMedicalTerminal) match { case Some(term : Terminal with ProximityUnit) => @@ -3809,7 +4102,7 @@ class WorldSessionActor extends Actor sendResponse(UnuseItemMessage(guid, guid)) accessedContainer = None } - case None => ; + case None => ; } val wepInHand : Boolean = player.Slot(player.DrawnSlot).Equipment match { case Some(item) => item.Definition == GlobalDefinitions.bolt_driver @@ -3818,7 +4111,7 @@ class WorldSessionActor extends Actor continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.PlayerState(avatar_guid, player.Position, player.Velocity, yaw, pitch, yaw_upper, seq_time, is_crouching, is_jumping, jump_thrust, is_cloaking, spectator, wepInHand)) updateSquad() - case msg @ ChildObjectStateMessage(object_guid, pitch, yaw) => + case msg@ChildObjectStateMessage(object_guid, pitch, yaw) => //the majority of the following check retrieves information to determine if we are in control of the child FindContainedWeapon match { case (Some(_), Some(tool)) => @@ -3833,11 +4126,11 @@ class WorldSessionActor extends Actor case (Some(obj), None) => log.warn(s"ChildObjectState: ${player.Name} can not find any controllable agent, let alone #${object_guid.guid}") case (None, _) => ; - //TODO status condition of "playing getting out of vehicle to allow for late packets without warning - //log.warn(s"ChildObjectState: player $player not related to anything with a controllable agent") + //TODO status condition of "playing getting out of vehicle to allow for late packets without warning + //log.warn(s"ChildObjectState: player $player not related to anything with a controllable agent") } - case msg @ VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, flying, unk6, unk7, wheels, unk9, is_cloaked) => + case msg@VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, flying, unk6, unk7, wheels, unk9, is_cloaked) => if(deadState == DeadState.Alive) { GetVehicleAndSeat() match { case (Some(obj), Some(0)) => @@ -3860,22 +4153,27 @@ class WorldSessionActor extends Actor obj.Velocity = None obj.Flying = false } - continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, obj.Position, ang, obj.Velocity, if(obj.Flying) { flying } else { None }, unk6, unk7, wheels, unk9, obj.Cloaked)) + continent.VehicleEvents ! VehicleServiceMessage(continent.Id, VehicleAction.VehicleState(player.GUID, vehicle_guid, unk1, obj.Position, ang, obj.Velocity, if(obj.Flying) { + flying + } + else { + None + }, unk6, unk7, wheels, unk9, obj.Cloaked)) updateSquad() case (None, _) => - //log.error(s"VehicleState: no vehicle $vehicle_guid found in zone") - //TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle + //log.error(s"VehicleState: no vehicle $vehicle_guid found in zone") + //TODO placing a "not driving" warning here may trigger as we are disembarking the vehicle case (_, Some(index)) => log.error(s"VehicleState: player should not be dispatching this kind of packet from vehicle#$vehicle_guid when not the driver ($index)") case _ => ; } } - //log.info(s"VehicleState: $msg") + //log.info(s"VehicleState: $msg") - case msg @ VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) => + case msg@VehicleSubStateMessage(vehicle_guid, player_guid, vehicle_pos, vehicle_ang, vel, unk1, unk2) => //log.info(s"VehicleSubState: $vehicle_guid, $player_guid, $vehicle_pos, $vehicle_ang, $vel, $unk1, $unk2") - case msg @ ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) => + case msg@ProjectileStateMessage(projectile_guid, shot_pos, shot_vel, shot_orient, seq, end, target_guid) => //log.trace(s"ProjectileState: $msg") val index = projectile_guid.guid - Projectile.BaseUID projectiles(index) match { @@ -3891,7 +4189,7 @@ class WorldSessionActor extends Actor log.warn(s"ProjectileState: constructed projectile ${projectile_guid.guid} can not be found") } - case msg @ ReleaseAvatarRequestMessage() => + case msg@ReleaseAvatarRequestMessage() => log.info(s"ReleaseAvatarRequest: ${player.GUID} on ${continent.Id} has released") reviveTimer.cancel GoToDeploymentMap() @@ -3920,7 +4218,7 @@ class WorldSessionActor extends Actor taskResolver ! GUIDTask.UnregisterPlayer(player)(continent.GUID) } - case msg @ SpawnRequestMessage(u1, spawn_type, u3, u4, zone_number) => + case msg@SpawnRequestMessage(u1, spawn_type, u3, u4, zone_number) => log.info(s"SpawnRequestMessage: $msg") if(deadState != DeadState.RespawnTime) { deadState = DeadState.RespawnTime @@ -3930,10 +4228,10 @@ class WorldSessionActor extends Actor log.warn("SpawnRequestMessage: request consumed; already respawning ...") } - case msg @ SetChatFilterMessage(send_channel, origin, whitelist) => - //log.info("SetChatFilters: " + msg) + case msg@SetChatFilterMessage(send_channel, origin, whitelist) => + //log.info("SetChatFilters: " + msg) - case msg @ ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) => + case msg@ChatMsg(messagetype, has_wide_contents, recipient, contents, note_contents) => var makeReply : Boolean = false var echoContents : String = contents val trimContents = contents.trim @@ -3944,7 +4242,8 @@ class WorldSessionActor extends Actor if(!flying) { flying = true sendResponse(ChatMsg(ChatMessageType.CMT_FLY, msg.wideContents, msg.recipient, "on", msg.note)) - } else { + } + else { flying = false sendResponse(ChatMsg(ChatMessageType.CMT_FLY, msg.wideContents, msg.recipient, "off", msg.note)) } @@ -3967,12 +4266,62 @@ class WorldSessionActor extends Actor if(!spectator) { spectator = true sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, msg.wideContents, msg.recipient, "on", msg.note)) - } else { + } + else { spectator = false sendResponse(ChatMsg(ChatMessageType.CMT_TOGGLESPECTATORMODE, msg.wideContents, msg.recipient, "off", msg.note)) } } - + else if(messagetype == ChatMessageType.CMT_RECALL) { + makeReply = false + val sanctuary = Zones.SanctuaryZoneId(player.Faction) + if(zoningType == Zoning.Method.InstantAction) { + sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@noinstantaction_instantactionting", None)) + } + else if(zoningType == Zoning.Method.Recall) { + sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "You already requested to recall to your sanctuary continent.", None)) + } + else if(continent.Id.equals(sanctuary)) { + //nonstandard message + sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "You can't recall when you are already on your faction's sanctuary continent.", None)) + } + else if(deadState != DeadState.Alive) { + sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@norecall_dead", None)) + } + else if(player.VehicleSeated.nonEmpty) { + sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@norecall_invehicle", None)) + } + else { + zoningType = Zoning.Method.Recall + zoningChatMessageType = messagetype + zoningStatus = Zoning.Status.Request + zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset()) + cluster ! Zoning.Recall.Request(player.Faction, sanctuary) + } + } + else if(messagetype == ChatMessageType.CMT_INSTANTACTION) { + makeReply = false + if(zoningType == Zoning.Method.InstantAction) { + sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@noinstantaction_instantactionting", None)) + } + else if(zoningType == Zoning.Method.Recall) { + //nonstandard message + sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "You already requested to recall to your sanctuary continent.", None)) + } + else if(deadState != DeadState.Alive) { + sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@noinstantaction_dead", None)) + } + else if(player.VehicleSeated.nonEmpty) { + sendResponse(ChatMsg(ChatMessageType.CMT_QUIT, false, "", "@noinstantaction_invehicle", None)) + } + else { + zoningType = Zoning.Method.InstantAction + zoningChatMessageType = messagetype + zoningStatus = Zoning.Status.Request + zoningReset = context.system.scheduler.scheduleOnce(10 seconds, self, ZoningReset()) + cluster ! Zoning.InstantAction.Request(player.Faction) + } + } CSRZone.read(traveler, msg) match { case (true, zone, pos) if player.isAlive => deadState = DeadState.Release //cancel movement updates @@ -3982,13 +4331,16 @@ class WorldSessionActor extends Actor vehicle.PassengerInSeat(player) match { case Some(0) => vehicle.Position = pos + CancelAllProximityUnits() LoadZonePhysicalSpawnPoint(zone, pos, Vector3.Zero, 0) case _ => //not seated as the driver, in which case we can't move deadState = DeadState.Alive } case None => player.Position = pos + CancelAllProximityUnits() //continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, player.GUID)) + CancelZoningProcess() LoadZonePhysicalSpawnPoint(zone, pos, Vector3.Zero, 0) case _ => //seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move deadState = DeadState.Alive @@ -3996,7 +4348,6 @@ class WorldSessionActor extends Actor case (_, _, _) => ; } - CSRWarp.read(traveler, msg) match { case (true, pos) if player.isAlive => deadState = DeadState.Release //cancel movement updates @@ -4006,12 +4357,15 @@ class WorldSessionActor extends Actor vehicle.PassengerInSeat(player) match { case Some(0) => vehicle.Position = pos + CancelAllProximityUnits() LoadZonePhysicalSpawnPoint(continent.Id, pos, Vector3.z(vehicle.Orientation.z), 0) case _ => //not seated as the driver, in which case we can't move deadState = DeadState.Alive } case None => player.Position = pos + CancelAllProximityUnits() + CancelZoningProcessWithDescriptiveReason("cancel_motion") sendResponse(PlayerStateShiftMessage(ShiftState(0, pos, player.Orientation.z, None))) deadState = DeadState.Alive //must be set here case _ => //seated in something that is not a vehicle or the vehicle is cargo, in which case we can't move @@ -4033,11 +4387,13 @@ class WorldSessionActor extends Actor if(player.isAlive && deadState != DeadState.Release) { Suicide(player) } - } else if(messagetype == ChatMessageType.CMT_CULLWATERMARK) { + } + else if(messagetype == ChatMessageType.CMT_CULLWATERMARK) { if(trimContents.contains("40 80")) connectionState = 100 else if(trimContents.contains("120 200")) connectionState = 25 else connectionState = 50 - } else if(messagetype == ChatMessageType.CMT_DESTROY) { + } + else if(messagetype == ChatMessageType.CMT_DESTROY) { makeReply = true val guid = contents.toInt continent.GUID(continent.Map.TerminalToSpawnPad.getOrElse(guid, guid)) match { @@ -4048,9 +4404,11 @@ class WorldSessionActor extends Actor case _ => self ! PacketCoding.CreateGamePacket(0, RequestDestroyMessage(PlanetSideGUID(guid))) } - } else if(messagetype == ChatMessageType.CMT_VOICE) { + } + else if(messagetype == ChatMessageType.CMT_VOICE) { sendResponse(ChatMsg(ChatMessageType.CMT_VOICE, false, player.Name, contents, None)) - } else if(messagetype == ChatMessageType.CMT_QUIT) { // TODO: handle this appropriately + } + else if(messagetype == ChatMessageType.CMT_QUIT) { // TODO: handle this appropriately sendResponse(DropCryptoSession()) sendResponse(DropSession(sessionId, "user quit")) } @@ -4336,36 +4694,34 @@ class WorldSessionActor extends Actor if(makeReply) { sendResponse(ChatMsg(messagetype, has_wide_contents, recipient, echoContents, note_contents)) } - - if (messagetype == ChatMessageType.CMT_OPEN && !player.silenced) { + if(messagetype == ChatMessageType.CMT_OPEN && !player.silenced) { chatService ! ChatServiceMessage("local", ChatAction.Local(player.GUID, player.Name, continent, player.Position, player.Faction, msg)) } - else if (messagetype == ChatMessageType.CMT_VOICE) { + else if(messagetype == ChatMessageType.CMT_VOICE) { chatService ! ChatServiceMessage("voice", ChatAction.Voice(player.GUID, player.Name, continent, player.Position, player.Faction, msg)) } - else if (messagetype == ChatMessageType.CMT_TELL && !player.silenced) { + else if(messagetype == ChatMessageType.CMT_TELL && !player.silenced) { chatService ! ChatServiceMessage("tell", ChatAction.Tell(player.GUID, player.Name, msg)) } - else if (messagetype == ChatMessageType.CMT_BROADCAST && !player.silenced) { + else if(messagetype == ChatMessageType.CMT_BROADCAST && !player.silenced) { chatService ! ChatServiceMessage("broadcast", ChatAction.Broadcast(player.GUID, player.Name, continent, player.Position, player.Faction, msg)) } - else if (messagetype == ChatMessageType.CMT_NOTE) { + else if(messagetype == ChatMessageType.CMT_NOTE) { chatService ! ChatServiceMessage("note", ChatAction.Note(player.GUID, player.Name, msg)) } - else if (messagetype == ChatMessageType.CMT_SILENCE && admin) { + else if(messagetype == ChatMessageType.CMT_SILENCE && admin) { chatService ! ChatServiceMessage("gm", ChatAction.GM(player.GUID, player.Name, msg)) } - else if (messagetype == ChatMessageType.CMT_SQUAD && !player.silenced) { + else if(messagetype == ChatMessageType.CMT_SQUAD && !player.silenced) { chatService ! ChatServiceMessage("squad", ChatAction.Squad(player.GUID, player.Name, continent, player.Position, player.Faction, msg)) } - else if (messagetype == ChatMessageType.CMT_WHO || messagetype == ChatMessageType.CMT_WHO_CSR || messagetype == ChatMessageType.CMT_WHO_CR || + else if(messagetype == ChatMessageType.CMT_WHO || messagetype == ChatMessageType.CMT_WHO_CSR || messagetype == ChatMessageType.CMT_WHO_CR || messagetype == ChatMessageType.CMT_WHO_PLATOONLEADERS || messagetype == ChatMessageType.CMT_WHO_SQUADLEADERS || messagetype == ChatMessageType.CMT_WHO_TEAMS) { val poplist = continent.Players val popTR = poplist.count(_.faction == PlanetSideEmpire.TR) val popNC = poplist.count(_.faction == PlanetSideEmpire.NC) val popVS = poplist.count(_.faction == PlanetSideEmpire.VS) val contName = continent.Map.Name - StartBundlingPackets() sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "That command doesn't work for now, but : ", None)) sendResponse(ChatMsg(ChatMessageType.CMT_WHO, true, "", "NC online : " + popNC + " on " + contName, None)) @@ -4374,17 +4730,17 @@ class WorldSessionActor extends Actor StopBundlingPackets() } - case msg @ VoiceHostRequest(unk, PlanetSideGUID(player_guid), data) => - log.info("Player "+player_guid+" requested in-game voice chat.") + case msg@VoiceHostRequest(unk, PlanetSideGUID(player_guid), data) => + log.info("Player " + player_guid + " requested in-game voice chat.") sendResponse(VoiceHostKill()) - case msg @ VoiceHostInfo(player_guid, data) => + case msg@VoiceHostInfo(player_guid, data) => sendResponse(VoiceHostKill()) - case msg @ ChangeAmmoMessage(item_guid, unk1) => + case msg@ChangeAmmoMessage(item_guid, unk1) => log.info("ChangeAmmo: " + msg) FindContainedEquipment match { - case(Some(_), Some(obj : ConstructionItem)) => + case (Some(_), Some(obj : ConstructionItem)) => PerformConstructionItemAmmoChange(obj, obj.AmmoTypeIndex) case (Some(obj), Some(tool : Tool)) => PerformToolAmmoChange(tool, obj) @@ -4394,7 +4750,7 @@ class WorldSessionActor extends Actor log.error(s"ChangeAmmo: can not find $item_guid") } - case msg @ ChangeFireModeMessage(item_guid, fire_mode) => + case msg@ChangeFireModeMessage(item_guid, fire_mode) => log.info("ChangeFireMode: " + msg) FindEquipment match { case Some(obj : PlanetSideGameObject with FireModeSwitch[_]) => @@ -4422,7 +4778,7 @@ class WorldSessionActor extends Actor log.error(s"ChangeFireMode: can not find $item_guid") } - case msg @ ChangeFireStateMessage_Start(item_guid) => + case msg@ChangeFireStateMessage_Start(item_guid) => log.trace("ChangeFireState_Start: " + msg) if(shooting.isEmpty) { FindEquipment match { @@ -4448,7 +4804,7 @@ class WorldSessionActor extends Actor } } - case msg @ ChangeFireStateMessage_Stop(item_guid) => + case msg@ChangeFireStateMessage_Stop(item_guid) => log.trace("ChangeFireState_Stop: " + msg) prefire = None val weapon : Option[Equipment] = if(shooting.contains(item_guid)) { @@ -4463,12 +4819,12 @@ class WorldSessionActor extends Actor if(tool.Definition == GlobalDefinitions.phoenix && tool.Projectile != GlobalDefinitions.phoenix_missile_guided_projectile) { //suppress the decimator's alternate fire mode, however - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid)) + continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Start(player.GUID, item_guid)) } - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid)) + continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid)) Some(tool) case Some(tool) => //permissible, for now - continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid)) + continent.AvatarEvents ! AvatarServiceMessage(continent.Id, AvatarAction.ChangeFireState_Stop(player.GUID, item_guid)) Some(tool) case _ => log.warn(s"ChangeFireState_Stop: received an unexpected message about $item_guid") @@ -4496,11 +4852,11 @@ class WorldSessionActor extends Actor } progressBarUpdate.cancel //TODO independent action? - case msg @ EmoteMsg(avatar_guid, emote) => + case msg@EmoteMsg(avatar_guid, emote) => log.info("Emote: " + msg) sendResponse(EmoteMsg(avatar_guid, emote)) - case msg @ DropItemMessage(item_guid) => + case msg@DropItemMessage(item_guid) => log.info(s"DropItem: $msg") ValidObject(item_guid) match { case Some(anItem : Equipment) => @@ -4519,7 +4875,7 @@ class WorldSessionActor extends Actor log.warn(s"DropItem: $player wanted to drop an item ($item_guid), but it was nowhere to be found") } - case msg @ PickupItemMessage(item_guid, player_guid, unk1, unk2) => + case msg@PickupItemMessage(item_guid, player_guid, unk1, unk2) => log.info(s"PickupItem: $msg") ValidObject(item_guid) match { case Some(item : Equipment) => @@ -4534,7 +4890,7 @@ class WorldSessionActor extends Actor sendResponse(ObjectDeleteMessage(item_guid, 0)) } - case msg @ ReloadMessage(item_guid, ammo_clip, unk1) => + case msg@ReloadMessage(item_guid, ammo_clip, unk1) => log.info("Reload: " + msg) FindContainedWeapon match { case (Some(obj), Some(tool : Tool)) => @@ -4546,7 +4902,7 @@ class WorldSessionActor extends Actor case Nil => log.warn(s"ReloadMessage: no ammunition could be found for $item_guid") case x :: xs => - val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match { + val (deleteFunc, modifyFunc) : ((Int, AmmoBox) => Unit, (AmmoBox, Int) => Unit) = obj match { case (veh : Vehicle) => (DeleteEquipmentFromVehicle(veh), ModifyAmmunitionInVehicle(veh)) case _ => @@ -4556,7 +4912,12 @@ class WorldSessionActor extends Actor deleteFunc(item.start, item.obj.asInstanceOf[AmmoBox]) }) val box = x.obj.asInstanceOf[AmmoBox] - val tailReloadValue : Int = if(xs.isEmpty) { 0 } else { xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).reduceLeft(_ + _) } + val tailReloadValue : Int = if(xs.isEmpty) { + 0 + } + else { + xs.map(_.obj.asInstanceOf[AmmoBox].Capacity).reduceLeft(_ + _) + } val sumReloadValue : Int = box.Capacity + tailReloadValue val actualReloadValue = (if(sumReloadValue <= reloadValue) { deleteFunc(x.start, box) @@ -4581,7 +4942,7 @@ class WorldSessionActor extends Actor log.error(s"ReloadMessage: can not find $item_guid") } - case msg @ ObjectHeldMessage(avatar_guid, held_holsters, unk1) => + case msg@ObjectHeldMessage(avatar_guid, held_holsters, unk1) => log.info(s"ObjectHeld: $msg") val before = player.DrawnSlot if(before != held_holsters) { @@ -4617,26 +4978,27 @@ class WorldSessionActor extends Actor } } - case msg @ AvatarJumpMessage(state) => + case msg@AvatarJumpMessage(state) => //log.info("AvatarJump: " + msg) player.Stamina = player.Stamina - 10 player.skipStaminaRegenForTurns = math.max(player.skipStaminaRegenForTurns, 5) - case msg @ ZipLineMessage(player_guid,forwards,action,path_id,pos) => + case msg@ZipLineMessage(player_guid, forwards, action, path_id, pos) => log.info("ZipLineMessage: " + msg) - - val (isTeleporter : Boolean, path: Option[ZipLinePath]) = continent.ZipLinePaths.find(x => x.PathId == path_id) match { + val (isTeleporter : Boolean, path : Option[ZipLinePath]) = continent.ZipLinePaths.find(x => x.PathId == path_id) match { case Some(x) => (x.IsTeleporter, Some(x)) case _ => log.warn(s"Couldn't find zipline path ${path_id} in zone ${continent.Number} / ${continent.Id}") (false, None) } - if(isTeleporter) { + CancelZoningProcessWithDescriptiveReason("cancel") val endPoint = path.get.ZipLinePoints.last sendResponse(ZipLineMessage(PlanetSideGUID(0), forwards, 0, path_id, pos)) // todo: send to zone to show teleport animation to all clients sendResponse(PlayerStateShiftMessage(ShiftState(0, endPoint, player.Orientation.z, None))) - } else { + } + else { + CancelZoningProcessWithDescriptiveReason("cancel_motion") action match { case 0 => // Travel along the zipline in the direction specified @@ -4652,7 +5014,7 @@ class WorldSessionActor extends Actor } } - case msg @ RequestDestroyMessage(object_guid) => + case msg@RequestDestroyMessage(object_guid) => // TODO: Make sure this is the correct response for all cases ValidObject(object_guid) match { case Some(vehicle : Vehicle) => @@ -4673,7 +5035,7 @@ class WorldSessionActor extends Actor case Some(boomer : BoomerDeployable) => boomer.Trigger = None continent.LocalEvents ! LocalServiceMessage.Deployables(RemoverActor.AddTask(boomer, continent, Some(0 seconds))) - //continent.Deployables ! Zone.Deployable.Dismiss(boomer) + //continent.Deployables ! Zone.Deployable.Dismiss(boomer) case Some(thing) => log.info(s"RequestDestroy: BoomerTrigger object connected to wrong object - $thing") case None => ; @@ -4737,11 +5099,11 @@ class WorldSessionActor extends Actor log.warn(s"RequestDestroy: object ${object_guid.guid} not found") } - case msg @ ObjectDeleteMessage(object_guid, unk1) => + case msg@ObjectDeleteMessage(object_guid, unk1) => sendResponse(ObjectDeleteMessage(object_guid, 0)) log.info("ObjectDelete: " + msg) - case msg @ MoveItemMessage(item_guid, source_guid, destination_guid, dest, _) => + case msg@MoveItemMessage(item_guid, source_guid, destination_guid, dest, _) => log.info(s"MoveItem: $msg") (continent.GUID(source_guid), continent.GUID(destination_guid), continent.GUID(item_guid)) match { case (Some(source : Container), Some(destination : Container), Some(item : Equipment)) => @@ -4797,24 +5159,23 @@ class WorldSessionActor extends Actor log.error(s"MoveItem: wanted to move $item_guid from $source_guid to $destination_guid, but multiple problems were encountered") } - case msg @ LootItemMessage(item_guid, target_guid) => + case msg@LootItemMessage(item_guid, target_guid) => log.info(s"LootItem: $msg") (ValidObject(item_guid), ValidObject(target_guid)) match { case (Some(item : Equipment), Some(target : Container)) => //figure out the source - ( - { - val findFunc : PlanetSideGameObject with Container => Option[(PlanetSideGameObject with Container, Option[Int])] = FindInLocalContainer(item_guid) - findFunc(player.Locker) - .orElse(findFunc(player)) - .orElse(accessedContainer match { - case Some(parent) => - findFunc(parent) - case None => - None - } + ( { + val findFunc : PlanetSideGameObject with Container => Option[(PlanetSideGameObject with Container, Option[Int])] = FindInLocalContainer(item_guid) + findFunc(player.Locker) + .orElse(findFunc(player)) + .orElse(accessedContainer match { + case Some(parent) => + findFunc(parent) + case None => + None + } ) - }, target.Fit(item)) match { + }, target.Fit(item)) match { case (Some((source, Some(index))), Some(dest)) => if(PermitEquipmentStow(item, target)) { StartBundlingPackets() @@ -4842,6 +5203,7 @@ class WorldSessionActor extends Actor case msg @ AvatarImplantMessage(player_guid, action, slot, status) => log.info("AvatarImplantMessage: " + msg) if(action == ImplantAction.Activation) { + CancelZoningProcessWithDescriptiveReason("cancel_implant") player.Actor ! Player.ImplantActivation(slot, status) } @@ -4850,7 +5212,7 @@ class WorldSessionActor extends Actor // TODO: Not all fields in the response are identical to source in real packet logs (but seems to be ok) // TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg) val equipment = player.Slot(player.DrawnSlot).Equipment match { - case out @ Some(item) if item.GUID == item_used_guid => out + case out@Some(item) if item.GUID == item_used_guid => out case _ => None } ValidObject(object_guid) match { @@ -4859,7 +5221,6 @@ class WorldSessionActor extends Actor case Some(lock_guid) => val lock = continent.GUID(lock_guid).get.asInstanceOf[IFFLock] val owner = lock.Owner.asInstanceOf[Building] - val playerIsOnInside = Vector3.ScalarProjection(lock.Outwards, player.Position - door.Position) < 0f // If an IFF lock exists and the IFF lock faction doesn't match the current player and one of the following conditions are met open the door: @@ -4885,11 +5246,13 @@ class WorldSessionActor extends Actor case Some(panel : IFFLock) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") panel.Actor ! CommonMessages.Use(player, Some(item)) case _ => ; } case Some(obj : Player) => + CancelZoningProcessWithDescriptiveReason("cancel_use") if(obj.isBackpack) { log.info(s"UseItem: $player looting the corpse of $obj") sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) @@ -4909,7 +5272,7 @@ class WorldSessionActor extends Actor } else { player.Find(kit) match { - case Some(index) => + case Some(index) => whenUsedLastKit = System.currentTimeMillis player.Slot(index).Equipment = None //remove from slot immediately; must exist on client for next packet sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, 0, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) @@ -5006,14 +5369,13 @@ class WorldSessionActor extends Actor log.warn(s"UseItem: anticipated a Kit $item_used_guid, but can't find it") } } - else if (itemType == ObjectClass.avatar && unk3) { + else if(itemType == ObjectClass.avatar && unk3) { equipment match { - case Some(tool: Tool) if tool.Definition == GlobalDefinitions.bank => + case Some(tool : Tool) if tool.Definition == GlobalDefinitions.bank => obj.Actor ! CommonMessages.Use(player, equipment) - case Some(tool: Tool) if tool.Definition == GlobalDefinitions.medicalapplicator => + case Some(tool : Tool) if tool.Definition == GlobalDefinitions.medicalapplicator => obj.Actor ! CommonMessages.Use(player, equipment) - case _ => ; } } @@ -5021,9 +5383,11 @@ class WorldSessionActor extends Actor case Some(locker : Locker) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") locker.Actor ! CommonMessages.Use(player, Some(item)) case None if locker.Faction == player.Faction || !locker.HackedBy.isEmpty => log.trace(s"UseItem: $player accessing a locker") + CancelZoningProcessWithDescriptiveReason("cancel_use") val container = player.Locker accessedContainer = Some(container) sendResponse(UseItemMessage(avatar_guid, item_used_guid, container.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, 456)) @@ -5033,6 +5397,7 @@ class WorldSessionActor extends Actor case Some(gen : Generator) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") gen.Actor ! CommonMessages.Use(player, Some(item)) case None => ; } @@ -5040,6 +5405,7 @@ class WorldSessionActor extends Actor case Some(mech : ImplantTerminalMech) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") mech.Actor ! CommonMessages.Use(player, Some(item)) case None => ; } @@ -5047,6 +5413,7 @@ class WorldSessionActor extends Actor case Some(captureTerminal : CaptureTerminal) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") captureTerminal.Actor ! CommonMessages.Use(player, Some(item)) case _ => ; } @@ -5054,6 +5421,7 @@ class WorldSessionActor extends Actor case Some(obj : FacilityTurret) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") obj.Actor ! CommonMessages.Use(player, Some(item)) //try generic obj.Actor ! CommonMessages.Use(player, Some((item, unk2.toInt))) //try upgrade path case _ => ; @@ -5062,17 +5430,19 @@ class WorldSessionActor extends Actor case Some(obj : Vehicle) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") obj.Actor ! CommonMessages.Use(player, Some(item)) case None if player.Faction == obj.Faction => //access to trunk if(obj.AccessingTrunk.isEmpty && (!obj.PermissionGroup(AccessPermissionGroup.Trunk.id).contains(VehicleLockState.Locked) || obj.Owner.contains(player.GUID))) { + CancelZoningProcessWithDescriptiveReason("cancel_use") obj.AccessingTrunk = player.GUID accessedContainer = Some(obj) AccessContents(obj) sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) - } + } case _ => ; } @@ -5080,12 +5450,14 @@ class WorldSessionActor extends Actor log.info(s"$msg") equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") terminal.Actor ! CommonMessages.Use(player, Some(item)) case None if terminal.Faction == player.Faction || terminal.HackedBy.nonEmpty => val tdef = terminal.Definition if(tdef.isInstanceOf[MatrixTerminalDefinition]) { //TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks) + CancelZoningProcessWithDescriptiveReason("cancel_use") sendResponse(BindPlayerMessage(BindStatus.Bind, "", true, true, SpawnGroup.Sanctuary, 0, 0, terminal.Position)) } else if(tdef == GlobalDefinitions.multivehicle_rearm_terminal || tdef == GlobalDefinitions.bfr_rearm_terminal || @@ -5100,12 +5472,14 @@ class WorldSessionActor extends Actor } else if(tdef == GlobalDefinitions.teleportpad_terminal) { //explicit request + CancelZoningProcessWithDescriptiveReason("cancel_use") terminal.Actor ! Terminal.Request( player, ItemTransactionMessage(object_guid, TransactionType.Buy, 0, "router_telepad", 0, PlanetSideGUID(0)) ) } else { + CancelZoningProcessWithDescriptiveReason("cancel_use") sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) } @@ -5115,9 +5489,11 @@ class WorldSessionActor extends Actor case Some(obj : SpawnTube) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") obj.Actor ! CommonMessages.Use(player, Some(item)) case None if player.Faction == obj.Faction => //deconstruction + CancelZoningProcessWithDescriptiveReason("cancel_use") PlayerActionsToCancel() CancelAllProximityUnits() continent.Population ! Zone.Population.Release(avatar) @@ -5128,6 +5504,7 @@ class WorldSessionActor extends Actor case Some(obj : SensorDeployable) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") obj.Actor ! CommonMessages.Use(player, Some(item)) case _ => ; } @@ -5135,6 +5512,7 @@ class WorldSessionActor extends Actor case Some(obj : TurretDeployable) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") obj.Actor ! CommonMessages.Use(player, Some(item)) case _ => ; } @@ -5142,6 +5520,7 @@ class WorldSessionActor extends Actor case Some(obj : TrapDeployable) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") obj.Actor ! CommonMessages.Use(player, Some(item)) case _ => ; } @@ -5149,6 +5528,7 @@ class WorldSessionActor extends Actor case Some(obj : ShieldGeneratorDeployable) => equipment match { case Some(item) => + CancelZoningProcessWithDescriptiveReason("cancel_use") obj.Actor ! CommonMessages.Use(player, Some(item)) case _ => ; } @@ -5158,6 +5538,7 @@ class WorldSessionActor extends Actor case Some(vehicle : Vehicle) => vehicle.Utility(UtilityType.internal_router_telepad_deployable) match { case Some(util : Utility.InternalTelepad) => + CancelZoningProcessWithDescriptiveReason("cancel") UseRouterTelepadSystem(router = vehicle, internalTelepad = util, remoteTelepad = obj, src = obj, dest = util) case _ => log.error(s"telepad@${object_guid.guid} is not linked to a router - ${vehicle.Definition.Name}, ${obj.Router}") @@ -5170,6 +5551,7 @@ class WorldSessionActor extends Actor case Some(obj : Utility.InternalTelepad) => continent.GUID(obj.Telepad) match { case Some(pad : TelepadDeployable) => + CancelZoningProcessWithDescriptiveReason("cancel") UseRouterTelepadSystem(router = obj.Owner.asInstanceOf[Vehicle], internalTelepad = obj, remoteTelepad = pad, src = obj, dest = pad) case Some(o) => log.error(s"internal telepad@${object_guid.guid} is not linked to a remote telepad - ${o.Definition.Name}@${o.GUID.guid}") @@ -5177,6 +5559,7 @@ class WorldSessionActor extends Actor } case Some(obj) => + CancelZoningProcessWithDescriptiveReason("cancel_use") log.warn(s"UseItem: don't know how to handle $obj; taking a shot in the dark") sendResponse(UseItemMessage(avatar_guid, item_used_guid, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) @@ -5229,6 +5612,7 @@ class WorldSessionActor extends Actor turret } log.info(s"DeployObject: Constructing a ${ammoType}") + CancelZoningProcessWithDescriptiveReason("cancel_use") val dObj : PlanetSideGameObject with Deployable = Deployables.Make(ammoType)() dObj.Position = pos dObj.Orientation = orient @@ -5345,6 +5729,7 @@ class WorldSessionActor extends Actor log.info(s"ItemTransaction: ${term.Definition.Name} found") if(lastTerminalOrderFulfillment) { lastTerminalOrderFulfillment = false + CancelZoningProcessWithDescriptiveReason("cancel_use") term.Actor ! Terminal.Request(player, msg) } case Some(obj : PlanetSideGameObject) => @@ -5375,6 +5760,7 @@ class WorldSessionActor extends Actor None }) match { case Some(owner : Player) => //InfantryLoadout + CancelZoningProcessWithDescriptiveReason("cancel_use") avatar.EquipmentLoadouts.SaveLoadout(owner, name, lineno) SaveLoadoutToDB(owner, name, lineno) import InfantryLoadout._ @@ -5388,6 +5774,7 @@ class WorldSessionActor extends Actor } case FavoritesAction.Delete => + CancelZoningProcessWithDescriptiveReason("cancel_use") avatar.EquipmentLoadouts.DeleteLoadout(lineno) sendResponse(FavoritesMessage(list, player_guid, line, "")) @@ -5409,6 +5796,7 @@ class WorldSessionActor extends Actor case msg @ WeaponFireMessage(seq_time, weapon_guid, projectile_guid, shot_origin, unk1, unk2, unk3, unk4, unk5, unk6, unk7) => log.info(s"WeaponFire: $msg") + CancelZoningProcessWithDescriptiveReason("cancel_fire") if(player.isShielded) { // Cancel NC MAX shield if it's active ToggleMaxSpecialState(enable = false) @@ -5572,6 +5960,7 @@ class WorldSessionActor extends Actor case msg @ WarpgateRequest(continent_guid, building_guid, dest_building_guid, dest_continent_guid, unk1, unk2) => log.info(s"WarpgateRequest: $msg") + CancelZoningProcessWithDescriptiveReason("cancel_use") if(deadState != DeadState.RespawnTime) { continent.Buildings.values.find(building => building.GUID == building_guid) match { case Some(wg : WarpGate) if (wg.Active && (GetKnownVehicleAndSeat() match { @@ -6045,7 +6434,7 @@ class WorldSessionActor extends Actor * Construct tasking that adds a completed and registered vehicle into the scene. * Use this function to renew the globally unique identifiers on a vehicle that has already been added to the scene once. * @param vehicle the `Vehicle` object - * @see `RegisterNewVehicle` + * @see `RegisterVehicleFromSpawnPad` * @return a `TaskResolver.GiveTask` message */ def RegisterVehicle(vehicle : Vehicle) : TaskResolver.GiveTask = { @@ -6072,9 +6461,62 @@ class WorldSessionActor extends Actor ) } + /** + * Use this function to facilitate registering a droppod for a globally unique identifier + * in the event that the user has instigated an instant action event to a destination within the current zone.
+ *
+ * If going to another zone instead, + * this is uneccessary as the normal vehicle gating protocol is partially intersected for droppod operation, + * and will properly register the droppod before introducing it into the new zone without additional concern. + * The droppod should actually not be completely unregistered. + * If inquired, it will act like a GUID had already been assigned to it, but it was invalidated. + * This condition is artificial, but it necessary to pass certain operations related to vehicle gating. + * Additionally, the driver is only partially associated with the vehicle at this time. + * `interstellarFerry` is properly keeping track of the vehicle during the transition + * and the user who is the driver (second param) is properly seated + * but the said driver does not know about the vehicle through his usual convention - VehicleSeated` - yet. + * @see `GlobalDefinitions.droppod` + * @see `GUIDTask.RegisterObjectTask` + * @see `interstellarFerry` + * @see `Player.VehicleSeated` + * @see `PlayerLoaded` + * @see `TaskResolver.GiveTask` + * @see `Vehicles.Own` + * @param vehicle the unregistered droppod + * @param tplayer the player using the droppod for instant action; + * should already be the driver of the droppod + * @return a `TaskResolver.GiveTask` message + */ + def RegisterDroppod(vehicle : Vehicle, tplayer : Player) : TaskResolver.GiveTask = { + TaskResolver.GiveTask( + new Task() { + private val localDriver = tplayer + private val localVehicle = vehicle + private val localAnnounce = self + + override def isComplete : Task.Resolution.Value = { + if(localVehicle.HasGUID) { + Task.Resolution.Success + } + else { + Task.Resolution.Incomplete + } + } + + def Execute(resolver : ActorRef) : Unit = { + log.info(s"Vehicle $localVehicle is registered") + localDriver.VehicleSeated = localVehicle.GUID + Vehicles.Own(localVehicle, localDriver) + localAnnounce ! PlayerLoaded(localDriver) + resolver ! scala.util.Success(this) + } + }, List(GUIDTask.RegisterObjectTask(vehicle)(continent.GUID)) + ) + } + /** * Construct tasking that adds a completed and registered vehicle into the scene. - * The major difference between `RegisterVehicle` and `RegisterNewVehicle` is the assumption that this vehicle lacks an internal `Actor`. + * The major difference between `RegisterVehicle` and `RegisterVehicleFromSpawnPad` is the assumption that this vehicle lacks an internal `Actor`. * Before being finished, that vehicle is supplied an `Actor` such that it may function properly. * This function wraps around `RegisterVehicle` and is used in case, prior to this event, * the vehicle is being brought into existence from scratch and was never a member of any `Zone`. @@ -6082,7 +6524,7 @@ class WorldSessionActor extends Actor * @see `RegisterVehicle` * @return a `TaskResolver.GiveTask` message */ - def RegisterNewVehicle(obj : Vehicle, pad : VehicleSpawnPad) : TaskResolver.GiveTask = { + def RegisterVehicleFromSpawnPad(obj : Vehicle, pad : VehicleSpawnPad) : TaskResolver.GiveTask = { TaskResolver.GiveTask( new Task() { private val localVehicle = obj @@ -9129,6 +9571,7 @@ class WorldSessionActor extends Actor deadState = DeadState.RespawnTime sendResponse(AvatarDeadStateMessage(DeadState.RespawnTime, respawnTimeMillis, respawnTimeMillis, Vector3.Zero, player.Faction, true)) shiftPosition = Some(pos) + shiftOrientation = Some(ori) val (target, msg) = if(backpack) { //if the player is dead, he is handled as dead infantry, even if he died in a vehicle //new player is spawning val newPlayer = RespawnClone(player) @@ -9138,7 +9581,7 @@ class WorldSessionActor extends Actor } else { interstellarFerry.orElse(continent.GUID(player.VehicleSeated)) match { - case Some(vehicle : Vehicle) => //driver or passenger in vehicle using a warp gate + case Some(vehicle : Vehicle) => //driver or passenger in vehicle using a warp gate, or a droppod LoadZoneInVehicle(vehicle, pos, ori, zone_id) case _ if player.HasGUID => //player is deconstructing self @@ -9289,8 +9732,19 @@ class WorldSessionActor extends Actor } // if(zone_id == continent.Id) { - //transferring a vehicle between spawn points (warp gates) in the same zone - (self, PlayerLoaded(player)) + if(vehicle.Definition == GlobalDefinitions.droppod) { + //instant action droppod in the same zone + (taskResolver, RegisterDroppod(vehicle, player)) + } + else { + //transferring a vehicle between spawn points (warp gates) in the same zone + (self, PlayerLoaded(player)) + } + } + else if(vehicle.Definition == GlobalDefinitions.droppod) { + LoadZoneCommonTransferActivity() + player.Continent = zone_id //forward-set the continent id to perform a test + (taskResolver, TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(player)(continent.GUID), zone_id)) } else { UnAccessContents(vehicle) @@ -9409,6 +9863,7 @@ class WorldSessionActor extends Actor * as indicated, the simulation is only concerned with certain angles */ def PutItemOnGround(item : Equipment, pos : Vector3, orient : Vector3) : Unit = { + CancelZoningProcessWithDescriptiveReason("cancel_use") //TODO delay or reverse dropping item when player is falling down item.Position = pos item.Orientation = Vector3.z(orient.z) @@ -9439,6 +9894,7 @@ class WorldSessionActor extends Actor def PutItemInHand(item : Equipment) : Boolean = { player.Fit(item) match { case Some(slotNum) => + CancelZoningProcessWithDescriptiveReason("cancel_use") item.Faction = player.Faction val item_guid = item.GUID val player_guid = player.GUID @@ -10708,6 +11164,7 @@ object WorldSessionActor { private final case class ListAccountCharacters() private final case class SetCurrentAvatar(tplayer : Player) private final case class VehicleLoaded(vehicle : Vehicle) + private final case class ZoningReset() /** * The message that progresses some form of user-driven activity with a certain eventual outcome @@ -10719,6 +11176,8 @@ object WorldSessionActor { */ final case class ProgressEvent(delta : Float, completionAction : ()=>Unit, tickAction : Float=>Boolean) + private final val zoningCountdownMessages : Seq[Int] = Seq(5,10,20) + protected final case class SquadUIElement(name : String, index : Int, zone : Int, health : Int, armor : Int, position : Vector3) private final case class NtuCharging(tplayer: Player, vehicle: Vehicle) diff --git a/pslogin/src/main/scala/zonemaps/Ugd01.scala b/pslogin/src/main/scala/zonemaps/Ugd01.scala index 18a92839..3d25740f 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd01.scala +++ b/pslogin/src/main/scala/zonemaps/Ugd01.scala @@ -15,6 +15,7 @@ import net.psforever.types.Vector3 object Ugd01 { // Supai val ZoneMap = new ZoneMap("ugd01") { Scale = MapScale.Dim2560 + Cavern = true Checksum = 3405929729L Building10140() diff --git a/pslogin/src/main/scala/zonemaps/Ugd02.scala b/pslogin/src/main/scala/zonemaps/Ugd02.scala index bf53df8a..753bacaa 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd02.scala +++ b/pslogin/src/main/scala/zonemaps/Ugd02.scala @@ -15,6 +15,7 @@ import net.psforever.types.Vector3 object Ugd02 { // Hunhau val ZoneMap = new ZoneMap("ugd02") { Scale = MapScale.Dim2560 + Cavern = true Checksum = 2702486449L Building10093() diff --git a/pslogin/src/main/scala/zonemaps/Ugd03.scala b/pslogin/src/main/scala/zonemaps/Ugd03.scala index a37de8bb..bd4ff509 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd03.scala +++ b/pslogin/src/main/scala/zonemaps/Ugd03.scala @@ -15,6 +15,7 @@ import net.psforever.types.Vector3 object Ugd03 { // Adlivun val ZoneMap = new ZoneMap("ugd03") { Scale = MapScale.Dim2048 + Cavern = true Checksum = 1673539651L Building10020() diff --git a/pslogin/src/main/scala/zonemaps/Ugd04.scala b/pslogin/src/main/scala/zonemaps/Ugd04.scala index 356da815..e6018fc5 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd04.scala +++ b/pslogin/src/main/scala/zonemaps/Ugd04.scala @@ -15,6 +15,7 @@ import net.psforever.types.Vector3 object Ugd04 { // Byblos val ZoneMap = new ZoneMap("ugd04") { Scale = MapScale.Dim2048 + Cavern = true Checksum = 3797992164L Building10076() diff --git a/pslogin/src/main/scala/zonemaps/Ugd05.scala b/pslogin/src/main/scala/zonemaps/Ugd05.scala index 0826707f..2c4d7e11 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd05.scala +++ b/pslogin/src/main/scala/zonemaps/Ugd05.scala @@ -15,6 +15,7 @@ import net.psforever.types.Vector3 object Ugd05 { // Annwn val ZoneMap = new ZoneMap("ugd05") { Scale = MapScale.Dim2048 + Cavern = true Checksum = 1769572498L Building10116() diff --git a/pslogin/src/main/scala/zonemaps/Ugd06.scala b/pslogin/src/main/scala/zonemaps/Ugd06.scala index 23d813ed..4713ac49 100644 --- a/pslogin/src/main/scala/zonemaps/Ugd06.scala +++ b/pslogin/src/main/scala/zonemaps/Ugd06.scala @@ -15,6 +15,7 @@ import net.psforever.types.Vector3 object Ugd06 { // Drugaskan val ZoneMap = new ZoneMap("ugd06") { Scale = MapScale.Dim2560 + Cavern = true Checksum = 4274683970L Building10077()