Building persistence

Buildings will now persist their faction in the database. At least
that's what I want you to believe this change is.

What it actually is: A rework of InterstellarCluster and groundwork
for further reworks.

InterstellarClusterService: This is the old InterstellarCluster, but
as a service (it has always been one in secret). It was converted to
a typed actor and it now handles all spawn point requests.

ZoneActor: Basically ZoneControl, but as a typed actor. It's more of a
stub right now, the eventual goal is to have it own the `Zone` object
rather than the other way around.

BuildingActor: BuildingControl, but as a typed actor.

Also includes some minor improvements to ChatActor and sets
SupervisorStrategy.restart for all typed actors (which is the default
for classic actors, but not for typed actors - we may want to get more
sophisticated here in the future).
This commit is contained in:
Jakob Gillich 2020-07-22 17:28:09 +02:00
parent 4634dffe00
commit 3345e56b38
51 changed files with 2457 additions and 2967 deletions

View file

@ -81,7 +81,7 @@ The Login and World servers require PostgreSQL for persistence.
The default database is named `psforever` and the credentials are
`psforever:psforever`. To change these, create a configuration file at
`config/psforever.conf`. For configuration options and their defaults, see
[`application.conf`](/pslogin/src/main/resources/application.conf). This database user will need
[`application.conf`](/common/src/main/resources/application.conf). The database user will need
ALL access to tables, sequences, and functions.
The permissions required can be summarized by the SQL below.
Loading this in requires access to a graphical tool such as [pgAdmin](https://www.pgadmin.org/download/) (highly recommended) or a PostgreSQL terminal (`psql`) for advanced users.

View file

@ -89,9 +89,8 @@ lazy val psloginPackSettings = Seq(
packMain := Map("ps-login" -> "net.psforever.pslogin.PsLogin"),
packArchivePrefix := "pslogin",
packExtraClasspath := Map("ps-login" -> Seq("${PROG_HOME}/pscrypto-lib", "${PROG_HOME}/config")),
packResourceDir += (baseDirectory.value / "pscrypto-lib" -> "pscrypto-lib"),
packResourceDir += (baseDirectory.value / "config" -> "config"),
packResourceDir += (baseDirectory.value / "pslogin/src/main/resources" -> "config")
packResourceDir += (baseDirectory.value / "pscrypto-lib" -> "pscrypto-lib"),
packResourceDir += (baseDirectory.value / "config" -> "config")
)
lazy val root = (project in file("."))
@ -142,4 +141,4 @@ lazy val decodePackets = (project in file("tools/decode-packets"))
lazy val decodePacketsPackSettings = Seq(packMain := Map("psf-decode-packets" -> "DecodePackets"))
// Special test configuration for really quiet tests (used in CI)
lazy val QuietTest = config("quiet") extend (Test)
lazy val QuietTest = config("quiet") extend Test

View file

@ -58,6 +58,15 @@ database {
# The SSL configuration of the database connection.
# One of: disable prefer require verify-full
sslmode = prefer
# The maximum number of active connections.
maxActiveConnections = 5
}
# Enable non-standard game properties
game {
# Allow instant action to AMS
instant-action-ams = no
}
anti-cheat {
@ -110,5 +119,13 @@ kamon {
apm.api-key = ""
}
include "akka"
include "dispatchers"
sentry {
# Enables submission of warnings and errors to Sentry
enable = no
# Sentry DSN (Data Source Name)
dsn = ""
}
include "akka.conf"
include "dispatchers.conf"

View file

@ -0,0 +1,29 @@
package net.psforever.actors.commands
import akka.actor.typed.ActorRef
import net.psforever.objects.NtuContainer
object NtuCommand {
trait Command
/** Message for announcing it has nanites it can offer the recipient.
*
* @param source the nanite container recognized as the sender
*/
final case class Offer(source: NtuContainer) extends Command
/** Message for asking for nanites from the recipient.
*
* @param amount the amount of nanites requested
*/
final case class Request(amount: Int, replyTo: ActorRef[Grant]) extends Command
/** Response for transferring nanites to a recipient.
*
* @param source the nanite container recognized as the sender
* @param amount the nanites transferred in this package
*/
final case class Grant(source: NtuContainer, amount: Int)
}

View file

@ -0,0 +1,175 @@
package net.psforever.actors.zone
import akka.actor.typed.receptionist.Receptionist
import akka.actor.typed.scaladsl.{ActorContext, Behaviors, StashBuffer}
import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy}
import akka.{actor => classic}
import net.psforever.actors.commands.NtuCommand
import net.psforever.objects.serverobject.structures.{Building, WarpGate}
import net.psforever.objects.zones.Zone
import net.psforever.persistence
import net.psforever.types.PlanetSideEmpire
import net.psforever.util.Database._
import services.galaxy.{GalaxyAction, GalaxyServiceMessage}
import services.local.{LocalAction, LocalServiceMessage}
import services.{InterstellarClusterService, ServiceManager}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
object BuildingActor {
def apply(zone: Zone, building: Building): Behavior[Command] =
Behaviors
.supervise[Command] {
Behaviors.withStash(100) { buffer =>
Behaviors.setup(context => new BuildingActor(context, buffer, zone, building).start())
}
}
.onFailure[Exception](SupervisorStrategy.restart)
sealed trait Command
private case class ReceptionistListing(listing: Receptionist.Listing) extends Command
private case class ServiceManagerLookupResult(result: ServiceManager.LookupResult) extends Command
final case class SetFaction(faction: PlanetSideEmpire.Value) extends Command
// TODO remove
// Changes to building objects should go through BuildingActor
// Once they do, we won't need this anymore
final case class MapUpdate() extends Command
final case class Ntu(command: NtuCommand.Command) extends Command
}
class BuildingActor(
context: ActorContext[BuildingActor.Command],
buffer: StashBuffer[BuildingActor.Command],
zone: Zone,
building: Building
) {
import BuildingActor._
private[this] val log = org.log4s.getLogger
var galaxyService: Option[classic.ActorRef] = None
var interstellarCluster: Option[ActorRef[InterstellarClusterService.Command]] = None
context.system.receptionist ! Receptionist.Find(
InterstellarClusterService.InterstellarClusterServiceKey,
context.messageAdapter[Receptionist.Listing](ReceptionistListing)
)
ServiceManager.serviceManager ! ServiceManager.LookupFromTyped(
"galaxy",
context.messageAdapter[ServiceManager.LookupResult](ServiceManagerLookupResult)
)
def start(): Behavior[Command] = {
Behaviors.receiveMessage {
case ReceptionistListing(InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings)) =>
interstellarCluster = listings.headOption
postStartBehaviour()
case ServiceManagerLookupResult(ServiceManager.LookupResult(request, endpoint)) =>
request match {
case "galaxy" => galaxyService = Some(endpoint)
}
postStartBehaviour()
case other =>
buffer.stash(other)
Behaviors.same
}
}
def postStartBehaviour(): Behavior[Command] = {
(galaxyService, interstellarCluster) match {
case (Some(galaxyService), Some(interstellarCluster)) =>
buffer.unstashAll(active(galaxyService, interstellarCluster))
case _ =>
Behaviors.same
}
}
def active(
galaxyService: classic.ActorRef,
interstellarCluster: ActorRef[InterstellarClusterService.Command]
): Behavior[Command] = {
Behaviors.receiveMessagePartial {
case SetFaction(faction) =>
import ctx._
ctx
.run(
query[persistence.Building]
.filter(_.localId == lift(building.MapId))
.filter(_.zoneId == lift(zone.Number))
)
.onComplete {
case Success(res) =>
res.headOption match {
case Some(_) =>
ctx
.run(
query[persistence.Building]
.filter(_.localId == lift(building.MapId))
.filter(_.zoneId == lift(zone.Number))
.update(_.factionId -> lift(building.Faction.id))
)
.onComplete {
case Success(_) =>
case Failure(e) => log.error(e.getMessage)
}
case _ =>
ctx
.run(
query[persistence.Building]
.insert(
_.localId -> lift(building.MapId),
_.factionId -> lift(building.Faction.id),
_.zoneId -> lift(zone.Number)
)
)
.onComplete {
case Success(_) =>
case Failure(e) => log.error(e.getMessage)
}
}
case Failure(e) => log.error(e.getMessage)
}
building.Faction = faction
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage()))
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, faction))
Behaviors.same
case MapUpdate() =>
galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(building.infoUpdateMessage()))
Behaviors.same
case Ntu(msg) =>
ntu(msg)
}
}
def ntu(msg: NtuCommand.Command): Behavior[Command] = {
import NtuCommand._
val ntuBuilding = building match {
case b: WarpGate => b
case _ => return Behaviors.unhandled
}
msg match {
case Offer(source) =>
case Request(amount, replyTo) =>
ntuBuilding match {
case warpGate: WarpGate => replyTo ! Grant(warpGate, if (warpGate.Active) amount else 0)
case _ => return Behaviors.unhandled
}
}
Behaviors.same
}
}

View file

@ -0,0 +1,124 @@
package net.psforever.actors.zone
import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
import net.psforever.objects.ballistics.SourceEntry
import net.psforever.objects.ce.Deployable
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.serverobject.structures.StructureType
import net.psforever.objects.zones.Zone
import net.psforever.objects.{ConstructionItem, PlanetSideGameObject, Player, Vehicle}
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
import scala.collection.mutable.ListBuffer
import akka.actor.typed.scaladsl.adapter._
import net.psforever.util.Database._
import net.psforever.persistence
import scala.util.{Failure, Success}
import scala.concurrent.ExecutionContext.Implicits.global
object ZoneActor {
def apply(zone: Zone): Behavior[Command] =
Behaviors
.supervise[Command] {
Behaviors.setup(context => new ZoneActor(context, zone))
}
.onFailure[Exception](SupervisorStrategy.restart)
sealed trait Command
final case class GetZone(replyTo: ActorRef[ZoneResponse]) extends Command
final case class ZoneResponse(zone: Zone)
final case class AddPlayer(player: Player) extends Command
final case class RemovePlayer(player: Player) extends Command
final case class DropItem(item: Equipment, position: Vector3, orientation: Vector3) extends Command
final case class PickupItem(guid: PlanetSideGUID) extends Command
final case class BuildDeployable(obj: PlanetSideGameObject with Deployable, withTool: ConstructionItem)
extends Command
final case class DismissDeployable(obj: PlanetSideGameObject with Deployable) extends Command
final case class SpawnVehicle(vehicle: Vehicle) extends Command
final case class DespawnVehicle(vehicle: Vehicle) extends Command
final case class HotSpotActivity(defender: SourceEntry, attacker: SourceEntry, location: Vector3) extends Command
// TODO remove
// Changes to zone objects should go through ZoneActor
// Once they do, we won't need this anymore
final case class ZoneMapUpdate() extends Command
}
class ZoneActor(context: ActorContext[ZoneActor.Command], zone: Zone)
extends AbstractBehavior[ZoneActor.Command](context) {
import ZoneActor._
import ctx._
private[this] val log = org.log4s.getLogger
val players: ListBuffer[Player] = ListBuffer()
zone.actor = context.self
zone.init(context.toClassic)
ctx.run(query[persistence.Building].filter(_.zoneId == lift(zone.Number))).onComplete {
case Success(buildings) =>
buildings.foreach { building =>
zone.BuildingByMapId(building.localId) match {
case Some(b) => b.Faction = PlanetSideEmpire(building.factionId)
case None => // TODO this happens during testing, need a way to not always persist during tests
}
}
case Failure(e) => log.error(e.getMessage)
}
override def onMessage(msg: Command): Behavior[Command] = {
msg match {
case GetZone(replyTo) =>
replyTo ! ZoneResponse(zone)
case AddPlayer(player) =>
players.addOne(player)
case RemovePlayer(player) =>
players.filterInPlace(p => p.CharId == player.CharId)
case DropItem(item, position, orientation) =>
zone.Ground ! Zone.Ground.DropItem(item, position, orientation)
case PickupItem(guid) =>
zone.Ground ! Zone.Ground.PickupItem(guid)
case BuildDeployable(obj, tool) =>
zone.Deployables ! Zone.Deployable.Build(obj, tool)
case DismissDeployable(obj) =>
zone.Deployables ! Zone.Deployable.Dismiss(obj)
case SpawnVehicle(vehicle) =>
zone.Transport ! Zone.Vehicle.Spawn(vehicle)
case DespawnVehicle(vehicle) =>
zone.Transport ! Zone.Vehicle.Despawn(vehicle)
case HotSpotActivity(defender, attacker, location) =>
zone.Activity ! Zone.HotSpot.Activity(defender, attacker, location)
case ZoneMapUpdate() =>
zone.Buildings
.filter(_._2.BuildingType == StructureType.Facility)
.values
.foreach(_.Actor ! BuildingActor.MapUpdate())
}
this
}
}

View file

