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