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 The default database is named `psforever` and the credentials are
`psforever:psforever`. To change these, create a configuration file at `psforever:psforever`. To change these, create a configuration file at
`config/psforever.conf`. For configuration options and their defaults, see `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. ALL access to tables, sequences, and functions.
The permissions required can be summarized by the SQL below. 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. 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"), packMain := Map("ps-login" -> "net.psforever.pslogin.PsLogin"),
packArchivePrefix := "pslogin", packArchivePrefix := "pslogin",
packExtraClasspath := Map("ps-login" -> Seq("${PROG_HOME}/pscrypto-lib", "${PROG_HOME}/config")), packExtraClasspath := Map("ps-login" -> Seq("${PROG_HOME}/pscrypto-lib", "${PROG_HOME}/config")),
packResourceDir += (baseDirectory.value / "pscrypto-lib" -> "pscrypto-lib"), packResourceDir += (baseDirectory.value / "pscrypto-lib" -> "pscrypto-lib"),
packResourceDir += (baseDirectory.value / "config" -> "config"), packResourceDir += (baseDirectory.value / "config" -> "config")
packResourceDir += (baseDirectory.value / "pslogin/src/main/resources" -> "config")
) )
lazy val root = (project in file(".")) 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")) lazy val decodePacketsPackSettings = Seq(packMain := Map("psf-decode-packets" -> "DecodePackets"))
// Special test configuration for really quiet tests (used in CI) // 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. # The SSL configuration of the database connection.
# One of: disable prefer require verify-full # One of: disable prefer require verify-full
sslmode = prefer 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 { anti-cheat {
@ -110,5 +119,13 @@ kamon {
apm.api-key = "" apm.api-key = ""
} }
include "akka" sentry {
include "dispatchers" # 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 = { def accountLogin(username: String, password: String): Unit = {
import ctx._ import ctx._
val newToken = this.generateToken() val newToken = this.generateToken()
log.info("accountLogin")
val result = for { val result = for {
// backwards compatibility: prefer exact match first, then try lowercase // backwards compatibility: prefer exact match first, then try lowercase
accountsExact <- ctx.run(query[persistence.Account].filter(_.username == lift(username))) accountsExact <- ctx.run(query[persistence.Account].filter(_.username == lift(username)))
@ -171,6 +171,7 @@ class LoginSessionActor extends Actor with MDCContextAware {
} }
login <- accountOption match { login <- accountOption match {
case Some(account) => case Some(account) =>
log.info(s"$account")
(account.inactive, password.isBcrypted(account.passhash)) match { (account.inactive, password.isBcrypted(account.passhash)) match {
case (false, true) => case (false, true) =>
accountIntermediary ! StoreAccountData(newToken, new Account(account.id, account.username, account.gm)) accountIntermediary ! StoreAccountData(newToken, new Account(account.id, account.username, account.gm))

View file

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

View file

@ -2,6 +2,7 @@
package net.psforever.objects package net.psforever.objects
import akka.actor.{Actor, ActorRef} import akka.actor.{Actor, ActorRef}
import net.psforever.actors.commands.NtuCommand
import net.psforever.objects.serverobject.transfer.{TransferBehavior, TransferContainer} import net.psforever.objects.serverobject.transfer.{TransferBehavior, TransferContainer}
object Ntu { object Ntu {
@ -9,77 +10,81 @@ object Ntu {
/** /**
* Message for a `sender` announcing it has nanites it can offer the recipient. * Message for a `sender` announcing it has nanites it can offer the recipient.
*
* @param src the nanite container recognized as the sender * @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. * Message for a `sender` asking for nanites from the recipient.
*
* @param min a minimum amount of nanites requested; * @param min a minimum amount of nanites requested;
* if 0, the `sender` has no expectations * if 0, the `sender` has no expectations
* @param max the amount of nanites required to not make further requests; * @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 * 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. * 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 * @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 { 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 { 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 = scala.math.max(0, scala.math.min(value, Definition.MaxNtuCapacitor))
NtuCapacitor NtuCapacitor
} }
def Definition : NtuContainerDefinition def Definition: NtuContainerDefinition
} }
trait 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 = max
MaxNtuCapacitor MaxNtuCapacitor
} }
} }
trait NtuStorageBehavior extends Actor { 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.Offer(src) => HandleNtuOffer(sender, src)
case Ntu.Grant(_, 0) | Ntu.Request(0, 0) | TransferBehavior.Stopping() => StopNtuBehavior(sender) case Ntu.Grant(_, 0) | Ntu.Request(0, 0) | TransferBehavior.Stopping() => StopNtuBehavior(sender)
case Ntu.Request(min, max) => HandleNtuRequest(sender, min, max) 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 { val (interface, slotNumber) = player.VehicleSeated match {
case Some(mech_guid) => 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 })) { if (!player.Implants.exists({ case (implantType, _, _) => implantType == implant_type })) {
//no duplicates //no duplicates
player.InstallImplant(implant) player.InstallImplant(implant)
@ -547,7 +547,7 @@ class PlayerControl(player: Player) extends Actor with JammableBehavior with Dam
val (interface, slotNumber) = player.VehicleSeated match { val (interface, slotNumber) = player.VehicleSeated match {
case Some(mech_guid) => case Some(mech_guid) =>
( (
zone.Map.TerminalToInterface.get(mech_guid.guid), zone.map.TerminalToInterface.get(mech_guid.guid),
player.UninstallImplant(implant_type) player.UninstallImplant(implant_type)
) )
case None => case None =>

View file

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

View file

@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.resourcesilo
import akka.actor.{Actor, ActorRef} import akka.actor.{Actor, ActorRef}
import net.psforever.objects.serverobject.CommonMessages 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.affinity.{FactionAffinity, FactionAffinityBehavior}
import net.psforever.objects.serverobject.transfer.TransferBehavior import net.psforever.objects.serverobject.transfer.TransferBehavior
import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.structures.Building
@ -10,7 +11,6 @@ import net.psforever.objects.{Ntu, NtuContainer, NtuStorageBehavior}
import net.psforever.types.PlanetSideEmpire import net.psforever.types.PlanetSideEmpire
import services.Service import services.Service
import services.avatar.{AvatarAction, AvatarServiceMessage} import services.avatar.{AvatarAction, AvatarServiceMessage}
import services.local.{LocalAction, LocalServiceMessage}
import services.vehicle.{VehicleAction, VehicleServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.ExecutionContext.Implicits.global 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`. * An `Actor` that handles messages being dispatched to a specific `Resource Silo`.
*
* @param resourceSilo the `Resource Silo` object being governed * @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 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 = { def receive: Receive = {
case "startup" => 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 // 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) context.become(Processing)
case _ => ; case _ => ;
} }
def Processing : Receive = checkBehavior def Processing: Receive =
.orElse(storageBehavior) checkBehavior
.orElse { .orElse(storageBehavior)
case CommonMessages.Use(player, _) => .orElse {
if(resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) { case CommonMessages.Use(player, _) =>
resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match { if (resourceSilo.Faction == PlanetSideEmpire.NEUTRAL || player.Faction == resourceSilo.Faction) {
case Some(vehicle) => resourceSilo.Zone.Vehicles.find(v => v.PassengerInSeat(player).contains(0)) match {
context.system.scheduler.scheduleOnce(delay = 1000 milliseconds, vehicle.Actor, TransferBehavior.Discharging(Ntu.Nanites)) case Some(vehicle) =>
case _ => context.system.scheduler.scheduleOnce(
delay = 1000 milliseconds,
vehicle.Actor,
TransferBehavior.Discharging(Ntu.Nanites)
)
case _ =>
}
} }
}
case ResourceSilo.LowNtuWarning(enabled: Boolean) => case ResourceSilo.LowNtuWarning(enabled: Boolean) =>
LowNtuWarning(enabled) LowNtuWarning(enabled)
case ResourceSilo.UpdateChargeLevel(amount: Int) => case ResourceSilo.UpdateChargeLevel(amount: Int) =>
UpdateChargeLevel(amount) UpdateChargeLevel(amount)
case _ => ; case _ => ;
} }
def LowNtuWarning(enabled : Boolean) : Unit = { def LowNtuWarning(enabled: Boolean): Unit = {
resourceSilo.LowNtuWarningOn = enabled resourceSilo.LowNtuWarningOn = enabled
log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled") log.trace(s"LowNtuWarning: Silo ${resourceSilo.GUID} low ntu warning set to $enabled")
val building = resourceSilo.Owner val building = resourceSilo.Owner
val zone = building.Zone val zone = building.Zone
building.Zone.AvatarEvents ! AvatarServiceMessage( building.Zone.AvatarEvents ! AvatarServiceMessage(
zone.Id, 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 = { def UpdateChargeLevel(amount: Int): Unit = {
val siloChargeBeforeChange = resourceSilo.NtuCapacitor val siloChargeBeforeChange = resourceSilo.NtuCapacitor
val siloDisplayBeforeChange = resourceSilo.CapacitorDisplay val siloDisplayBeforeChange = resourceSilo.CapacitorDisplay
val building = resourceSilo.Owner.asInstanceOf[Building] val building = resourceSilo.Owner.asInstanceOf[Building]
val zone = building.Zone val zone = building.Zone
// Increase if positive passed in or decrease charge level if negative number is passed in // Increase if positive passed in or decrease charge level if negative number is passed in
resourceSilo.NtuCapacitor += amount 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 // Only send updated capacitor display value to all clients if it has actually changed
if (resourceSilo.CapacitorDisplay != siloDisplayBeforeChange) { if (resourceSilo.CapacitorDisplay != siloDisplayBeforeChange) {
log.trace(s"Silo ${resourceSilo.GUID} NTU bar level has changed from $siloDisplayBeforeChange to ${resourceSilo.CapacitorDisplay}") log.trace(
resourceSilo.Owner.Actor ! Building.SendMapUpdate(all_clients = true) s"Silo ${resourceSilo.GUID} NTU bar level has changed from $siloDisplayBeforeChange to ${resourceSilo.CapacitorDisplay}"
)
resourceSilo.Owner.Actor ! BuildingActor.MapUpdate()
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(
zone.Id, zone.Id,
AvatarAction.PlanetsideAttribute(resourceSilo.GUID, 45, resourceSilo.CapacitorDisplay) 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 val ntuIsLow = resourceSilo.NtuCapacitor.toFloat / resourceSilo.Definition.MaxNtuCapacitor.toFloat < 0.2f
if (resourceSilo.LowNtuWarningOn && !ntuIsLow) { if (resourceSilo.LowNtuWarningOn && !ntuIsLow) {
LowNtuWarning(enabled = false) LowNtuWarning(enabled = false)
} } else if (!resourceSilo.LowNtuWarningOn && ntuIsLow) {
else if (!resourceSilo.LowNtuWarningOn && ntuIsLow) {
LowNtuWarning(enabled = true) LowNtuWarning(enabled = true)
} }
if (resourceSilo.NtuCapacitor == 0 && siloChargeBeforeChange > 0) { if (resourceSilo.NtuCapacitor == 0 && siloChargeBeforeChange > 0) {
// Oops, someone let the base run out of power. Shut it all down. // Oops, someone let the base run out of power. Shut it all down.
zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(building.GUID, 48, 1)) zone.AvatarEvents ! AvatarServiceMessage(zone.Id, AvatarAction.PlanetsideAttribute(building.GUID, 48, 1))
building.Faction = PlanetSideEmpire.NEUTRAL building.Actor ! BuildingActor.SetFaction(PlanetSideEmpire.NEUTRAL)
zone.LocalEvents ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, PlanetSideEmpire.NEUTRAL)) } else if (siloChargeBeforeChange == 0 && resourceSilo.NtuCapacitor > 0) {
building.TriggerZoneMapUpdate()
}
else if (siloChargeBeforeChange == 0 && resourceSilo.NtuCapacitor > 0) {
// Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal. // Power restored. Reactor Online. Sensors Online. Weapons Online. All systems nominal.
//todo: Check generator is online before starting up //todo: Check generator is online before starting up
zone.AvatarEvents ! AvatarServiceMessage( zone.AvatarEvents ! AvatarServiceMessage(
zone.Id, zone.Id,
AvatarAction.PlanetsideAttribute(building.GUID, 48, 0) 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. * The silo will agree to offers until its nanite capacitor is completely full.
*/ */
def HandleNtuOffer(sender : ActorRef, src : NtuContainer) : Unit = { def HandleNtuOffer(sender: ActorRef, src: NtuContainer): Unit = {
sender ! (if(resourceSilo.NtuCapacitor < resourceSilo.MaxNtuCapacitor) { sender ! (if (resourceSilo.NtuCapacitor < resourceSilo.MaxNtuCapacitor) {
Ntu.Request(0, resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor) Ntu.Request(0, resourceSilo.MaxNtuCapacitor - resourceSilo.NtuCapacitor)
} } else {
else { StopNtuBehavior(sender)
StopNtuBehavior(sender) Ntu.Request(0, 0)
Ntu.Request(0, 0) })
})
} }
/** /**
* Reset the animation trigger and attempt the stop animation. * Reset the animation trigger and attempt the stop animation.
*/ */
def StopNtuBehavior(sender : ActorRef) : Unit = { def StopNtuBehavior(sender: ActorRef): Unit = {
panelAnimationFunc = PanelAnimation panelAnimationFunc = PanelAnimation
panelAnimationFunc(0) panelAnimationFunc(0)
} }
/** /**
* na * na
*
* @param sender na * @param sender na
* @param min a minimum amount of nanites requested; * @param min a minimum amount of nanites requested;
* @param max the amount of nanites required to not make further requests; * @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 val originalAmount = resourceSilo.NtuCapacitor
UpdateChargeLevel(-min) UpdateChargeLevel(-min)
sender ! Ntu.Grant(resourceSilo, originalAmount - resourceSilo.NtuCapacitor) 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. * Accept nanites into the silo capacitor and set the animation state.
*/ */
def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit = { def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Int): Unit = {
if(amount != 0) { if (amount != 0) {
val originalAmount = resourceSilo.NtuCapacitor val originalAmount = resourceSilo.NtuCapacitor
UpdateChargeLevel(amount) UpdateChargeLevel(amount)
panelAnimationFunc(resourceSilo.NtuCapacitor - originalAmount) 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 * 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. * 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. * These effects are both controlled by thee same packet.
*
* @param trigger if positive, activate the animation; * @param trigger if positive, activate the animation;
* if negative or zero, disable the animation * if negative or zero, disable the animation
*/ */
def PanelAnimation(trigger : Int) : Unit = { def PanelAnimation(trigger: Int): Unit = {
val zone = resourceSilo.Zone val zone = resourceSilo.Zone
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.Id, 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 ) // panel glow on & orb particles on
} }
/** /**
* Do nothing this turn. * 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 java.util.concurrent.TimeUnit
import akka.actor.ActorContext import akka.actor.ActorContext
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.{Default, GlobalDefinitions, Player} import net.psforever.objects.{Default, GlobalDefinitions, Player}
import net.psforever.objects.definition.ObjectDefinition import net.psforever.objects.definition.ObjectDefinition
import net.psforever.objects.serverobject.generator.Generator 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.terminals.CaptureTerminal
import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects.zones.Zone 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 net.psforever.types.{PlanetSideEmpire, PlanetSideGUID, PlanetSideGeneratorState, Vector3}
import scalax.collection.{Graph, GraphEdge} import scalax.collection.{Graph, GraphEdge}
import services.Service import services.Service
import services.local.{LocalAction, LocalServiceMessage} import services.local.{LocalAction, LocalServiceMessage}
import akka.actor.typed.scaladsl.adapter._
class Building( class Building(
private val name: String, private val name: String,
private val building_guid: Int, private val building_guid: Int,
private val map_id: Int, private val map_id: Int,
private val zone: Zone, private val zone: Zone,
private val buildingType: StructureType.Value, private val buildingType: StructureType,
private val buildingDefinition: BuildingDefinition private val buildingDefinition: BuildingDefinition
) extends AmenityOwner { ) extends AmenityOwner {
@ -71,7 +73,8 @@ class Building(
} else if (IsCapitol) { } else if (IsCapitol) {
UpdateForceDomeStatus() UpdateForceDomeStatus()
} }
TriggerZoneMapUpdate() // FIXME null check is a bad idea but tests rely on it
if (Zone.actor != null) Zone.actor ! ZoneActor.ZoneMapUpdate()
Faction Faction
} }
@ -126,10 +129,6 @@ class Building(
} }
} }
def TriggerZoneMapUpdate(): Unit = {
if (Actor != Default.Actor) Actor ! Building.TriggerZoneMapUpdate(Zone.Number)
}
def UpdateForceDomeStatus(): Unit = { def UpdateForceDomeStatus(): Unit = {
if (IsCapitol) { if (IsCapitol) {
val originalStatus = ForceDomeActive val originalStatus = ForceDomeActive
@ -155,7 +154,7 @@ class Building(
Zone.Id, Zone.Id,
LocalAction.UpdateForceDomeStatus(Service.defaultPlayerGUID, GUID, ForceDomeActive) LocalAction.UpdateForceDomeStatus(Service.defaultPlayerGUID, GUID, ForceDomeActive)
) )
Actor ! Building.SendMapUpdate(all_clients = true) Actor ! BuildingActor.MapUpdate()
} }
} }
} }
@ -171,27 +170,7 @@ class Building(
} }
} }
def Info: ( def infoUpdateMessage(): BuildingInfoUpdateMessage = {
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
) = {
val ntuLevel: Int = NtuLevel val ntuLevel: Int = NtuLevel
//if we have a capture terminal, get the hack status & time (in milliseconds) from control console if it exists //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 { val (hacking, hackingFaction, hackTime): (Boolean, PlanetSideEmpire.Value, Long) = CaptureTerminal match {
@ -264,31 +243,33 @@ class Building(
} }
} }
} }
//out
( BuildingInfoUpdateMessage(
Zone.Number,
MapId,
ntuLevel, ntuLevel,
hacking, hacking,
hackingFaction, hackingFaction,
hackTime, hackTime,
if (ntuLevel > 0) Faction else PlanetSideEmpire.NEUTRAL, 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, None,
generatorState, generatorState,
spawnTubesNormal, spawnTubesNormal,
ForceDomeActive, forceDomeActive,
latticeBenefit, latticeBenefit,
48, //cavern_benefit; !! Field > 0 will cause malformed packet. See class def. 48, // cavern benefit
Nil, //unk4 Nil, // unk4,
0, //unk5 0, // unk5
false, //unk6 false, // unk6
8, //!! unk7 Field != 8 will cause malformed packet. See class def. 8, // unk7 Field != 8 will cause malformed packet
None, //unk7x None, // unk7x
boostSpawnPain, //boost_spawn_pain boostSpawnPain,
boostGeneratorPain //boost_generator_pain 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 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) 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) new Building(name, guid, map_id, zone, buildingType, GlobalDefinitions.building)
} }
def Structure( def Structure(
buildingType: StructureType.Value, buildingType: StructureType,
location: Vector3, location: Vector3,
rotation: Vector3, rotation: Vector3,
definition: BuildingDefinition definition: BuildingDefinition
)(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = { )(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) val obj = new Building(name, guid, map_id, zone, buildingType, definition)
obj.Position = location obj.Position = location
obj.Orientation = rotation 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 obj
} }
def Structure( def Structure(
buildingType: StructureType.Value, buildingType: StructureType,
location: Vector3 location: Vector3
)(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = { )(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) val obj = new Building(name, guid, map_id, zone, buildingType, GlobalDefinitions.building)
obj.Position = location 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 obj
} }
def Structure( def Structure(
buildingType: StructureType.Value buildingType: StructureType
)(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): Building = { )(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) 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 obj
} }
def Structure( def Structure(
buildingType: StructureType.Value, buildingType: StructureType,
buildingDefinition: BuildingDefinition, buildingDefinition: BuildingDefinition,
location: Vector3 location: Vector3
)(name: String, guid: Int, id: Int, zone: Zone, context: ActorContext): Building = { )(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) val obj = new Building(name, guid, id, zone, buildingType, buildingDefinition)
obj.Position = location 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 obj
} }
final case class AmenityStateChange(obj: Amenity) 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 // Copyright (c) 2017 PSForever
package net.psforever.objects.serverobject.structures package net.psforever.objects.serverobject.structures
/** import enumeratum.{EnumEntry, Enum}
* 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
val Bridge, // technically, a "bridge section" sealed trait StructureType extends EnumEntry
Building, // generic
Bunker, // low accessible ground cover object StructureType extends Enum[StructureType] {
Facility, // large base val values = findValues
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 case object Bridge extends StructureType // technically, a "bridge section"
WarpGate // transport point between zones case object Building extends StructureType // generic
= Value 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.serverobject.PlanetSideServerObject
import net.psforever.objects.{GlobalDefinitions, NtuContainer, SpawnPoint} import net.psforever.objects.{GlobalDefinitions, NtuContainer, SpawnPoint}
import net.psforever.objects.zones.Zone 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 net.psforever.types.{PlanetSideEmpire, PlanetSideGeneratorState, Vector3}
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.BuildingActor
import scala.collection.mutable 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 */ /** what faction views this warp gate as a broadcast gate */
private var broadcast: mutable.Set[PlanetSideEmpire.Value] = mutable.Set.empty[PlanetSideEmpire.Value] private var broadcast: mutable.Set[PlanetSideEmpire.Value] = mutable.Set.empty[PlanetSideEmpire.Value]
override def Info: ( override def infoUpdateMessage(): BuildingInfoUpdateMessage = {
Int, BuildingInfoUpdateMessage(
Boolean, Zone.Number,
PlanetSideEmpire.Value, MapId,
Long,
PlanetSideEmpire.Value,
Long,
Option[Additional1],
PlanetSideGeneratorState.Value,
Boolean,
Boolean,
Int,
Int,
List[Additional2],
Long,
Boolean,
Int,
Option[Additional3],
Boolean,
Boolean
) = {
(
0, 0,
false, false,
PlanetSideEmpire.NEUTRAL, PlanetSideEmpire.NEUTRAL,
@ -179,19 +163,17 @@ object WarpGate {
} }
def Structure(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): 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) 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 obj
} }
def Structure( def Structure(
location: Vector3 location: Vector3
)(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): WarpGate = { )(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) val obj = new WarpGate(name, guid, map_id, zone, GlobalDefinitions.warpgate)
obj.Position = location 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 obj
} }
@ -199,10 +181,9 @@ object WarpGate {
location: Vector3, location: Vector3,
buildingDefinition: WarpGateDefinition buildingDefinition: WarpGateDefinition
)(name: String, guid: Int, map_id: Int, zone: Zone, context: ActorContext): WarpGate = { )(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) val obj = new WarpGate(name, guid, map_id, zone, buildingDefinition)
obj.Position = location 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 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 package net.psforever.objects.vehicles
import akka.actor.{ActorRef, Cancellable} 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.deploy.Deployment
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo import net.psforever.objects.serverobject.resourcesilo.ResourceSilo
import net.psforever.objects.serverobject.structures.WarpGate import net.psforever.objects.serverobject.structures.WarpGate
@ -10,21 +12,22 @@ import net.psforever.objects.{NtuContainer, _}
import net.psforever.types.DriveState import net.psforever.types.DriveState
import services.Service import services.Service
import services.vehicle.{VehicleAction, VehicleServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage}
import akka.actor.typed.scaladsl.adapter._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._ import scala.concurrent.duration._
trait AntTransferBehavior extends TransferBehavior trait AntTransferBehavior extends TransferBehavior with NtuStorageBehavior {
with NtuStorageBehavior { var ntuChargingTick: Cancellable = Default.Cancellable
var ntuChargingTick : Cancellable = Default.Cancellable var panelAnimationFunc: () => Unit = NoCharge
var panelAnimationFunc : ()=>Unit = NoCharge
def TransferMaterial = Ntu.Nanites 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 val zone = vehicle.Zone
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.Id, zone.Id,
@ -33,7 +36,7 @@ trait AntTransferBehavior extends TransferBehavior
} }
/** Charging */ /** Charging */
def StartNtuChargingEvent(vehicle : NtuContainer) : Unit = { def StartNtuChargingEvent(vehicle: NtuContainer): Unit = {
val zone = vehicle.Zone val zone = vehicle.Zone
zone.VehicleEvents ! VehicleServiceMessage( zone.VehicleEvents ! VehicleServiceMessage(
zone.Id, zone.Id,
@ -41,8 +44,8 @@ trait AntTransferBehavior extends TransferBehavior
) // orb particle effect on ) // orb particle effect on
} }
def UpdateNtuUI(vehicle : Vehicle with NtuContainer) : Unit = { def UpdateNtuUI(vehicle: Vehicle with NtuContainer): Unit = {
if(vehicle.Seats.values.exists(_.isOccupied)) { if (vehicle.Seats.values.exists(_.isOccupied)) {
val display = scala.math.ceil(vehicle.NtuCapacitorScaled).toLong val display = scala.math.ceil(vehicle.NtuCapacitorScaled).toLong
vehicle.Zone.VehicleEvents ! VehicleServiceMessage( vehicle.Zone.VehicleEvents ! VehicleServiceMessage(
vehicle.Actor.toString, vehicle.Actor.toString,
@ -51,76 +54,82 @@ trait AntTransferBehavior extends TransferBehavior
} }
} }
def HandleChargingEvent(target : TransferContainer) : Boolean = { def HandleChargingEvent(target: TransferContainer): Boolean = {
ntuChargingTick.cancel ntuChargingTick.cancel
val obj = ChargeTransferObject val obj = ChargeTransferObject
//log.trace(s"NtuCharging: Vehicle $guid is charging NTU capacitor.") //log.trace(s"NtuCharging: Vehicle $guid is charging NTU capacitor.")
if(obj.NtuCapacitor < obj.Definition.MaxNtuCapacitor) { if (obj.NtuCapacitor < obj.Definition.MaxNtuCapacitor) {
//charging //charging
panelAnimationFunc = InitialCharge panelAnimationFunc = InitialCharge
transferTarget = Some(target) transferTarget = Some(target)
transferEvent = TransferBehavior.Event.Charging transferEvent = TransferBehavior.Event.Charging
val (min, max) = target match { target match {
case _ : WarpGate => 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) //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 max = obj.Definition.MaxNtuCapacitor - obj.NtuCapacitor
val ntuMin = scala.math.min(obj.Definition.MaxNtuCapacitor/75, ntuMax) target.Actor ! BuildingActor.Ntu(
(ntuMin, ntuMax) NtuCommand.Request(scala.math.min(obj.Definition.MaxNtuCapacitor / 75, max), context.self)
)
case _ => 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 true
} } else {
else {
// Fully charged // Fully charged
TryStopChargingEvent(obj) TryStopChargingEvent(obj)
false false
} }
} }
def ReceiveAndDepositUntilFull(vehicle : Vehicle, amount : Int) : Boolean = { def ReceiveAndDepositUntilFull(vehicle: Vehicle, amount: Int): Boolean = {
val isNotFull = (vehicle.NtuCapacitor += amount) < vehicle.Definition.MaxNtuCapacitor val isNotFull = (vehicle.NtuCapacitor += amount) < vehicle.Definition.MaxNtuCapacitor
UpdateNtuUI(vehicle) UpdateNtuUI(vehicle)
isNotFull isNotFull
} }
/** Discharging */ /** Discharging */
def HandleDischargingEvent(target : TransferContainer) : Boolean = { def HandleDischargingEvent(target: TransferContainer): Boolean = {
//log.trace(s"NtuDischarging: Vehicle $guid is discharging NTU into silo $silo_guid") //log.trace(s"NtuDischarging: Vehicle $guid is discharging NTU into silo $silo_guid")
val obj = ChargeTransferObject val obj = ChargeTransferObject
if(obj.NtuCapacitor > 0) { if (obj.NtuCapacitor > 0) {
panelAnimationFunc = InitialDischarge panelAnimationFunc = InitialDischarge
transferTarget = Some(target) transferTarget = Some(target)
transferEvent = TransferBehavior.Event.Discharging transferEvent = TransferBehavior.Event.Discharging
target.Actor ! Ntu.Offer(obj) target.Actor ! Ntu.Offer(obj)
ntuChargingTick.cancel 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 true
} } else {
else {
TryStopChargingEvent(obj) TryStopChargingEvent(obj)
false false
} }
} }
def NoCharge() : Unit = {} def NoCharge(): Unit = {}
def InitialCharge() : Unit = { def InitialCharge(): Unit = {
panelAnimationFunc = NoCharge panelAnimationFunc = NoCharge
val obj = ChargeTransferObject val obj = ChargeTransferObject
ActivatePanelsForChargingEvent(obj) ActivatePanelsForChargingEvent(obj)
StartNtuChargingEvent(obj) StartNtuChargingEvent(obj)
} }
def InitialDischarge() : Unit = { def InitialDischarge(): Unit = {
panelAnimationFunc = NoCharge panelAnimationFunc = NoCharge
ActivatePanelsForChargingEvent(ChargeTransferObject) ActivatePanelsForChargingEvent(ChargeTransferObject)
} }
def WithdrawAndTransmit(vehicle : Vehicle, maxRequested : Int) : Any = { def WithdrawAndTransmit(vehicle: Vehicle, maxRequested: Int): Any = {
val chargeable = ChargeTransferObject val chargeable = ChargeTransferObject
var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), maxRequested) var chargeToDeposit = Math.min(Math.min(chargeable.NtuCapacitor, 100), maxRequested)
chargeable.NtuCapacitor -= chargeToDeposit chargeable.NtuCapacitor -= chargeToDeposit
UpdateNtuUI(chargeable) UpdateNtuUI(chargeable)
@ -128,27 +137,34 @@ trait AntTransferBehavior extends TransferBehavior
} }
/** Stopping */ /** Stopping */
override def TryStopChargingEvent(container : TransferContainer) : Unit = { override def TryStopChargingEvent(container: TransferContainer): Unit = {
val vehicle = ChargeTransferObject val vehicle = ChargeTransferObject
ntuChargingTick.cancel ntuChargingTick.cancel
if(transferEvent != TransferBehavior.Event.None) { if (transferEvent != TransferBehavior.Event.None) {
if(vehicle.DeploymentState == DriveState.Deployed) { if (vehicle.DeploymentState == DriveState.Deployed) {
//turning off glow/orb effects on ANT doesn't seem to work when deployed. Try to undeploy ANT first //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) vehicle.Actor ! Deployment.TryUndeploy(DriveState.Undeploying)
ntuChargingTick = context.system.scheduler.scheduleOnce(250 milliseconds, self, TransferBehavior.Stopping()) ntuChargingTick = context.system.scheduler.scheduleOnce(250 milliseconds, self, TransferBehavior.Stopping())
} } else {
else {
//vehicle is not deployed; just do cleanup //vehicle is not deployed; just do cleanup
val vguid = vehicle.GUID val vguid = vehicle.GUID
val zone = vehicle.Zone val zone = vehicle.Zone
val zoneId = zone.Id val zoneId = zone.Id
val events = zone.VehicleEvents val events = zone.VehicleEvents
if(transferEvent == TransferBehavior.Event.Charging) { if (transferEvent == TransferBehavior.Event.Charging) {
events ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L)) // panel glow off events ! VehicleServiceMessage(
events ! VehicleServiceMessage(zoneId, VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 49, 0L)) // orb particle effect off zoneId,
} VehicleAction.PlanetsideAttribute(Service.defaultPlayerGUID, vguid, 52, 0L)
else if(transferEvent == TransferBehavior.Event.Discharging) { ) // panel glow off
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 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 = { def HandleNtuRequest(sender: ActorRef, min: Int, max: Int): Unit = {
if(transferEvent == TransferBehavior.Event.Discharging) { if (transferEvent == TransferBehavior.Event.Discharging) {
val chargeable = ChargeTransferObject val chargeable = ChargeTransferObject
val chargeToDeposit = if(min == 0) { val chargeToDeposit = if (min == 0) {
transferTarget match { 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) // 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) scala.math.min(scala.math.min(silo.MaxNtuCapacitor / 105, chargeable.NtuCapacitor), max)
case _ => case _ =>
0 0
} }
} } else {
else {
scala.math.min(min, chargeable.NtuCapacitor) 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 chargeable.NtuCapacitor -= chargeToDeposit
UpdateNtuUI(chargeable) UpdateNtuUI(chargeable)
sender ! Ntu.Grant(chargeable, chargeToDeposit) sender ! Ntu.Grant(chargeable, chargeToDeposit)
} }
} }
def HandleNtuGrant(sender : ActorRef, src : NtuContainer, amount : Int) : Unit = { def HandleNtuGrant(sender: ActorRef, src: NtuContainer, amount: Int): Unit = {
if(transferEvent == TransferBehavior.Event.Charging) { if (transferEvent == TransferBehavior.Event.Charging) {
val obj = ChargeTransferObject val obj = ChargeTransferObject
if(ReceiveAndDepositUntilFull(obj, amount)) { if (ReceiveAndDepositUntilFull(obj, amount)) {
panelAnimationFunc() panelAnimationFunc()
} } else {
else {
TryStopChargingEvent(obj) TryStopChargingEvent(obj)
sender ! Ntu.Request(0, 0) 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.inventory.Container
import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition} import net.psforever.objects.serverobject.painbox.{Painbox, PainboxDefinition}
import net.psforever.objects.serverobject.resourcesilo.ResourceSilo 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.turret.FacilityTurret
import net.psforever.objects.serverobject.zipline.ZipLinePath 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.avatar.AvatarService
import services.local.LocalService import services.local.LocalService
import services.vehicle.VehicleService import services.vehicle.VehicleService
@ -33,6 +34,10 @@ import scalax.collection.GraphPredef._
import scalax.collection.GraphEdge._ import scalax.collection.GraphEdge._
import scala.util.Try 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> * 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`. * Static server objects originate from the `ZoneMap`.
* Dynamic game objects originate from player characters. * Dynamic game objects originate from player characters.
* (Write more later.) * (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; * @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` * also used by `LivePlayerList` to indicate a specific `Zone`
* @see `ZoneMap`<br> * @see `ZoneMap`<br>
* `LoadMapMessage`<br> * `LoadMapMessage`<br>
* `LivePlayerList` * `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. */ /** 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 private var soi = Default.Actor
/** Used by the globally unique identifier system to coordinate requests. */ /** 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] = private val constructions: ListBuffer[PlanetSideGameObject with Deployable] = ListBuffer()
ListBuffer[PlanetSideGameObject with Deployable]()
/** /**
*/ */
@ -106,8 +111,6 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
private var lattice: Graph[Building, UnDiEdge] = Graph() private var lattice: Graph[Building, UnDiEdge] = Graph()
private var zipLinePaths: List[ZipLinePath] = List()
/** key - spawn zone id, value - buildings belonging to spawn zone */ /** key - spawn zone id, value - buildings belonging to spawn zone */
private var spawnGroups: Map[Building, List[SpawnPoint]] = PairMap[Building, List[SpawnPoint]]() 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. * Execution of this operation should be fail-safe.
* The chances of failure should be mitigated or skipped. * The chances of failure should be mitigated or skipped.
* A testing routine should be run after the fact on the results of the process. * A testing routine should be run after the fact on the results of the process.
*
* @see `ZoneActor.ZoneSetupCheck` * @see `ZoneActor.ZoneSetupCheck`
* @param context a reference to an `ActorContext` necessary for `Props` * @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) { if (accessor == Default.Actor) {
SetupNumberPools() SetupNumberPools()
accessor = context.actorOf( accessor = context.actorOf(
RandomPool(25).props( RandomPool(25).props(
Props(classOf[UniqueNumberSystem], this.guid, UniqueNumberSystem.AllocateNumberPoolActors(this.guid)) 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") ground = context.actorOf(Props(classOf[ZoneGroundActor], this, equipmentOnGround), s"zone-$Id-ground")
deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"$Id-deployables") deployables = context.actorOf(Props(classOf[ZoneDeployableActor], this, constructions), s"zone-$Id-deployables")
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"$Id-vehicles") transport = context.actorOf(Props(classOf[ZoneVehicleActor], this, vehicles), s"zone-$Id-vehicles")
population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"$Id-players") population = context.actorOf(Props(classOf[ZonePopulationActor], this, players, corpses), s"zone-$Id-players")
projector = context.actorOf( projector = context.actorOf(
Props(classOf[ZoneHotSpotDisplay], this, hotspots, 15 seconds, hotspotHistory, 60 seconds), 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") avatarEvents = context.actorOf(Props(classOf[AvatarService], this), s"zone-$Id-avatar-events")
localEvents = context.actorOf(Props(classOf[LocalService], this), s"$Id-local-events") localEvents = context.actorOf(Props(classOf[LocalService], this), s"zone-$Id-local-events")
vehicleEvents = context.actorOf(Props(classOf[VehicleService], this), s"$Id-vehicle-events") vehicleEvents = context.actorOf(Props(classOf[VehicleService], this), s"zone-$Id-vehicle-events")
implicit val guid: NumberPoolHub = this.guid //passed into builderObject.Build implicitly implicit val guid: NumberPoolHub = this.guid //passed into builderObject.Build implicitly
BuildLocalObjects(context, guid) BuildLocalObjects(context, guid)
@ -189,7 +193,118 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
AssignAmenities() AssignAmenities()
CreateSpawnGroups() 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 //guid.AddPool("l", (60001 to 65535).toList).Selector = new RandomSelector
} }
/** def findSpawns(
* A reference to the primary `Actor` that governs this `Zone`. faction: PlanetSideEmpire.Value,
* @return an `ActorRef` spawnGroups: Seq[SpawnGroup]
* @see `ZoneActor`<br> ): List[(AmenityOwner, Iterable[SpawnPoint])] = {
* `Zone.Init` val ams = spawnGroups.contains(SpawnGroup.AMS)
*/ val structures = spawnGroups.collect {
def Actor: ActorRef = actor case SpawnGroup.Facility =>
StructureType.Facility
/** case SpawnGroup.Tower =>
* Give this `Zone` an `Actor` that will govern its interactions sequentially. StructureType.Tower
* @param zoneActor an `ActorRef` for this `Zone`; case SpawnGroup.WarpGate =>
* will not overwrite any existing governance unless `noSender` StructureType.WarpGate
* @return an `ActorRef` case SpawnGroup.Sanctuary =>
* @see `ZoneActor` StructureType.Building
*/
def Actor_=(zoneActor: ActorRef): ActorRef = {
if (actor == Default.Actor) {
actor = zoneActor
} }
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`. * The privileged name that can be used as the second parameter in the packet `LoadMapMessage`.
*
* @return the name * @return the name
*/ */
def Id: String = zoneId 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. * The numerical index of the `Zone` as it is recognized in a variety of packets.
*
* @return the abstract index position of this `Zone` * @return the abstract index position of this `Zone`
*/ */
def Number: Int = zoneNumber 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 * @return synchronized reference to the globally unique identifier system
*/ */
def GUID(hub: NumberPoolHub): Boolean = { 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.Color.RED
import org.fusesource.jansi.Ansi.ansi import org.fusesource.jansi.Ansi.ansi
println( println(
@ -407,12 +569,12 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
lattice lattice
} }
def ZipLinePaths: List[ZipLinePath] = { def zipLinePaths: List[ZipLinePath] = {
zipLinePaths map.ZipLinePaths
} }
private def BuildLocalObjects(implicit context: ActorContext, guid: NumberPoolHub): Unit = { private def BuildLocalObjects(implicit context: ActorContext, guid: NumberPoolHub): Unit = {
Map.LocalObjects.foreach({ builderObject => map.LocalObjects.foreach({ builderObject =>
builderObject.Build builderObject.Build
val obj = guid(builderObject.Id) 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 //guard against errors here, but don't worry about specifics; let ZoneActor.ZoneSetupCheck complain about problems
val other: ListBuffer[IdentifiableEntity] = new ListBuffer[IdentifiableEntity]() val other: ListBuffer[IdentifiableEntity] = new ListBuffer[IdentifiableEntity]()
//turret to weapon //turret to weapon
Map.TurretToWeapon.foreach({ map.TurretToWeapon.foreach({
case (turret_guid, weapon_guid) => case (turret_guid, weapon_guid) =>
((GUID(turret_guid) match { ((GUID(turret_guid) match {
case Some(obj: FacilityTurret) => 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] = { 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 { val registrationKeys: Map[Int, Try[LoanedKey]] = buildingList.map {
case ((_, building_guid: Int, _), _) => case ((_, building_guid: Int, _), _) =>
(building_guid, guid.register(building_guid)) (building_guid, guid.register(building_guid))
@ -471,7 +633,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
} }
private def AssignAmenities(): Unit = { private def AssignAmenities(): Unit = {
Map.ObjectToBuilding.foreach({ map.ObjectToBuilding.foreach({
case (object_guid, building_id) => case (object_guid, building_id) =>
(buildings.get(building_id), guid(object_guid)) match { (buildings.get(building_id), guid(object_guid)) match {
case (Some(building), Some(amenity)) => case (Some(building), Some(amenity)) =>
@ -498,7 +660,7 @@ class Zone(private val zoneId: String, zoneMap: ZoneMap, zoneNumber: Int) {
} }
private def MakeLattice(): Unit = { private def MakeLattice(): Unit = {
lattice ++= Map.LatticeLink.map { lattice ++= map.LatticeLink.map {
case (source, target) => case (source, target) =>
val (sourceBuilding, targetBuilding) = (Building(source), Building(target)) match { val (sourceBuilding, targetBuilding) = (Building(source), Building(target)) match {
case (Some(sBuilding), Some(tBuilding)) => (sBuilding, tBuilding) case (Some(sBuilding), Some(tBuilding)) => (sBuilding, tBuilding)
@ -650,12 +812,6 @@ object Zone {
new Zone(id, map, number) new Zone(id, map, number)
} }
/**
* Message to initialize the `Zone`.
* @see `Zone.Init(implicit ActorContext)`
*/
final case class Init()
object Population { object Population {
/** /**
@ -730,79 +886,6 @@ object Zone {
final case class Remove(player: Player) 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 { object Ground {
final case class DropItem(item: Equipment, pos: Vector3, orient: Vector3) final case class DropItem(item: Equipment, pos: Vector3, orient: Vector3)
final case class ItemOnGround(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)) createEnumerationCodec(enum, storageCodec.xmap[Int](_.toInt, _.toLong))
} }
/** Create a Codec for enumeratum's Enum type */ /** Create a Codec for enumeratum's IntEnum type */
def createEnumCodec[E <: IntEnumEntry](enum: IntEnum[E], storageCodec: Codec[Int]): Codec[E] = { def createIntEnumCodec[E <: IntEnumEntry](enum: IntEnum[E], storageCodec: Codec[Int]): Codec[E] = {
type Struct = Int :: HNil type Struct = Int :: HNil
val struct: Codec[Struct] = storageCodec.hlist val struct: Codec[Struct] = storageCodec.hlist
@ -149,6 +149,10 @@ object PacketHelpers {
struct.narrow[E](from, to) 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 /** Common codec for how PlanetSide stores string sizes
* *
* When the first bit of the byte is set, the size can be between [0, 127]. * 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, bind_desc: String,
display_icon: Boolean, display_icon: Boolean,
logging: Boolean, logging: Boolean,
spawn_group: SpawnGroup.Value, spawn_group: SpawnGroup,
zone_number: Long, zone_number: Long,
unk4: Long, unk4: Long,
pos: Vector3 pos: Vector3
@ -84,7 +84,7 @@ object BindPlayerMessage extends Marshallable[BindPlayerMessage] {
*/ */
val Standard = BindPlayerMessage(BindStatus.Unbind, "", false, false, SpawnGroup.BoundAMS, 0, 0, Vector3.Zero) 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] = ( implicit val codec: Codec[BindPlayerMessage] = (
("action" | BindStatus.codec) :: ("action" | BindStatus.codec) ::

View file

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

View file

@ -26,7 +26,7 @@ object ServerType extends IntEnum[ServerType] {
case object ReleasedGemini extends ServerType(4, "released_gemini") case object ReleasedGemini extends ServerType(4, "released_gemini")
val values = findValues 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 // 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 // Copyright (c) 2017 PSForever
package net.psforever.types package net.psforever.types
import enumeratum.values.{IntEnum, IntEnumEntry}
/** /**
* The spawn group.<br> * The spawn group.<br>
* <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 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 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. * The AMS spawn group icons have an overhead AMS glyph and are smaller in radius, identical otherwise.
*
* @see `BindPlayerMessage` * @see `BindPlayerMessage`
*/ */
object SpawnGroup extends Enumeration { sealed abstract class SpawnGroup(val value: Int) extends IntEnumEntry
type Type = Value
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, world: WorldConfig,
admin: AdminConfig, admin: AdminConfig,
database: DatabaseConfig, database: DatabaseConfig,
game: GameConfig,
antiCheat: AntiCheatConfig, antiCheat: AntiCheatConfig,
network: NetworkConfig, network: NetworkConfig,
developer: DeveloperConfig, developer: DeveloperConfig,
@ -103,6 +104,10 @@ case class SessionConfig(
outboundGraceTime: Duration outboundGraceTime: Duration
) )
case class GameConfig(
instantActionAms: Boolean
)
case class DeveloperConfig( case class DeveloperConfig(
netSim: NetSimConfig netSim: NetSimConfig
) )

View file

@ -1,20 +1,24 @@
package net.psforever.util package net.psforever.util
import io.getquill.{PostgresJAsyncContext, SnakeCase} import io.getquill.{PostgresJAsyncContext, SnakeCase}
import net.psforever.persistence import net.psforever.persistence.{Account, Building, Loadout, Locker, Login, Character}
object Database { object Database {
implicit val accountSchemaMeta = ctx.schemaMeta[persistence.Account]("accounts", _.id -> "id") val ctx = new PostgresJAsyncContext(SnakeCase, Config.config.getConfig("database"))
implicit val characterSchemaMeta = ctx.schemaMeta[persistence.Character]("characters", _.id -> "id")
implicit val loadoutSchemaMeta = ctx.schemaMeta[persistence.Loadout]("loadouts", _.id -> "id") implicit val accountSchemaMeta: ctx.SchemaMeta[Account] = ctx.schemaMeta[Account]("accounts")
implicit val lockerSchemaMeta = ctx.schemaMeta[persistence.Locker]("lockers", _.id -> "id") implicit val characterSchemaMeta: ctx.SchemaMeta[Character] = ctx.schemaMeta[Character]("characters")
implicit val loginSchemaMeta = ctx.schemaMeta[persistence.Login]("logins", _.id -> "id") 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 // TODO remove if this gets merged https://github.com/getquill/quill/pull/1765
implicit class ILike(s1: String) { implicit class ILike(s1: String) {
import ctx._ import ctx._
def ilike(s2: String) = quote(infix"$s1 ilike $s2".as[Boolean]) 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( val zones: HashMap[String, Zone] = HashMap(
( (
"z1", "z1",
new Zone("z1", Await.result(Maps.map01, 30 seconds), 1) { new Zone("z1", Await.result(Maps.map01, 60 seconds), 1) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -26,10 +26,10 @@ object Zones {
), ),
( (
"z2", "z2",
new Zone("z2", Await.result(Maps.map02, 30 seconds), 2) { new Zone("z2", Await.result(Maps.map02, 60 seconds), 2) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -38,10 +38,10 @@ object Zones {
), ),
( (
"z3", "z3",
new Zone("z3", Await.result(Maps.map03, 30 seconds), 3) { new Zone("z3", Await.result(Maps.map03, 60 seconds), 3) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -50,84 +50,22 @@ object Zones {
), ),
( (
"z4", "z4",
new Zone("z4", Await.result(Maps.map04, 30 seconds), 4) { new Zone("z4", Await.result(Maps.map04, 60 seconds), 4) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) 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", "z5",
new Zone("z5", Await.result(Maps.map05, 30 seconds), 5) { new Zone("z5", Await.result(Maps.map05, 60 seconds), 5) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -136,27 +74,22 @@ object Zones {
), ),
( (
"z6", "z6",
new Zone("z6", Await.result(Maps.map06, 30 seconds), 6) { new Zone("z6", Await.result(Maps.map06, 60 seconds), 6) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules 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) InitZoneAmenities(zone = this)
} }
} }
), ),
( (
"z7", "z7",
new Zone("z7", Await.result(Maps.map07, 30 seconds), 7) { new Zone("z7", Await.result(Maps.map07, 60 seconds), 7) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -165,10 +98,10 @@ object Zones {
), ),
( (
"z8", "z8",
new Zone("z8", Await.result(Maps.map08, 30 seconds), 8) { new Zone("z8", Await.result(Maps.map08, 60 seconds), 8) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -177,10 +110,10 @@ object Zones {
), ),
( (
"z9", "z9",
new Zone("z9", Await.result(Maps.map09, 30 seconds), 9) { new Zone("z9", Await.result(Maps.map09, 60 seconds), 9) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -189,10 +122,10 @@ object Zones {
), ),
( (
"z10", "z10",
new Zone("z10", Await.result(Maps.map10, 30 seconds), 10) { new Zone("z10", Await.result(Maps.map10, 60 seconds), 10) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -201,12 +134,14 @@ object Zones {
), ),
( (
"home1", "home1",
new Zone("home1", Await.result(Maps.map11, 30 seconds), 11) { new Zone("home1", Await.result(Maps.map11, 60 seconds), 11) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
import net.psforever.types.PlanetSideEmpire import net.psforever.types.PlanetSideEmpire
Buildings.values.foreach { _.Faction = PlanetSideEmpire.NC } Buildings.values.foreach {
_.Faction = PlanetSideEmpire.NC
}
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
} }
@ -214,12 +149,14 @@ object Zones {
), ),
( (
"home2", "home2",
new Zone("home2", Await.result(Maps.map12, 30 seconds), 12) { new Zone("home2", Await.result(Maps.map12, 60 seconds), 12) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
import net.psforever.types.PlanetSideEmpire import net.psforever.types.PlanetSideEmpire
Buildings.values.foreach { _.Faction = PlanetSideEmpire.TR } Buildings.values.foreach {
_.Faction = PlanetSideEmpire.TR
}
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
} }
@ -227,12 +164,14 @@ object Zones {
), ),
( (
"home3", "home3",
new Zone("home3", Await.result(Maps.map13, 30 seconds), 13) { new Zone("home3", Await.result(Maps.map13, 60 seconds), 13) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
import net.psforever.types.PlanetSideEmpire import net.psforever.types.PlanetSideEmpire
Buildings.values.foreach { _.Faction = PlanetSideEmpire.VS } Buildings.values.foreach {
_.Faction = PlanetSideEmpire.VS
}
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
} }
@ -276,10 +215,10 @@ object Zones {
), ),
( (
"c1", "c1",
new Zone("c1", Await.result(Maps.ugd01, 30 seconds), 23) { new Zone("c1", Await.result(Maps.ugd01, 60 seconds), 23) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -288,10 +227,10 @@ object Zones {
), ),
( (
"c2", "c2",
new Zone("c2", Await.result(Maps.ugd02, 30 seconds), 24) { new Zone("c2", Await.result(Maps.ugd02, 60 seconds), 24) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -300,10 +239,10 @@ object Zones {
), ),
( (
"c3", "c3",
new Zone("c3", Await.result(Maps.ugd03, 30 seconds), 25) { new Zone("c3", Await.result(Maps.ugd03, 60 seconds), 25) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -312,10 +251,10 @@ object Zones {
), ),
( (
"c4", "c4",
new Zone("c4", Await.result(Maps.ugd04, 30 seconds), 26) { new Zone("c4", Await.result(Maps.ugd04, 60 seconds), 26) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -324,10 +263,10 @@ object Zones {
), ),
( (
"c5", "c5",
new Zone("c5", Await.result(Maps.ugd05, 30 seconds), 27) { new Zone("c5", Await.result(Maps.ugd05, 60 seconds), 27) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -336,10 +275,10 @@ object Zones {
), ),
( (
"c6", "c6",
new Zone("c6", Await.result(Maps.ugd06, 30 seconds), 28) { new Zone("c6", Await.result(Maps.ugd06, 60 seconds), 28) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -348,10 +287,10 @@ object Zones {
), ),
( (
"i1", "i1",
new Zone("i1", Await.result(Maps.map99, 30 seconds), 29) { new Zone("i1", Await.result(Maps.map99, 60 seconds), 29) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -360,10 +299,10 @@ object Zones {
), ),
( (
"i2", "i2",
new Zone("i2", Await.result(Maps.map98, 30 seconds), 30) { new Zone("i2", Await.result(Maps.map98, 60 seconds), 30) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -372,10 +311,10 @@ object Zones {
), ),
( (
"i3", "i3",
new Zone("i3", Await.result(Maps.map97, 30 seconds), 31) { new Zone("i3", Await.result(Maps.map97, 60 seconds), 31) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -384,10 +323,10 @@ object Zones {
), ),
( (
"i4", "i4",
new Zone("i4", Await.result(Maps.map96, 30 seconds), 32) { new Zone("i4", Await.result(Maps.map96, 60 seconds), 32) {
override def Init(implicit context: ActorContext): Unit = { override def init(implicit context: ActorContext): Unit = {
super.Init(context) super.init(context)
HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(Map.Scale, 80, 80) HotSpotCoordinateFunction = Zones.HotSpots.StandardRemapping(map.Scale, 80, 80)
HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules HotSpotTimeFunction = Zones.HotSpots.StandardTimeRules
InitZoneAmenities(zone = this) InitZoneAmenities(zone = this)
@ -560,9 +499,9 @@ object Zones {
case t: ObjectSource if t.Definition == GlobalDefinitions.manned_turret => case t: ObjectSource if t.Definition == GlobalDefinitions.manned_turret =>
60 seconds 60 seconds
case _: DeployableSource => case _: DeployableSource =>
30 seconds 60 seconds
case _: ComplexDeployableSource => case _: ComplexDeployableSource =>
30 seconds 60 seconds
case _ => case _ =>
0 seconds 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 package services
import akka.actor.{Actor, ActorIdentity, ActorRef, ActorSystem, Identify, Props} 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 import scala.collection.mutable
object ServiceManager { object ServiceManager {
var serviceManager = ActorRef.noSender var serviceManager = ActorRef.noSender
var receptionist: typed.ActorRef[Receptionist.Command] = null
def boot(implicit system: ActorSystem) = { def boot(implicit system: ActorSystem) = {
serviceManager = system.actorOf(Props[ServiceManager], "service") serviceManager = system.actorOf(Props[ServiceManager], "service")
receptionist = system.toTyped.receptionist
serviceManager serviceManager
} }
case class Register(props: Props, name: String) case class Register(props: Props, name: String)
case class Lookup(name: String) case class Lookup(name: String)
case class LookupFromTyped(name: String, replyTo: typed.ActorRef[LookupResult])
case class LookupResult(request: String, endpoint: ActorRef) case class LookupResult(request: String, endpoint: ActorRef)
} }
class ServiceManager extends Actor { class ServiceManager extends Actor {
@ -38,6 +49,11 @@ class ServiceManager extends Actor {
lookups += nextLookupId -> RequestEntry(name, sender()) lookups += nextLookupId -> RequestEntry(name, sender())
nextLookupId += 1 nextLookupId += 1
case LookupFromTyped(name, replyTo) =>
context.actorSelection(name) ! Identify(nextLookupId)
lookups += nextLookupId -> RequestEntry(name, replyTo.toClassic)
nextLookupId += 1
case ActorIdentity(id, Some(ref)) => case ActorIdentity(id, Some(ref)) =>
val idNumber = id.asInstanceOf[Long] val idNumber = id.asInstanceOf[Long]
lookups.get(idNumber) match { lookups.get(idNumber) match {

View file

@ -2,6 +2,7 @@
package services.local package services.local
import akka.actor.{Actor, ActorRef, Props} import akka.actor.{Actor, ActorRef, Props}
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.ce.Deployable import net.psforever.objects.ce.Deployable
import net.psforever.objects.serverobject.structures.{Amenity, Building} import net.psforever.objects.serverobject.structures.{Amenity, Building}
import net.psforever.objects.serverobject.terminals.{CaptureTerminal, Terminal} 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 // 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 { zone.Lattice find building match {
case Some(_) => building.TriggerZoneMapUpdate() case Some(_) => building.Zone.actor ! ZoneActor.ZoneMapUpdate()
case None => ; case None => ;
} }
case LocalAction.RouterTelepadTransport(player_guid, passenger_guid, src_guid, dest_guid) => 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) { if (building.NtuLevel > 0) {
log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction") log.info(s"Setting base ${building.GUID} / MapId: ${building.MapId} as owned by $hackedByFaction")
building.Actor ! BuildingActor.SetFaction(hackedByFaction)
building.Faction = hackedByFaction
self ! LocalServiceMessage(zone.Id, LocalAction.SetEmpire(building.GUID, hackedByFaction))
} else { } else {
log.info("Base hack completed, but base was out of NTU.") log.info("Base hack completed, but base was out of NTU.")
} }

View file

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

View file

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

View file

@ -1,19 +1,15 @@
// Copyright (c) 2017 PSForever // Copyright (c) 2017 PSForever
package objects package objects
import akka.actor.Props
import base.ActorTest import base.ActorTest
import net.psforever.actors.zone.BuildingActor
import net.psforever.objects.{Default, GlobalDefinitions} import net.psforever.objects.{Default, GlobalDefinitions}
import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.objects.serverobject.doors.Door
import net.psforever.objects.serverobject.doors.{Door, DoorControl}
import net.psforever.objects.serverobject.structures._ import net.psforever.objects.serverobject.structures._
import net.psforever.objects.zones.Zone import net.psforever.objects.zones.Zone
import net.psforever.types.{PlanetSideEmpire, PlanetSideGUID} import net.psforever.types.PlanetSideEmpire
import org.specs2.mutable.Specification import org.specs2.mutable.Specification
import services.ServiceManager import akka.actor.typed.scaladsl.adapter._
import services.galaxy.GalaxyService
import scala.concurrent.duration._
class AmenityTest extends Specification { class AmenityTest extends Specification {
val definition = new AmenityDefinition(0) { val definition = new AmenityDefinition(0) {
@ -115,82 +111,12 @@ class WarpGateTest extends Specification {
} }
} }
class BuildingControl1Test extends ActorTest { class BuildingActor1Test extends ActorTest {
"Building Control" should { "Building Control" should {
"construct" in { "construct" in {
val bldg = Building("Building", 0, 10, Zone.Nowhere, StructureType.Building) 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) 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.routing.RandomPool
import akka.testkit.TestProbe import akka.testkit.TestProbe
import base.ActorTest import base.ActorTest
import net.psforever.actors.zone.{BuildingActor, ZoneActor}
import net.psforever.objects.guid.{NumberPoolHub, TaskResolver} import net.psforever.objects.guid.{NumberPoolHub, TaskResolver}
import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.serverobject.CommonMessages 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.resourcesilo.{ResourceSilo, ResourceSiloControl, ResourceSiloDefinition}
import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects.serverobject.structures.{Building, StructureType}
import net.psforever.objects.serverobject.transfer.TransferBehavior 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.packet.game.UseItemMessage
import net.psforever.types._ import net.psforever.types._
import org.specs2.mutable.Specification import org.specs2.mutable.Specification
import services.ServiceManager import services.ServiceManager
import services.avatar.{AvatarAction, AvatarServiceMessage} import services.avatar.{AvatarAction, AvatarServiceMessage}
import akka.actor.typed.scaladsl.adapter._
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -97,8 +99,7 @@ class ResourceSiloControlUseTest extends ActorTest {
override def SetupNumberPools() = {} override def SetupNumberPools() = {}
GUID(guid) GUID(guid)
} }
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-zone-actor") zone.actor = system.spawnAnonymous(ZoneActor(zone))
zone.Actor ! Zone.Init()
val building = new Building( val building = new Building(
"Building", "Building",
building_guid = 0, building_guid = 0,
@ -117,7 +118,7 @@ class ResourceSiloControlUseTest extends ActorTest {
new Avatar(0L, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute) new Avatar(0L, "TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Mute)
) //guid=3 ) //guid=3
val vehicle = Vehicle(GlobalDefinitions.ant) //guid=4 val vehicle = Vehicle(GlobalDefinitions.ant) //guid=4
val probe = new TestProbe(system) val probe = new TestProbe(system)
guid.register(building, 1) guid.register(building, 1)
guid.register(obj, 2) guid.register(obj, 2)
@ -139,7 +140,7 @@ class ResourceSiloControlUseTest extends ActorTest {
val reply = probe.receiveOne(2000 milliseconds) val reply = probe.receiveOne(2000 milliseconds)
assert(reply match { assert(reply match {
case TransferBehavior.Discharging(Ntu.Nanites) => true case TransferBehavior.Discharging(Ntu.Nanites) => true
case _ => false case _ => false
}) })
} }
} }
@ -221,21 +222,21 @@ class ResourceSiloControlUpdate1Test extends ActorTest {
assert(obj.CapacitorDisplay == 4) assert(obj.CapacitorDisplay == 4)
assert(reply1 match { assert(reply1 match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(1), 45, 4)) => true 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) val reply3 = zoneEvents.receiveOne(500 milliseconds)
assert(!obj.LowNtuWarningOn) assert(!obj.LowNtuWarningOn)
assert(reply3 match { assert(reply3 match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => true case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 47, 0)) => true
case _ => false case _ => false
}) })
val reply4 = zoneEvents.receiveOne(500 milliseconds) val reply4 = zoneEvents.receiveOne(500 milliseconds)
assert(reply4 match { assert(reply4 match {
case AvatarServiceMessage("nowhere", AvatarAction.PlanetsideAttribute(PlanetSideGUID(6), 48, 0)) => true 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 .attribute_value == 3
) )
assert(reply2.isInstanceOf[Building.SendMapUpdate]) assert(reply2.isInstanceOf[BuildingActor.MapUpdate])
val reply3 = zoneEvents.receiveOne(500 milliseconds) val reply3 = zoneEvents.receiveOne(500 milliseconds)
assert(!obj.LowNtuWarningOn) assert(!obj.LowNtuWarningOn)
@ -355,7 +356,9 @@ class ResourceSiloControlNoUpdateTest extends ActorTest {
expectNoMessage(500 milliseconds) expectNoMessage(500 milliseconds)
zoneEvents.expectNoMessage(500 milliseconds) zoneEvents.expectNoMessage(500 milliseconds)
buildingEvents.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.CapacitorDisplay == 3)
assert(!obj.LowNtuWarningOn) 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.serverobject.mount.Mountable
import net.psforever.objects.vehicles._ import net.psforever.objects.vehicles._
import net.psforever.objects.vital.VehicleShieldCharge 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.packet.game.{CargoMountPointStatusMessage, ObjectDetachMessage, PlanetsideAttributeMessage}
import net.psforever.types.{PlanetSideGUID, _} import net.psforever.types.{PlanetSideGUID, _}
import org.specs2.mutable._ import org.specs2.mutable._
@ -19,8 +19,11 @@ import services.{RemoverActor, ServiceManager}
import services.vehicle.{VehicleAction, VehicleServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage}
import scala.concurrent.duration._ import scala.concurrent.duration._
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.ZoneActor
class VehicleTest extends Specification { class VehicleTest extends Specification {
import VehicleTest._ import VehicleTest._
"SeatDefinition" should { "SeatDefinition" should {
@ -408,10 +411,12 @@ class VehicleControlPrepareForDeletionMountedInTest extends FreedContextActorTes
val guid = new NumberPoolHub(new LimitedNumberSource(10)) val guid = new NumberPoolHub(new LimitedNumberSource(10))
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone = new Zone("test", new ZoneMap("test"), 0) {
GUID(guid) GUID(guid)
override def SetupNumberPools(): Unit = {} override def SetupNumberPools(): Unit = {}
} }
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-zone-actor") zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
zone.Init(context) // crappy workaround but without it the zone doesn't get initialized in time
expectNoMessage(400 milliseconds)
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.Faction = PlanetSideEmpire.TR vehicle.Faction = PlanetSideEmpire.TR
@ -533,10 +538,12 @@ class VehicleControlPrepareForDeletionMountedCargoTest extends FreedContextActor
ServiceManager.boot ServiceManager.boot
val zone = new Zone("test", new ZoneMap("test"), 0) { val zone = new Zone("test", new ZoneMap("test"), 0) {
GUID(guid) GUID(guid)
override def SetupNumberPools(): Unit = {} override def SetupNumberPools(): Unit = {}
} }
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-zone-actor") zone.actor = system.spawn(ZoneActor(zone), "test-zone-actor")
zone.Init(context) // crappy workaround but without it the zone doesn't get initialized in time
expectNoMessage(200 milliseconds)
val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) val vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy)
vehicle.Faction = PlanetSideEmpire.TR vehicle.Faction = PlanetSideEmpire.TR

View file

@ -3,7 +3,7 @@ package objects
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import akka.actor.{ActorContext, ActorRef, Props} import akka.actor.ActorContext
import base.ActorTest import base.ActorTest
import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment.Equipment import net.psforever.objects.equipment.Equipment
@ -14,14 +14,18 @@ import net.psforever.objects.serverobject.tube.SpawnTube
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.types._ import net.psforever.types._
import net.psforever.objects.serverobject.structures.{Building, FoundationBuilder, StructureType} 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 net.psforever.objects.Vehicle
import org.specs2.mutable.Specification import org.specs2.mutable.Specification
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.ZoneActor
import scala.concurrent.duration._ import scala.concurrent.duration._
class ZoneTest extends Specification { 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 { "ZoneMap" should {
"construct" in { "construct" in {
@ -116,29 +120,26 @@ class ZoneTest extends Specification {
class ZoneActorTest extends ActorTest { class ZoneActorTest extends ActorTest {
"Zone" should { "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 { "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))) zone.GUID(new NumberPoolHub(new LimitedNumberSource(10)))
assert(zone.AddPool("test1", 1 to 2)) 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)) assert(zone.AddPool("test2", 3 to 4))
} }
"remove existing number pools before the Actor is started" in { "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))) zone.GUID(new NumberPoolHub(new LimitedNumberSource(10)))
assert(zone.AddPool("test1", 1 to 2)) assert(zone.AddPool("test1", 1 to 2))
assert(zone.RemovePool("test1")) 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.AddPool("test2", 3 to 4))
assert(zone.RemovePool("test2")) assert(zone.RemovePool("test2"))
} }
@ -146,8 +147,7 @@ class ZoneActorTest extends ActorTest {
"refuse new number pools after the Actor is started" in { "refuse new number pools after 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(40150))) zone.GUID(new NumberPoolHub(new LimitedNumberSource(40150)))
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-add-pool-actor-init") zone.actor = system.spawn(ZoneActor(zone), "test-add-pool-actor-init")
zone.Actor ! Zone.Init()
expectNoMessage(Duration.create(500, "ms")) expectNoMessage(Duration.create(500, "ms"))
assert(!zone.AddPool("test1", 1 to 2)) assert(!zone.AddPool("test1", 1 to 2))
@ -158,8 +158,7 @@ class ZoneActorTest extends ActorTest {
zone.GUID(new NumberPoolHub(new LimitedNumberSource(10))) zone.GUID(new NumberPoolHub(new LimitedNumberSource(10)))
zone.AddPool("test", 1 to 2) zone.AddPool("test", 1 to 2)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-remove-pool-actor-init") zone.actor = system.spawn(ZoneActor(zone), "test-remove-pool-actor-init")
zone.Actor ! Zone.Init()
expectNoMessage(Duration.create(300, "ms")) expectNoMessage(Duration.create(300, "ms"))
assert(!zone.RemovePool("test")) assert(!zone.RemovePool("test"))
@ -203,8 +202,7 @@ class ZoneActorTest extends ActorTest {
ObjectToBuilding(10, 7) ObjectToBuilding(10, 7)
} }
val zone = new Zone("test", map6, 1) { override def SetupNumberPools() = {} } val zone = new Zone("test", map6, 1) { override def SetupNumberPools() = {} }
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), "test-init") zone.actor = system.spawn(ZoneActor(zone), "test-init")
zone.Actor ! Zone.Init()
expectNoMessage(Duration.create(1, "seconds")) expectNoMessage(Duration.create(1, "seconds"))
val groups = zone.SpawnGroups() 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 { "add new user to zones" in {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
assert(zone.Players.isEmpty) assert(zone.Players.isEmpty)
@ -328,8 +251,7 @@ class ZonePopulationTest extends ActorTest {
"remove user from zones" in { "remove user from zones" in {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
receiveOne(Duration.create(200, "ms")) //consume receiveOne(Duration.create(200, "ms")) //consume
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms")) 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 zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar) val player = Player(avatar)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms")) 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 zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar) val player = Player(avatar)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms")) 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 avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar) val player = Player(avatar)
player.GUID = PlanetSideGUID(1) player.GUID = PlanetSideGUID(1)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms")) 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 avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player1 = Player(avatar) val player1 = Player(avatar)
val player2 = Player(avatar) val player2 = Player(avatar)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms")) 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 zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
val player = Player(avatar) val player = Player(avatar)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
assert(zone.Players.isEmpty) 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 { "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 zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5) val avatar = Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Population.Join(avatar) zone.Population ! Zone.Population.Join(avatar)
expectNoMessage(Duration.create(100, "ms")) 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 zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.Release player.Release
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
@ -498,8 +413,7 @@ class ZonePopulationTest extends ActorTest {
val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player.Release player.Release
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Corpse.Add(player) zone.Population ! Zone.Corpse.Add(player)
expectNoMessage(Duration.create(500, "ms")) expectNoMessage(Duration.create(500, "ms"))
@ -519,8 +433,7 @@ class ZonePopulationTest extends ActorTest {
player2.Release player2.Release
val player3 = Player(Avatar("Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) val player3 = Player(Avatar("Chord3", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
player3.Release player3.Release
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
zone.Population ! Zone.Corpse.Add(player1) zone.Population ! Zone.Corpse.Add(player1)
zone.Population ! Zone.Corpse.Add(player2) 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 zone = new Zone("test", new ZoneMap(""), 0) { override def SetupNumberPools() = {} }
val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5)) val player = Player(Avatar("Chord", PlanetSideEmpire.TR, CharacterGender.Male, 0, CharacterVoice.Voice5))
//player.Release !!important //player.Release !!important
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
assert(zone.Corpses.isEmpty) assert(zone.Corpses.isEmpty)
@ -560,8 +472,7 @@ class ZoneGroundDropItemTest extends ActorTest {
hub.register(item, 10) hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub) zone.GUID(hub)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
"DropItem" should { "DropItem" should {
@ -586,8 +497,7 @@ class ZoneGroundCanNotDropItem1Test extends ActorTest {
//hub.register(item, 10) //!important //hub.register(item, 10) //!important
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub) zone.GUID(hub)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
"DropItem" should { "DropItem" should {
@ -612,8 +522,7 @@ class ZoneGroundCanNotDropItem2Test extends ActorTest {
hub.register(item, 10) //!important hub.register(item, 10) //!important
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
//zone.GUID(hub) //!important //zone.GUID(hub) //!important
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
"DropItem" should { "DropItem" should {
@ -638,8 +547,7 @@ class ZoneGroundCanNotDropItem3Test extends ActorTest {
hub.register(item, 10) //!important hub.register(item, 10) //!important
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub) //!important zone.GUID(hub) //!important
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
"DropItem" should { "DropItem" should {
@ -672,8 +580,7 @@ class ZoneGroundPickupItemTest extends ActorTest {
hub.register(item, 10) hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub) zone.GUID(hub)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
"PickupItem" should { "PickupItem" should {
@ -701,8 +608,7 @@ class ZoneGroundCanNotPickupItemTest extends ActorTest {
hub.register(item, 10) hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub) //still registered to this zone zone.GUID(hub) //still registered to this zone
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
"PickupItem" should { "PickupItem" should {
@ -726,8 +632,7 @@ class ZoneGroundRemoveItemTest extends ActorTest {
hub.register(item, 10) hub.register(item, 10)
val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} } val zone = new Zone("test", new ZoneMap("test-map"), 0) { override def SetupNumberPools() = {} }
zone.GUID(hub) //still registered to this zone zone.GUID(hub) //still registered to this zone
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), ZoneTest.TestName) zone.actor = system.spawn(ZoneActor(zone), ZoneTest.TestName)
zone.Actor ! Zone.Init()
expectNoMessage(200 milliseconds) expectNoMessage(200 milliseconds)
"RemoveItem" should { "RemoveItem" should {

View file

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

View file

@ -26,8 +26,8 @@
<pattern>%date{ISO8601} [%thread] %5level "%X" %logger{35} - %msg%n</pattern> <pattern>%date{ISO8601} [%thread] %5level "%X" %logger{35} - %msg%n</pattern>
</encoder> </encoder>
<filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
<level>OFF</level> <!--<level>OFF</level>-->
<!--<level>TRACE</level>--> <level>TRACE</level>
</filter> </filter>
</appender> </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 java.util.Locale
import akka.{actor => classic} import akka.{actor => classic}
import akka.actor.typed.ActorSystem import akka.actor.typed.scaladsl.adapter._
import akka.routing.RandomPool import akka.routing.RandomPool
import ch.qos.logback.classic.LoggerContext import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.joran.JoranConfigurator import ch.qos.logback.classic.joran.JoranConfigurator
@ -15,7 +15,7 @@ import net.psforever.objects.guid.TaskResolver
import org.slf4j import org.slf4j
import org.fusesource.jansi.Ansi._ import org.fusesource.jansi.Ansi._
import org.fusesource.jansi.Ansi.Color._ import org.fusesource.jansi.Ansi.Color._
import services.ServiceManager import services.{InterstellarClusterService, ServiceManager}
import services.account.{AccountIntermediaryService, AccountPersistenceService} import services.account.{AccountIntermediaryService, AccountPersistenceService}
import services.chat.ChatService import services.chat.ChatService
import services.galaxy.GalaxyService import services.galaxy.GalaxyService
@ -27,7 +27,6 @@ import org.flywaydb.core.Flyway
import java.nio.file.Paths import java.nio.file.Paths
import scopt.OParser import scopt.OParser
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.session.SessionActor import net.psforever.actors.session.SessionActor
import net.psforever.login.psadmin.PsAdminActor import net.psforever.login.psadmin.PsAdminActor
import net.psforever.login.{ import net.psforever.login.{
@ -94,8 +93,6 @@ object PsLogin {
implicit val system = classic.ActorSystem("PsLogin") implicit val system = classic.ActorSystem("PsLogin")
Default(system) Default(system)
val typedSystem: ActorSystem[Nothing] = system.toTyped
/** Create pipelines for the login and world servers /** Create pipelines for the login and world servers
* *
* The first node in the pipe is an Actor that handles the crypto for protecting packets. * The first node in the pipe is an Actor that handles the crypto for protecting packets.
@ -130,16 +127,16 @@ object PsLogin {
None 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 val serviceManager = ServiceManager.boot
serviceManager ! ServiceManager.Register(classic.Props[AccountIntermediaryService], "accountIntermediary") serviceManager ! ServiceManager.Register(classic.Props[AccountIntermediaryService], "accountIntermediary")
serviceManager ! ServiceManager.Register(RandomPool(150).props(classic.Props[TaskResolver]), "taskResolver") serviceManager ! ServiceManager.Register(RandomPool(150).props(classic.Props[TaskResolver]), "taskResolver")
serviceManager ! ServiceManager.Register(classic.Props[GalaxyService], "galaxy") serviceManager ! ServiceManager.Register(classic.Props[GalaxyService], "galaxy")
serviceManager ! ServiceManager.Register(classic.Props[SquadService], "squad") 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[AccountPersistenceService], "accountPersistence")
serviceManager ! ServiceManager.Register(classic.Props[PropertyOverrideManager], "propertyOverrideManager") 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 net.psforever.types.{PlanetSideGUID, _}
import services.RemoverActor import services.RemoverActor
import services.vehicle.{VehicleAction, VehicleServiceMessage} import services.vehicle.{VehicleAction, VehicleServiceMessage}
import akka.actor.typed.scaladsl.adapter._
import net.psforever.actors.zone.ZoneActor
import scala.concurrent.duration._ import scala.concurrent.duration._
@ -213,7 +215,6 @@ object VehicleSpawnPadControlTest {
import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.objects.serverobject.structures.Building import net.psforever.objects.serverobject.structures.Building
import net.psforever.objects.vehicles.VehicleControl import net.psforever.objects.vehicles.VehicleControl
import net.psforever.objects.zones.ZoneActor
import net.psforever.objects.Tool import net.psforever.objects.Tool
import net.psforever.types.CharacterGender import net.psforever.types.CharacterGender
@ -228,8 +229,7 @@ object VehicleSpawnPadControlTest {
override def SetupNumberPools(): Unit = {} override def SetupNumberPools(): Unit = {}
} }
zone.GUID(guid) zone.GUID(guid)
zone.Actor = system.actorOf(Props(classOf[ZoneActor], zone), s"test-zone-${System.nanoTime()}") zone.actor = system.spawn(ZoneActor(zone), s"test-zone-${System.nanoTime()}")
zone.Actor ! Zone.Init()
// Hack: Wait for the Zone to finish booting, otherwise later tests will fail randomly due to race conditions // Hack: Wait for the Zone to finish booting, otherwise later tests will fail randomly due to race conditions
// with actor probe setting // with actor probe setting

View file

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