@ -140,7 +140,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
def accountLogin(username: String, password: String): Unit = {
import ctx._
val newToken = this.generateToken()
log.info("accountLogin")
val result = for {
// backwards compatibility: prefer exact match first, then try lowercase
accountsExact <- ctx.run(query[persistence.Account].filter(_.username == lift(username)))
@ -171,6 +171,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
}
login <- accountOption match {
case Some(account) =>
log.info(s"$account")
(account.inactive, password.isBcrypted(account.passhash)) match {
case (false, true) =>
accountIntermediary ! StoreAccountData(newToken, new Account(account.id, account.username, account.gm))

View file

@ -1,27 +1,40 @@
package net.psforever.login.psadmin
import akka.actor.typed.receptionist.Receptionist
import akka.actor.{Actor, ActorRef}
import net.psforever.objects.zones.InterstellarCluster
import services.{InterstellarClusterService, ServiceManager}
import scala.collection.mutable.Map
import akka.actor.typed.scaladsl.adapter._
class CmdListPlayers(args: Array[String], services: Map[String, ActorRef]) extends Actor {
private[this] val log = org.log4s.getLogger(self.path.name)
override def preStart = {
services { "cluster" } ! InterstellarCluster.ListPlayers()
ServiceManager.receptionist ! Receptionist.Find(
InterstellarClusterService.InterstellarClusterServiceKey,
context.self
)
}
override def receive = {
case InterstellarCluster.PlayerList(players) =>
case InterstellarClusterService.InterstellarClusterServiceKey.Listing(listings) =>
listings.head ! InterstellarClusterService.GetPlayers(context.self)
case InterstellarClusterService.PlayersResponse(players) =>
val data = Map[String, Any]()
data { "player_count" } = players.size
data { "player_list" } = Array[String]()
data {
"player_count"
} = players.size
data {
"player_list"
} = Array[String]()
if (players.isEmpty) {
context.parent ! CommandGoodResponse("No players currently online!", data)
} else {
data { "player_list" } = players
data {
"player_list"
} = players
context.parent ! CommandGoodResponse(s"${players.length} players online\n", data)
}
case default => log.error(s"Unexpected message $default")

View file

@ -2,6 +2,7 @@
package net.psforever.objects
import akka.actor.{Actor, ActorRef}
import net.psforever.actors.commands.NtuCommand
import net.psforever.objects.serverobject.transfer.{TransferBehavior, TransferContainer}
object Ntu {
@ -9,77 +10,81 @@ object Ntu {
/**
* Message for a `sender` announcing it has nanites it can offer the recipient.
*
* @param src the nanite container recognized as the sender
*/
final case class Offer(src : NtuContainer)
final case class Offer(src: NtuContainer)
/**
* Message for a `sender` asking for nanites from the recipient.
*
* @param min a minimum amount of nanites requested;
* if 0, the `sender` has no expectations
* @param max the amount of nanites required to not make further requests;
* if 0, the `sender` is full and the message is for clean up operations
*/
final case class Request(min : Int, max : Int)
final case class Request(min: Int, max: Int)
/**
* Message for transferring nanites to a recipient.
* @param src the nanite container recognized as the sender
*
* @param src the nanite container recognized as the sender
* @param amount the nanites transferred in this package
*/
final case class Grant(src : NtuContainer, amount : Int)
final case class Grant(src: NtuContainer, amount: Int)
}
trait NtuContainer extends TransferContainer {
def NtuCapacitor : Int
def NtuCapacitor: Int
def NtuCapacitor_=(value: Int) : Int
def NtuCapacitor_=(value: Int): Int
def Definition : NtuContainerDefinition
def Definition: NtuContainerDefinition
}
trait CommonNtuContainer extends NtuContainer {
private var ntuCapacitor : Int = 0
private var ntuCapacitor: Int = 0
def NtuCapacitor : Int = ntuCapacitor
def NtuCapacitor: Int = ntuCapacitor
def NtuCapacitor_=(value: Int) : Int = {
def NtuCapacitor_=(value: Int): Int = {
ntuCapacitor = scala.math.max(0, scala.math.min(value, Definition.MaxNtuCapacitor))
NtuCapacitor
}
def Definition : NtuContainerDefinition
def Definition: NtuContainerDefinition
}
trait NtuContainerDefinition {
private var maxNtuCapacitor : Int = 0
private var maxNtuCapacitor: Int = 0
def MaxNtuCapacitor : Int = maxNtuCapacitor
def MaxNtuCapacitor: Int = maxNtuCapacitor
def MaxNtuCapacitor_=(max: Int) : Int = {
def MaxNtuCapacitor_=(max: Int): Int = {
maxNtuCapacitor = max
MaxNtuCapacitor
}
}
trait NtuStorageBehavior extends Actor {
def NtuStorageObject : NtuContainer = null
def NtuStorageObject: NtuContainer = null
def storageBehavior : Receive = {
def storageBehavior: Receive = {
case Ntu.Offer(src) => HandleNtuOffer(sender, src)
case Ntu.Grant(_, 0) | Ntu.Request(0, 0) | TransferBehavior.Stopping() => StopNtuBehavior(sender)
case Ntu.Request(min, max) => HandleNtuRequest(sender, min, max)
case Ntu.Grant(src, amount) => HandleNtuGrant(sender, src, amount)
case Ntu.Grant(src, amount) => HandleNtuGrant(sender, src, amount)
case NtuCommand.Grant(src, amount) => HandleNtuGrant(sender, src, amount)
}
def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit
def StopNtuBehavior(sender : ActorRef) : Unit
def StopNtuBehavior(sender: ActorRef): Unit
def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit
def HandleNtuRequest(sender: ActorRef, min: Int, max: Int): Unit
def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit
def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Int): Unit
}

View file

@ -498,7 +498,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
val (interface, slotNumber) = player.VehicleSeated match {
case Some(mech_guid) =>
(
zone.Map.TerminalToInterface.get(mech_guid.guid),
zone.map.TerminalToInterface.get(mech_guid.guid),
if (!player.Implants.exists({ case (implantType, _, _) => implantType == implant_type })) {
//no duplicates
player.InstallImplant(implant)
@ -547,7 +547,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
val (interface, slotNumber) = player.VehicleSeated match {
case Some(mech_guid) =>
(
zone.Map.TerminalToInterface.get(mech_guid.guid),
zone.map.TerminalToInterface.get(mech_guid.guid),
player.UninstallImplant(implant_type)
)
case None =>

View file

@ -60,7 +60,7 @@ class ImplantTerminalMechControl(mech: ImplantTerminalMech)
player: Player
): Boolean = {
val zone = obj.Zone
zone.Map.TerminalToInterface.get(obj.GUID.guid) match {
zone.map.TerminalToInterface.get(obj.GUID.guid) match {
case Some(interface_guid) =>
(zone.GUID(interface_guid) match {
case Some(interface) => !interface.Destroyed

View file

@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.resourcesilo
import akka.actor.{Actor, ActorRef}
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.transfer.TransferBehavior
import net.psforever.objects.serverobject.structures.Building
@ -10,7 +11,6 @@ import net.psforever.objects.{Ntu, NtuContainer, NtuStorageBehavior}
import net.psforever.types.PlanetSideEmpire
import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.ExecutionContext.Implicits.global
@ -18,59 +18,69 @@ import scala.concurrent.duration._
/**
* An `Actor` that handles messages being dispatched to a specific `Resource Silo`.
*
* @param resourceSilo the `Resource Silo` object being governed
*/
class ResourceSiloControl(resourceSilo: ResourceSilo) extends Actor with FactionAffinityBehavior.Check with NtuStorageBehavior {
class ResourceSiloControl(resourceSilo: ResourceSilo)
extends Actor
with FactionAffinityBehavior.Check
with NtuStorageBehavior {
def FactionObject: FactionAffinity = resourceSilo
private[this] val log = org.log4s.getLogger
var panelAnimationFunc : Int=>Unit = PanelAnimation
private[this] val log = org.log4s.getLogger
var panelAnimationFunc: Int => Unit = PanelAnimation
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 _ => ;
}
def Processing : Receive = checkBehavior
.orElse(storageBehavior)
.orElse {
case CommonMessages.Use(player, _) =>
if(resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) {
resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match {
case Some(vehicle) =>
context.system.scheduler.scheduleOnce(delay = 1000 milliseconds, vehicle.Actor, TransferBehavior.Discharging(Ntu.Nanites))
case _ =>
def Processing: Receive =
checkBehavior
.orElse(storageBehavior)
.orElse {
case CommonMessages.Use(player, _) =>
if (resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) {
resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match {
case Some(vehicle) =>
context.system.scheduler.scheduleOnce(
delay = 1000 milliseconds,
vehicle.Actor,
TransferBehavior.Discharging(Ntu.Nanites)
)
case _ =>
}
}
}
case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
LowNtuWarning(enabled)
case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
LowNtuWarning(enabled)
case ResourceSilo.UpdateChargeLevel(amount: Int) =>
UpdateChargeLevel(amount)
case ResourceSilo.UpdateChargeLevel(amount: Int) =>
UpdateChargeLevel(amount)
case _ => ;
}
case _ => ;
}
def LowNtuWarning(enabled : Boolean) : Unit = {
def LowNtuWarning(enabled: Boolean): Unit = {
resourceSilo.LowNtuWarningOn = enabled
log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled")
val building = resourceSilo.Owner
val zone = building.Zone
val zone = building.Zone
building.Zone.AvatarEvents ! AvatarServiceMessage(
zone.Id,
AvatarAction.PlanetsideAttribute(building.GUID, 47, if(resourceSilo.LowNtuWarningOn) 1 else 0)
AvatarAction.PlanetsideAttribute(building.GUID, 47, if (resourceSilo.LowNtuWarningOn) 1 else 0)
)
}
def UpdateChargeLevel(amount: Int) : Unit = {
val siloChargeBeforeChange = resourceSilo.NtuCapacitor
def UpdateChargeLevel(amount: Int): Unit = {
val siloChargeBeforeChange = resourceSilo.NtuCapacitor
val siloDisplayBeforeChange = resourceSilo.CapacitorDisplay
val building = resourceSilo.Owner.asInstanceOf[Building]
val zone = building.Zone
val building = resourceSilo.Owner.asInstanceOf[Building]
val zone = building.Zone
// Increase if positive passed in or decrease charge level if negative number is passed in
resourceSilo.NtuCapacitor += amount
@ -80,67 +90,65 @@ class ResourceSiloControl(resourceSilo: ResourceSilo) extends Actor with Faction
// Only send updated capacitor display value to all clients if it has actually changed
if (resourceSilo.CapacitorDisplay != siloDisplayBeforeChange) {
log.trace(s"Silo ${resourceSilo.GUID} NTU bar level has changed from $siloDisplayBeforeChange to ${resourceSilo.CapacitorDisplay}")
resourceSilo.Owner.Actor ! Building.SendMapUpdate(all_clients = true)
log.trace(
s"Silo ${resourceSilo.GUID} NTU bar level has changed from $siloDisplayBeforeChange to ${resourceSilo.CapacitorDisplay}"
)
resourceSilo.Owner.Actor ! BuildingActor.MapUpdate()
zone.AvatarEvents ! AvatarServiceMessage(
zone.Id,
AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay)
)
building.Actor ! Building.SendMapUpdate(all_clients = true)
building.Actor ! BuildingActor.MapUpdate()
}
val ntuIsLow = resourceSilo.NtuCapacitor.toFloat / resourceSilo.Definition.MaxNtuCapacitor.toFloat < 0.2f
if (resourceSilo.LowNtuWarningOn && !ntuIsLow) {
LowNtuWarning(enabled = false)
}
else if (!resourceSilo.LowNtuWarningOn && ntuIsLow) {
} else if (!resourceSilo.LowNtuWarningOn && ntuIsLow) {
LowNtuWarning(enabled = true)
}
if (resourceSilo.NtuCapacitor == 0 && siloChargeBeforeChange > 0) {
// Oops, someone let the base run out of power. Shut it all down.
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(building.GUID, 48, 1))
building.Faction = PlanetSideEmpire.NEUTRAL
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, PlanetSideEmpire.NEUTRAL))
building.TriggerZoneMapUpdate()
}
else if (siloChargeBeforeChange == 0 && resourceSilo.NtuCapacitor > 0) {
building.Actor ! BuildingActor.SetFaction(PlanetSideEmpire.NEUTRAL)
} else if (siloChargeBeforeChange == 0 && resourceSilo.NtuCapacitor > 0) {
// Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal.
//todo: Check generator is online before starting up
zone.AvatarEvents ! AvatarServiceMessage(
zone.Id,
AvatarAction.PlanetsideAttribute(building.GUID, 48, 0)
)
building.TriggerZoneMapUpdate()
building.Zone.actor ! ZoneActor.ZoneMapUpdate()
}
}
/**
* The silo will agree to offers until its nanite capacitor is completely full.
*/
def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit = {
sender ! (if(resourceSilo.NtuCapacitor < resourceSilo.MaxNtuCapacitor) {
Ntu.Request(0, resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor)
}
else {
StopNtuBehavior(sender)
Ntu.Request(0, 0)
})
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = {
sender ! (if (resourceSilo.NtuCapacitor < resourceSilo.MaxNtuCapacitor) {
Ntu.Request(0, resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor)
} else {
StopNtuBehavior(sender)
Ntu.Request(0, 0)
})
}
/**
* Reset the animation trigger and attempt the stop animation.
*/
def StopNtuBehavior(sender : ActorRef) : Unit = {
def StopNtuBehavior(sender: ActorRef): Unit = {
panelAnimationFunc = PanelAnimation
panelAnimationFunc(0)
}
/**
* na
*
* @param sender na
* @param min a minimum amount of nanites requested;
* @param max the amount of nanites required to not make further requests;
* @param min a minimum amount of nanites requested;
* @param max the amount of nanites required to not make further requests;
*/
def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit = {
def HandleNtuRequest(sender: ActorRef, min: Int, max: Int): Unit = {
val originalAmount = resourceSilo.NtuCapacitor
UpdateChargeLevel(-min)
sender ! Ntu.Grant(resourceSilo, originalAmount - resourceSilo.NtuCapacitor)
@ -149,8 +157,8 @@ class ResourceSiloControl(resourceSilo: ResourceSilo) extends Actor with Faction
/**
* Accept nanites into the silo capacitor and set the animation state.
*/
def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit = {
if(amount != 0) {
def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Int): Unit = {
if (amount != 0) {
val originalAmount = resourceSilo.NtuCapacitor
UpdateChargeLevel(amount)
panelAnimationFunc(resourceSilo.NtuCapacitor - originalAmount)
@ -162,19 +170,20 @@ class ResourceSiloControl(resourceSilo: ResourceSilo) extends Actor with Faction
* When charging from another source of nanites, the silo's panels will glow
* and a particle affect will traverse towards the panels from about ten meters in front of the silo.
* These effects are both controlled by thee same packet.
*
* @param trigger if positive, activate the animation;
* if negative or zero, disable the animation
*/
def PanelAnimation(trigger : Int) : Unit = {
def PanelAnimation(trigger: Int): Unit = {
val zone = resourceSilo.Zone
zone.VehicleEvents ! VehicleServiceMessage(
zone.Id,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, resourceSilo.GUID, 49, if(trigger > 0) 1 else 0)
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, resourceSilo.GUID, 49, if (trigger > 0) 1 else 0)
) // panel glow on & orb particles on
}
/**
* Do nothing this turn.
*/
def SkipPanelAnimation(trigger : Int) : Unit = { }
def SkipPanelAnimation(trigger: Int): Unit = {}
}

View file

@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.structures
import java.util.concurrent.TimeUnit
import akka.actor.ActorContext
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.{Default, GlobalDefinitions, Player}
import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.generator.Generator
@ -13,18 +14,19 @@ import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.terminals.CaptureTerminal
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{Additional1, Additional2, Additional3}
import net.psforever.packet.game.BuildingInfoUpdateMessage
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, PlanetSideGeneratorState, Vector3}
import scalax.collection.{Graph, GraphEdge}
import services.Service
import services.local.{LocalAction, LocalServiceMessage}
import akka.actor.typed.scaladsl.adapter._
class Building(
private val name: String,
private val building_guid: Int,
private val map_id: Int,
private val zone: Zone,
private val buildingType: StructureType.Value,
private val buildingType: StructureType,
private val buildingDefinition: BuildingDefinition
) extends AmenityOwner {
@ -71,7 +73,8 @@ class Building(
} else if (IsCapitol) {
UpdateForceDomeStatus()
}
TriggerZoneMapUpdate()
// FIXME null check is a bad idea but tests rely on it
if (Zone.actor != null) Zone.actor ! ZoneActor.ZoneMapUpdate()
Faction
}
@ -126,10 +129,6 @@ class Building(
}
}
def TriggerZoneMapUpdate(): Unit = {
if (Actor != Default.Actor) Actor ! Building.TriggerZoneMapUpdate(Zone.Number)
}
def UpdateForceDomeStatus(): Unit = {
if (IsCapitol) {
val originalStatus = ForceDomeActive
@ -155,7 +154,7 @@ class Building(
Zone.Id,
LocalAction.UpdateForceDomeStatus(Service.defaultPlayerGUID, GUID, ForceDomeActive)
)
Actor ! Building.SendMapUpdate(all_clients = true)
Actor ! BuildingActor.MapUpdate()
}
}
}
@ -171,27 +170,7 @@ class Building(
}
}
def Info: (
Int,
Boolean,
PlanetSideEmpire.Value,
Long,
PlanetSideEmpire.Value,
Long,
Option[Additional1],
PlanetSideGeneratorState.Value,
Boolean,
Boolean,
Int,
Int,
List[Additional2],
Long,
Boolean,
Int,
Option[Additional3],
Boolean,
Boolean
) = {
def infoUpdateMessage(): BuildingInfoUpdateMessage = {
val ntuLevel: Int = NtuLevel
//if we have a capture terminal, get the hack status & time (in milliseconds) from control console if it exists
val (hacking, hackingFaction, hackTime): (Boolean, PlanetSideEmpire.Value, Long) = CaptureTerminal match {
@ -264,31 +243,33 @@ class Building(
}
}
}
//out
(
BuildingInfoUpdateMessage(
Zone.Number,
MapId,
ntuLevel,
hacking,
hackingFaction,
hackTime,
if (ntuLevel > 0) Faction else PlanetSideEmpire.NEUTRAL,
0, //!! Field != 0 will cause malformed packet. See class def.
0, // Field != 0 will cause malformed packet
None,
generatorState,
spawnTubesNormal,
ForceDomeActive,
forceDomeActive,
latticeBenefit,
48, //cavern_benefit; !! Field > 0 will cause malformed packet. See class def.
Nil, //unk4
0, //unk5
false, //unk6
8, //!! unk7 Field != 8 will cause malformed packet. See class def.
None, //unk7x
boostSpawnPain, //boost_spawn_pain
boostGeneratorPain //boost_generator_pain
48, // cavern benefit
Nil, // unk4,
0, // unk5
false, // unk6
8, // unk7 Field != 8 will cause malformed packet
None, // unk7x
boostSpawnPain,
boostGeneratorPain
)
}
def BuildingType: StructureType.Value = buildingType
def BuildingType: StructureType = buildingType
override def Zone_=(zone: Zone): Zone = Zone //building never leaves zone after being set in constructor
@ -307,58 +288,51 @@ object Building {
GUID = net.psforever.types.PlanetSideGUID(0)
}
def apply(name: String, guid: Int, map_id: Int, zone: Zone, buildingType: StructureType.Value): Building = {
def apply(name: String, guid: Int, map_id: Int, zone: Zone, buildingType: StructureType): Building = {
new Building(name, guid, map_id, zone, buildingType, GlobalDefinitions.building)
}
def Structure(
buildingType: StructureType.Value,
buildingType: StructureType,
location: Vector3,
rotation: 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
obj.Orientation = rotation
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-$buildingType-building")
obj.Actor = context.spawn(BuildingActor(zone, obj), s"$map_id-$buildingType-building").toClassic
obj
}
def Structure(
buildingType: StructureType.Value,
buildingType: StructureType,
location: Vector3
)(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, GlobalDefinitions.building)
obj.Position = location
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-$buildingType-building")
obj.Actor = context.spawn(BuildingActor(zone, obj), s"$map_id-$buildingType-building").toClassic
obj
}
def Structure(
buildingType: StructureType.Value
buildingType: StructureType
)(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, GlobalDefinitions.building)
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$map_id-$buildingType-building")
obj.Actor = context.spawn(BuildingActor(zone, obj), s"$map_id-$buildingType-building").toClassic
obj
}
def Structure(
buildingType: StructureType.Value,
buildingType: StructureType,
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
obj.Actor = context.actorOf(Props(classOf[BuildingControl], obj), s"$id-$buildingType-building")
context.spawn(BuildingActor(zone, obj), s"$id-$buildingType-building").toClassic
obj
}
final case class AmenityStateChange(obj: Amenity)
final case class SendMapUpdate(all_clients: Boolean)
final case class TriggerZoneMapUpdate(zone_num: Int)
}

View file

@ -1,121 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.structures
import akka.actor.{Actor, ActorRef}
import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.generator.Generator
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.zones.InterstellarCluster
import net.psforever.packet.game.BuildingInfoUpdateMessage
import services.ServiceManager
import services.ServiceManager.Lookup
import services.galaxy.{GalaxyAction, GalaxyResponse, GalaxyServiceMessage, GalaxyServiceResponse}
class BuildingControl(building: Building) extends Actor with FactionAffinityBehavior.Check {
def FactionObject: FactionAffinity = building
var galaxyService: ActorRef = ActorRef.noSender
var interstellarCluster: ActorRef = ActorRef.noSender
private[this] val log = org.log4s.getLogger
override def preStart = {
log.trace(s"Starting BuildingControl for ${building.GUID} / ${building.MapId}")
ServiceManager.serviceManager ! Lookup("galaxy")
ServiceManager.serviceManager ! Lookup("cluster")
}
def receive: Receive =
checkBehavior.orElse {
case ServiceManager.LookupResult("galaxy", endpoint) =>
galaxyService = endpoint
log.trace("BuildingControl: Building " + building.GUID + " Got galaxy service " + endpoint)
case ServiceManager.LookupResult("cluster", endpoint) =>
interstellarCluster = endpoint
log.trace("BuildingControl: Building " + building.GUID + " Got interstellar cluster service " + endpoint)
case FactionAffinity.ConvertFactionAffinity(faction) =>
val originalAffinity = building.Faction
if (originalAffinity != (building.Faction = faction)) {
building.Amenities.foreach(_.Actor forward FactionAffinity.ConfirmFactionAffinity())
}
sender ! FactionAffinity.AssertFactionAffinity(building, faction)
case Building.AmenityStateChange(obj: SpawnTube) =>
if (building.Amenities.contains(obj)) {
SendMapUpdate(allClients = true)
}
case Building.AmenityStateChange(obj: Generator) =>
if (building.Amenities.contains(obj)) {
SendMapUpdate(allClients = true)
}
case Building.TriggerZoneMapUpdate(zone_num: Int) =>
if (interstellarCluster != ActorRef.noSender) interstellarCluster ! InterstellarCluster.ZoneMapUpdate(zone_num)
case Building.SendMapUpdate(all_clients: Boolean) =>
SendMapUpdate(all_clients)
case _ =>
}
/**
* na
* @param allClients na
*/
def SendMapUpdate(allClients: Boolean): Unit = {
val zoneNumber = building.Zone.Number
val buildingNumber = building.MapId
log.trace(s"sending BuildingInfoUpdateMessage update - zone=$zoneNumber, building=$buildingNumber")
val (
ntuLevel,
isHacked,
empireHack,
hackTimeRemaining,
controllingEmpire,
unk1,
unk1x,
generatorState,
spawnTubesNormal,
forceDomeActive,
latticeBenefit,
cavernBenefit,
unk4,
unk5,
unk6,
unk7,
unk7x,
boostSpawnPain,
boostGeneratorPain
) = building.Info
val msg = BuildingInfoUpdateMessage(
zoneNumber,
buildingNumber,
ntuLevel,
isHacked,
empireHack,
hackTimeRemaining,
controllingEmpire,
unk1,
unk1x,
generatorState,
spawnTubesNormal,
forceDomeActive,
latticeBenefit,
cavernBenefit,
unk4,
unk5,
unk6,
unk7,
unk7x,
boostSpawnPain,
boostGeneratorPain
)
if (allClients) {
if (galaxyService != ActorRef.noSender) galaxyService ! GalaxyServiceMessage(GalaxyAction.MapUpdate(msg))
} else {
// Fake a GalaxyServiceResponse response back to just the sender
sender ! GalaxyServiceResponse("", GalaxyResponse.MapUpdate(msg))
}
}
}

View file

@ -1,19 +1,19 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.structures
/**
* An `Enumeration` of the kinds of building structures found in the game.
* This is merely a kludge for more a future, more complicated internal object that handles base operations.
*/
object StructureType extends Enumeration {
type Type = Value
import enumeratum.{EnumEntry, Enum}
val Bridge, // technically, a "bridge section"
Building, // generic
Bunker, // low accessible ground cover
Facility, // large base
Platform, // outdoor amenities disconnected from a proper base like the vehicle spawn pads in sanctuary
Tower, // also called field towers: watchtower, air tower, gun tower
WarpGate // transport point between zones
= Value
sealed trait StructureType extends EnumEntry
object StructureType extends Enum[StructureType] {
val values = findValues
case object Bridge extends StructureType // technically, a "bridge section"
case object Building extends StructureType // generic
case object Bunker extends StructureType // low accessible ground cover
case object Facility extends StructureType // large base
case object Platform
extends StructureType // outdoor amenities disconnected from a proper base like the vehicle spawn pads in sanctuary
case object Tower extends StructureType // also called field towers: watchtower, air tower, gun tower
case object WarpGate extends StructureType // transport point between zones
}

View file

@ -5,8 +5,10 @@ import akka.actor.ActorContext
import net.psforever.objects.serverobject.PlanetSideServerObject
import net.psforever.objects.{GlobalDefinitions, NtuContainer, SpawnPoint}
import net.psforever.objects.zones.Zone
import net.psforever.packet.game.{Additional1, Additional2, Additional3}
import net.psforever.packet.game.BuildingInfoUpdateMessage
import net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3}
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.BuildingActor
import scala.collection.mutable
@ -20,28 +22,10 @@ class WarpGate(name: String, building_guid: Int, map_id: Int, zone: Zone, buildi
/** what faction views this warp gate as a broadcast gate */
private var broadcast: mutable.Set[PlanetSideEmpire.Value] = mutable.Set.empty[PlanetSideEmpire.Value]
override def Info: (
Int,
Boolean,
PlanetSideEmpire.Value,
Long,
PlanetSideEmpire.Value,
Long,
Option[Additional1],
PlanetSideGeneratorState.Value,
Boolean,
Boolean,
Int,
Int,
List[Additional2],
Long,
Boolean,
Int,
Option[Additional3],
Boolean,
Boolean
) = {
(
override def infoUpdateMessage(): BuildingInfoUpdateMessage = {
BuildingInfoUpdateMessage(
Zone.Number,
MapId,
0,
false,
PlanetSideEmpire.NEUTRAL,
@ -179,19 +163,17 @@ object WarpGate {
}
def Structure(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, GlobalDefinitions.warpgate)
obj.Actor = context.actorOf(Props(classOf[WarpGateControl], obj), name = s"$map_id-$guid-gate")
obj.Actor = context.spawn(BuildingActor(zone, obj), name = s"$map_id-$guid-gate").toClassic
obj
}
def Structure(
location: Vector3
)(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, GlobalDefinitions.warpgate)
obj.Position = location
obj.Actor = context.actorOf(Props(classOf[WarpGateControl], obj), name = s"$map_id-$guid-gate")
obj.Actor = context.spawn(BuildingActor(zone, obj), name = s"$map_id-$guid-gate").toClassic
obj
}
@ -199,10 +181,9 @@ object WarpGate {
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
obj.Actor = context.actorOf(Props(classOf[WarpGateControl], obj), name = s"$map_id-$guid-gate")
obj.Actor = context.spawn(BuildingActor(zone, obj), name = s"$map_id-$guid-gate").toClassic
obj
}
}

View file

@ -1,38 +0,0 @@
// Copyright (c) 2020 PSForever
package net.psforever.objects.serverobject.structures
import akka.actor.ActorRef
import net.psforever.objects.{Ntu, NtuContainer, NtuStorageBehavior}
class WarpGateControl(gate : WarpGate) extends BuildingControl(gate)
with NtuStorageBehavior {
override def receive : Receive = storageBehavior.orElse(super.receive)
/**
* Warp gates don't need to respond to offers.
*/
def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit = {}
/**
* Warp gates don't need to stop.
*/
def StopNtuBehavior(sender : ActorRef) : Unit = {}
/**
* When processing a request, the only important consideration is whether the warp gate is active.
* @param sender na
* @param min a minimum amount of nanites requested;
* @param max the amount of nanites required to not make further requests;
*/
def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit = {
sender ! Ntu.Grant(gate, if (gate.Active) min else 0)
}
/**
* Warp gates doesn't need additional nanites.
* For the sake of not letting any go to waste, it will give back those nanites for free.
*/
def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit = {
sender ! Ntu.Grant(gate, amount)
}
}

View file

@ -2,6 +2,8 @@
package net.psforever.objects.vehicles
import akka.actor.{ActorRef, Cancellable}
import net.psforever.actors.commands.NtuCommand
import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.serverobject.deploy.Deployment
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.WarpGate
@ -10,21 +12,22 @@ import net.psforever.objects.{NtuContainer, _}
import net.psforever.types.DriveState
import services.Service
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import akka.actor.typed.scaladsl.adapter._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._
trait AntTransferBehavior extends TransferBehavior
with NtuStorageBehavior {
var ntuChargingTick : Cancellable = Default.Cancellable
var panelAnimationFunc : ()=>Unit = NoCharge
trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
var ntuChargingTick: Cancellable = Default.Cancellable
var panelAnimationFunc: () => Unit = NoCharge
def TransferMaterial = Ntu.Nanites
def ChargeTransferObject : Vehicle with NtuContainer
def antBehavior : Receive = storageBehavior.orElse(transferBehavior)
def ChargeTransferObject: Vehicle with NtuContainer
def ActivatePanelsForChargingEvent(vehicle : NtuContainer) : Unit = {
def antBehavior: Receive = storageBehavior.orElse(transferBehavior)
def ActivatePanelsForChargingEvent(vehicle: NtuContainer): Unit = {
val zone = vehicle.Zone
zone.VehicleEvents ! VehicleServiceMessage(
zone.Id,
@ -33,7 +36,7 @@ trait AntTransferBehavior extends TransferBehavior
}
/** Charging */
def StartNtuChargingEvent(vehicle : NtuContainer) : Unit = {
def StartNtuChargingEvent(vehicle: NtuContainer): Unit = {
val zone = vehicle.Zone
zone.VehicleEvents ! VehicleServiceMessage(
zone.Id,
@ -41,8 +44,8 @@ trait AntTransferBehavior extends TransferBehavior
) // orb particle effect on
}
def UpdateNtuUI(vehicle : Vehicle with NtuContainer) : Unit = {
if(vehicle.Seats.values.exists(_.isOccupied)) {
def UpdateNtuUI(vehicle: Vehicle with NtuContainer): Unit = {
if (vehicle.Seats.values.exists(_.isOccupied)) {
val display = scala.math.ceil(vehicle.NtuCapacitorScaled).toLong
vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
vehicle.Actor.toString,
@ -51,76 +54,82 @@ trait AntTransferBehavior extends TransferBehavior
}
}
def HandleChargingEvent(target : TransferContainer) : Boolean = {
def HandleChargingEvent(target: TransferContainer): Boolean = {
ntuChargingTick.cancel
val obj = ChargeTransferObject
//log.trace(s"NtuCharging: Vehicle $guid is charging NTU capacitor.")
if(obj.NtuCapacitor < obj.Definition.MaxNtuCapacitor) {
if (obj.NtuCapacitor < obj.Definition.MaxNtuCapacitor) {
//charging
panelAnimationFunc = InitialCharge
transferTarget = Some(target)
transferEvent = TransferBehavior.Event.Charging
val (min, max) = target match {
case _ : WarpGate =>
target match {
case _: WarpGate =>
//ANTs would charge from 0-100% in roughly 75s on live (https://www.youtube.com/watch?v=veOWToR2nSk&feature=youtu.be&t=1194)
val ntuMax = obj.Definition.MaxNtuCapacitor - obj.NtuCapacitor
val ntuMin = scala.math.min(obj.Definition.MaxNtuCapacitor/75, ntuMax)
(ntuMin, ntuMax)
val max = obj.Definition.MaxNtuCapacitor - obj.NtuCapacitor
target.Actor ! BuildingActor.Ntu(
NtuCommand.Request(scala.math.min(obj.Definition.MaxNtuCapacitor / 75, max), context.self)
)
case _ =>
(0, 0)
}
target.Actor ! Ntu.Request(min, max)
ntuChargingTick = context.system.scheduler.scheduleOnce(delay = 1000 milliseconds, self, TransferBehavior.Charging(TransferMaterial)) // Repeat until fully charged, or minor delay
ntuChargingTick = context.system.scheduler.scheduleOnce(
delay = 1000 milliseconds,
self,
TransferBehavior.Charging(TransferMaterial)
) // Repeat until fully charged, or minor delay
true
}
else {
} else {
// Fully charged
TryStopChargingEvent(obj)
false
}
}
def ReceiveAndDepositUntilFull(vehicle : Vehicle, amount : Int) : Boolean = {
def ReceiveAndDepositUntilFull(vehicle: Vehicle, amount: Int): Boolean = {
val isNotFull = (vehicle.NtuCapacitor += amount) < vehicle.Definition.MaxNtuCapacitor
UpdateNtuUI(vehicle)
isNotFull
}
/** Discharging */
def HandleDischargingEvent(target : TransferContainer) : Boolean = {
def HandleDischargingEvent(target: TransferContainer): Boolean = {
//log.trace(s"NtuDischarging: Vehicle $guid is discharging NTU into silo $silo_guid")
val obj = ChargeTransferObject
if(obj.NtuCapacitor > 0) {
if (obj.NtuCapacitor > 0) {
panelAnimationFunc = InitialDischarge
transferTarget = Some(target)
transferEvent = TransferBehavior.Event.Discharging
target.Actor ! Ntu.Offer(obj)
ntuChargingTick.cancel
ntuChargingTick = context.system.scheduler.scheduleOnce(delay = 1000 milliseconds, self, TransferBehavior.Discharging(TransferMaterial))
ntuChargingTick = context.system.scheduler.scheduleOnce(
delay = 1000 milliseconds,
self,
TransferBehavior.Discharging(TransferMaterial)
)
true
}
else {
} else {
TryStopChargingEvent(obj)
false
}
}
def NoCharge() : Unit = {}
def NoCharge(): Unit = {}
def InitialCharge() : Unit = {
def InitialCharge(): Unit = {
panelAnimationFunc = NoCharge
val obj = ChargeTransferObject
ActivatePanelsForChargingEvent(obj)
StartNtuChargingEvent(obj)
}
def InitialDischarge() : Unit = {
def InitialDischarge(): Unit = {
panelAnimationFunc = NoCharge
ActivatePanelsForChargingEvent(ChargeTransferObject)
}
def WithdrawAndTransmit(vehicle : Vehicle, maxRequested : Int) : Any = {
val chargeable = ChargeTransferObject
def WithdrawAndTransmit(vehicle: Vehicle, maxRequested: Int): Any = {
val chargeable = ChargeTransferObject
var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), maxRequested)
chargeable.NtuCapacitor -= chargeToDeposit
UpdateNtuUI(chargeable)
@ -128,27 +137,34 @@ trait AntTransferBehavior extends TransferBehavior
}
/** Stopping */
override def TryStopChargingEvent(container : TransferContainer) : Unit = {
override def TryStopChargingEvent(container: TransferContainer): Unit = {
val vehicle = ChargeTransferObject
ntuChargingTick.cancel
if(transferEvent != TransferBehavior.Event.None) {
if(vehicle.DeploymentState == DriveState.Deployed) {
if (transferEvent != TransferBehavior.Event.None) {
if (vehicle.DeploymentState == DriveState.Deployed) {
//turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT first
vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
ntuChargingTick = context.system.scheduler.scheduleOnce(250 milliseconds, self, TransferBehavior.Stopping())
}
else {
} else {
//vehicle is not deployed; just do cleanup
val vguid = vehicle.GUID
val zone = vehicle.Zone
val vguid = vehicle.GUID
val zone = vehicle.Zone
val zoneId = zone.Id
val events = zone.VehicleEvents
if(transferEvent == TransferBehavior.Event.Charging) {
events ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L)) // panel glow off
events ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 49, 0L)) // orb particle effect off
}
else if(transferEvent == TransferBehavior.Event.Discharging) {
events ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L)) // panel glow off
if (transferEvent == TransferBehavior.Event.Charging) {
events ! VehicleServiceMessage(
zoneId,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L)
) // panel glow off
events ! VehicleServiceMessage(
zoneId,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 49, 0L)
) // orb particle effect off
} else if (transferEvent == TransferBehavior.Event.Discharging) {
events ! VehicleServiceMessage(
zoneId,
VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L)
) // panel glow off
}
}
panelAnimationFunc = NoCharge
@ -156,39 +172,37 @@ trait AntTransferBehavior extends TransferBehavior
}
}
def StopNtuBehavior(sender : ActorRef) : Unit = TryStopChargingEvent(ChargeTransferObject)
def StopNtuBehavior(sender: ActorRef): Unit = TryStopChargingEvent(ChargeTransferObject)
def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit = { }
def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = {}
def HandleNtuRequest(sender : ActorRef, min : Int, max : Int) : Unit = {
if(transferEvent == TransferBehavior.Event.Discharging) {
def HandleNtuRequest(sender: ActorRef, min: Int, max: Int): Unit = {
if (transferEvent == TransferBehavior.Event.Discharging) {
val chargeable = ChargeTransferObject
val chargeToDeposit = if(min == 0) {
val chargeToDeposit = if (min == 0) {
transferTarget match {
case Some(silo : ResourceSilo) =>
case Some(silo: ResourceSilo) =>
// Silos would charge from 0-100% in roughly 105s on live (~20%-100% https://youtu.be/veOWToR2nSk?t=1402)
scala.math.min(scala.math.min(silo.MaxNtuCapacitor / 105, chargeable.NtuCapacitor), max)
case _ =>
0
}
}
else {
} else {
scala.math.min(min, chargeable.NtuCapacitor)
}
// var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), max)
// var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), max)
chargeable.NtuCapacitor -= chargeToDeposit
UpdateNtuUI(chargeable)
sender ! Ntu.Grant(chargeable, chargeToDeposit)
}
}
def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit = {
if(transferEvent == TransferBehavior.Event.Charging) {
def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Int): Unit = {
if (transferEvent == TransferBehavior.Event.Charging) {
val obj = ChargeTransferObject
if(ReceiveAndDepositUntilFull(obj, amount)) {
if (ReceiveAndDepositUntilFull(obj, amount)) {
panelAnimationFunc()
}
else {
} else {
TryStopChargingEvent(obj)
sender ! Ntu.Request(0, 0)
}

View file

@ -1,228 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.zones
import akka.actor.{Actor, Props}
import net.psforever.objects.serverobject.structures.{Building, StructureType}
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."
* Constructs each zone and thus instigates the construction of every server object in the game world.
* The nanite flow connecting all of these `Zone`s is called the "Intercontinental Lattice."<br>
* <br>
* The process of "construction" and "initialization" and "configuration" are referenced at this level.
* These concepts are not the same thing;
* the distinction is important.
* "Construction" and "instantiation" of the cluster merely produces the "facade" of the different `Zone` entities.
* In such a `List`, every built `Zone` is capable of being a destination on the "Intercontinental lattice."
* "Initialization" and "configuration" of the cluster refers to the act of completing the "Intercontinental Lattice"
* by connecting different terminus warp gates together.
* Other activities involve event management and managing wide-reaching and factional attributes.
* @param zones a `List` of continental `Zone` arenas
*/
class InterstellarCluster(zones: List[Zone]) extends Actor {
private[this] val log = org.log4s.getLogger
val recallRandom = new Random()
log.info("Starting interplanetary cluster ...")
/**
* Create a `ZoneActor` for each `Zone`.
* That `Actor` is sent a packet that would start the construction of the `Zone`'s server objects.
* The process is maintained this way to allow every planet to be created and configured in separate stages.
*/
override def preStart(): Unit = {
super.preStart()
for (zone <- zones) {
log.info(s"Built continent ${zone.Id}")
zone.Actor = context.actorOf(Props(classOf[ZoneActor], zone), s"${zone.Id}-actor")
zone.Actor ! Zone.Init()
}
}
def receive: Receive = {
case InterstellarCluster.GetWorld(zoneId) =>
log.info(s"Asked to find $zoneId")
recursiveFindWorldInCluster(zones.iterator, _.Id == zoneId) match {
case Some(continent) =>
sender ! InterstellarCluster.GiveWorld(zoneId, continent)
case None =>
log.error(s"Requested zone $zoneId could not be found")
}
case InterstellarCluster.RequestClientInitialization() =>
zones.foreach(zone => { sender ! Zone.ClientInitialization(zone.ClientInitialization()) })
sender ! InterstellarCluster.ClientInitializationComplete() //will be processed after all Zones
case msg @ Zone.Lattice.RequestSpawnPoint(zone_number, _, _, _) =>
recursiveFindWorldInCluster(zones.iterator, _.Number == zone_number) match {
case Some(zone) =>
zone.Actor forward msg
case None => //zone_number does not exist
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None)
}
case InterstellarCluster.ListPlayers() =>
var players: List[String] = List()
for (zone <- zones) {
val zonePlayers = zone.Players
for (player <- zonePlayers) {
players ::= player.name
}
}
sender ! InterstellarCluster.PlayerList(players)
case InterstellarCluster.GetZoneIds() =>
sender ! InterstellarCluster.ZoneIds(zones.map(_.Number))
case msg @ Zone.Lattice.RequestSpecificSpawnPoint(zone_number, _, _, _) =>
recursiveFindWorldInCluster(zones.iterator, _.Number == zone_number) match {
case Some(zone) =>
zone.Actor forward msg
case None => //zone_number does not exist
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None)
}
case InterstellarCluster.ZoneMapUpdate(zone_num: Int) =>
val zone = zones.find(x => x.Number == zone_num).get
zone.Buildings
.filter(_._2.BuildingType == StructureType.Facility)
.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");
}
/**
* Search through the `List` of `Zone` entities and find the one with the matching designation.
* @param iter an `Iterator` of `Zone` entities
* @param predicate a condition to check against to determine when the appropriate `Zone` is discovered
* @return the discovered `Zone`
*/
@tailrec private def recursiveFindWorldInCluster(iter: Iterator[Zone], predicate: Zone => Boolean): Option[Zone] = {
if (!iter.hasNext) {
None
} else {
val cont = iter.next
if (predicate.apply(cont)) {
Some(cont)
} else {
recursiveFindWorldInCluster(iter, predicate)
}
}
}
}
object InterstellarCluster {
/**
* Request a hard reference to a `Zone`.
* @param zoneId the name of the `Zone`
*/
final case class GetWorld(zoneId: String)
/**
* Provide a hard reference to a `Zone`.
* @param zoneId the name of the `Zone`
* @param zone the `Zone`
*/
final case class GiveWorld(zoneId: String, zone: Zone)
final case class ListPlayers()
final case class PlayerList(players: List[String])
/**
* Signal to the cluster that a new client needs to be initialized for all listed `Zone` destinations.
* @see `Zone`
*/
final case class RequestClientInitialization()
/**
* Return signal intended to inform the original sender that all `Zone`s have finished being initialized.
* @see `WorldSessionActor`
*/
final case class ClientInitializationComplete()
/**
* Requests that all buildings within a zone send a map update for the purposes of refreshing lattice benefits, such as when a base is hacked, changes faction or loses power
* @see `BuildingInfoUpdateMessage`
* @param zone_num the zone number to request building map updates for
*/
final case class ZoneMapUpdate(zone_num: Int)
final case class GetZoneIds()
final case class ZoneIds(zoneIds: List[Int])
}

View file

@ -16,10 +16,11 @@ import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.inventory.Container
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.structures.{Amenity, AmenityOwner, Building, StructureType, WarpGate}
import net.psforever.objects.serverobject.turret.FacilityTurret
import net.psforever.objects.serverobject.zipline.ZipLinePath
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, Vector3}
import net.psforever.types.{DriveState, PlanetSideEmpire, PlanetSideGUID, SpawnGroup, Vector3}
import org.log4s.Logger
import services.avatar.AvatarService
import services.local.LocalService
import services.vehicle.VehicleService
@ -33,6 +34,10 @@ import scalax.collection.GraphPredef._
import scalax.collection.GraphEdge._
import scala.util.Try
import akka.actor.typed
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.vehicles.UtilityType
/**
* A server object representing the one-landmass planets as well as the individual subterranean caverns.<br>
@ -45,20 +50,21 @@ import scala.util.Try
* Static server objects originate from the `ZoneMap`.
* Dynamic game objects originate from player characters.
* (Write more later.)
* @param zoneId the privileged name that can be used as the second parameter in the packet `LoadMapMessage`
* @param zoneMap the map of server objects upon which this `Zone` is based
*
* @param zoneId the privileged name that can be used as the second parameter in the packet `LoadMapMessage`
* @param map the map of server objects upon which this `Zone` is based
* @param zoneNumber the numerical index of the `Zone` as it is recognized in a variety of packets;
* also used by `LivePlayerList` to indicate a specific `Zone`
* @see `ZoneMap`<br>
* `LoadMapMessage`<br>
* `LivePlayerList`
*/
class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
class Zone(private val zoneId: String, val map: ZoneMap, zoneNumber: Int) {
/** Governs general synchronized external requests. */
private var actor = Default.Actor
var actor: typed.ActorRef[ZoneActor.Command] = _
/** Actor that handles SOI related functionality, for example if a player is in a SOI * */
/** Actor that handles SOI related functionality, for example if a player is in a SOI */
private var soi = Default.Actor
/** Used by the globally unique identifier system to coordinate requests. */
@ -79,8 +85,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
/**
*/
private val constructions: ListBuffer[PlanetSideGameObject with Deployable] =
ListBuffer[PlanetSideGameObject with Deployable]()
private val constructions: ListBuffer[PlanetSideGameObject with Deployable] = ListBuffer()
/**
*/
@ -106,8 +111,6 @@ 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]]()
@ -155,31 +158,32 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
* Execution of this operation should be fail-safe.
* The chances of failure should be mitigated or skipped.
* A testing routine should be run after the fact on the results of the process.
*
* @see `ZoneActor.ZoneSetupCheck`
* @param context a reference to an `ActorContext` necessary for `Props`
*/
def Init(implicit context: ActorContext): Unit = {
def init(implicit context: ActorContext): Unit = {
if (accessor == Default.Actor) {
SetupNumberPools()
accessor = context.actorOf(
RandomPool(25).props(
Props(classOf[UniqueNumberSystem], this.guid, UniqueNumberSystem.AllocateNumberPoolActors(this.guid))
),
s"$Id-uns"
s"zone-$Id-uns"
)
ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"$Id-ground")
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")
ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"zone-$Id-ground")
deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"zone-$Id-deployables")
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-$Id-vehicles")
population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"zone-$Id-players")
projector = context.actorOf(
Props(classOf[ZoneHotSpotDisplay], this, hotspots, 15 seconds, hotspotHistory, 60 seconds),
s"$Id-hotspots"
s"zone-$Id-hotspots"
)
soi = context.actorOf(Props(classOf[SphereOfInfluenceActor], this), s"$Id-soi")
soi = context.actorOf(Props(classOf[SphereOfInfluenceActor], this), s"zone-$Id-soi")
avatarEvents = context.actorOf(Props(classOf[AvatarService], this), s"$Id-avatar-events")
localEvents = context.actorOf(Props(classOf[LocalService], this), s"$Id-local-events")
vehicleEvents = context.actorOf(Props(classOf[VehicleService], this), s"$Id-vehicle-events")
avatarEvents = context.actorOf(Props(classOf[AvatarService], this), s"zone-$Id-avatar-events")
localEvents = context.actorOf(Props(classOf[LocalService], this), s"zone-$Id-local-events")
vehicleEvents = context.actorOf(Props(classOf[VehicleService], this), s"zone-$Id-vehicle-events")
implicit val guid: NumberPoolHub = this.guid //passed into builderObject.Build implicitly
BuildLocalObjects(context, guid)
@ -189,7 +193,118 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
AssignAmenities()
CreateSpawnGroups()
zipLinePaths = Map.ZipLinePaths
validate()
}
}
def validate(): Unit = {
implicit val log: Logger = org.log4s.getLogger(s"zone/$Id/sanity")
//check bases
map.ObjectToBuilding.values
.toSet[Int]
.foreach(building_id => {
val target = Building(building_id)
if (target.isEmpty) {
log.error(s"expected a building for id #$building_id")
} else if (!target.get.HasGUID) {
log.error(s"building #$building_id was not registered")
}
})
//check base to object associations
map.ObjectToBuilding.keys.foreach(object_guid =>
if (guid(object_guid).isEmpty) {
log.error(s"expected object id $object_guid to exist, but it did not")
}
)
//check door to lock association
map.DoorToLock.foreach({
case (doorGuid, lockGuid) =>
validateObject(doorGuid, (x: PlanetSideGameObject) => x.isInstanceOf[serverobject.doors.Door], "door")
validateObject(lockGuid, (x: PlanetSideGameObject) => x.isInstanceOf[serverobject.locks.IFFLock], "IFF lock")
})
//check vehicle terminal to spawn pad association
map.TerminalToSpawnPad.foreach({
case (termGuid, padGuid) =>
validateObject(
termGuid,
(x: PlanetSideGameObject) => x.isInstanceOf[serverobject.terminals.Terminal],
"vehicle terminal"
)
validateObject(
padGuid,
(x: PlanetSideGameObject) => x.isInstanceOf[serverobject.pad.VehicleSpawnPad],
"vehicle spawn pad"
)
})
//check implant terminal mech to implant terminal interface association
map.TerminalToInterface.foreach({
case (mechGuid, interfaceGuid) =>
validateObject(
mechGuid,
(x: PlanetSideGameObject) => x.isInstanceOf[serverobject.implantmech.ImplantTerminalMech],
"implant terminal mech"
)
validateObject(
interfaceGuid,
(o: PlanetSideGameObject) => o.isInstanceOf[serverobject.terminals.Terminal],
"implant terminal interface"
)
})
//check manned turret to weapon association
map.TurretToWeapon.foreach({
case (turretGuid, weaponGuid) =>
validateObject(
turretGuid,
(o: PlanetSideGameObject) => o.isInstanceOf[serverobject.turret.FacilityTurret],
"facility turret mount"
)
if (
validateObject(
weaponGuid,
(o: PlanetSideGameObject) => o.isInstanceOf[net.psforever.objects.Tool],
"facility turret weapon"
)
) {
if (GUID(weaponGuid).get.asInstanceOf[Tool].AmmoSlots.count(!_.Box.HasGUID) > 0) {
log.error(s"expected weapon $weaponGuid has an unregistered ammunition unit")
}
}
})
}
/**
* Recover an object from a collection and perform any number of validating tests upon it.
* If the object fails any tests, log an error.
*
* @param objectGuid the unique indentifier being checked against the `guid` access point
* @param test a test for the discovered object;
* expects at least `Type` checking
* @param description an explanation of how the object, if not discovered, should be identified
* @return `true` if the object was discovered and validates correctly;
* `false` if the object failed any tests
*/
def validateObject(
objectGuid: Int,
test: PlanetSideGameObject => Boolean,
description: String
)(implicit log: Logger): Boolean = {
try {
if (!test(GUID(objectGuid).get)) {
log.error(s"expected id $objectGuid to be a $description, but it was not")
false
} else {
true
}
} catch {
case e: Exception =>
log.error(s"expected a $description at id $objectGuid but no object is initialized - $e")
false
}
}
@ -213,42 +328,89 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
//guid.AddPool("l", (60001 to 65535).toList).Selector = new RandomSelector
}
/**
* A reference to the primary `Actor` that governs this `Zone`.
* @return an `ActorRef`
* @see `ZoneActor`<br>
* `Zone.Init`
*/
def Actor: ActorRef = actor
/**
* Give this `Zone` an `Actor` that will govern its interactions sequentially.
* @param zoneActor an `ActorRef` for this `Zone`;
* will not overwrite any existing governance unless `noSender`
* @return an `ActorRef`
* @see `ZoneActor`
*/
def Actor_=(zoneActor: ActorRef): ActorRef = {
if (actor == Default.Actor) {
actor = zoneActor
def findSpawns(
faction: PlanetSideEmpire.Value,
spawnGroups: Seq[SpawnGroup]
): List[(AmenityOwner, Iterable[SpawnPoint])] = {
val ams = spawnGroups.contains(SpawnGroup.AMS)
val structures = spawnGroups.collect {
case SpawnGroup.Facility =>
StructureType.Facility
case SpawnGroup.Tower =>
StructureType.Tower
case SpawnGroup.WarpGate =>
StructureType.WarpGate
case SpawnGroup.Sanctuary =>
StructureType.Building
}
Actor
SpawnGroups()
.filter {
case (building, spawns) =>
spawns.nonEmpty &&
spawns.exists(_.Offline == false) &&
structures.contains(building.BuildingType)
}
.filter {
case (building, _) =>
building match {
case warpGate: WarpGate =>
warpGate.Faction == faction || warpGate.Faction == PlanetSideEmpire.NEUTRAL || warpGate.Broadcast
case building =>
building.Faction == faction
}
}
.map {
case (building, spawns) =>
(building, spawns.filter(!_.Offline))
}
.concat(
(if (ams) Vehicles else List())
.filter(vehicle =>
vehicle.Definition == GlobalDefinitions.ams &&
!vehicle.Destroyed &&
vehicle.DeploymentState == DriveState.Deployed &&
vehicle.Faction == faction
)
.map(vehicle =>
(
vehicle,
vehicle.Utilities.values
.filter(util => util.UtilType == UtilityType.ams_respawn_tube)
.map(_().asInstanceOf[SpawnTube])
)
)
)
.toList
}
def findNearestSpawnPoints(
faction: PlanetSideEmpire.Value,
location: Vector3,
spawnGroups: Seq[SpawnGroup]
): Option[List[SpawnPoint]] = {
findSpawns(faction, spawnGroups)
.sortBy {
case (spawn, _) =>
Vector3.DistanceSquared(location, spawn.Position.xy)
}
.collectFirst {
case (_, spawnPoints) if spawnPoints.nonEmpty =>
spawnPoints.toList
}
}
/**
* The privileged name that can be used as the second parameter in the packet `LoadMapMessage`.
*
* @return the name
*/
def Id: String = zoneId
/**
* The map of server objects upon which this `Zone` is based
* @return the map
*/
def Map: ZoneMap = zoneMap
/**
* The numerical index of the `Zone` as it is recognized in a variety of packets.
*
* @return the abstract index position of this `Zone`
*/
def Number: Int = zoneNumber
@ -268,7 +430,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
* @return synchronized reference to the globally unique identifier system
*/
def GUID(hub: NumberPoolHub): Boolean = {
if (actor == Default.Actor && guid.Pools.values.foldLeft(0)(_ + _.Count) == 0) {
if (actor == null && guid.Pools.values.foldLeft(0)(_ + _.Count) == 0) {
import org.fusesource.jansi.Ansi.Color.RED
import org.fusesource.jansi.Ansi.ansi
println(
@ -407,12 +569,12 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
lattice
}
def ZipLinePaths: List[ZipLinePath] = {
zipLinePaths
def zipLinePaths: List[ZipLinePath] = {
map.ZipLinePaths
}
private def BuildLocalObjects(implicit context: ActorContext, guid: NumberPoolHub): Unit = {
Map.LocalObjects.foreach({ builderObject =>
map.LocalObjects.foreach({ builderObject =>
builderObject.Build
val obj = guid(builderObject.Id)
@ -426,7 +588,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
//guard against errors here, but don't worry about specifics; let ZoneActor.ZoneSetupCheck complain about problems
val other: ListBuffer[IdentifiableEntity] = new ListBuffer[IdentifiableEntity]()
//turret to weapon
Map.TurretToWeapon.foreach({
map.TurretToWeapon.foreach({
case (turret_guid, weapon_guid) =>
((GUID(turret_guid) match {
case Some(obj: FacilityTurret) =>
@ -456,7 +618,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
}
private def MakeBuildings(implicit context: ActorContext): PairMap[Int, Building] = {
val buildingList = Map.LocalBuildings
val buildingList = map.LocalBuildings
val registrationKeys: Map[Int, Try[LoanedKey]] = buildingList.map {
case ((_, building_guid: Int, _), _) =>
(building_guid, guid.register(building_guid))
@ -471,7 +633,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
}
private def AssignAmenities(): Unit = {
Map.ObjectToBuilding.foreach({
map.ObjectToBuilding.foreach({
case (object_guid, building_id) =>
(buildings.get(building_id), guid(object_guid)) match {
case (Some(building), Some(amenity)) =>
@ -498,7 +660,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
}
private def MakeLattice(): Unit = {
lattice ++= Map.LatticeLink.map {
lattice ++= map.LatticeLink.map {
case (source, target) =>
val (sourceBuilding, targetBuilding) = (Building(source), Building(target)) match {
case (Some(sBuilding), Some(tBuilding)) => (sBuilding, tBuilding)
@ -650,12 +812,6 @@ object Zone {
new Zone(id, map, number)
}
/**
* Message to initialize the `Zone`.
* @see `Zone.Init(implicit ActorContext)`
*/
final case class Init()
object Population {
/**
@ -730,79 +886,6 @@ object Zone {
final case class Remove(player: Player)
}
object Lattice {
/**
* Message requesting that the current zone determine where a `player` can spawn.
* @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 spawn_group the category of spawn points the request wants searched
*/
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)
}
}
/**
* Message that returns a discovered spawn point to a request source.
* @param zone_id the zone's text identifier
* @param spawn_point the spawn point holding object
*/
final case class SpawnPoint(zone_id: String, spawn_point: net.psforever.objects.SpawnPoint)
/**
* Message that informs a request source that a spawn point could not be discovered with the previous criteria.
* @param zone_number this zone's numeric identifier
* @param spawn_group the spawn point holding object;
* if `None`, then the previous `zone_number` could not be found;
* otherwise, no spawn points could be found in the zone
*/
final case class NoValidSpawnPoint(zone_number: Int, spawn_group: Option[Int])
}
object Ground {
final case class DropItem(item: Equipment, pos: Vector3, orient: Vector3)
final case class ItemOnGround(item: Equipment, pos: Vector3, orient: Vector3)

View file

@ -1,395 +0,0 @@
// Copyright (c) 2017 PSForever
package net.psforever.objects.zones
import java.util.concurrent.atomic.AtomicInteger
import akka.actor.Actor
import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject, SpawnPoint, Tool}
import net.psforever.objects.serverobject.structures.{AmenityOwner, StructureType, WarpGate}
import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.vehicles.UtilityType
import net.psforever.types.{DriveState, PlanetSideEmpire, Vector3}
import org.log4s.Logger
/**
* na
* @param zone the `Zone` governed by this `Actor`
*/
class ZoneActor(zone: Zone) extends Actor {
private[this] val log = org.log4s.getLogger
def receive: Receive = Init
def Init: Receive = {
case Zone.Init() =>
zone.Init
ZoneSetupCheck()
context.become(Processing)
case _ => ;
}
def Processing: Receive = {
//frwd to Population Actor
case msg @ Zone.Population.Join =>
zone.Population forward msg
case msg @ Zone.Population.Leave =>
zone.Population forward msg
case msg @ Zone.Population.Spawn =>
zone.Population forward msg
case msg @ Zone.Population.Release =>
zone.Population forward msg
case msg @ Zone.Corpse.Add =>
zone.Population forward msg
case msg @ Zone.Corpse.Remove =>
zone.Population forward msg
//frwd to Ground Actor
case msg @ Zone.Ground.DropItem =>
zone.Ground forward msg
case msg @ Zone.Ground.PickupItem =>
zone.Ground forward msg
//frwd to Deployable Actor
case msg @ Zone.Deployable.Build =>
zone.Deployables forward msg
case msg @ Zone.Deployable.Dismiss =>
zone.Deployables forward msg
//frwd to Vehicle Actor
case msg @ Zone.Vehicle.Spawn =>
zone.Transport forward msg
case msg @ Zone.Vehicle.Despawn =>
zone.Transport forward msg
//frwd to Projector actor
case msg @ Zone.HotSpot.Activity =>
zone.Activity forward msg
case msg @ Zone.HotSpot.UpdateNow =>
zone.Activity forward msg
case msg @ Zone.HotSpot.ClearAll =>
zone.Activity forward msg
//own
case Zone.Lattice.RequestSpawnPoint(zone_number, position, faction, spawn_group) =>
if (zone_number == zone.Number) {
ZoneActor.FindLocalSpawnPointsInZone(zone, position, faction, spawn_group) match {
case Some(Nil) | None =>
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, Some(spawn_group))
case Some(List(tube)) =>
sender ! Zone.Lattice.SpawnPoint(zone.Id, tube)
case Some(tubes) =>
val tube = scala.util.Random.shuffle(tubes).head
sender ! Zone.Lattice.SpawnPoint(zone.Id, tube)
}
} else { //wrong zone_number
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None)
}
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 == faction && vehicle.GUID == target =>
Some(vehicle) //the vehicle itself is the spawn point
case vehicle if vehicle.Faction == faction && vehicle.GUID == target =>
vehicle.Utilities.values.find { util =>
util().isInstanceOf[SpawnPoint]
} match {
case None =>
None
case Some(util) =>
Some(util().asInstanceOf[SpawnTube]) //the vehicle's utility is the spawn point
}
})
.orElse({
//is our spawn point a building itself (like a warp gate)?
val friendlySpawnGroups = zone.SpawnGroups().filter {
case (building, _) =>
building match {
case wg: WarpGate =>
building.Faction == faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast
case _ =>
building.Faction == faction
}
}
friendlySpawnGroups
.collectFirst({
case (building, points) if building.MapId == target.guid && points.nonEmpty =>
scala.util.Random.shuffle(points).head
})
.orElse {
//is our spawn a conventional amenity?
friendlySpawnGroups.values.flatten.find { point => point.GUID == target }
}
}) match {
case Some(point: SpawnPoint) =>
sender ! Zone.Lattice.SpawnPoint(zone.Id, point)
case _ =>
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, Some(target.guid))
}
} else { //wrong zone_number
sender ! Zone.Lattice.NoValidSpawnPoint(zone_number, None)
}
case msg =>
log.warn(s"Received unexpected message - $msg")
}
def ZoneSetupCheck(): Int = {
import ZoneActor._
val map = zone.Map
def guid(id: Int) = zone.GUID(id)
val slog = org.log4s.getLogger(s"zone/${zone.Id}/sanity")
val errors = new AtomicInteger(0)
val validateObject: (Int, PlanetSideGameObject => Boolean, String) => Boolean = ValidateObject(guid, slog, errors)
//check bases
map.ObjectToBuilding.values
.toSet[Int]
.foreach(building_id => {
val target = zone.Building(building_id)
if (target.isEmpty) {
slog.error(s"expected a building for id #$building_id")
errors.incrementAndGet()
} else if (!target.get.HasGUID) {
slog.error(s"building #$building_id was not registered")
errors.incrementAndGet()
}
})
//check base to object associations
map.ObjectToBuilding.keys.foreach(object_guid =>
if (guid(object_guid).isEmpty) {
slog.error(s"expected object id $object_guid to exist, but it did not")
errors.incrementAndGet()
}
)
//check door to lock association
map.DoorToLock.foreach({
case (door_guid, lock_guid) =>
validateObject(door_guid, DoorCheck, "door")
validateObject(lock_guid, LockCheck, "IFF lock")
})
//check vehicle terminal to spawn pad association
map.TerminalToSpawnPad.foreach({
case (term_guid, pad_guid) =>
validateObject(term_guid, TerminalCheck, "vehicle terminal")
validateObject(pad_guid, VehicleSpawnPadCheck, "vehicle spawn pad")
})
//check implant terminal mech to implant terminal interface association
map.TerminalToInterface.foreach({
case (mech_guid, interface_guid) =>
validateObject(mech_guid, ImplantMechCheck, "implant terminal mech")
validateObject(interface_guid, TerminalCheck, "implant terminal interface")
})
//check manned turret to weapon association
map.TurretToWeapon.foreach({
case (turret_guid, weapon_guid) =>
validateObject(turret_guid, FacilityTurretCheck, "facility turret mount")
if (validateObject(weapon_guid, WeaponCheck, "facility turret weapon")) {
if (guid(weapon_guid).get.asInstanceOf[Tool].AmmoSlots.count(!_.Box.HasGUID) > 0) {
slog.error(s"expected weapon $weapon_guid has an unregistered ammunition unit")
errors.incrementAndGet()
}
}
})
//output number of errors
errors.intValue()
}
}
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]] = {
(if (spawn_group == 2) {
FindVehicleSpawnPointsInZone(zone, position, faction)
} else {
FindBuildingSpawnPointsInZone(zone, position, faction, spawn_group)
}).headOption match {
case None | Some((_, Nil)) =>
None
case Some((_, tubes)) =>
Some(tubes toList)
}
}
/**
* na
* @param zone na
* @param position na
* @param faction na
* @return na
*/
private def FindVehicleSpawnPointsInZone(
zone: Zone,
position: Vector3,
faction: PlanetSideEmpire.Value
): List[(AmenityOwner, Iterable[SpawnTube])] = {
val xy = position.xy
//ams
zone.Vehicles
.filter(veh =>
veh.Definition == GlobalDefinitions.ams &&
!veh.Destroyed &&
veh.DeploymentState == DriveState.Deployed &&
veh.Faction == faction
)
.sortBy(veh => Vector3.DistanceSquared(xy, veh.Position.xy))
.map(veh =>
(
veh,
veh.Utilities.values
.filter(util => util.UtilType == UtilityType.ams_respawn_tube)
.map(_().asInstanceOf[SpawnTube])
)
)
}
/**
* na
* @param zone na
* @param position na
* @param faction na
* @param spawn_group na
* @return na
*/
private def FindBuildingSpawnPointsInZone(
zone: Zone,
position: Vector3,
faction: PlanetSideEmpire.Value,
spawn_group: Int
): List[(AmenityOwner, Iterable[SpawnPoint])] = {
val xy = position.xy
//facilities, towers, warp gates, and other 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()
.collect({
case (building, spawns) if buildingTypeSet.contains(building.BuildingType) && (building match {
case wg: WarpGate =>
building.Faction == faction || building.Faction == PlanetSideEmpire.NEUTRAL || wg.Broadcast
case _ =>
building.Faction == faction && spawns.nonEmpty && spawns.exists(_.Offline == false)
}) =>
(building, spawns.filter(_.Offline == false))
})
.toSeq
.sortBy({
case (building, _) =>
Vector3.DistanceSquared(xy, building.Position.xy)
})
.toList
}
/**
* Recover an object from a collection and perform any number of validating tests upon it.
* If the object fails any tests, log an error.
* @param guid access to an association between unique numbers and objects using some of those unique numbers
* @param elog a contraction of "error log;"
* accepts `String` data
* @param object_guid the unique indentifier being checked against the `guid` access point
* @param test a test for the discovered object;
* expects at least `Type` checking
* @param description an explanation of how the object, if not discovered, should be identified
* @return `true` if the object was discovered and validates correctly;
* `false` if the object failed any tests
*/
def ValidateObject(
guid: Int => Option[PlanetSideGameObject],
elog: Logger,
errorCounter: AtomicInteger
)(object_guid: Int, test: PlanetSideGameObject => Boolean, description: String): Boolean = {
try {
if (!test(guid(object_guid).get)) {
elog.error(s"expected id $object_guid to be a $description, but it was not")
errorCounter.incrementAndGet()
false
} else {
true
}
} catch {
case e: Exception =>
elog.error(s"expected a $description at id $object_guid but no object is initialized - $e")
errorCounter.incrementAndGet()
false
}
}
def LockCheck(obj: PlanetSideGameObject): Boolean = {
import net.psforever.objects.serverobject.locks.IFFLock
obj.isInstanceOf[IFFLock]
}
def DoorCheck(obj: PlanetSideGameObject): Boolean = {
import net.psforever.objects.serverobject.doors.Door
obj.isInstanceOf[Door]
}
def TerminalCheck(obj: PlanetSideGameObject): Boolean = {
import net.psforever.objects.serverobject.terminals.Terminal
obj.isInstanceOf[Terminal]
}
def ImplantMechCheck(obj: PlanetSideGameObject): Boolean = {
import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech
obj.isInstanceOf[ImplantTerminalMech]
}
def VehicleSpawnPadCheck(obj: PlanetSideGameObject): Boolean = {
import net.psforever.objects.serverobject.pad.VehicleSpawnPad
obj.isInstanceOf[VehicleSpawnPad]
}
def FacilityTurretCheck(obj: PlanetSideGameObject): Boolean = {
import net.psforever.objects.serverobject.turret.FacilityTurret
obj.isInstanceOf[FacilityTurret]
}
def WeaponCheck(obj: PlanetSideGameObject): Boolean = {
import net.psforever.objects.Tool
obj.isInstanceOf[Tool]
}
}

View file

@ -86,127 +86,4 @@ object ZoneDeployableActor {
}
}
}
// /**
// * Add an `avatar` as the key of an `Avatar` to `Player` object pair in the given collection.
// * @param avatar an `Avatar` object
// * @param playerMap the mapping of `Avatar` objects to `Player` objects
// * @return true, if the mapping is for a new key;
// * false, if the key already exists
// */
// def PopulationJoin(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Boolean = {
// playerMap.get(avatar) match {
// case Some(_) =>
// false
// case None =>
// playerMap += avatar -> None
// true
// }
// }
// /**
// * Remove an `avatar` from the key of an `Avatar` to `Player` object pair in the given collection.
// * If a `Player` object is associated at the time, return it safely.
// * @param avatar an `Avatar` object
// * @param playerMap the mapping of `Avatar` objects to `Player` objects
// * @return any `Player` object that was associated at the time the `avatar` was removed
// */
// def PopulationLeave(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = {
// playerMap.remove(avatar) match {
// case None =>
// None
// case Some(tplayer) =>
// tplayer
// }
// }
//
// /**
// * Associate a `Player` object as a value to an existing `Avatar` object that will be its key.
// * Do not overwrite players that are already associated.
// * @param avatar an `Avatar` object
// * @param player a `Player` object
// * @param playerMap the mapping of `Avatar` objects to `Player` objects
// * @return the `Player` object that is associated with the `Avatar` key
// */
// def PopulationSpawn(avatar : Avatar, player : Player, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = {
// playerMap.get(avatar) match {
// case None =>
// None
// case Some(tplayer) =>
// tplayer match {
// case Some(aplayer) =>
// Some(aplayer)
// case None =>
// playerMap(avatar) = Some(player)
// Some(player)
// }
// }
// }
//
// /**
// * Disassociate a `Player` object from an existing `Avatar` object that was be its key.
// * @param avatar an `Avatar` object
// * @param playerMap the mapping of `Avatar` objects to `Player` objects
// * @return any `Player` object that is associated at the time
// */
// def PopulationRelease(avatar : Avatar, playerMap : TrieMap[Avatar, Option[Player]]) : Option[Player] = {
// playerMap.get(avatar) match {
// case None =>
// None
// case Some(tplayer) =>
// playerMap(avatar) = None
// tplayer
// }
// }
//
// /**
// * If the given `player` passes a condition check, add it to the list.
// * @param player a `Player` object
// * @param corpseList a list of `Player` objects
// * @return true, if the `player` was added to the list;
// * false, otherwise
// */
// def CorpseAdd(player : Player, corpseList : ListBuffer[Player]) : Boolean = {
// if(player.isBackpack) {
// corpseList += player
// true
// }
// else {
// false
// }
// }
//
// /**
// * Remove the given `player` from the list.
// * @param player a `Player` object
// * @param corpseList a list of `Player` objects
// */
// def CorpseRemove(player : Player, corpseList : ListBuffer[Player]) : Unit = {
// recursiveFindCorpse(corpseList.iterator, player) match {
// case None => ;
// case Some(index) =>
// corpseList.remove(index)
// }
// }
//
// /**
// * A recursive function that finds and removes a specific player from a list of players.
// * @param iter an `Iterator` of `Player` objects
// * @param player the target `Player`
// * @param index the index of the discovered `Player` object
// * @return the index of the `Player` object in the list to be removed;
// * `None`, otherwise
// */
// @tailrec final def recursiveFindCorpse(iter : Iterator[Player], player : Player, index : Int = 0) : Option[Int] = {
// if(!iter.hasNext) {
// None
// }
// else {
// if(iter.next == player) {
// Some(index)
// }
// else {
// recursiveFindCorpse(iter, player, index + 1)
// }
// }
// }
}

View file

@ -127,8 +127,8 @@ object PacketHelpers {
createEnumerationCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong))
}
/** Create a Codec for enumeratum's Enum type */
def createEnumCodec[E <: IntEnumEntry](enum: IntEnum[E], storageCodec: Codec[Int]): Codec[E] = {
/** Create a Codec for enumeratum's IntEnum type */
def createIntEnumCodec[E <: IntEnumEntry](enum: IntEnum[E], storageCodec: Codec[Int]): Codec[E] = {
type Struct = Int :: HNil
val struct: Codec[Struct] = storageCodec.hlist
@ -149,6 +149,10 @@ object PacketHelpers {
struct.narrow[E](from, to)
}
def createLongIntEnumCodec[E <: IntEnumEntry](enum: IntEnum[E], storageCodec: Codec[Long]): Codec[E] = {
createIntEnumCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong))
}
/** Common codec for how PlanetSide stores string sizes
*
* When the first bit of the byte is set, the size can be between [0, 127].

View file

@ -67,7 +67,7 @@ final case class BindPlayerMessage(
bind_desc: String,
display_icon: Boolean,
logging: Boolean,
spawn_group: SpawnGroup.Value,
spawn_group: SpawnGroup,
zone_number: Long,
unk4: Long,
pos: Vector3
@ -84,7 +84,7 @@ object BindPlayerMessage extends Marshallable[BindPlayerMessage] {
*/
val Standard = BindPlayerMessage(BindStatus.Unbind, "", false, false, SpawnGroup.BoundAMS, 0, 0, Vector3.Zero)
private val spawnGroupCodec = PacketHelpers.createEnumerationCodec(SpawnGroup, uint4)
private val spawnGroupCodec = PacketHelpers.createIntEnumCodec(SpawnGroup, uint4)
implicit val codec: Codec[BindPlayerMessage] = (
("action" | BindStatus.codec) ::

View file

@ -8,22 +8,25 @@ import scodec.codecs._
/**
* na
* @param unk1 when defined, na;
* non-zero when selecting the sanctuary option from a non-sanctuary continent deployment map
* @param spawn_type the type of spawn point destination
* @param unk3 na
* @param unk4 na
* @param zone_number when defined, the continent number
*
* @param unk1 when defined, na;
* non-zero when selecting the sanctuary option from a non-sanctuary continent deployment map
* @param spawn_type the type of spawn point destination
* @param unk3 na
* @param unk4 na
* @param zone_number when defined, the zone number
*/
final case class SpawnRequestMessage(unk1: Int, spawn_type: SpawnGroup.Value, unk3: Int, unk4: Int, zone_number: Int)
final case class SpawnRequestMessage(unk1: Int, spawn_type: SpawnGroup, unk3: Int, unk4: Int, zone_number: Int)
extends PlanetSideGamePacket {
type Packet = SpawnRequestMessage
def opcode = GamePacketOpcode.SpawnRequestMessage
def encode = SpawnRequestMessage.encode(this)
}
object SpawnRequestMessage extends Marshallable[SpawnRequestMessage] {
private val spawnGroupCodec = PacketHelpers.createLongEnumerationCodec(SpawnGroup, uint32L)
private val spawnGroupCodec = PacketHelpers.createLongIntEnumCodec(SpawnGroup, uint32L)
implicit val codec: Codec[SpawnRequestMessage] = (
("unk1" | uint16L) ::

View file

@ -26,7 +26,7 @@ object ServerType extends IntEnum[ServerType] {
case object ReleasedGemini extends ServerType(4, "released_gemini")
val values = findValues
implicit val codec = PacketHelpers.createEnumCodec(this, uint8L)
implicit val codec = PacketHelpers.createIntEnumCodec(this, uint8L)
}
// This MUST be an IP address. The client DOES NOT do name resolution properly

View file

@ -0,0 +1,7 @@
package net.psforever.persistence
case class Building(
localId: Int, // aka map id
zoneId: Int, // aka zone number
factionId: Int
)

View file

@ -1,6 +1,8 @@
// Copyright (c) 2017 PSForever
package net.psforever.types
import enumeratum.values.{IntEnum, IntEnumEntry}
/**
* The spawn group.<br>
* <br>
@ -17,11 +19,37 @@ package net.psforever.types
* The icons produced by the normal and the bound tower and facility groups are not detailed.
* The ones that are not designated as "bound" also do not display icons when manually set.
* The AMS spawn group icons have an overhead AMS glyph and are smaller in radius, identical otherwise.
*
* @see `BindPlayerMessage`
*/
object SpawnGroup extends Enumeration {
type Type = Value
sealed abstract class SpawnGroup(val value: Int) extends IntEnumEntry
object SpawnGroup extends IntEnum[SpawnGroup] {
val values = findValues
case object Sanctuary extends SpawnGroup(0)
case object BoundAMS extends SpawnGroup(1)
case object AMS extends SpawnGroup(2)
case object Unknown3 extends SpawnGroup(3)
case object BoundTower extends SpawnGroup(4) // unused?
case object BoundFacility extends SpawnGroup(5)
case object Tower extends SpawnGroup(6)
case object Facility extends SpawnGroup(7)
case object Unknown8 extends SpawnGroup(8)
case object Unknown9 extends SpawnGroup(9)
case object Unknown10 extends SpawnGroup(10)
case object Unknown11 extends SpawnGroup(11)
case object WarpGate extends SpawnGroup(12)
val Sanctuary, BoundAMS, AMS, Unknown3, BoundTower, //unused?
BoundFacility, Tower, Facility, Unknown8, Unknown9, Unknown10, Unknown11, Unknown12 = Value
}

View file

@ -57,6 +57,7 @@ case class AppConfig(
world: WorldConfig,
admin: AdminConfig,
database: DatabaseConfig,
game: GameConfig,
antiCheat: AntiCheatConfig,
network: NetworkConfig,
developer: DeveloperConfig,
@ -103,6 +104,10 @@ case class SessionConfig(
outboundGraceTime: Duration
)
case class GameConfig(
instantActionAms: Boolean
)
case class DeveloperConfig(
netSim: NetSimConfig
)

View file

@ -1,20 +1,24 @@
package net.psforever.util
import io.getquill.{PostgresJAsyncContext, SnakeCase}
import net.psforever.persistence
import net.psforever.persistence.{Account, Building, Loadout, Locker, Login, Character}
object Database {
implicit val accountSchemaMeta = ctx.schemaMeta[persistence.Account]("accounts", _.id -> "id")
implicit val characterSchemaMeta = ctx.schemaMeta[persistence.Character]("characters", _.id -> "id")
implicit val loadoutSchemaMeta = ctx.schemaMeta[persistence.Loadout]("loadouts", _.id -> "id")
implicit val lockerSchemaMeta = ctx.schemaMeta[persistence.Locker]("lockers", _.id -> "id")
implicit val loginSchemaMeta = ctx.schemaMeta[persistence.Login]("logins", _.id -> "id")
val ctx = new PostgresJAsyncContext(SnakeCase, Config.config.getConfig("database"))
implicit val accountSchemaMeta: ctx.SchemaMeta[Account] = ctx.schemaMeta[Account]("accounts")
implicit val characterSchemaMeta: ctx.SchemaMeta[Character] = ctx.schemaMeta[Character]("characters")
implicit val loadoutSchemaMeta: ctx.SchemaMeta[Loadout] = ctx.schemaMeta[Loadout]("loadouts")
implicit val lockerSchemaMeta: ctx.SchemaMeta[Locker] = ctx.schemaMeta[Locker]("lockers")
implicit val loginSchemaMeta: ctx.SchemaMeta[Login] = ctx.schemaMeta[Login]("logins")
implicit val buildingSchemaMeta: ctx.SchemaMeta[Building] = ctx.schemaMeta[Building]("buildings")
// TODO remove if this gets merged https://github.com/getquill/quill/pull/1765
implicit class ILike(s1: String) {
import ctx._
def ilike(s2: String) = quote(infix"$s1 ilike $s2".as[Boolean])
}
val ctx = new PostgresJAsyncContext(SnakeCase, Config.config.getConfig("database"))
}

View file

@ -14,10 +14,10 @@ object Zones {
val zones: HashMap[String, Zone] = HashMap(
(
"z1",
new Zone("z1", Await.result(Maps.map01, 30 seconds), 1) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("z1", Await.result(Maps.map01, 60 seconds), 1) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -26,10 +26,10 @@ object Zones {
),
(
"z2",
new Zone("z2", Await.result(Maps.map02, 30 seconds), 2) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("z2", Await.result(Maps.map02, 60 seconds), 2) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -38,10 +38,10 @@ object Zones {
),
(
"z3",
new Zone("z3", Await.result(Maps.map03, 30 seconds), 3) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("z3", Await.result(Maps.map03, 60 seconds), 3) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -50,84 +50,22 @@ object Zones {
),
(
"z4",
new Zone("z4", Await.result(Maps.map04, 30 seconds), 4) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("z4", Await.result(Maps.map04, 60 seconds), 4) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
BuildingByMapId(5).get.Faction = PlanetSideEmpire.TR //Akkan
BuildingByMapId(6).get.Faction = PlanetSideEmpire.TR //Baal
BuildingByMapId(7).get.Faction = PlanetSideEmpire.TR //Dagon
BuildingByMapId(8).get.Faction = PlanetSideEmpire.NC //Enkidu
BuildingByMapId(9).get.Faction = PlanetSideEmpire.VS //Girru
BuildingByMapId(10).get.Faction = PlanetSideEmpire.VS //Hanish
BuildingByMapId(11).get.Faction = PlanetSideEmpire.VS //Irkalla
BuildingByMapId(12).get.Faction = PlanetSideEmpire.VS //Kusag
BuildingByMapId(13).get.Faction = PlanetSideEmpire.VS //Lahar
BuildingByMapId(14).get.Faction = PlanetSideEmpire.NC //Marduk
BuildingByMapId(15).get.Faction = PlanetSideEmpire.NC //Neti
BuildingByMapId(16).get.Faction = PlanetSideEmpire.NC //Zaqar
BuildingByMapId(17).get.Faction = PlanetSideEmpire.NC //S_Marduk_Tower
BuildingByMapId(18).get.Faction = PlanetSideEmpire.NC //W_Neti_Tower
BuildingByMapId(19).get.Faction = PlanetSideEmpire.NC //W_Zaqar_Tower
BuildingByMapId(20).get.Faction = PlanetSideEmpire.NC //E_Zaqar_Tower
BuildingByMapId(21).get.Faction = PlanetSideEmpire.NC //NE_Neti_Tower
BuildingByMapId(22).get.Faction = PlanetSideEmpire.NC //SE_Ceryshen_Warpgate_Tower
BuildingByMapId(23).get.Faction = PlanetSideEmpire.VS //S_Kusag_Tower
BuildingByMapId(24).get.Faction = PlanetSideEmpire.VS //NW_Kusag_Tower
BuildingByMapId(25).get.Faction = PlanetSideEmpire.VS //N_Ceryshen_Warpgate_Tower
BuildingByMapId(26).get.Faction = PlanetSideEmpire.VS //SE_Irkalla_Tower
BuildingByMapId(27).get.Faction = PlanetSideEmpire.VS //S_Irkalla_Tower
BuildingByMapId(28).get.Faction = PlanetSideEmpire.TR //NE_Enkidu_Tower
BuildingByMapId(29).get.Faction = PlanetSideEmpire.NC //SE_Akkan_Tower
BuildingByMapId(30).get.Faction = PlanetSideEmpire.NC //SW_Enkidu_Tower
BuildingByMapId(31).get.Faction = PlanetSideEmpire.TR //E_Searhus_Warpgate_Tower
BuildingByMapId(32).get.Faction = PlanetSideEmpire.TR //N_Searhus_Warpgate_Tower
BuildingByMapId(33).get.Faction = PlanetSideEmpire.VS //E_Girru_Tower
BuildingByMapId(34).get.Faction = PlanetSideEmpire.VS //SE_Hanish_Tower
BuildingByMapId(35).get.Faction = PlanetSideEmpire.TR //SW_Hanish_Tower
BuildingByMapId(36).get.Faction = PlanetSideEmpire.VS //W_Girru_Tower
BuildingByMapId(37).get.Faction = PlanetSideEmpire.TR //E_Dagon_Tower
BuildingByMapId(38).get.Faction = PlanetSideEmpire.TR //NE_Baal_Tower
BuildingByMapId(39).get.Faction = PlanetSideEmpire.TR //SE_Baal_Tower
BuildingByMapId(40).get.Faction = PlanetSideEmpire.TR //S_Dagon_Tower
BuildingByMapId(41).get.Faction = PlanetSideEmpire.NC //W_Ceryshen_Warpgate_Tower
BuildingByMapId(42).get.Faction = PlanetSideEmpire.NEUTRAL //dagon bunker
BuildingByMapId(43).get.Faction = PlanetSideEmpire.NEUTRAL //Akkan North Bunker
BuildingByMapId(44).get.Faction = PlanetSideEmpire.NEUTRAL //Enkidu East Bunker
BuildingByMapId(45).get.Faction = PlanetSideEmpire.NEUTRAL //Neti bunker
BuildingByMapId(46).get.Faction = PlanetSideEmpire.NEUTRAL //Hanish West Bunker
BuildingByMapId(47).get.Faction = PlanetSideEmpire.NEUTRAL //Irkalla East Bunker
BuildingByMapId(48).get.Faction = PlanetSideEmpire.NEUTRAL //Zaqar bunker
BuildingByMapId(49).get.Faction = PlanetSideEmpire.NEUTRAL //Kusag West Bunker
BuildingByMapId(50).get.Faction = PlanetSideEmpire.NEUTRAL //marduk bunker
BuildingByMapId(51).get.Faction = PlanetSideEmpire.TR //baal bunker
BuildingByMapId(52).get.Faction = PlanetSideEmpire.NEUTRAL //girru bunker
BuildingByMapId(53).get.Faction = PlanetSideEmpire.NEUTRAL //lahar bunker
BuildingByMapId(54).get.Faction = PlanetSideEmpire.NEUTRAL //akkan bunker
BuildingByMapId(55).get.Faction = PlanetSideEmpire.VS //Irkalla_Tower
BuildingByMapId(56).get.Faction = PlanetSideEmpire.VS //Hanish_Tower
BuildingByMapId(57).get.Faction = PlanetSideEmpire.VS //E_Ceryshen_Warpgate_Tower
BuildingByMapId(58).get.Faction = PlanetSideEmpire.VS //Lahar_Tower
BuildingByMapId(59).get.Faction = PlanetSideEmpire.VS //VSSanc_Warpgate_Tower
BuildingByMapId(60).get.Faction = PlanetSideEmpire.TR //Akkan_Tower
BuildingByMapId(61).get.Faction = PlanetSideEmpire.NC //TRSanc_Warpgate_Tower
BuildingByMapId(62).get.Faction = PlanetSideEmpire.NC //Marduk_Tower
BuildingByMapId(63).get.Faction = PlanetSideEmpire.TR //NW_Dagon_Tower
BuildingByMapId(64).get.Faction = PlanetSideEmpire.NEUTRAL //E7 East Bunker (at north from bridge)
BuildingByMapId(65).get.Faction = PlanetSideEmpire.VS //W_Hanish_Tower
}
}
),
(
"z5",
new Zone("z5", Await.result(Maps.map05, 30 seconds), 5) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("z5", Await.result(Maps.map05, 60 seconds), 5) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -136,27 +74,22 @@ object Zones {
),
(
"z6",
new Zone("z6", Await.result(Maps.map06, 30 seconds), 6) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("z6", Await.result(Maps.map06, 60 seconds), 6) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
import net.psforever.types.PlanetSideEmpire
BuildingByMapId(2).get.Faction = PlanetSideEmpire.VS
BuildingByMapId(48).get.Faction = PlanetSideEmpire.VS
BuildingByMapId(49).get.Faction = PlanetSideEmpire.VS
InitZoneAmenities(zone = this)
}
}
),
(
"z7",
new Zone("z7", Await.result(Maps.map07, 30 seconds), 7) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("z7", Await.result(Maps.map07, 60 seconds), 7) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -165,10 +98,10 @@ object Zones {
),
(
"z8",
new Zone("z8", Await.result(Maps.map08, 30 seconds), 8) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("z8", Await.result(Maps.map08, 60 seconds), 8) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -177,10 +110,10 @@ object Zones {
),
(
"z9",
new Zone("z9", Await.result(Maps.map09, 30 seconds), 9) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("z9", Await.result(Maps.map09, 60 seconds), 9) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -189,10 +122,10 @@ object Zones {
),
(
"z10",
new Zone("z10", Await.result(Maps.map10, 30 seconds), 10) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("z10", Await.result(Maps.map10, 60 seconds), 10) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -201,12 +134,14 @@ object Zones {
),
(
"home1",
new Zone("home1", Await.result(Maps.map11, 30 seconds), 11) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
new Zone("home1", Await.result(Maps.map11, 60 seconds), 11) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
import net.psforever.types.PlanetSideEmpire
Buildings.values.foreach { _.Faction = PlanetSideEmpire.NC }
Buildings.values.foreach {
_.Faction = PlanetSideEmpire.NC
}
InitZoneAmenities(zone = this)
}
@ -214,12 +149,14 @@ object Zones {
),
(
"home2",
new Zone("home2", Await.result(Maps.map12, 30 seconds), 12) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
new Zone("home2", Await.result(Maps.map12, 60 seconds), 12) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
import net.psforever.types.PlanetSideEmpire
Buildings.values.foreach { _.Faction = PlanetSideEmpire.TR }
Buildings.values.foreach {
_.Faction = PlanetSideEmpire.TR
}
InitZoneAmenities(zone = this)
}
@ -227,12 +164,14 @@ object Zones {
),
(
"home3",
new Zone("home3", Await.result(Maps.map13, 30 seconds), 13) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
new Zone("home3", Await.result(Maps.map13, 60 seconds), 13) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
import net.psforever.types.PlanetSideEmpire
Buildings.values.foreach { _.Faction = PlanetSideEmpire.VS }
Buildings.values.foreach {
_.Faction = PlanetSideEmpire.VS
}
InitZoneAmenities(zone = this)
}
@ -276,10 +215,10 @@ object Zones {
),
(
"c1",
new Zone("c1", Await.result(Maps.ugd01, 30 seconds), 23) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("c1", Await.result(Maps.ugd01, 60 seconds), 23) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -288,10 +227,10 @@ object Zones {
),
(
"c2",
new Zone("c2", Await.result(Maps.ugd02, 30 seconds), 24) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("c2", Await.result(Maps.ugd02, 60 seconds), 24) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -300,10 +239,10 @@ object Zones {
),
(
"c3",
new Zone("c3", Await.result(Maps.ugd03, 30 seconds), 25) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("c3", Await.result(Maps.ugd03, 60 seconds), 25) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -312,10 +251,10 @@ object Zones {
),
(
"c4",
new Zone("c4", Await.result(Maps.ugd04, 30 seconds), 26) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("c4", Await.result(Maps.ugd04, 60 seconds), 26) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -324,10 +263,10 @@ object Zones {
),
(
"c5",
new Zone("c5", Await.result(Maps.ugd05, 30 seconds), 27) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("c5", Await.result(Maps.ugd05, 60 seconds), 27) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -336,10 +275,10 @@ object Zones {
),
(
"c6",
new Zone("c6", Await.result(Maps.ugd06, 30 seconds), 28) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("c6", Await.result(Maps.ugd06, 60 seconds), 28) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -348,10 +287,10 @@ object Zones {
),
(
"i1",
new Zone("i1", Await.result(Maps.map99, 30 seconds), 29) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("i1", Await.result(Maps.map99, 60 seconds), 29) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -360,10 +299,10 @@ object Zones {
),
(
"i2",
new Zone("i2", Await.result(Maps.map98, 30 seconds), 30) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("i2", Await.result(Maps.map98, 60 seconds), 30) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -372,10 +311,10 @@ object Zones {
),
(
"i3",
new Zone("i3", Await.result(Maps.map97, 30 seconds), 31) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("i3", Await.result(Maps.map97, 60 seconds), 31) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -384,10 +323,10 @@ object Zones {
),
(
"i4",
new Zone("i4", Await.result(Maps.map96, 30 seconds), 32) {
override def Init(implicit context: ActorContext): Unit = {
super.Init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80)
new Zone("i4", Await.result(Maps.map96, 60 seconds), 32) {
override def init(implicit context: ActorContext): Unit = {
super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this)
@ -560,9 +499,9 @@ object Zones {
case t: ObjectSource if t.Definition == GlobalDefinitions.manned_turret =>
60 seconds
case _: DeployableSource =>
30 seconds
60 seconds
case _: ComplexDeployableSource =>
30 seconds
60 seconds
case _ =>
0 seconds
}

View file

@ -0,0 +1,218 @@
package services
import akka.actor.typed.receptionist.{Receptionist, ServiceKey}
import akka.actor.typed.scaladsl.{AbstractBehavior, ActorContext, Behaviors}
import akka.actor.typed.{ActorRef, Behavior, SupervisorStrategy}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.{Avatar, Player, SpawnPoint, Vehicle}
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, SpawnGroup, Vector3}
import net.psforever.util.Config
import scala.collection.mutable
import scala.util.Random
object InterstellarClusterService {
val InterstellarClusterServiceKey: ServiceKey[Command] =
ServiceKey[InterstellarClusterService.Command]("interstellarCluster")
def apply(zones: Iterable[Zone]): Behavior[Command] =
Behaviors
.supervise[Command] {
Behaviors.setup { context =>
context.system.receptionist ! Receptionist.Register(InterstellarClusterServiceKey, context.self)
new InterstellarClusterService(context, zones)
}
}
.onFailure[Exception](SupervisorStrategy.restart)
sealed trait Command
final case class FindZoneActor(predicate: Zone => Boolean, replyTo: ActorRef[ZoneActorResponse]) extends Command
final case class ZoneActorResponse(zoneActor: Option[ActorRef[ZoneActor.Command]])
final case class FindZone(predicate: Zone => Boolean, replyTo: ActorRef[ZoneResponse]) extends Command
final case class ZoneResponse(zoneActor: Option[Zone])
final case class FilterZones(predicate: Zone => Boolean, replyTo: ActorRef[ZonesResponse]) extends Command
final case class ZonesResponse(zoneActor: Iterable[Zone])
final case class GetInstantActionSpawnPoint(faction: PlanetSideEmpire.Value, replyTo: ActorRef[SpawnPointResponse])
extends Command
final case class GetSpawnPoint(
zoneNumber: Int,
player: Player,
target: PlanetSideGUID,
replyTo: ActorRef[SpawnPointResponse]
) extends Command
final case class GetNearbySpawnPoint(
zoneNumber: Int,
player: Player,
spawnGroups: Seq[SpawnGroup],
replyTo: ActorRef[SpawnPointResponse]
) extends Command
final case class GetRandomSpawnPoint(
zoneNumber: Int,
faction: PlanetSideEmpire.Value,
spawnGroups: Seq[SpawnGroup],
replyTo: ActorRef[SpawnPointResponse]
) extends Command
final case class SpawnPointResponse(response: Option[(Zone, SpawnPoint)])
final case class GetPlayers(replyTo: ActorRef[PlayersResponse]) extends Command
final case class PlayersResponse(players: Seq[Avatar])
}
class InterstellarClusterService(context: ActorContext[InterstellarClusterService.Command], _zones: Iterable[Zone])
extends AbstractBehavior[InterstellarClusterService.Command](context) {
import InterstellarClusterService._
private[this] val log = org.log4s.getLogger
val zoneActors: mutable.Map[String, (ActorRef[ZoneActor.Command], Zone)] = mutable.Map(
_zones.map {
case zone =>
val zoneActor = context.spawn(ZoneActor(zone), s"zone-${zone.Id}")
(zone.Id, (zoneActor, zone))
}.toSeq: _*
)
val zones = zoneActors.map {
case (id, (_, zone)) => zone
}
override def onMessage(msg: Command): Behavior[Command] = {
log.info(s"$msg")
msg match {
case GetPlayers(replyTo) =>
replyTo ! PlayersResponse(zones.map(_.Players).flatten.toSeq)
case FindZoneActor(predicate, replyTo) =>
replyTo ! ZoneActorResponse(
zoneActors.collectFirst {
case (_, (actor, zone)) if predicate(zone) => actor
}
)
case FindZone(predicate, replyTo) =>
replyTo ! ZoneResponse(zones.find(predicate))
case FilterZones(predicate, replyTo) =>
replyTo ! ZonesResponse(zones.filter(predicate))
case GetInstantActionSpawnPoint(faction, replyTo) =>
val res = zones
.filter(_.Players.nonEmpty)
.flatMap { zone =>
zone.HotSpotData.collect {
case spot => (zone, spot)
}
}
.map {
case (zone, spot) =>
(
zone,
spot,
zone.findNearestSpawnPoints(
faction,
spot.DisplayLocation,
if (Config.app.game.instantActionAms) Seq(SpawnGroup.Tower, SpawnGroup.Facility, SpawnGroup.AMS)
else Seq(SpawnGroup.Tower, SpawnGroup.Facility)
)
)
}
.collect {
case (zone, info, Some(spawns)) => (zone, info, spawns)
}
.toList
.sortBy { case (_, spot, _) => spot.Activity.values.foldLeft(0)(_ + _.Heat) }(
Ordering[Int].reverse
) // greatest > least
.sortWith {
case ((_, spot1, _), (_, spot2, _)) =>
spot1.ActivityBy().contains(faction) // prefer own faction activity
}
.headOption
.flatMap {
case (zone, info, spawns) =>
val pos = info.DisplayLocation
val spawnPoint = spawns.minBy(point => Vector3.DistanceSquared(point.Position, pos))
//Some(zone, pos, spawnPoint)
Some(zone, spawnPoint)
case _ => None
}
replyTo ! SpawnPointResponse(res)
case GetRandomSpawnPoint(zoneNumber, faction, spawnGroups, replyTo) =>
val response = zones.find(_.Number == zoneNumber) match {
case Some(zone) =>
/*
val location = math.abs(Random.nextInt() % 4) match {
case 0 => Vector3(sanctuary.map.Scale.width, sanctuary.map.Scale.height, 0) //NE
case 1 => Vector3(sanctuary.map.Scale.width, 0, 0) //SE
case 2 => Vector3.Zero //SW
case 3 => Vector3(0, sanctuary.map.Scale.height, 0) //NW
}
sanctuary.findNearestSpawnPoints(
faction,
location,
structures
) */
Random.shuffle(zone.findSpawns(faction, spawnGroups)).headOption match {
case Some((_, spawnPoints)) if spawnPoints.nonEmpty =>
Some((zone, Random.shuffle(spawnPoints.toList).head))
case None =>
None
}
case None =>
log.error(s"no zone $zoneNumber")
None
}
replyTo ! SpawnPointResponse(response)
case GetSpawnPoint(zoneNumber, player, target, replyTo) =>
zones.find(_.Number == zoneNumber) match {
case Some(zone) =>
zone.findSpawns(player.Faction, SpawnGroup.values).find {
case (spawn: Building, spawnPoints) =>
spawn.MapId == target.guid || spawnPoints.exists(_.GUID == target)
case (spawn: Vehicle, spawnPoints) =>
spawn.GUID == target || spawnPoints.exists(_.GUID.guid == target.guid)
case _ => false
} match {
case Some((_, spawnPoints)) =>
replyTo ! SpawnPointResponse(Some(zone, Random.shuffle(spawnPoints.toList).head))
case _ =>
replyTo ! SpawnPointResponse(None)
}
case None =>
replyTo ! SpawnPointResponse(None)
}
case GetNearbySpawnPoint(zoneNumber, player, spawnGroups, replyTo) =>
zones.find(_.Number == zoneNumber) match {
case Some(zone) =>
zone.findNearestSpawnPoints(player.Faction, player.Position, spawnGroups) match {
case None | Some(Nil) =>
replyTo ! SpawnPointResponse(None)
case Some(spawnPoints) =>
replyTo ! SpawnPointResponse(Some(zone, scala.util.Random.shuffle(spawnPoints).head))
}
case None =>
replyTo ! SpawnPointResponse(None)
}
}
this
}
}

View file

@ -2,20 +2,31 @@
package services
import akka.actor.{Actor, ActorIdentity, ActorRef, ActorSystem, Identify, Props}
import akka.actor.typed.scaladsl.adapter._
import akka.actor.typed
import akka.actor.typed.receptionist.Receptionist
import scala.collection.mutable
object ServiceManager {
var serviceManager = ActorRef.noSender
var receptionist: typed.ActorRef[Receptionist.Command] = null
def boot(implicit system: ActorSystem) = {
serviceManager = system.actorOf(Props[ServiceManager], "service")
receptionist = system.toTyped.receptionist
serviceManager
}
case class Register(props: Props, name: String)
case class Lookup(name: String)
case class LookupFromTyped(name: String, replyTo: typed.ActorRef[LookupResult])
case class LookupResult(request: String, endpoint: ActorRef)
}
class ServiceManager extends Actor {
@ -38,6 +49,11 @@ class ServiceManager extends Actor {
lookups += nextLookupId -> RequestEntry(name, sender())
nextLookupId += 1
case LookupFromTyped(name, replyTo) =>
context.actorSelection(name) ! Identify(nextLookupId)
lookups += nextLookupId -> RequestEntry(name, replyTo.toClassic)
nextLookupId += 1
case ActorIdentity(id, Some(ref)) =>
val idNumber = id.asInstanceOf[Long]
lookups.get(idNumber) match {

View file

@ -2,6 +2,7 @@
package services.local
import akka.actor.{Actor, ActorRef, Props}
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.structures.{Amenity, Building}
import net.psforever.objects.serverobject.terminals.{CaptureTerminal, Terminal}
@ -131,7 +132,7 @@ class LocalService(zone: Zone) extends Actor {
// If the owner of this capture terminal is on the lattice trigger a zone wide map update to update lattice benefits
zone.Lattice find building match {
case Some(_) => building.TriggerZoneMapUpdate()
case Some(_) => building.Zone.actor ! ZoneActor.ZoneMapUpdate()
case None => ;
}
case LocalAction.RouterTelepadTransport(player_guid, passenger_guid, src_guid, dest_guid) =>
@ -247,9 +248,7 @@ class LocalService(zone: Zone) extends Actor {
if (building.NtuLevel > 0) {
log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction")
building.Faction = hackedByFaction
self ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, hackedByFaction))
building.Actor ! BuildingActor.SetFaction(hackedByFaction)
} else {
log.info("Base hack completed, but base was out of NTU.")
}

View file

@ -1,6 +1,7 @@
package services.local.support
import akka.actor.{Actor, Cancellable}
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.Default
import net.psforever.objects.serverobject.hackable.Hackable
import net.psforever.objects.serverobject.structures.Building
@ -44,7 +45,9 @@ class HackCaptureActor extends Actor {
// Restart the timer, in case this is the first object in the hacked objects list or the object was removed and re-added
RestartTimer()
if (target.isInstanceOf[CaptureTerminal]) { target.Owner.asInstanceOf[Building].TriggerZoneMapUpdate() }
if (target.isInstanceOf[CaptureTerminal]) {
target.Owner.asInstanceOf[Building].Zone.actor ! ZoneActor.ZoneMapUpdate()
}
case HackCaptureActor.ProcessCompleteHacks() =>
log.trace("Processing complete hacks")
@ -74,7 +77,9 @@ class HackCaptureActor extends Actor {
case HackCaptureActor.ClearHack(target, _) =>
hackedObjects = hackedObjects.filterNot(x => x.target == target)
if (target.isInstanceOf[CaptureTerminal]) { target.Owner.asInstanceOf[Building].TriggerZoneMapUpdate() }
if (target.isInstanceOf[CaptureTerminal]) {
target.Owner.asInstanceOf[Building].Zone.actor ! ZoneActor.ZoneMapUpdate()
}
// Restart the timer in case the object we just removed was the next one scheduled
RestartTimer()

View file

@ -1,55 +1,29 @@
package services.properties
import akka.actor.{Actor, ActorRef, Stash}
import net.psforever.objects.zones.InterstellarCluster
import akka.actor.Actor
import net.psforever.packet.game.{GamePropertyTarget, PropertyOverrideMessage}
import net.psforever.packet.game.PropertyOverrideMessage.GamePropertyScope
import net.psforever.packet.game.objectcreate.ObjectClass
import services.ServiceManager
import services.ServiceManager.Lookup
import net.psforever.zones.Zones
import scala.collection.mutable.ListBuffer
class PropertyOverrideManager extends Actor with Stash {
class PropertyOverrideManager extends Actor {
private[this] val log = org.log4s.getLogger("PropertyOverrideManager")
private var overrides: Map[Int, Map[String, List[(String, String)]]] = Map()
private var gamePropertyScopes: List[PropertyOverrideMessage.GamePropertyScope] = List()
private var interstellarCluster: ActorRef = Actor.noSender
private var zoneIds: List[Int] = List()
lazy private val zoneIds: Iterable[Int] = Zones.zones.values.map(_.Number)
override def preStart = {
log.info(s"Starting PropertyOverrideManager")
ServiceManager.serviceManager ! Lookup("cluster")
LoadOverridesFromFile(zoneId = 0) // Global overrides
for (zoneId <- zoneIds) {
LoadOverridesFromFile(zoneId)
}
ProcessGamePropertyScopes()
}
override def receive = ServiceLookup
def ServiceLookup: Receive = {
case ServiceManager.LookupResult("cluster", endpoint) =>
interstellarCluster = endpoint
if (interstellarCluster != ActorRef.noSender) {
interstellarCluster ! InterstellarCluster.GetZoneIds()
}
case InterstellarCluster.ZoneIds(zoneIds) =>
this.zoneIds = zoneIds
unstashAll()
LoadOverridesFromFile(zoneId = 0) // Global overrides
for (zoneId <- zoneIds) {
LoadOverridesFromFile(zoneId)
}
ProcessGamePropertyScopes()
context.become(ReceiveCommand)
case _ => stash()
}
def ReceiveCommand: Receive = {
override def receive: Receive = {
case PropertyOverrideManager.GetOverridesMessage => {
sender ! gamePropertyScopes
}

View file

@ -1,19 +1,15 @@
// Copyright (c) 2017 PSForever
package objects
import akka.actor.Props
import base.ActorTest
import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.{Default, GlobalDefinitions}
import net.psforever.objects.serverobject.affinity.FactionAffinity
import net.psforever.objects.serverobject.doors.{Door, DoorControl}
import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.structures._
import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID}
import net.psforever.types.PlanetSideEmpire
import org.specs2.mutable.Specification
import services.ServiceManager
import services.galaxy.GalaxyService
import scala.concurrent.duration._
import akka.actor.typed.scaladsl.adapter._
class AmenityTest extends Specification {
val definition = new AmenityDefinition(0) {
@ -115,82 +111,12 @@ class WarpGateTest extends Specification {
}
}
class BuildingControl1Test extends ActorTest {
class BuildingActor1Test extends ActorTest {
"Building Control" should {
"construct" in {
val bldg = Building("Building", 0, 10, Zone.Nowhere, StructureType.Building)
bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test")
bldg.Actor = system.spawn(BuildingActor(Zone.Nowhere, bldg), "test").toClassic
assert(bldg.Actor != Default.Actor)
}
}
}
class BuildingControl2Test extends ActorTest {
ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService], "galaxy")
val bldg = Building("Building", 0, 10, Zone.Nowhere, StructureType.Building)
bldg.Faction = PlanetSideEmpire.TR
bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test")
bldg.Actor ! "startup"
"Building Control" should {
"convert and assert faction affinity on convert request" in {
expectNoMessage(500 milliseconds)
assert(bldg.Faction == PlanetSideEmpire.TR)
bldg.Actor ! FactionAffinity.ConvertFactionAffinity(PlanetSideEmpire.VS)
val reply = receiveOne(500 milliseconds)
assert(reply.isInstanceOf[FactionAffinity.AssertFactionAffinity])
assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].obj == bldg)
assert(reply.asInstanceOf[FactionAffinity.AssertFactionAffinity].faction == PlanetSideEmpire.VS)
assert(bldg.Faction == PlanetSideEmpire.VS)
}
}
}
class BuildingControl3Test extends ActorTest {
ServiceManager.boot(system) ! ServiceManager.Register(Props[GalaxyService], "galaxy")
val bldg = Building("Building", 0, 10, Zone.Nowhere, StructureType.Building)
bldg.Faction = PlanetSideEmpire.TR
bldg.Actor = system.actorOf(Props(classOf[BuildingControl], bldg), "test")
val door1 = Door(GlobalDefinitions.door)
door1.GUID = PlanetSideGUID(1)
door1.Actor = system.actorOf(Props(classOf[DoorControl], door1), "door1-test")
val door2 = Door(GlobalDefinitions.door)
door2.GUID = PlanetSideGUID(2)
door2.Actor = system.actorOf(Props(classOf[DoorControl], door2), "door2-test")
bldg.Amenities = door2
bldg.Amenities = door1
bldg.Actor ! "startup"
"Building Control" should {
"convert and assert faction affinity on convert request, and for each of its amenities" in {
expectNoMessage(500 milliseconds)
assert(bldg.Faction == PlanetSideEmpire.TR)
assert(bldg.Amenities.length == 2)
assert(bldg.Amenities.head == door2)
assert(bldg.Amenities(1) == door1)
bldg.Actor ! FactionAffinity.ConvertFactionAffinity(PlanetSideEmpire.VS)
val reply = ActorTest.receiveMultiple(3, 500 milliseconds, this)
//val reply = receiveN(3, Duration.create(5000, "ms"))
assert(reply.length == 3)
var building_count = 0
var door_count = 0
reply.foreach(item => {
assert(item.isInstanceOf[FactionAffinity.AssertFactionAffinity])
val item2 = item.asInstanceOf[FactionAffinity.AssertFactionAffinity]
item2.obj match {
case _: Building =>
building_count += 1
case _: Door =>
door_count += 1
case _ =>
assert(false)
}
assert(item2.faction == PlanetSideEmpire.VS)
})
assert(building_count == 1 && door_count == 2)
}
}
}

View file

@ -5,6 +5,7 @@ import akka.actor.{Actor, Props}
import akka.routing.RandomPool
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.guid.{NumberPoolHub, TaskResolver}
import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.serverobject.CommonMessages
@ -12,12 +13,13 @@ import net.psforever.objects.{Avatar, GlobalDefinitions, Ntu, Player, Vehicle}
import net.psforever.objects.serverobject.resourcesilo.{ResourceSilo, ResourceSiloControl, ResourceSiloDefinition}
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.serverobject.transfer.TransferBehavior
import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.UseItemMessage
import net.psforever.types._
import org.specs2.mutable.Specification
import services.ServiceManager
import services.avatar.{AvatarAction, AvatarServiceMessage}
import akka.actor.typed.scaladsl.adapter._
import scala.concurrent.duration._
@ -97,8 +99,7 @@ class ResourceSiloControlUseTest extends ActorTest {
override def SetupNumberPools() = {}
GUID(guid)
}
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-zone-actor")
zone.Actor ! Zone.Init()
zone.actor = system.spawnAnonymous(ZoneActor(zone))
val building = new Building(
"Building",
building_guid = 0,
@ -117,7 +118,7 @@ class ResourceSiloControlUseTest extends ActorTest {
new Avatar(0L, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
) //guid=3
val vehicle = Vehicle(GlobalDefinitions.ant) //guid=4
val probe = new TestProbe(system)
val probe = new TestProbe(system)
guid.register(building, 1)
guid.register(obj, 2)
@ -139,7 +140,7 @@ class ResourceSiloControlUseTest extends ActorTest {
val reply = probe.receiveOne(2000 milliseconds)
assert(reply match {
case TransferBehavior.Discharging(Ntu.Nanites) => true
case _ => false
case _ => false
})
}
}
@ -221,21 +222,21 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
assert(obj.CapacitorDisplay == 4)
assert(reply1 match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(1), 45, 4)) => true
case _ => false
case _ => false
})
assert(reply2.isInstanceOf[Building.SendMapUpdate])
assert(reply2.isInstanceOf[BuildingActor.MapUpdate])
val reply3 = zoneEvents.receiveOne(500 milliseconds)
assert(!obj.LowNtuWarningOn)
assert(reply3 match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => true
case _ => false
case _ => false
})
val reply4 = zoneEvents.receiveOne(500 milliseconds)
assert(reply4 match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 48, 0)) => true
case _ => false
case _ => false
})
}
}
@ -295,7 +296,7 @@ class ResourceSiloControlUpdate2Test extends ActorTest {
.attribute_value == 3
)
assert(reply2.isInstanceOf[Building.SendMapUpdate])
assert(reply2.isInstanceOf[BuildingActor.MapUpdate])
val reply3 = zoneEvents.receiveOne(500 milliseconds)
assert(!obj.LowNtuWarningOn)
@ -355,7 +356,9 @@ class ResourceSiloControlNoUpdateTest extends ActorTest {
expectNoMessage(500 milliseconds)
zoneEvents.expectNoMessage(500 milliseconds)
buildingEvents.expectNoMessage(500 milliseconds)
assert(obj.NtuCapacitor == 299 || obj.NtuCapacitor == 300) // Just in case the capacitor level drops while waiting for the message check 299 & 300
assert(
obj.NtuCapacitor == 299 || obj.NtuCapacitor == 300
) // Just in case the capacitor level drops while waiting for the message check 299 & 300
assert(obj.CapacitorDisplay == 3)
assert(!obj.LowNtuWarningOn)
}

View file

@ -11,7 +11,7 @@ import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.serverobject.mount.Mountable
import net.psforever.objects.vehicles._
import net.psforever.objects.vital.VehicleShieldCharge
import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.{CargoMountPointStatusMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
import net.psforever.types.{PlanetSideGUID, _}
import org.specs2.mutable._
@ -19,8 +19,11 @@ import services.{RemoverActor, ServiceManager}
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.ZoneActor
class VehicleTest extends Specification {
import VehicleTest._
"SeatDefinition" should {
@ -408,10 +411,12 @@ class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTes
val guid = new NumberPoolHub(new LimitedNumberSource(10))
val zone = new Zone("test", new ZoneMap("test"), 0) {
GUID(guid)
override def SetupNumberPools(): Unit = {}
}
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-zone-actor")
zone.Init(context)
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
// crappy workaround but without it the zone doesn't get initialized in time
expectNoMessage(400 milliseconds)
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.Faction = PlanetSideEmpire.TR
@ -533,10 +538,12 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
ServiceManager.boot
val zone = new Zone("test", new ZoneMap("test"), 0) {
GUID(guid)
override def SetupNumberPools(): Unit = {}
}
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-zone-actor")
zone.Init(context)
zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
// crappy workaround but without it the zone doesn't get initialized in time
expectNoMessage(200 milliseconds)
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.Faction = PlanetSideEmpire.TR

View file

@ -3,7 +3,7 @@ package objects
import java.util.concurrent.atomic.AtomicInteger
import akka.actor.{ActorContext, ActorRef, Props}
import akka.actor.ActorContext
import base.ActorTest
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment.Equipment
@ -14,14 +14,18 @@ import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects._
import net.psforever.types._
import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType}
import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.objects.Vehicle
import org.specs2.mutable.Specification
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.ZoneActor
import scala.concurrent.duration._
class ZoneTest extends Specification {
def test(a: String, b: Int, c: Int, d: Zone, e: ActorContext): Building = { Building.NoBuilding }
def test(a: String, b: Int, c: Int, d: Zone, e: ActorContext): Building = {
Building.NoBuilding
}
"ZoneMap" should {
"construct" in {
@ -116,29 +120,26 @@ class ZoneTest extends Specification {
class ZoneActorTest extends ActorTest {
"Zone" should {
"have an Actor" in {
val zone = new Zone("test", new ZoneMap("map6"), 1) { override def SetupNumberPools() = {} }
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-actor")
expectNoMessage(Duration.create(100, "ms"))
assert(zone.Actor != ActorRef.noSender)
}
"create new number pools before the Actor is started" in {
val zone = new Zone("test", new ZoneMap("map6"), 1) { override def SetupNumberPools() = {} }
val zone = new Zone("test", new ZoneMap("map6"), 1) {
override def SetupNumberPools() = {}
}
zone.GUID(new NumberPoolHub(new LimitedNumberSource(10)))
assert(zone.AddPool("test1", 1 to 2))
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-add-pool-actor") //note: not Init'd yet
zone.actor = system.spawn(ZoneActor(zone), "test-add-pool-actor") //note: not Init'd yet
assert(zone.AddPool("test2", 3 to 4))
}
"remove existing number pools before the Actor is started" in {
val zone = new Zone("test", new ZoneMap("map6"), 1) { override def SetupNumberPools() = {} }
val zone = new Zone("test", new ZoneMap("map6"), 1) {
override def SetupNumberPools() = {}
}
zone.GUID(new NumberPoolHub(new LimitedNumberSource(10)))
assert(zone.AddPool("test1", 1 to 2))
assert(zone.RemovePool("test1"))
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-remove-pool-actor") //note: not Init'd yet
zone.actor = system.spawn(ZoneActor(zone), "test-remove-pool-actor") //note: not Init'd yet
assert(zone.AddPool("test2", 3 to 4))
assert(zone.RemovePool("test2"))
}
@ -146,8 +147,7 @@ class ZoneActorTest extends ActorTest {
"refuse new number pools after the Actor is started" in {
val zone = new Zone("test", new ZoneMap("map6"), 1) { override def SetupNumberPools() = {} }
zone.GUID(new NumberPoolHub(new LimitedNumberSource(40150)))
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-add-pool-actor-init")
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), "test-add-pool-actor-init")
expectNoMessage(Duration.create(500, "ms"))
assert(!zone.AddPool("test1", 1 to 2))
@ -158,8 +158,7 @@ class ZoneActorTest extends ActorTest {
zone.GUID(new NumberPoolHub(new LimitedNumberSource(10)))
zone.AddPool("test", 1 to 2)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-remove-pool-actor-init")
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), "test-remove-pool-actor-init")
expectNoMessage(Duration.create(300, "ms"))
assert(!zone.RemovePool("test"))
@ -203,8 +202,7 @@ class ZoneActorTest extends ActorTest {
ObjectToBuilding(10, 7)
}
val zone = new Zone("test", map6, 1) { override def SetupNumberPools() = {} }
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-init")
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), "test-init")
expectNoMessage(Duration.create(1, "seconds"))
val groups = zone.SpawnGroups()
@ -230,80 +228,6 @@ class ZoneActorTest extends ActorTest {
}
})
}
"select spawn points based on the position of the player in reference to buildings" in {
val map6 = new ZoneMap("map6") {
LocalBuilding(
"Building",
building_guid = 1,
map_id = 1,
FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1, 1, 1)))
)
LocalObject(2, SpawnTube.Constructor(Vector3(1, 0, 0), Vector3.Zero))
ObjectToBuilding(2, 1)
LocalBuilding(
"Building",
building_guid = 3,
map_id = 3,
FoundationBuilder(Building.Structure(StructureType.Building, Vector3(4, 4, 4)))
)
LocalObject(4, SpawnTube.Constructor(Vector3(1, 0, 0), Vector3.Zero))
ObjectToBuilding(4, 3)
}
val zone = new Zone("test", map6, 1) { override def SetupNumberPools() = {} }
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-spawn")
zone.Actor ! Zone.Init()
expectNoMessage(Duration.create(1, "seconds"))
val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, CharacterVoice.Voice5))
val bldg1 = zone.Building(1).get
val bldg3 = zone.Building(3).get
player.Position = Vector3(1, 1, 1) //closer to bldg1
zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7)
val reply1 = receiveOne(Duration.create(200, "ms"))
assert(reply1.isInstanceOf[Zone.Lattice.SpawnPoint])
assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test")
assert(reply1.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_point.Owner == bldg1)
player.Position = Vector3(3, 3, 3) //closer to bldg3
zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7)
val reply3 = receiveOne(Duration.create(200, "ms"))
assert(reply3.isInstanceOf[Zone.Lattice.SpawnPoint])
assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].zone_id == "test")
assert(reply3.asInstanceOf[Zone.Lattice.SpawnPoint].spawn_point.Owner == bldg3)
}
"will report if no spawn points have been found in a zone" in {
val map6 = new ZoneMap("map6") {
LocalBuilding(
"Building",
building_guid = 1,
map_id = 1,
FoundationBuilder(Building.Structure(StructureType.Building, Vector3(1, 1, 1)))
)
LocalBuilding(
"Building",
building_guid = 3,
map_id = 3,
FoundationBuilder(Building.Structure(StructureType.Tower, Vector3(4, 4, 4)))
)
LocalObject(5, SpawnTube.Constructor(Vector3.Zero, Vector3.Zero))
ObjectToBuilding(5, 3)
}
val zone = new Zone("test", map6, 1) { override def SetupNumberPools() = {} }
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-no-spawn")
zone.Actor ! Zone.Init()
expectNoMessage(Duration.create(300, "ms"))
val player = Player(Avatar("Chord", PlanetSideEmpire.NEUTRAL, CharacterGender.Male, 0, CharacterVoice.Voice5))
zone.Actor ! Zone.Lattice.RequestSpawnPoint(1, player, 7)
val reply = receiveOne(Duration.create(200, "ms"))
assert(reply.isInstanceOf[Zone.Lattice.NoValidSpawnPoint])
assert(reply.asInstanceOf[Zone.Lattice.NoValidSpawnPoint].zone_number == 1)
assert(reply.asInstanceOf[Zone.Lattice.NoValidSpawnPoint].spawn_group.contains(7))
}
}
}
@ -312,8 +236,7 @@ class ZonePopulationTest extends ActorTest {
"add new user to zones" in {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
assert(zone.Players.isEmpty)
@ -328,8 +251,7 @@ class ZonePopulationTest extends ActorTest {
"remove user from zones" in {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
receiveOne(Duration.create(200, "ms")) //consume
zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms"))
@ -345,8 +267,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms"))
@ -366,8 +287,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms"))
@ -390,8 +310,7 @@ class ZonePopulationTest extends ActorTest {
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
player.GUID = PlanetSideGUID(1)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms"))
@ -416,8 +335,7 @@ class ZonePopulationTest extends ActorTest {
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player1 = Player(avatar)
val player2 = Player(avatar)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms"))
@ -442,8 +360,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
assert(zone.Players.isEmpty)
@ -460,8 +377,7 @@ class ZonePopulationTest extends ActorTest {
"user tries to Release a character, but did not Spawn a character first" in {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms"))
@ -483,8 +399,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.Release
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
assert(zone.Corpses.isEmpty)
@ -498,8 +413,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.Release
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
zone.Population ! Zone.Corpse.Add(player)
expectNoMessage(Duration.create(500, "ms"))
@ -519,8 +433,7 @@ class ZonePopulationTest extends ActorTest {
player2.Release
val player3 = Player(Avatar("Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player3.Release
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
zone.Population ! Zone.Corpse.Add(player1)
zone.Population ! Zone.Corpse.Add(player2)
@ -542,8 +455,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
//player.Release !!important
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
assert(zone.Corpses.isEmpty)
@ -560,8 +472,7 @@ class ZoneGroundDropItemTest extends ActorTest {
hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
"DropItem" should {
@ -586,8 +497,7 @@ class ZoneGroundCanNotDropItem1Test extends ActorTest {
//hub.register(item, 10) //!important
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
"DropItem" should {
@ -612,8 +522,7 @@ class ZoneGroundCanNotDropItem2Test extends ActorTest {
hub.register(item, 10) //!important
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
//zone.GUID(hub) //!important
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
"DropItem" should {
@ -638,8 +547,7 @@ class ZoneGroundCanNotDropItem3Test extends ActorTest {
hub.register(item, 10) //!important
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub) //!important
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
"DropItem" should {
@ -672,8 +580,7 @@ class ZoneGroundPickupItemTest extends ActorTest {
hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
"PickupItem" should {
@ -701,8 +608,7 @@ class ZoneGroundCanNotPickupItemTest extends ActorTest {
hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub) //still registered to this zone
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
"PickupItem" should {
@ -726,8 +632,7 @@ class ZoneGroundRemoveItemTest extends ActorTest {
hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub) //still registered to this zone
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
expectNoMessage(200 milliseconds)
"RemoveItem" should {

View file

@ -4,6 +4,7 @@ package objects.terminal
import akka.actor.Props
import akka.testkit.TestProbe
import base.ActorTest
import net.psforever.actors.zone.ZoneActor
import net.psforever.objects.serverobject.CommonMessages
import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.serverobject.terminals.{
@ -12,14 +13,14 @@ import net.psforever.objects.serverobject.terminals.{
ProximityUnit,
Terminal
}
import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.objects.{Avatar, GlobalDefinitions, Player}
import net.psforever.types.{CharacterGender, CharacterVoice, PlanetSideEmpire, PlanetSideGUID}
import org.specs2.mutable.Specification
import services.Service
import services.local.LocalService
import scala.concurrent.duration._
import akka.actor.typed.scaladsl.adapter._
class ProximityTest extends Specification {
"ProximityUnit" should {
@ -106,7 +107,7 @@ class ProximityTerminalControlStartTest extends ActorTest {
"ProximityTerminalControl" should {
//setup
val zone: Zone = new Zone("test", new ZoneMap("test-map"), 0) {
Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone")
actor = system.spawn(ZoneActor(this), "test-zone")
override def SetupNumberPools() = {
AddPool("dynamic", 1 to 10)
}
@ -146,7 +147,7 @@ class ProximityTerminalControlTwoUsersTest extends ActorTest {
"ProximityTerminalControl" should {
//setup
val zone: Zone = new Zone("test", new ZoneMap("test-map"), 0) {
Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone")
actor = system.spawn(ZoneActor(this), "test-zone")
override def SetupNumberPools() = {
AddPool("dynamic", 1 to 10)
}
@ -199,7 +200,7 @@ class ProximityTerminalControlStopTest extends ActorTest {
"ProximityTerminalControl" should {
//setup
val zone: Zone = new Zone("test", new ZoneMap("test-map"), 0) {
Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone")
actor = system.spawn(ZoneActor(this), "test-zone")
override def SetupNumberPools() = {
AddPool("dynamic", 1 to 10)
}
@ -242,7 +243,7 @@ class ProximityTerminalControlNotStopTest extends ActorTest {
"ProximityTerminalControl" should {
//setup
val zone: Zone = new Zone("test", new ZoneMap("test-map"), 0) {
Actor = system.actorOf(Props(classOf[ZoneActor], this), "test-zone")
actor = system.spawn(ZoneActor(this), "test-zone")
override def SetupNumberPools() = {
AddPool("dynamic", 1 to 10)
}

View file

@ -26,8 +26,8 @@
<pattern>%date{ISO8601} [%thread] %5level "%X" %logger{35} - %msg%n</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>OFF</level>
<!--<level>TRACE</level>-->
<!--<level>OFF</level>-->
<level>TRACE</level>
</filter>
</appender>

View file

@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS "buildings" (
local_id INT NOT NULL,
zone_id INT NOT NULL,
faction_id INT NOT NULL,
PRIMARY KEY (local_id, zone_id)
);

View file

@ -4,7 +4,7 @@ import java.net.InetAddress
import java.util.Locale
import akka.{actor => classic}
import akka.actor.typed.ActorSystem
import akka.actor.typed.scaladsl.adapter._
import akka.routing.RandomPool
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.joran.JoranConfigurator
@ -15,7 +15,7 @@ import net.psforever.objects.guid.TaskResolver
import org.slf4j
import org.fusesource.jansi.Ansi._
import org.fusesource.jansi.Ansi.Color._
import services.ServiceManager
import services.{InterstellarClusterService, ServiceManager}
import services.account.{AccountIntermediaryService, AccountPersistenceService}
import services.chat.ChatService
import services.galaxy.GalaxyService
@ -27,7 +27,6 @@ import org.flywaydb.core.Flyway
import java.nio.file.Paths
import scopt.OParser
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.session.SessionActor
import net.psforever.login.psadmin.PsAdminActor
import net.psforever.login.{
@ -94,8 +93,6 @@ object PsLogin {
implicit val system = classic.ActorSystem("PsLogin")
Default(system)
val typedSystem: ActorSystem[Nothing] = system.toTyped
/** Create pipelines for the login and world servers
*
* The first node in the pipe is an Actor that handles the crypto for protecting packets.
@ -130,16 +127,16 @@ object PsLogin {
None
}
val continents = Zones.zones.values ++ Seq(Zone.Nowhere)
val zones = Zones.zones.values ++ Seq(Zone.Nowhere)
system.spawnAnonymous(ChatService())
system.spawn(ChatService(), ChatService.ChatServiceKey.id)
system.spawn(InterstellarClusterService(zones), InterstellarClusterService.InterstellarClusterServiceKey.id)
val serviceManager = ServiceManager.boot
serviceManager ! ServiceManager.Register(classic.Props[AccountIntermediaryService], "accountIntermediary")
serviceManager ! ServiceManager.Register(RandomPool(150).props(classic.Props[TaskResolver]), "taskResolver")
serviceManager ! ServiceManager.Register(classic.Props[GalaxyService], "galaxy")
serviceManager ! ServiceManager.Register(classic.Props[SquadService], "squad")
serviceManager ! ServiceManager.Register(classic.Props(classOf[InterstellarCluster], continents), "cluster")
serviceManager ! ServiceManager.Register(classic.Props[AccountPersistenceService], "accountPersistence")
serviceManager ! ServiceManager.Register(classic.Props[PropertyOverrideManager], "propertyOverrideManager")

View file

@ -11,6 +11,8 @@ import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideGUID, _}
import services.RemoverActor
import services.vehicle.{VehicleAction, VehicleServiceMessage}
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.ZoneActor
import scala.concurrent.duration._
@ -213,7 +215,6 @@ object VehicleSpawnPadControlTest {
import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.vehicles.VehicleControl
import net.psforever.objects.zones.ZoneActor
import net.psforever.objects.Tool
import net.psforever.types.CharacterGender
@ -228,8 +229,7 @@ object VehicleSpawnPadControlTest {
override def SetupNumberPools(): Unit = {}
}
zone.GUID(guid)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), s"test-zone-${System.nanoTime()}")
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), s"test-zone-${System.nanoTime()}")
// Hack: Wait for the Zone to finish booting, otherwise later tests will fail randomly due to race conditions
// with actor probe setting

View file

@ -6,7 +6,7 @@ import akka.routing.RandomPool
import actor.base.ActorTest
import net.psforever.objects._
import net.psforever.objects.guid.{GUIDTask, TaskResolver}
import net.psforever.objects.zones.{Zone, ZoneActor, ZoneMap}
import net.psforever.objects.zones.{Zone, ZoneMap}
import net.psforever.packet.game.objectcreate.{DroppedItemData, ObjectClass, ObjectCreateMessageParent, PlacementData}
import net.psforever.packet.game.{ObjectCreateMessage, PlayerStateMessageUpstream}
import net.psforever.types._
@ -14,6 +14,8 @@ import services.{RemoverActor, Service, ServiceManager}
import services.avatar._
import scala.concurrent.duration._
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.ZoneActor
class AvatarService1Test extends ActorTest {
"AvatarService" should {
@ -510,8 +512,7 @@ class AvatarReleaseTest extends ActorTest {
}
val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service")
val taskResolver = system.actorOf(Props[TaskResolver], "release-test-resolver")
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone")
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), "release-test-zone")
val obj = Player(Avatar("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
obj.Continent = "test"
obj.Release
@ -561,8 +562,7 @@ class AvatarReleaseEarly1Test extends ActorTest {
}
val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service")
val taskResolver = system.actorOf(Props[TaskResolver], "release-test-resolver")
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone")
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), "release-test-zone")
val obj = Player(Avatar("TestCharacter1", PlanetSideEmpire.VS, CharacterGender.Female, 1, CharacterVoice.Voice1))
obj.Continent = "test"
obj.Release
@ -613,8 +613,7 @@ class AvatarReleaseEarly2Test extends ActorTest {
}
val service = system.actorOf(Props(classOf[AvatarService], zone), "release-test-service")
val taskResolver = system.actorOf(Props[TaskResolver], "release-test-resolver")
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "release-test-zone")
zone.Actor ! Zone.Init()
zone.actor = system.spawn(ZoneActor(zone), "release-test-zone")
val objAlt =
Player(
Avatar("TestCharacter2", PlanetSideEmpire.NC, CharacterGender.Male, 1, CharacterVoice.Voice1)

View file

@ -1,51 +0,0 @@
import re
def getmap():
data = open("tmp").read()
lines = data.split("\n")
lines_stripped = [l.strip().rstrip() for l in lines]
datalines = []
for i in lines_stripped:
m = re.findall(r'^[A-Z0-9a-z_]+', i)
if len(m):
datalines.append(m[0])
return datalines
def top():
datalines = getmap()
for i in range(0, len(datalines), 16):
print("// OPCODES 0x%02x-%02x" % (i, i+15))
print("\n".join([d+"," for d in datalines[i:min(i+8, len(datalines))]]))
print("// 0x%02x" % (i+8))
print("\n".join([d+"," for d in datalines[min(i+8,len(datalines)):min(i+16, len(datalines))]]))
print("")
def bot():
data = open("tmp2").read()
lines = data.split("\n")
lines_stripped = [l.strip().rstrip() for l in lines]
datalinesMap = getmap()
datalines = []
for i in lines_stripped:
m = re.findall(r'^case ([0-9]+)', i)
if len(m):
num = int(m[0])
m = re.findall(r'=> (.*)', i)[0]
if m.startswith("noDecoder"):
datalines.append((num, "noDecoder(%s)" % datalinesMap[num]))
else:
datalines.append((num, m))
for i in range(0, len(datalines), 16):
print("// OPCODES 0x%02x-%02x" % (i, i+15))
print("\n".join(["case 0x%02x => %s" % (n,d) for n,d in datalines[i:min(i+8, len(datalines))]]))
print("// 0x%02x" % (i+8))
print("\n".join(["case 0x%02x => %s" % (n,d) for n,d in datalines[i+8:min(i+16, len(datalines))]]))
print("")
top()