From 61535bc232e6c6ff3dde85787a75d92a2c17acc1 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 23 Sep 2017 20:34:41 -0400 Subject: [PATCH] moved files from /continent/ to /zones/; separated Actor for managing items on the ground; added comments --- .../continent/IntergalacticCluster.scala | 64 ----- .../continent/ServerObjectBuilder.scala | 10 - .../continent/TerminalObjectBuilder.scala | 22 -- .../psforever/objects/continent/Zone.scala | 104 ------- .../psforever/objects/continent/ZoneMap.scala | 18 -- .../objects/zones/InterstellarCluster.scala | 113 ++++++++ .../objects/zones/ServerObjectBuilder.scala | 27 ++ .../objects/zones/TerminalObjectBuilder.scala | 33 +++ .../net/psforever/objects/zones/Zone.scala | 259 ++++++++++++++++++ .../psforever/objects/zones/ZoneActor.scala | 20 ++ .../ZoneGroundActor.scala} | 30 +- .../net/psforever/objects/zones/ZoneMap.scala | 43 +++ pslogin/src/main/scala/PsLogin.scala | 6 +- .../src/main/scala/WorldSessionActor.scala | 20 +- 14 files changed, 526 insertions(+), 243 deletions(-) delete mode 100644 common/src/main/scala/net/psforever/objects/continent/IntergalacticCluster.scala delete mode 100644 common/src/main/scala/net/psforever/objects/continent/ServerObjectBuilder.scala delete mode 100644 common/src/main/scala/net/psforever/objects/continent/TerminalObjectBuilder.scala delete mode 100644 common/src/main/scala/net/psforever/objects/continent/Zone.scala delete mode 100644 common/src/main/scala/net/psforever/objects/continent/ZoneMap.scala create mode 100644 common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala create mode 100644 common/src/main/scala/net/psforever/objects/zones/ServerObjectBuilder.scala create mode 100644 common/src/main/scala/net/psforever/objects/zones/TerminalObjectBuilder.scala create mode 100644 common/src/main/scala/net/psforever/objects/zones/Zone.scala create mode 100644 common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala rename common/src/main/scala/net/psforever/objects/{continent/ZoneActor.scala => zones/ZoneGroundActor.scala} (62%) create mode 100644 common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala diff --git a/common/src/main/scala/net/psforever/objects/continent/IntergalacticCluster.scala b/common/src/main/scala/net/psforever/objects/continent/IntergalacticCluster.scala deleted file mode 100644 index 3bbd2619a..000000000 --- a/common/src/main/scala/net/psforever/objects/continent/IntergalacticCluster.scala +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.continent - -import akka.actor.{Actor, Props} -import net.psforever.objects.Player - -import scala.annotation.tailrec - -class IntergalacticCluster(zones : List[Zone]) extends Actor { - private[this] val log = org.log4s.getLogger - log.info("Starting interplanetary cluster ...") - - override def preStart() : Unit = { - super.preStart() - for(zone <- zones) { - log.info(s"Built continent ${zone.ZoneId}") - zone.Actor = context.actorOf(Props(classOf[ZoneActor], zone), s"${zone.ZoneId}-actor") - } - } - - def receive : Receive = { - case IntergalacticCluster.GetWorld(zoneId) => - log.info(s"Asked to find $zoneId") - findWorldInCluster(zones.iterator, zoneId) match { - case Some(continent) => - sender ! IntergalacticCluster.GiveWorld(zoneId, continent) - case None => - log.error(s"Requested zone with id $zoneId could not be found") - } - - case IntergalacticCluster.RequestZoneInitialization(tplayer) => - zones.foreach(zone => { - sender ! Zone.ClientInitialization(zone.ClientInitialization()) - }) - sender ! IntergalacticCluster.ClientInitializationComplete(tplayer) - - case _ => ; - } - - @tailrec private def findWorldInCluster(iter : Iterator[Zone], zoneId : String) : Option[Zone] = { - if(!iter.hasNext) { - None - } - else { - val cont = iter.next - if(cont.ZoneId == zoneId) { - Some(cont) - } - else { - findWorldInCluster(iter, zoneId) - } - } - } -} - -object IntergalacticCluster { - final case class GetWorld(zoneId : String) - - final case class GiveWorld(zoneId : String, zone : Zone) - - final case class RequestZoneInitialization(tplayer : Player) - - final case class ClientInitializationComplete(tplayer : Player) -} diff --git a/common/src/main/scala/net/psforever/objects/continent/ServerObjectBuilder.scala b/common/src/main/scala/net/psforever/objects/continent/ServerObjectBuilder.scala deleted file mode 100644 index b5ca451ea..000000000 --- a/common/src/main/scala/net/psforever/objects/continent/ServerObjectBuilder.scala +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.continent - -import akka.actor.ActorContext -import net.psforever.objects.PlanetSideGameObject -import net.psforever.objects.guid.NumberPoolHub - -trait ServerObjectBuilder { - def Build(implicit context : ActorContext, guid : NumberPoolHub) : PlanetSideGameObject -} diff --git a/common/src/main/scala/net/psforever/objects/continent/TerminalObjectBuilder.scala b/common/src/main/scala/net/psforever/objects/continent/TerminalObjectBuilder.scala deleted file mode 100644 index ae59d2c84..000000000 --- a/common/src/main/scala/net/psforever/objects/continent/TerminalObjectBuilder.scala +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.continent - -import net.psforever.objects.terminals.{Terminal, TerminalDefinition} - -class TerminalObjectBuilder(tdef : TerminalDefinition, id : Int) extends ServerObjectBuilder { - import akka.actor.ActorContext - import net.psforever.objects.guid.NumberPoolHub - - def Build(implicit context : ActorContext, guid : NumberPoolHub) : Terminal = { - val obj = Terminal(tdef) - guid.register(obj, id) - obj.Actor - obj - } -} - -object TerminalObjectBuilder { - def apply(tdef : TerminalDefinition, id : Int) : TerminalObjectBuilder = { - new TerminalObjectBuilder(tdef, id) - } -} diff --git a/common/src/main/scala/net/psforever/objects/continent/Zone.scala b/common/src/main/scala/net/psforever/objects/continent/Zone.scala deleted file mode 100644 index 33ba16238..000000000 --- a/common/src/main/scala/net/psforever/objects/continent/Zone.scala +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.continent - -import akka.actor.{ActorContext, ActorRef, Props} -import net.psforever.objects.{PlanetSideGameObject, Player} -import net.psforever.objects.equipment.Equipment -import net.psforever.objects.guid.NumberPoolHub -import net.psforever.objects.guid.actor.{NumberPoolAccessorActor, NumberPoolActor} -import net.psforever.objects.guid.selector.RandomSelector -import net.psforever.objects.guid.source.LimitedNumberSource -import net.psforever.packet.GamePacket -import net.psforever.packet.game.PlanetSideGUID -import net.psforever.types.Vector3 - -import scala.collection.mutable.ListBuffer - -class Zone(id : String, map : ZoneMap, zoneNumber : Int) { - private var actor = ActorRef.noSender - private var accessor : ActorRef = ActorRef.noSender - //private var startupUtilities : List[ServerObjectBuilder] = List() - private var guid : NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(65536)) - - def Actor : ActorRef = actor - - def Init(implicit context : ActorContext) : Unit = { - //TODO wrong initialization - implicit val guid = this.guid - val pool = guid.AddPool("pool", (200 to 1000).toList) - pool.Selector = new RandomSelector - val poolActor = context.actorOf(Props(classOf[NumberPoolActor], pool), name = s"$ZoneId-poolActor") - accessor = context.actorOf(Props(classOf[NumberPoolAccessorActor], guid, pool, poolActor), s"$ZoneId-accessor") - - map.LocalObjects.foreach({builderObject => - builderObject.Build - }) - } - - def Actor_=(zoneActor : ActorRef) : ActorRef = { - if(actor == ActorRef.noSender) { - actor = zoneActor - } - Actor - } - - private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]() - - def ZoneId : String = id - - def ZoneNumber : Int = zoneNumber - - def Map : ZoneMap = map - - def GUID : ActorRef = accessor - - def GUID(hub : NumberPoolHub) : ActorRef = { - if(actor == ActorRef.noSender) { - guid = hub - } - Actor - } - - def GUID(object_guid : PlanetSideGUID) : Option[PlanetSideGameObject] = GUID(object_guid.guid) - - def GUID(object_guid : Int) : Option[PlanetSideGameObject] = guid(object_guid) match { - case Some(obj) => - Some(obj.asInstanceOf[PlanetSideGameObject]) //potential casting error - case None => - None - } - - def EquipmentOnGround : ListBuffer[Equipment] = equipmentOnGround - -// def AddUtility(obj : ServerObjectBuilder) : Unit = { -// startupUtilities = startupUtilities :+ obj -// } -// -// def StartupUtilities : List[ServerObjectBuilder] = { -// val utilities = startupUtilities -// startupUtilities = Nil -// utilities -// } - - def ClientInitialization() : List[GamePacket] = { - List.empty[GamePacket] - } - - def ClientConfiguration() : List[GamePacket] = { - List.empty[GamePacket] - } -} - -object Zone { - final case class DropItemOnGround(item : Equipment, pos : Vector3, orient : Vector3) - - final case class GetItemOnGround(player : Player, item_guid : PlanetSideGUID) - - final case class ItemFromGround(player : Player, item : Equipment) - - final case class ClientInitialization(list : List[GamePacket]) - - def apply(id : String, map : ZoneMap, number : Int) : Zone = { - new Zone(id, map, number) - } -} diff --git a/common/src/main/scala/net/psforever/objects/continent/ZoneMap.scala b/common/src/main/scala/net/psforever/objects/continent/ZoneMap.scala deleted file mode 100644 index ee0edbffb..000000000 --- a/common/src/main/scala/net/psforever/objects/continent/ZoneMap.scala +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.continent - -class ZoneMap(private val name : String) { - private var localObjects : List[ServerObjectBuilder] = List() - - def Name : String = name - - def LocalObject(obj : ServerObjectBuilder) : Unit = { - localObjects = localObjects :+ obj - } - - def LocalObjects : List[ServerObjectBuilder] = { - val utilities = localObjects - localObjects = Nil - utilities - } -} diff --git a/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala new file mode 100644 index 000000000..e09d55c27 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/InterstellarCluster.scala @@ -0,0 +1,113 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.zones + +import akka.actor.{Actor, Props} +import net.psforever.objects.Player + +import scala.annotation.tailrec + +/** + * 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."
+ *
+ * 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 + 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") + findWorldInCluster(zones.iterator, 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(tplayer) => + zones.foreach(zone => { + sender ! Zone.ClientInitialization(zone.ClientInitialization()) //do this for each Zone + }) + sender ! InterstellarCluster.ClientInitializationComplete(tplayer) //will be processed after all Zones + + case _ => ; + } + + /** + * Search through the `List` of `Zone` entities and find the one with the matching designation. + * @param iter an `Iterator` of `Zone` entities + * @param zoneId the name of the `Zone` + * @return the discovered `Zone` + */ + @tailrec private def findWorldInCluster(iter : Iterator[Zone], zoneId : String) : Option[Zone] = { + if(!iter.hasNext) { + None + } + else { + val cont = iter.next + if(cont.Id == zoneId) { + Some(cont) + } + else { + findWorldInCluster(iter, zoneId) + } + } + } +} + +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) + + /** + * Signal to the cluster that a new client needs to be initialized for all listed `Zone` destinations. + * @param tplayer the `Player` belonging to the client; + * may be superfluous + * @see `Zone` + */ + final case class RequestClientInitialization(tplayer : Player) + + /** + * Return signal intended to inform the original sender that all `Zone`s have finished being initialized. + * @param tplayer the `Player` belonging to the client; + * may be superfluous + * @see `WorldSessionActor` + */ + final case class ClientInitializationComplete(tplayer : Player) +} diff --git a/common/src/main/scala/net/psforever/objects/zones/ServerObjectBuilder.scala b/common/src/main/scala/net/psforever/objects/zones/ServerObjectBuilder.scala new file mode 100644 index 000000000..2635df096 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/ServerObjectBuilder.scala @@ -0,0 +1,27 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.zones + +import akka.actor.ActorContext +import net.psforever.objects.PlanetSideGameObject +import net.psforever.objects.guid.NumberPoolHub + +/** + * Wrapper `Trait` designed to be extended to implement custom object instantiation logic at the `ZoneMap` level. + * @see `Zone.Init` + */ +trait ServerObjectBuilder { + /** + * Instantiate and configure the given server object + * (at a later time compared to the construction of the builder class).
+ *
+ * Externally, it expects a `context` to properly integrate within an `ActorSystem` + * and is provided with a source for globally unique identifiers to integrate into the `Zone`. + * Neither is required of the `return` type, however. + * @param context a context to allow the object to properly set up `ActorSystem` functionality; + * defaults to `null` + * @param guid the local globally unique identifier system to complete the process of object introduction; + * defaults to `null` + * @return the object that was created and integrated into the `Zone` + */ + def Build(implicit context : ActorContext = null, guid : NumberPoolHub = null) : PlanetSideGameObject +} diff --git a/common/src/main/scala/net/psforever/objects/zones/TerminalObjectBuilder.scala b/common/src/main/scala/net/psforever/objects/zones/TerminalObjectBuilder.scala new file mode 100644 index 000000000..2ced4d84d --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/TerminalObjectBuilder.scala @@ -0,0 +1,33 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.zones + +import net.psforever.objects.terminals.{Terminal, TerminalDefinition} + +/** + * Wrapper `Class` designed to instantiate a `Terminal` server object. + * @param tdef a `TerminalDefinition` object, indicating the specific functionality of the resulting `Terminal` + * @param id the globally unique identifier to which this `Terminal` will be registered + */ +class TerminalObjectBuilder(private val tdef : TerminalDefinition, private val id : Int) extends ServerObjectBuilder { + import akka.actor.ActorContext + import net.psforever.objects.guid.NumberPoolHub + + def Build(implicit context : ActorContext, guid : NumberPoolHub) : Terminal = { + val obj = Terminal(tdef) + guid.register(obj, id) //non-Actor GUID registration + obj.Actor //it's necessary to register beforehand because the Actor name utilizes the GUID + obj + } +} + +object TerminalObjectBuilder { + /** + * Overloaded constructor for a `TerminalObjectBuilder`. + * @param tdef a `TerminalDefinition` object + * @param id a globally unique identifier + * @return a `TerminalObjectBuilder` object + */ + def apply(tdef : TerminalDefinition, id : Int) : TerminalObjectBuilder = { + new TerminalObjectBuilder(tdef, id) + } +} diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala new file mode 100644 index 000000000..21497a74d --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala @@ -0,0 +1,259 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.zones + +import akka.actor.{ActorContext, ActorRef, Props} +import net.psforever.objects.{PlanetSideGameObject, Player} +import net.psforever.objects.equipment.Equipment +import net.psforever.objects.guid.NumberPoolHub +import net.psforever.objects.guid.actor.{NumberPoolAccessorActor, NumberPoolActor} +import net.psforever.objects.guid.selector.RandomSelector +import net.psforever.objects.guid.source.LimitedNumberSource +import net.psforever.packet.GamePacket +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.Vector3 + +import scala.collection.mutable.ListBuffer + +/** + * A server object representing the one-landmass planets as well as the individual subterranean caverns.
+ *
+ * The concept of a "zone" is synonymous to the common vernacular "continent," + * commonly referred by names such as Hossin or Ishundar and internally identified as c2 and c7, respectively. + * A `Zone` is composed of the abstracted concept of all the information pertinent for the simulation of the environment. + * That is, "everything about the continent." + * Physically, server objects and dynamic game objects are maintained through a local unique identifier system. + * Static server objects originate from the `ZoneMap`. + * Dynamic game objects originate from player characters. + * (Write more later.) + * @param zoneId the privileged name that can be used as the second parameter in the packet `LoadMapMessage` + * @param zoneMap the map of server objects upon which this `Zone` is based + * @param zoneNumber the numerical index of the `Zone` as it is recognized in a variety of packets; + * also used by `LivePlayerList` to indicate a specific `Zone` + * @see `ZoneMap`
+ * `LoadMapMessage`
+ * `LivePlayerList` + */ +class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) { + /** Governs general synchronized external requests. */ + private var actor = ActorRef.noSender + + /** Used by the globally unique identifier system to coordinate requests. */ + private var accessor : ActorRef = ActorRef.noSender + /** The basic support structure for the globally unique number system used by this `Zone`. */ + private var guid : NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(65536)) + /** A synchronized `List` of items (`Equipment`) dropped by players on the ground and can be collected again. */ + private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]() + /** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */ + private var ground : ActorRef = ActorRef.noSender + + /** + * Establish the basic accessible conditions necessary for a functional `Zone`.
+ *
+ * Called from the `Actor` that governs this `Zone` when it is passed a constructor reference to the `Zone`. + * Specifically, the order of calling follows: `InterstellarCluster.preStart -> ZoneActor.receive(Zone.Init()) -> Zone.Init`. + * The basic method performs three main operations. + * First, the `Actor`-driven aspect of the globally unique identifier system for this `Zone` is finalized. + * Second, all supporting `Actor` agents are created, e.g., `ground`. + * Third, the `ZoneMap` server objects are loaded and constructed within that aforementioned system. + * To avoid being called more than once, there is a test whether the `accessor` for the globally unique identifier system has been changed. + * @param context a reference to an `ActorContext` necessary for `Props` + */ + def Init(implicit context : ActorContext) : Unit = { + if(accessor == ActorRef.noSender) { + //TODO wrong initialization for GUID + implicit val guid = this.guid + //passed into builderObject.Build implicitly + val pool = guid.AddPool("pool", (200 to 1000).toList) + pool.Selector = new RandomSelector + val poolActor = context.actorOf(Props(classOf[NumberPoolActor], pool), name = s"$Id-poolActor") + accessor = context.actorOf(Props(classOf[NumberPoolAccessorActor], guid, pool, poolActor), s"$Id-accessor") + ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground") + + Map.LocalObjects.foreach({ builderObject => + builderObject.Build + }) + } + } + + /** + * A reference to the primary `Actor` that governs this `Zone`. + * @return an `ActorRef` + * @see `ZoneActor`
+ * `Zone.Init` + */ + def Actor : ActorRef = actor + + /** + * Give this `Zone` an `Actor` that will govern its interactions sequentially. + * @param zoneActor an `ActorRef` for this `Zone`; + * will not overwrite any existing governance unless `noSender` + * @return an `ActorRef` + * @see `ZoneActor` + */ + def Actor_=(zoneActor : ActorRef) : ActorRef = { + if(actor == ActorRef.noSender) { + actor = zoneActor + } + Actor + } + + /** + * The privileged name that can be used as the second parameter in the packet `LoadMapMessage`. + * @return the name + */ + def Id : String = zoneId + + /** + * The map of server objects upon which this `Zone` is based + * @return the map + */ + def Map : ZoneMap = zoneMap + + /** + * The numerical index of the `Zone` as it is recognized in a variety of packets. + * @return the abstract index position of this `Zone` + */ + def Number : Int = zoneNumber + + /** + * The globally unique identifier system is synchronized via an `Actor` to ensure that concurrent requests do not clash. + * A clash is merely when the same number is produced more than once by the same system due to concurrent requests. + * @return synchronized reference to the globally unique identifier system + */ + def GUID : ActorRef = accessor + + /** + * Replace the current globally unique identifier system with a new one. + * The replacement will not occur if the current system is populated or if its synchronized reference has been created. + * @return synchronized reference to the globally unique identifier system + */ + def GUID(hub : NumberPoolHub) : ActorRef = { + if(actor == ActorRef.noSender && guid.Pools.map({case ((_, pool)) => pool.Count}).sum == 0) { + guid = hub + } + Actor + } + + /** + * Recover an object from the globally unique identifier system by the number that was assigned previously. + * @param object_guid the globally unique identifier requested + * @return the associated object, if it exists + * @see `GUID(Int)` + */ + def GUID(object_guid : PlanetSideGUID) : Option[PlanetSideGameObject] = GUID(object_guid.guid) + + /** + * Recover an object from the globally unique identifier system by the number that was assigned previously. + * The object must be upcast into due to the differtence between the storage type and the return type. + * @param object_guid the globally unique identifier requested + * @return the associated object, if it exists + * @see `NumberPoolHub(Int)` + */ + def GUID(object_guid : Int) : Option[PlanetSideGameObject] = guid(object_guid) match { + case Some(obj) => + Some(obj.asInstanceOf[PlanetSideGameObject]) + case None => + None + } + + /** + * The `List` of items (`Equipment`) dropped by players on the ground and can be collected again. + * @return the `List` of `Equipment` + */ + def EquipmentOnGround : List[Equipment] = equipmentOnGround.toList + + /** + * Coordinate `Equipment` that has been dropped on the ground or to-be-dropped on the ground. + * @return synchronized reference to the ground + * @see `ZoneGroundActor`
+ * `Zone.DropItemOnGround`
+ * `Zone.GetItemOnGround`
+ * `Zone.ItemFromGround` + */ + def Ground : ActorRef = ground + + /** + * Provide bulk correspondence on all map entities that can be composed into packet messages and reported to a client. + * These messages are sent in this fashion at the time of joining the server:
+ * - `BroadcastWarpgateUpdateMessage`
+ * - `BuildingInfoUpdateMessage`
+ * - `CaptureFlagUpdateMessage`
+ * - `ContinentalLockUpdateMessage`
+ * - `DensityLevelUpdateMessage`
+ * - `ModuleLimitsMessage`
+ * - `VanuModuleUpdateMessage`
+ * - `ZoneForcedCavernConnectionMessage`
+ * - `ZoneInfoMessage`
+ * - `ZoneLockInfoMessage`
+ * - `ZonePopulationUpdateMessage` + * @return a `List` of `GamePacket` messages + */ + def ClientInitialization() : List[GamePacket] = { + //TODO unimplemented + List.empty[GamePacket] + } + + /** + * Provide bulk correspondence on all server objects that can be composed into packet messages and reported to a client. + * These messages are sent in this fashion at the time of joining a specific `Zone`:
+ * - `HackMessage`
+ * - `PlanetsideAttributeMessage`
+ * - `SetEmpireMessage`
+ * - `TimeOfDayMessage`
+ * - `WeatherMessage` + * @return a `List` of `GamePacket` messages + */ + def ClientConfiguration() : List[GamePacket] = { + //TODO unimplemented + List.empty[GamePacket] + } +} + +object Zone { + /** + * Message to initialize the `Zone`. + * @see `Zone.Init(implicit ActorContext)` + */ + final case class Init() + + /** + * Message to relinguish an item and place in on the ground. + * @param item the piece of `Equipment` + * @param pos where it is dropped + * @param orient in which direction it is facing when dropped + */ + final case class DropItemOnGround(item : Equipment, pos : Vector3, orient : Vector3) + + /** + * Message to attempt to acquire an item from the ground (before somoene else?). + * @param player who wants the piece of `Equipment` + * @param item_guid the unique identifier of the piece of `Equipment` + */ + final case class GetItemOnGround(player : Player, item_guid : PlanetSideGUID) + + /** + * Message to give an item from the ground to a specific user. + * @param player who wants the piece of `Equipment` + * @param item the piece of `Equipment` + */ + final case class ItemFromGround(player : Player, item : Equipment) + + /** + * Message to report the packet messages that initialize the client. + * @param list a `List` of `GamePacket` messages + * @see `Zone.ClientInitialization()`
+ * `InterstallarCluster` + */ + final case class ClientInitialization(list : List[GamePacket]) + + /** + * Overloaded constructor. + * @param id 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 number the numerical index of the `Zone` as it is recognized in a variety of packets + * @return a `Zone` object + */ + def apply(id : String, map : ZoneMap, number : Int) : Zone = { + new Zone(id, map, number) + } +} diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala new file mode 100644 index 000000000..811b6ef9f --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala @@ -0,0 +1,20 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.zones + +import akka.actor.Actor + +/** + * 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 = { + case Zone.Init() => + zone.Init + + case msg => + log.warn(s"Received unexpected message - $msg") + } +} diff --git a/common/src/main/scala/net/psforever/objects/continent/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneGroundActor.scala similarity index 62% rename from common/src/main/scala/net/psforever/objects/continent/ZoneActor.scala rename to common/src/main/scala/net/psforever/objects/zones/ZoneGroundActor.scala index 9d45a3630..5a001e47d 100644 --- a/common/src/main/scala/net/psforever/objects/continent/ZoneActor.scala +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneGroundActor.scala @@ -1,41 +1,47 @@ // Copyright (c) 2017 PSForever -package net.psforever.objects.continent +package net.psforever.objects.zones import akka.actor.Actor import net.psforever.objects.equipment.Equipment import net.psforever.packet.game.PlanetSideGUID import scala.annotation.tailrec +import scala.collection.mutable.ListBuffer -class ZoneActor(zone : Zone) extends Actor { - private[this] val log = org.log4s.getLogger - - override def preStart() : Unit = { - super.preStart() - zone.Init - } +/** + * na + * @param equipmentOnGround a `List` of items (`Equipment`) dropped by players on the ground and can be collected again + */ +class ZoneGroundActor(equipmentOnGround : ListBuffer[Equipment]) extends Actor { + //private[this] val log = org.log4s.getLogger def receive : Receive = { case Zone.DropItemOnGround(item, pos, orient) => item.Position = pos item.Orientation = orient - zone.EquipmentOnGround += item + equipmentOnGround += item case Zone.GetItemOnGround(player, item_guid) => FindItemOnGround(item_guid) match { case Some(item) => sender ! Zone.ItemFromGround(player, item) case None => - log.warn(s"item on ground $item_guid was requested by $player for pickup but was not found") + org.log4s.getLogger.warn(s"item on ground $item_guid was requested by $player for pickup but was not found") } case _ => ; } + /** + * Shift through objects on the ground to find the location of a specific item. + * @param item_guid the global unique identifier of the piece of `Equipment` being sought + * @return the index of the object matching `item_guid`, if found; + * `None`, otherwise + */ private def FindItemOnGround(item_guid : PlanetSideGUID) : Option[Equipment] = { - recursiveFindItemOnGround(zone.EquipmentOnGround.iterator, item_guid) match { + recursiveFindItemOnGround(equipmentOnGround.iterator, item_guid) match { case Some(index) => - Some(zone.EquipmentOnGround.remove(index)) + Some(equipmentOnGround.remove(index)) case None => None } diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala new file mode 100644 index 000000000..b1cf2e9a3 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala @@ -0,0 +1,43 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.zones + +/** + * The fixed instantiation and relation of a series of server objects.
+ *
+ * Asides from a `List` of server objects to be built, the operation between any server objects + * and the connected functionality emerging from more complex data structures is codified by this object. + * In the former case, all `Terminal` server objects for a `Zone` are to be defined herein. + * In the latter case, the arrangement of server objects into groups called facilities is also to be defined herein. + * Much like a `BasicDefinition` to an object, `ZoneMap` should not maintain mutable information for the companion `Zone`. + * Use it as a blueprint.
+ *
+ * The "training zones" are the best example of the difference between a `ZoneMap` and a `Zone.` + * `tzdrtr` is the Terran Republic driving course. + * `tzdrvs` is the Vanu Sovereignty driving course. + * While each course can have different objects and object states (`Zone`), + * both courses have the same basic server objects because they are built from the same blueprint (`ZoneMap`). + * @param name the privileged name that can be used as the first parameter in the packet `LoadMapMessage` + * @see `ServerObjectBuilder`
+ * `LoadMapMessage` + */ +class ZoneMap(private val name : String) { + private var localObjects : List[ServerObjectBuilder] = List() + + def Name : String = name + + /** + * Append the builder for a server object to the list of builders known to this `ZoneMap`. + * @param obj the builder for a server object + */ + def LocalObject(obj : ServerObjectBuilder) : Unit = { + localObjects = localObjects :+ obj + } + + /** + * The list of all server object builder wrappers that have been assigned to this `ZoneMap`. + * @return the `List` of all `ServerObjectBuilders` known to this `ZoneMap` + */ + def LocalObjects : List[ServerObjectBuilder] = { + localObjects + } +} diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala index 9f1ee2529..c1403fc43 100644 --- a/pslogin/src/main/scala/PsLogin.scala +++ b/pslogin/src/main/scala/PsLogin.scala @@ -3,7 +3,7 @@ import java.net.InetAddress import java.io.File import java.util.Locale -import akka.actor.{ActorContext, ActorRef, ActorSystem, Props} +import akka.actor.{ActorRef, ActorSystem, Props} import akka.routing.RandomPool import ch.qos.logback.classic.LoggerContext import ch.qos.logback.classic.joran.JoranConfigurator @@ -12,7 +12,7 @@ import ch.qos.logback.core.status._ import ch.qos.logback.core.util.StatusPrinter import com.typesafe.config.ConfigFactory import net.psforever.crypto.CryptoInterface -import net.psforever.objects.continent.{IntergalacticCluster, TerminalObjectBuilder, Zone, ZoneMap} +import net.psforever.objects.zones.{InterstellarCluster, TerminalObjectBuilder, Zone, ZoneMap} import net.psforever.objects.guid.TaskResolver import org.slf4j import org.fusesource.jansi.Ansi._ @@ -202,7 +202,7 @@ object PsLogin { val serviceManager = ServiceManager.boot serviceManager ! ServiceManager.Register(RandomPool(50).props(Props[TaskResolver]), "taskResolver") serviceManager ! ServiceManager.Register(Props[AvatarService], "avatar") - serviceManager ! ServiceManager.Register(Props(classOf[IntergalacticCluster], createContinents()), "galaxy") + serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], createContinents()), "galaxy") /** Create two actors for handling the login and world server endpoints */ loginRouter = Props(new SessionRouter("Login", loginTemplate)) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 070d27c33..f11ab3434 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -11,7 +11,7 @@ import org.log4s.MDC import MDCContextAware.Implicits._ import ServiceManager.Lookup import net.psforever.objects._ -import net.psforever.objects.continent.{Zone, IntergalacticCluster} +import net.psforever.objects.zones.{InterstellarCluster, Zone} import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.equipment._ import net.psforever.objects.guid.{Task, TaskResolver} @@ -410,7 +410,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0))) - case IntergalacticCluster.GiveWorld(zoneId, zone) => + case InterstellarCluster.GiveWorld(zoneId, zone) => log.info(s"Zone $zoneId has been loaded") player.Continent = zoneId continent = zone @@ -419,7 +419,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case PlayerLoaded(tplayer) => log.info(s"Player $tplayer has been loaded") //init for whole server - galaxy ! IntergalacticCluster.RequestZoneInitialization(tplayer) + galaxy ! InterstellarCluster.RequestClientInitialization(tplayer) case PlayerFailedToLoad(tplayer) => player.Continent match { @@ -460,9 +460,9 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, BroadcastWarpgateUpdateMessage(PlanetSideGUID(13), PlanetSideGUID(1), false, false, true))) // VS Sanctuary: Inactive Warpgate -> Broadcast Warpgate sendResponse(PacketCoding.CreateGamePacket(0, ZonePopulationUpdateMessage(PlanetSideGUID(13), 414, 138, 0, 138, 0, 138, 0, 138, 0))) - case IntergalacticCluster.ClientInitializationComplete(tplayer)=> + case InterstellarCluster.ClientInitializationComplete(tplayer)=> //this will cause the client to send back a BeginZoningMessage packet (see below) - sendResponse(PacketCoding.CreateGamePacket(0, LoadMapMessage(continent.Map.Name, continent.ZoneId, 40100,25,true,3770441820L))) //VS Sanctuary + sendResponse(PacketCoding.CreateGamePacket(0, LoadMapMessage(continent.Map.Name, continent.Id, 40100,25,true,3770441820L))) //VS Sanctuary log.info("Load the now-registered player") //load the now-registered player tplayer.Spawn @@ -474,7 +474,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case SetCurrentAvatar(tplayer) => val guid = tplayer.GUID - LivePlayerList.Assign(continent.ZoneNumber, sessionId, guid) + LivePlayerList.Assign(continent.Number, sessionId, guid) sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0))) sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))) @@ -633,7 +633,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO check if can spawn on last continent/location from player? //TODO if yes, get continent guid accessors //TODO if no, get sanctuary guid accessors and reset the player's expectations - galaxy ! IntergalacticCluster.GetWorld("home3") + galaxy ! InterstellarCluster.GetWorld("home3") import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global @@ -657,7 +657,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, ReplicationStreamMessage(5, Some(6), Vector(SquadListing())))) //clear squad list //load active players in zone - LivePlayerList.ZonePopulation(continent.ZoneNumber, _ => true).foreach(char => { + LivePlayerList.ZonePopulation(continent.Number, _ => true).foreach(char => { sendResponse( PacketCoding.CreateGamePacket(0, ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get) @@ -752,7 +752,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(item.GUID == item_guid) { val orient : Vector3 = Vector3(0f, 0f, player.Orientation.z) player.FreeHand.Equipment = None - continent.Actor ! Zone.DropItemOnGround(item, player.Position, orient) //TODO do I need to wait for callback? + continent.Ground ! Zone.DropItemOnGround(item, player.Position, orient) sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item.GUID, player.Position, 0f, 0f, player.Orientation.z))) avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, player.Position, orient, item)) } @@ -765,7 +765,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ PickupItemMessage(item_guid, player_guid, unk1, unk2) => log.info("PickupItem: " + msg) - continent.Actor ! Zone.GetItemOnGround(player, item_guid) + continent.Ground ! Zone.GetItemOnGround(player, item_guid) case msg @ ReloadMessage(item_guid, ammo_clip, unk1) => log.info("Reload: " + msg)