Ziplines, teleporters and caverns (#323)

* GlobalDefinitions for cave objects and a few fixes for cave functionality

* Fixes for Ziplines, and by extension new functionality to allow for zipline teleporters

* Generated map files for caves

* Add SOI radius for cavern facilities

* Make ziplines bi-directional

* Fix useradius on crystals_health_b

* Reduce cavern CC hack time from 15m to 10m

* Fix /fly /speed and /spectator for GM accounts

* GOSM / PSAM Documentation

* Allow players to be in multiple overlapping SOIs

* Add some extra logging to startup procedure

* Fix orders from zone-owned terminals (non-facility buildings in caverns)

* Fix Extinction map checksum

* Add checksums for cave maps (Special thanks to Chord)

* Initialize ZoneMaps in parallel to reduce server startup delay

* Update SphereOfInfluenceActor.scala

Just line breaks spacing the update `foreach`.  That mistake was on me.

Co-authored-by: Fate-JH <Jason_DiDonato@yahoo.com>
This commit is contained in:
Mazo 2020-01-17 18:36:15 +00:00 committed by Fate-JH
parent 3869785591
commit 80af2e84a9
37 changed files with 5432 additions and 219 deletions

View file

@ -924,6 +924,8 @@ object GlobalDefinitions {
val order_terminalb = new OrderTerminalDefinition(614)
val vanu_equipment_term = new OrderTerminalDefinition(933)
val cert_terminal = new OrderTerminalDefinition(171)
val implant_terminal_mech = new ImplantTerminalMechDefinition
@ -938,6 +940,10 @@ object GlobalDefinitions {
val vehicle_terminal_combined = new OrderTerminalDefinition(952)
val vanu_air_vehicle_term = new OrderTerminalDefinition(928)
val vanu_vehicle_term = new OrderTerminalDefinition(949)
val bfr_terminal = new OrderTerminalDefinition(143)
val respawn_tube = new SpawnTubeDefinition(732)
@ -968,6 +974,8 @@ object GlobalDefinitions {
val dropship_pad_doors = new VehicleSpawnPadDefinition(261)
val vanu_vehicle_creation_pad = new VehicleSpawnPadDefinition(947)
val mb_locker = new LockerDefinition
val lock_external = new IFFLockDefinition
@ -980,6 +988,8 @@ object GlobalDefinitions {
val secondary_capture = new CaptureTerminalDefinition(751) // Tower CC
val vanu_control_console = new CaptureTerminalDefinition(930) // Cavern CC
val lodestar_repair_terminal = new MedicalTerminalDefinition(461)
val multivehicle_rearm_terminal = new OrderTerminalDefinition(576)
@ -1018,6 +1028,32 @@ object GlobalDefinitions {
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 vanu_core : ObjectDefinition = new ObjectDefinition(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 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 hst : ObjectDefinition with SpawnPointDefinition = new ObjectDefinition(402) with SpawnPointDefinition
hst.Name = "hst"
hst.UseRadius = 20.4810f
@ -1042,13 +1078,13 @@ object GlobalDefinitions {
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) { Name = "redoubt" }
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) { Name = "vanu_control_point" }
val vanu_vehicle_station : ObjectDefinition = new ObjectDefinition(948) { Name = "vanu_vehicle_station" }
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 warpgate : ObjectDefinition with SpawnPointDefinition = new ObjectDefinition(993) with SpawnPointDefinition
warpgate.Name = "warpgate"
@ -6088,6 +6124,14 @@ object GlobalDefinitions {
order_terminalb.Tab(4).asInstanceOf[OrderTerminalDefinition.InfantryLoadoutPage].Exclude = ExoSuitType.MAX
order_terminalb.SellEquipmentByDefault = true
vanu_equipment_term.Name = "vanu_equipment_term"
vanu_equipment_term.Tab += 0 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.infantryAmmunition ++ EquipmentTerminalDefinition.infantryWeapons)
vanu_equipment_term.Tab += 1 -> OrderTerminalDefinition.ArmorWithAmmoPage(EquipmentTerminalDefinition.suits ++ EquipmentTerminalDefinition.maxSuits, EquipmentTerminalDefinition.maxAmmo)
vanu_equipment_term.Tab += 2 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.supportAmmunition ++ EquipmentTerminalDefinition.supportWeapons)
vanu_equipment_term.Tab += 3 -> OrderTerminalDefinition.EquipmentPage(EquipmentTerminalDefinition.vehicleAmmunition)
vanu_equipment_term.Tab += 4 -> OrderTerminalDefinition.InfantryLoadoutPage()
vanu_equipment_term.SellEquipmentByDefault = true
cert_terminal.Name = "cert_terminal"
cert_terminal.Tab += 0 -> OrderTerminalDefinition.CertificationPage(CertTerminalDefinition.certs)
@ -6110,6 +6154,14 @@ object GlobalDefinitions {
vehicle_terminal_combined.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles ++ VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
vehicle_terminal_combined.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
vanu_air_vehicle_term.Name = "vanu_air_vehicle_term"
vanu_air_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.flight1Vehicles, VehicleTerminalDefinition.trunk)
vanu_air_vehicle_term.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
vanu_vehicle_term.Name = "vanu_vehicle_term"
vanu_vehicle_term.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.groundVehicles, VehicleTerminalDefinition.trunk)
vanu_vehicle_term.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
bfr_terminal.Name = "bfr_terminal"
bfr_terminal.Tab += 46769 -> OrderTerminalDefinition.VehiclePage(VehicleTerminalDefinition.bfrVehicles, VehicleTerminalDefinition.trunk)
bfr_terminal.Tab += 4 -> OrderTerminalDefinition.VehicleLoadoutPage()
@ -6152,7 +6204,7 @@ object GlobalDefinitions {
crystals_health_b.Name = "crystals_health_b"
crystals_health_b.Interval = 500
crystals_health_b.HealAmount = 4
crystals_health_b.UseRadius = 1.3f
crystals_health_b.UseRadius = 5
crystals_health_b.TargetValidation += EffectTarget.Category.Player -> EffectTarget.Validation.HealthCrystal
portable_med_terminal.Name = "portable_med_terminal"

View file

@ -3,11 +3,10 @@ package net.psforever.objects.serverobject.terminals
import net.psforever.objects.definition.ObjectDefinition
class CaptureTerminalDefinition(objectId : Int) extends ObjectDefinition(objectId) {
Name = if(objectId == 158) {
"capture_terminal"
} else if (objectId == 751) {
"secondary_capture"
} else {
throw new IllegalArgumentException("Not a valid capture terminal object id")
Name = objectId match {
case 158 => "capture_terminal"
case 751 => "secondary_capture"
case 930 => "vanu_control_console"
case _ => throw new IllegalArgumentException("Not a valid capture terminal object id")
}
}

View file

@ -6,7 +6,7 @@ import net.psforever.objects.definition.VehicleDefinition
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.Amenity
import net.psforever.packet.game.{ItemTransactionMessage, TriggeredSound}
import net.psforever.types.Vector3
import net.psforever.types.{PlanetSideGUID, Vector3}
/**
* A server object that can be accessed for services and other amenities.
@ -37,7 +37,10 @@ class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
/**
* Process a message (a "request") dispatched by the user.
* To be accessible, the terminal must be owned by the same faction by the user or must be compromised.
* To be accessible, the terminal must be:
* owned by the same faction by the user
* or must be compromised
* or must be a zone owned object (GUID == 0, e.g. non-facility buildings in caves)
* @see `FactionAffinity`
* @see `PlanetSideEmpire`
* @param player the player
@ -45,7 +48,7 @@ class Terminal(tdef : TerminalDefinition) extends Amenity with Hackable {
* @return an actionable message that explains what resulted from interacting with this `Terminal`
*/
def Request(player : Player, msg : Any) : Terminal.Exchange = {
if(Faction == player.Faction || HackedBy.isDefined) {
if(Faction == player.Faction || HackedBy.isDefined || Owner.GUID == PlanetSideGUID(0)) {
tdef.Request(player, msg)
}
else {

View file

@ -0,0 +1,9 @@
package net.psforever.objects.serverobject.zipline
import net.psforever.types.Vector3
class ZipLinePath(private val pathId: Integer, private val isTeleporter: Boolean, private val zipLinePoints: List[Vector3]) {
def PathId : Integer = pathId
def IsTeleporter : Boolean = isTeleporter
def ZipLinePoints : List[Vector3] = zipLinePoints
}

View file

@ -52,7 +52,9 @@ class SphereOfInfluenceActor(zone: Zone) extends Actor {
}
def UpdateSOI(): Unit = {
SOI.Populate(sois.iterator, zone.LivePlayers)
sois.foreach { case (facility, radius) =>
facility.PlayersInSOI = zone.LivePlayers.filter(p => Vector3.DistanceSquared(facility.Position.xy, p.Position.xy) < radius)
}
populateTick.cancel
populateTick = context.system.scheduler.scheduleOnce(5 seconds, self, SOI.Populate())
}
@ -67,20 +69,4 @@ object SOI {
final case class Start()
/** Stop sorting players into sois */
final case class Stop()
/**
* Recursively populate each facility's sphere of influence with players.
* @param buildings an iterator of buildings and the radius of its sphere of influence
* @param players a list of players to allocate;
* the list gets shorter as each building is allocated
*/
@tailrec
def Populate(buildings : Iterator[(Building, Int)], players : List[Player]) : Unit = {
if(players.nonEmpty && buildings.hasNext) {
val (facility, radius) = buildings.next
val (tenants, remainder) = players.partition(p => Vector3.DistanceSquared(facility.Position.xy, p.Position.xy) < radius)
facility.PlayersInSOI = tenants
Populate(buildings, remainder)
}
}
}

View file

@ -17,6 +17,7 @@ import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition}
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.{Amenity, Building, WarpGate}
import net.psforever.objects.serverobject.turret.FacilityTurret
import net.psforever.objects.serverobject.zipline.ZipLinePath
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
import services.avatar.AvatarService
import services.local.LocalService
@ -83,6 +84,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
private var lattice : Graph[Building, UnDiEdge] = Graph()
private var zipLinePaths : List[ZipLinePath] = List()
/** key - spawn zone id, value - buildings belonging to spawn zone */
private var spawnGroups : Map[Building, List[SpawnPoint]] = PairMap[Building, List[SpawnPoint]]()
/** */
@ -139,6 +142,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
MakeLattice()
AssignAmenities()
CreateSpawnGroups()
zipLinePaths = Map.ZipLinePaths
}
}
@ -352,6 +357,10 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
lattice
}
def ZipLinePaths : List[ZipLinePath] = {
zipLinePaths
}
private def BuildLocalObjects(implicit context : ActorContext, guid : NumberPoolHub) : Unit = {
Map.LocalObjects.foreach({ builderObject => builderObject.Build })
}

View file

@ -2,9 +2,8 @@
package net.psforever.objects.zones
import net.psforever.objects.serverobject.structures.FoundationBuilder
import net.psforever.objects.serverobject.zipline.ZipLinePath
import net.psforever.objects.serverobject.{PlanetSideServerObject, ServerObjectBuilder}
import scalax.collection.Graph
import scalax.collection.GraphPredef._, scalax.collection.GraphEdge._
/**
* The fixed instantiation and relation of a series of server objects.<br>
@ -37,6 +36,7 @@ class ZoneMap(private val name : String) {
private var buildings : Map[(String, Int, Int), FoundationBuilder] = Map()
private var lattice: Set[(String, String)] = Set()
private var checksum : Long = 0
private var zipLinePaths : List[ZipLinePath] = List()
def Name : String = name
@ -90,6 +90,13 @@ class ZoneMap(private val name : String) {
localObjects.size
}
def ZipLinePaths : List[ZipLinePath] = zipLinePaths
def ZipLinePaths(path: ZipLinePath): Int = {
zipLinePaths = zipLinePaths :+ path
zipLinePaths.size
}
def LocalBuildings : Map[(String, Int, Int), FoundationBuilder] = buildings
def LocalBuilding(name : String, building_guid : Int, map_id : Int, constructor : FoundationBuilder) : Int = {

View file

@ -6,6 +6,13 @@ import net.psforever.types.PlanetSideGUID
import scodec.Codec
import scodec.codecs._
/**
*
* @param object_guid the target object
* @param state the state code
* 16 - open door
* 17 - close door
*/
final case class GenericObjectStateMsg(object_guid : PlanetSideGUID,
state : Long)
extends PlanetSideGamePacket {

View file

@ -145,6 +145,7 @@ import scodec.codecs._
* -- e.g., 13 = 8 + 4 + 1 = fire and LLU and plasma<br>
* `55 - "Someone is attempting to Heal you". Value is 1`<br>
* `56 - "Someone is attempting to Repair you". Value is 1`<br>
* `64 - ????? related to using router telepads`
* `67 - Enables base shields (from cavern module/lock). MUST use base MapId not GUID`<br>
* `73 - "You are locked into the Core Beam. Charging your Module now.". Value is 1 to active`<br>
* `77 - Cavern Facility Captures. Value is the number of captures`<br>

View file

@ -16,16 +16,16 @@ import scodec.codecs._
* `1 - Arrived at destination`<br>
* `2 - Forcibly detach from zip line in mid-transit`
* @param player_guid the player
* @param origin_side whether this corresponds with the "entry" or the "exit" of the zip line, as per the direction of the light pulse visuals
* @param forwards true if the player is travelling in the direction of the light pulses
* @param action how the player interacts with the zip line
* @param guid a number that is consistent to a terminus
* @param path_id the path id that this zipline belongs to, from the relevant .zpl file
* @param pos the coordinates of the point where the player is interacting with the zip line;
* "optional," in theory
*/
final case class ZipLineMessage(player_guid : PlanetSideGUID,
origin_side : Boolean,
forwards : Boolean,
action : Int,
guid : Long,
path_id : Long,
pos : Option[Vector3] = None)
extends PlanetSideGamePacket {
type Packet = ZipLineMessage
@ -36,20 +36,21 @@ final case class ZipLineMessage(player_guid : PlanetSideGUID,
object ZipLineMessage extends Marshallable[ZipLineMessage] {
/**
* Alternate constructor for `ZipLineMessage` that requirement for the last field.
* @param player_guid the player
* @param origin_side whether this corresponds with the "entry" or the "exit" of the zip line, as per the direction of the light pulse visuals
* @param action how the player interacts with the zip line
* @param guid a number that is consistent to a terminus
* @param pos the coordinates of the point where the player is interacting with the zip line
*
* @param player_guid the player
* @param forwards true if the player is travelling in the direction of the light pulses
* @param action how the player interacts with the zip line
* @param path_id the path id that this zipline belongs to, from the relevant .zpl file
* @param pos the coordinates of the point where the player is interacting with the zip line
* @return a `ZipLineMessage` object
*/
def apply(player_guid : PlanetSideGUID, origin_side : Boolean, action : Int, guid : Long, pos : Vector3) : ZipLineMessage = {
ZipLineMessage(player_guid, origin_side, action, guid, Some(pos))
def apply(player_guid : PlanetSideGUID, forwards : Boolean, action : Int, path_id : Long, pos : Vector3) : ZipLineMessage = {
ZipLineMessage(player_guid, forwards, action, path_id, Some(pos))
}
implicit val codec : Codec[ZipLineMessage] = (
("player_guid" | PlanetSideGUID.codec) >>:~ { player =>
("origin_side" | bool) ::
("forwards" | bool) ::
("action" | uint2) ::
("id" | uint32L) ::
conditional(player.guid > 0, Vector3.codec_float) // !(player.guid == 0)

View file

@ -106,6 +106,8 @@ class LocalService(zone : Zone) extends Actor {
case GlobalDefinitions.secondary_capture =>
// Tower CC
hackCapturer ! HackCaptureActor.ObjectIsHacked(target, zone, unk1, unk2, duration = 1 nanosecond)
case GlobalDefinitions.vanu_control_console =>
hackCapturer ! HackCaptureActor.ObjectIsHacked(target, zone, unk1, unk2, duration = 10 minutes)
}
}