Instant Action / Recall to Sanctuary (#348)

* refactored ZoneActor for external calls; earliest code for calculating Instant Action placement

* created a building definition so that SOI is no longer indeterminate; gave hot spots projector a longer-lasting backup for purposes of activity retention; instant action ramp-up works

* filled out instant action messages; refactored main method

* packet and initial tests for DroppodFreefallingMessage; drop pod definition, packet converter, and consideration in WSA and InterstellarCluster instant action functionality; droppods now work

* duplicated soi information; modified priority of instant action; assigned cavern status; added reset for instant action failure; implant interrupt condition; wrote comments

* no instant action droppods; added messages for cancelling instant action when certain conditions occur; wilderness instant action request

* made generic the entire instant action process to shoehorn the whole of the sanctuary recall process into it; I hope you're happy

* test fix; vehicle hacking fix; no more artificial NTU drain

* escape case for zoning last chance; descriptive mesages condense similar calls

* something of a merge repair
This commit is contained in:
Fate-JH 2020-04-16 21:21:33 -04:00 committed by GitHub
parent c80bb2836f
commit a23643b240
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
27 changed files with 1342 additions and 460 deletions

View file

@ -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
}
/**

View file

@ -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

View file

@ -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
)
)
}
}
}

View file

@ -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 _ => ;

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
*/

View file

@ -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)
}
}

View file

@ -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

View file

@ -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.

View file

@ -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.<br>
* <br>
* 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 {

View file

@ -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
}
}

View file

@ -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)
}
}

View file

@ -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

View file

@ -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
}
)
}

View file

@ -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

View file

@ -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
}
}
}

View file

@ -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"))

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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