renamed Continent* to Zone*; fleshed out example loading fucntionality

This commit is contained in:
FateJH 2017-09-19 20:51:48 -04:00
parent ce8d61a4d3
commit 402d4c5b3e
6 changed files with 238 additions and 104 deletions

View file

@ -3,6 +3,7 @@ package net.psforever.objects
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import scala.annotation.tailrec
import scala.collection.concurrent.{Map, TrieMap} import scala.collection.concurrent.{Map, TrieMap}
/** /**
@ -12,18 +13,28 @@ import scala.collection.concurrent.{Map, TrieMap}
private class LivePlayerList { private class LivePlayerList {
/** key - the session id; value - a `Player` object */ /** key - the session id; value - a `Player` object */
private val sessionMap : Map[Long, Player] = new TrieMap[Long, Player] private val sessionMap : Map[Long, Player] = new TrieMap[Long, Player]
/** key - the global unique identifier; value - the session id */ /** the index of the List corresponds to zone number 1-32 with 0 being "Nowhere" */
private val playerMap : Map[Int, Long] = new TrieMap[Int, Long] /** each mapping: key - the global unique identifier; value - the session id */
private val zoneMap : List[Map[Int, Long]] = List.fill(33)(new TrieMap[Int,Long])
def WorldPopulation(predicate : ((_, Player)) => Boolean) : List[Player] = { def WorldPopulation(predicate : ((_, Player)) => Boolean) : List[Player] = {
sessionMap.filter(predicate).map({ case(_, char) => char }).toList sessionMap.filter(predicate).values.toList
}
def ZonePopulation(zone : Int, predicate : ((_, Player)) => Boolean) : List[Player] = {
zoneMap.lift(zone) match {
case Some(map) =>
val list = map.values.toList
sessionMap.filter({ case ((sess, _)) => list.contains(sess) }).filter(predicate).values.toList
case None =>
Nil
}
} }
def Add(sessionId : Long, player : Player) : Boolean = { def Add(sessionId : Long, player : Player) : Boolean = {
sessionMap.values.find(char => char.equals(player)) match { sessionMap.values.find(char => char.equals(player)) match {
case None => case None =>
sessionMap.putIfAbsent(sessionId, player).isEmpty sessionMap.putIfAbsent(sessionId, player).isEmpty
true
case Some(_) => case Some(_) =>
false false
} }
@ -32,46 +43,62 @@ private class LivePlayerList {
def Remove(sessionId : Long) : Option[Player] = { def Remove(sessionId : Long) : Option[Player] = {
sessionMap.remove(sessionId) match { sessionMap.remove(sessionId) match {
case Some(char) => case Some(char) =>
playerMap.find({ case(_, sess) => sess == sessionId }) match { zoneMap.foreach(zone => {
case Some((guid, _)) => recursiveRemoveSession(zone.iterator, sessionId) match {
playerMap.remove(guid) case Some(guid) =>
case None => ; zone.remove(guid)
} case None => ;
}
})
Some(char) Some(char)
case None => case None =>
None None
} }
} }
def Get(guid : PlanetSideGUID) : Option[Player] = { @tailrec private def recursiveRemoveSession(iter : Iterator[(Int, Long)], sessionId : Long) : Option[Int] = {
Get(guid.guid) if(!iter.hasNext) {
None
}
else {
val (guid : Int, sess : Long) = iter.next
if(sess == sessionId) {
Some(guid)
}
else {
recursiveRemoveSession(iter, sessionId)
}
}
} }
def Get(guid : Int) : Option[Player] = { def Get(zone : Int, guid : PlanetSideGUID) : Option[Player] = {
playerMap.get(guid) match { Get(zone, guid.guid)
case Some(sess) => }
sessionMap.get(sess)
case _ => def Get(zone : Int, guid : Int) : Option[Player] = {
zoneMap.lift(zone) match {
case Some(map) =>
map.get(guid) match {
case Some(sessionId) =>
sessionMap.get(sessionId)
case _ =>
None
}
case None =>
None None
} }
} }
def Assign(sessionId : Long, guid : PlanetSideGUID) : Boolean = Assign(sessionId, guid.guid) def Assign(zone: Int, sessionId : Long, guid : PlanetSideGUID) : Boolean = Assign(zone, sessionId, guid.guid)
def Assign(sessionId : Long, guid : Int) : Boolean = { def Assign(zone : Int, sessionId : Long, guid : Int) : Boolean = {
sessionMap.find({ case(sess, _) => sess == sessionId}) match { sessionMap.get(sessionId) match {
case Some((_, char)) => case Some(_) =>
if(char.GUID.guid == guid) { zoneMap.lift(zone) match {
playerMap.find({ case(_, sess) => sess == sessionId }) match { case Some(zn) =>
case Some((id, _)) => AssignToZone(zn, sessionId, guid)
playerMap.remove(id) case None =>
case None => ; false
}
playerMap.put(guid, sessionId)
true
}
else {
false
} }
case None => case None =>
@ -79,10 +106,36 @@ private class LivePlayerList {
} }
} }
private def AssignToZone(zone : Map[Int, Long], sessionId : Long, guid : Int) : Boolean = {
zone.get(guid) match {
case Some(_) =>
false
case None =>
zone(guid) = sessionId
true
}
}
def Drop(zone : Int, guid : PlanetSideGUID) : Option[Player] = Drop(zone, guid.guid)
def Drop(zone : Int, guid : Int) : Option[Player] = {
zoneMap.lift(zone) match {
case Some(map) =>
map.remove(guid) match {
case Some(sessionId) =>
sessionMap.get(sessionId)
case None =>
None
}
case None =>
None
}
}
def Shutdown : List[Player] = { def Shutdown : List[Player] = {
val list = sessionMap.values.toList val list = sessionMap.values.toList
sessionMap.clear sessionMap.clear
playerMap.clear zoneMap.foreach(map => map.clear())
list list
} }
} }
@ -90,20 +143,26 @@ private class LivePlayerList {
/** /**
* A class for storing `Player` mappings for users that are currently online. * A class for storing `Player` mappings for users that are currently online.
* The mapping system is tightly coupled between the `Player` class and to an instance of `WorldSessionActor`. * The mapping system is tightly coupled between the `Player` class and to an instance of `WorldSessionActor`.
* A loose coupling between the current globally unique identifier (GUID) and the user is also present.<br> * Looser couplings exist between the instance of `WorldSessionActor` and a given `Player`'s globally unique id.
* These looser couplings are zone-specific.
* Though the user may have local knowledge of the zone they inhabit on their `Player` object,
* it should not be trusted.<br>
* <br> * <br>
* Use:<br> * Use:<br>
* 1) When a users logs in during `WorldSessionActor`, associate that user's session id and the character.<br> * 1) When a users logs in during `WorldSessionActor`, associate that user's session id and the character.<br>
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.Add(session, player)`<br> * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.Add(session, player)`<br>
* 2) When that user's chosen character is declared his avatar using `SetCurrentAvatarMessage`, * 2) When that user's chosen character is declared his avatar using `SetCurrentAvatarMessage`,
* also associate the user's session with their current GUID.<br> * also associate the user's session with their current GUID.<br>
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.Assign(session, guid)`<br> * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.Assign(zone, session, guid)`<br>
* 3) Repeat the previous step for as many times the user's GUID changes, especially during the aforementioned condition.<br> * 3) Repeat the previous step for as many times the user's GUID changes, especially during the aforementioned condition.<br>
* 4a) In between the previous two steps, a user's character may be referenced by their current GUID.<br> * 4a) In between the previous two steps, a user's character may be referenced by their current GUID.<br>
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.Get(guid)`<br> * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.Get(zone, guid)`<br>
* 4b) Also in between those same previous steps, a range of characters may be queried based on provided statistics.<br> * 4b) Also in between those same previous steps, a range of characters may be queried based on provided statistics.<br>
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.WorldPopulation(...)`<br> * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.WorldPopulation(...)`<br>
* 5) When the user leaves the game, his character's entries are removed from the mappings.<br> * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.ZonePopulation(zone, ...)`<br>
* 5) When the user navigates away from a region completely, their entry is forgotten.<br>
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.Drop(zone, guid)`<br>
* 6) When the user leaves the game entirely, his character's entries are removed from the mappings.<br>
* &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.Remove(session)` * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;`LivePlayerList.Remove(session)`
*/ */
object LivePlayerList { object LivePlayerList {
@ -114,7 +173,7 @@ object LivePlayerList {
* Given some criteria, examine the mapping of user characters and find the ones that fulfill the requirements.<br> * Given some criteria, examine the mapping of user characters and find the ones that fulfill the requirements.<br>
* <br> * <br>
* Note the signature carefully. * Note the signature carefully.
* A two-element tuple is checked, but only the second element of that tuple - a character - is eligible for being queried. * A two-element tuple is checked, but only the second element of that tuple - a `Player` - is eligible for being queried.
* The first element is ignored. * The first element is ignored.
* Even a predicate as simple as `{ case ((x : Long, _)) => x > 0 }` will not work for that reason. * Even a predicate as simple as `{ case ((x : Long, _)) => x > 0 }` will not work for that reason.
* @param predicate the conditions for filtering the live `Player`s * @param predicate the conditions for filtering the live `Player`s
@ -122,6 +181,19 @@ object LivePlayerList {
*/ */
def WorldPopulation(predicate : ((_, Player)) => Boolean) : List[Player] = Instance.WorldPopulation(predicate) def WorldPopulation(predicate : ((_, Player)) => Boolean) : List[Player] = Instance.WorldPopulation(predicate)
/**
* Given some criteria, examine the mapping of user characters for a zone and find the ones that fulfill the requirements.<br>
* <br>
* Note the signature carefully.
* A two-element tuple is checked, but only the second element of that tuple - a `Player` - is eligible for being queried.
* The first element is ignored.
* Even a predicate as simple as `{ case ((x : Long, _)) => x > 0 }` will not work for that reason.
* @param zone the number of the zone
* @param predicate the conditions for filtering the live `Player`s
* @return a list of users's `Player`s that fit the criteria
*/
def ZonePopulation(zone : Int, predicate : ((_, Player)) => Boolean) : List[Player] = Instance.ZonePopulation(zone, predicate)
/** /**
* Create a mapped entry between the user's session and a user's character. * Create a mapped entry between the user's session and a user's character.
* Neither the player nor the session may exist in the current mappings if this is to work. * Neither the player nor the session may exist in the current mappings if this is to work.
@ -142,39 +214,61 @@ object LivePlayerList {
/** /**
* Get a user's character from the mappings. * Get a user's character from the mappings.
* @param zone the number of the zone
* @param guid the current GUID of the character * @param guid the current GUID of the character
* @return the character, if it can be found using the GUID * @return the character, if it can be found using the GUID
*/ */
def Get(guid : PlanetSideGUID) : Option[Player] = Instance.Get(guid) def Get(zone : Int, guid : PlanetSideGUID) : Option[Player] = Instance.Get(zone, guid)
/** /**
* Get a user's character from the mappings. * Get a user's character from the mappings.
* @param zone the number of the zone
* @param guid the current GUID of the character * @param guid the current GUID of the character
* @return the character, if it can be found using the GUID * @return the character, if it can be found using the GUID
*/ */
def Get(guid : Int) : Option[Player] = Instance.Get(guid) def Get(zone : Int, guid : Int) : Option[Player] = Instance.Get(zone, guid)
/** /**
* Given a session that maps to a user's character, create a mapping between the character's current GUID and the session. * Given a session that maps to a user's character, create a mapping between the character's current GUID and the session.
* If the user already has a GUID in the mappings, remove it and assert the new one. * If the user already has a GUID in the mappings, remove it and assert the new one.
* @param zone the number of the zone
* @param sessionId the session * @param sessionId the session
* @param guid the GUID to associate with the character; * @param guid the GUID to associate with the character;
* technically, it has already been assigned and should be findable using `{character}.GUID.guid` * technically, it has already been assigned and should be findable using `{character}.GUID.guid`
* @return `true`, if the mapping was created; * @return `true`, if the mapping was created;
* `false`, if the session can not be found or if the character's GUID doesn't match the one provided * `false`, if the session can not be found or if the character's GUID doesn't match the one provided
*/ */
def Assign(sessionId : Long, guid : PlanetSideGUID) : Boolean = Instance.Assign(sessionId, guid) def Assign(zone : Int, sessionId : Long, guid : PlanetSideGUID) : Boolean = Instance.Assign(zone, sessionId, guid)
/** /**
* Given a session that maps to a user's character, create a mapping between the character's current GUID and the session. * Given a session that maps to a user's character, create a mapping between the character's current GUID and the session.
* If the user already has a GUID in the mappings, remove it and assert the new one. * If the user already has a GUID in the mappings, remove it and assert the new one.
* @param zone the number of the zone
* @param sessionId the session * @param sessionId the session
* @param guid the GUID to associate with the character; * @param guid the GUID to associate with the character;
* technically, it has already been assigned and should be findable using `{character}.GUID.guid` * technically, it has already been assigned and should be findable using `{character}.GUID.guid`
* @return `true`, if the mapping was created; * @return `true`, if the mapping was created;
* `false`, if the session can not be found or if the character's GUID doesn't match the one provided * `false`, if the session can not be found or if the character's GUID doesn't match the one provided
*/ */
def Assign(sessionId : Long, guid : Int) : Boolean = Instance.Assign(sessionId, guid) def Assign(zone : Int, sessionId : Long, guid : Int) : Boolean = Instance.Assign(zone, sessionId, guid)
/**
* Given a GUID, remove any record of it.
* @param zone the number of the zone
* @param guid a GUID associated with the character;
* it does not have to be findable using `{character}.GUID.guid`
* @return any `Player` that may have been associated with this GUID
*/
def Drop(zone : Int, guid : PlanetSideGUID) : Option[Player] = Instance.Drop(zone, guid)
/**
* Given a GUID, remove any record of it.
* @param zone the number of the zone
* @param guid a GUID associated with the character;
* it does not have to be findable using `{character}.GUID.guid`
* @return any `Player` that may have been associated with this GUID
*/
def Drop(zone : Int, guid : Int) : Option[Player] = Instance.Drop(zone, guid)
/** /**
* Hastily remove all mappings and ids. * Hastily remove all mappings and ids.

View file

@ -2,10 +2,11 @@
package net.psforever.objects.continent package net.psforever.objects.continent
import akka.actor.Actor import akka.actor.Actor
import net.psforever.objects.Player
import scala.annotation.tailrec import scala.annotation.tailrec
class IntergalacticCluster(continents : List[Continent]) extends Actor { class IntergalacticCluster(continents : List[Zone]) extends Actor {
//private[this] val log = org.log4s.getLogger //private[this] val log = org.log4s.getLogger
for(continent <- continents) { for(continent <- continents) {
continent.Actor //seed context continent.Actor //seed context
@ -17,13 +18,19 @@ class IntergalacticCluster(continents : List[Continent]) extends Actor {
case Some(continent) => case Some(continent) =>
sender ! IntergalacticCluster.GiveWorld(zoneId, continent) sender ! IntergalacticCluster.GiveWorld(zoneId, continent)
case None => case None =>
sender ! IntergalacticCluster.GiveWorld(zoneId, Continent.Nowhere) sender ! IntergalacticCluster.GiveWorld(zoneId, Zone.Nowhere)
} }
case IntergalacticCluster.RequestZoneInitialization(tplayer) =>
continents.foreach(zone => {
sender ! Zone.ZoneInitialization(zone.ZoneInitialization())
})
sender ! IntergalacticCluster.ZoneInitializationComplete(tplayer)
case _ => ; case _ => ;
} }
@tailrec private def findWorldInCluster(iter : Iterator[Continent], zoneId : String) : Option[Continent] = { @tailrec private def findWorldInCluster(iter : Iterator[Zone], zoneId : String) : Option[Zone] = {
if(!iter.hasNext) { if(!iter.hasNext) {
None None
} }
@ -42,5 +49,9 @@ class IntergalacticCluster(continents : List[Continent]) extends Actor {
object IntergalacticCluster { object IntergalacticCluster {
final case class GetWorld(zoneId : String) final case class GetWorld(zoneId : String)
final case class GiveWorld(zoneId : String, zone : Continent) final case class GiveWorld(zoneId : String, zone : Zone)
final case class RequestZoneInitialization(tplayer : Player)
final case class ZoneInitializationComplete(tplayer : Player)
} }

View file

@ -9,21 +9,22 @@ import net.psforever.objects.guid.NumberPoolHub
import net.psforever.objects.guid.actor.{NumberPoolAccessorActor, NumberPoolActor} import net.psforever.objects.guid.actor.{NumberPoolAccessorActor, NumberPoolActor}
import net.psforever.objects.guid.selector.RandomSelector import net.psforever.objects.guid.selector.RandomSelector
import net.psforever.objects.guid.source.LimitedNumberSource import net.psforever.objects.guid.source.LimitedNumberSource
import net.psforever.packet.GamePacket
import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.PlanetSideGUID
import net.psforever.types.Vector3 import net.psforever.types.Vector3
import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer
class Continent(zoneId : String, map : String) { class Zone(id : String, zoneNumber : Int, map : String) {
private var actor = ActorRef.noSender private var actor = ActorRef.noSender
private val guid : NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(65536)) private var guid : NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(6))
private var accessor : ActorRef = ActorRef.noSender private var accessor : ActorRef = ActorRef.noSender
def Actor(implicit context : ActorContext) : ActorRef = { def Actor(implicit context : ActorContext) : ActorRef = {
if(actor == ActorRef.noSender) { if(actor == ActorRef.noSender) {
actor = context.actorOf(Props(classOf[ContinentActor], this), s"$zoneId-actor") actor = context.actorOf(Props(classOf[ZoneActor], this), s"$id-actor")
val pool = guid.AddPool("pool", (400 to 599).toList) val pool = guid.AddPool("pool", 6 :: Nil)//(400 to 599).toList)
val poolActor = context.actorOf(Props(classOf[NumberPoolActor], pool), name = s"$ZoneId-poolActor") val poolActor = context.actorOf(Props(classOf[NumberPoolActor], pool), name = s"$ZoneId-poolActor")
pool.Selector = new RandomSelector pool.Selector = new RandomSelector
accessor = context.actorOf(Props(classOf[NumberPoolAccessorActor], guid, pool, poolActor), s"$ZoneId-accessor") accessor = context.actorOf(Props(classOf[NumberPoolAccessorActor], guid, pool, poolActor), s"$ZoneId-accessor")
@ -33,19 +34,36 @@ class Continent(zoneId : String, map : String) {
private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]() private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]()
def ZoneId : String = zoneId def ZoneId : String = id
def ZoneNumber : Int = zoneNumber
def Map : String = map def Map : String = map
def GUID : ActorRef = accessor def GUID : ActorRef = accessor
def GUID_=(guidSrc : NumberPoolHub) : ActorRef = {
if(accessor == ActorRef.noSender) {
guid = guidSrc
}
accessor
}
def GUID(object_guid : PlanetSideGUID) : Option[IdentifiableEntity] = guid(object_guid.guid) def GUID(object_guid : PlanetSideGUID) : Option[IdentifiableEntity] = guid(object_guid.guid)
def EquipmentOnGround : ListBuffer[Equipment] = equipmentOnGround def EquipmentOnGround : ListBuffer[Equipment] = equipmentOnGround
def ZoneInitialization() : List[GamePacket] = {
List.empty[GamePacket]
}
def ZoneConfiguration() : List[GamePacket] = {
List.empty[GamePacket]
}
} }
object Continent { object Zone {
final def Nowhere : Continent = { Continent("", "") } //TODO needs overrides final def Nowhere : Zone = { Zone("nowhere", 0, "nowhere") } //TODO needs overrides
final case class DropItemOnGround(item : Equipment, pos : Vector3, orient : Vector3) final case class DropItemOnGround(item : Equipment, pos : Vector3, orient : Vector3)
@ -53,7 +71,9 @@ object Continent {
final case class ItemFromGround(player : Player, item : Equipment) final case class ItemFromGround(player : Player, item : Equipment)
def apply(zoneId : String, map : String) : Continent = { final case class ZoneInitialization(list : List[GamePacket])
new Continent(zoneId, map)
def apply(zoneId : String, zoneNumber : Int, map : String) : Zone = {
new Zone(zoneId, zoneNumber, map)
} }
} }

View file

@ -7,9 +7,9 @@ import net.psforever.packet.game.PlanetSideGUID
import scala.annotation.tailrec import scala.annotation.tailrec
class ContinentActor(continent : Continent) extends Actor { class ZoneActor(continent : Zone) extends Actor {
private[this] val log = org.log4s.getLogger private[this] val log = org.log4s.getLogger
import Continent._ import Zone._
def receive : Receive = { def receive : Receive = {
case DropItemOnGround(item, pos, orient) => case DropItemOnGround(item, pos, orient) =>

View file

@ -12,7 +12,7 @@ import ch.qos.logback.core.status._
import ch.qos.logback.core.util.StatusPrinter import ch.qos.logback.core.util.StatusPrinter
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import net.psforever.crypto.CryptoInterface import net.psforever.crypto.CryptoInterface
import net.psforever.objects.continent.{Continent, IntergalacticCluster} import net.psforever.objects.continent.{Zone, IntergalacticCluster}
import net.psforever.objects.guid.TaskResolver import net.psforever.objects.guid.TaskResolver
import org.slf4j import org.slf4j
import org.fusesource.jansi.Ansi._ import org.fusesource.jansi.Ansi._
@ -219,8 +219,8 @@ object PsLogin {
} }
} }
def createContinents() : List[Continent] = { def createContinents() : List[Zone] = {
Continent("home3","map13") :: Nil Zone("home3",13,"map13") :: Nil
} }
def main(args : Array[String]) : Unit = { def main(args : Array[String]) : Unit = {

View file

@ -11,7 +11,7 @@ import org.log4s.MDC
import MDCContextAware.Implicits._ import MDCContextAware.Implicits._
import ServiceManager.Lookup import ServiceManager.Lookup
import net.psforever.objects._ import net.psforever.objects._
import net.psforever.objects.continent.{Continent, IntergalacticCluster} import net.psforever.objects.continent.{Zone, IntergalacticCluster}
import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment._ import net.psforever.objects.equipment._
import net.psforever.objects.guid.{Task, TaskResolver} import net.psforever.objects.guid.{Task, TaskResolver}
@ -34,7 +34,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
var avatarService = Actor.noSender var avatarService = Actor.noSender
var taskResolver = Actor.noSender var taskResolver = Actor.noSender
var galaxy = Actor.noSender var galaxy = Actor.noSender
var continent : Continent = Continent.Nowhere var continent : Zone = Zone.Nowhere
var clientKeepAlive : Cancellable = WorldSessionActor.DefaultCancellable var clientKeepAlive : Cancellable = WorldSessionActor.DefaultCancellable
@ -45,10 +45,12 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatarService ! Leave() avatarService ! Leave()
LivePlayerList.Remove(sessionId) match { LivePlayerList.Remove(sessionId) match {
case Some(tplayer) => case Some(tplayer) =>
val guid = tplayer.GUID if(tplayer.HasGUID) {
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid)) val guid = tplayer.GUID
taskResolver ! UnregisterAvatar(tplayer) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid))
//TODO normally, the actual player avatar persists a minute or so after the user disconnects taskResolver ! UnregisterAvatar(tplayer)
//TODO normally, the actual player avatar persists a minute or so after the user disconnects
}
case None => ; case None => ;
} }
} }
@ -417,7 +419,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
case PlayerLoaded(tplayer) => case PlayerLoaded(tplayer) =>
log.info(s"Player $tplayer has been loaded") log.info(s"Player $tplayer has been loaded")
//init for whole server //init for whole server
//... galaxy ! IntergalacticCluster.RequestZoneInitialization(tplayer)
case PlayerFailedToLoad(tplayer) =>
player.Continent match {
case "tzshvs" =>
log.error(s"$tplayer failed to load anywhere")
self ! IntergalacticCluster.GiveWorld("", Zone.Nowhere)
case "tzdrvs" =>
galaxy ! IntergalacticCluster.GetWorld("tzshvs")
case "home3" =>
galaxy ! IntergalacticCluster.GetWorld("tzdrvs")
case _ =>
galaxy ! IntergalacticCluster.GetWorld("home3")
}
case Zone.ZoneInitialization(initList) =>
//TODO iterate over initList; for now, just do this
sendResponse( sendResponse(
PacketCoding.CreateGamePacket(0, PacketCoding.CreateGamePacket(0,
BuildingInfoUpdateMessage( BuildingInfoUpdateMessage(
@ -447,8 +465,11 @@ class WorldSessionActor extends Actor with MDCContextAware {
) )
sendResponse(PacketCoding.CreateGamePacket(0, ContinentalLockUpdateMessage(PlanetSideGUID(13), PlanetSideEmpire.VS))) // "The VS have captured the VS Sanctuary." sendResponse(PacketCoding.CreateGamePacket(0, ContinentalLockUpdateMessage(PlanetSideGUID(13), PlanetSideEmpire.VS))) // "The VS have captured the VS Sanctuary."
sendResponse(PacketCoding.CreateGamePacket(0, BroadcastWarpgateUpdateMessage(PlanetSideGUID(13), PlanetSideGUID(1), false, false, true))) // VS Sanctuary: Inactive Warpgate -> Broadcast Warpgate sendResponse(PacketCoding.CreateGamePacket(0, BroadcastWarpgateUpdateMessage(PlanetSideGUID(13), PlanetSideGUID(1), false, false, true))) // VS Sanctuary: Inactive Warpgate -> Broadcast Warpgate
//LoadMapMessage -> BeginZoningMessage sendResponse(PacketCoding.CreateGamePacket(0, ZonePopulationUpdateMessage(PlanetSideGUID(13), 414, 138, 0, 138, 0, 138, 0, 138, 0)))
sendResponse(PacketCoding.CreateGamePacket(0, LoadMapMessage("map13","home3",40100,25,true,3770441820L))) //VS Sanctuary
case IntergalacticCluster.ZoneInitializationComplete(tplayer)=>
//this will cause the client to send back a BeginZoningMessage packet (see below)
sendResponse(PacketCoding.CreateGamePacket(0, LoadMapMessage(continent.Map, continent.ZoneId, 40100,25,true,3770441820L))) //VS Sanctuary
//load the now-registered player //load the now-registered player
tplayer.Spawn tplayer.Spawn
sendResponse(PacketCoding.CreateGamePacket(0, sendResponse(PacketCoding.CreateGamePacket(0,
@ -457,33 +478,18 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.LoadPlayer(tplayer.GUID, tplayer.Definition.Packet.ConstructorData(tplayer).get)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.LoadPlayer(tplayer.GUID, tplayer.Definition.Packet.ConstructorData(tplayer).get))
log.debug(s"ObjectCreateDetailedMessage: ${tplayer.Definition.Packet.DetailedConstructorData(tplayer).get}") log.debug(s"ObjectCreateDetailedMessage: ${tplayer.Definition.Packet.DetailedConstructorData(tplayer).get}")
case PlayerFailedToLoad(tplayer) =>
player.Continent match {
case "tzshvs" =>
failWithError(s"$tplayer failed to load anywhere")
case "tzdrvs" =>
galaxy ! IntergalacticCluster.GetWorld("tzshvs")
case "home3" =>
galaxy ! IntergalacticCluster.GetWorld("tzdrvs")
case _ =>
galaxy ! IntergalacticCluster.GetWorld("home3")
}
case SetCurrentAvatar(tplayer) => case SetCurrentAvatar(tplayer) =>
//avatar-specific
val guid = tplayer.GUID val guid = tplayer.GUID
LivePlayerList.Assign(sessionId, guid) LivePlayerList.Assign(continent.ZoneNumber, sessionId, guid)
sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0))) sendResponse(PacketCoding.CreateGamePacket(0, SetCurrentAvatarMessage(guid,0,0)))
sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))) sendResponse(PacketCoding.CreateGamePacket(0, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT)))
//temporary location case Zone.ItemFromGround(tplayer, item) =>
case Continent.ItemFromGround(tplayer, item) =>
val obj_guid = item.GUID val obj_guid = item.GUID
val player_guid = tplayer.GUID val player_guid = tplayer.GUID
tplayer.Fit(item) match { tplayer.Fit(item) match {
case Some(slot) => case Some(slot) =>
tplayer.Slot(slot).Equipment = item tplayer.Slot(slot).Equipment = item
//sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(tplayer.GUID, obj_guid, slot)))
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(player_guid, obj_guid)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(player_guid, obj_guid))
val definition = item.Definition val definition = item.Definition
sendResponse( sendResponse(
@ -500,7 +506,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentInHand(player_guid, slot, item)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentInHand(player_guid, slot, item))
} }
case None => case None =>
continent.Actor ! Continent.DropItemOnGround(item, item.Position, item.Orientation) //restore continent.Actor ! Zone.DropItemOnGround(item, item.Position, item.Orientation) //restore
} }
case ResponseToSelf(pkt) => case ResponseToSelf(pkt) =>
@ -603,7 +609,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
player = Player("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) player = Player("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1)
player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f) player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f)
player.Orientation = Vector3(0f, 0f, 90f) player.Orientation = Vector3(0f, 0f, 90f)
player.Continent = "home3" //player.Continent = "home3"
player.Slot(0).Equipment = beamer1 player.Slot(0).Equipment = beamer1
player.Slot(2).Equipment = suppressor1 player.Slot(2).Equipment = suppressor1
player.Slot(4).Equipment = forceblade1 player.Slot(4).Equipment = forceblade1
@ -684,13 +690,14 @@ class WorldSessionActor extends Actor with MDCContextAware {
sendResponse(PacketCoding.CreateGamePacket(0, ActionResultMessage(false, Some(1)))) sendResponse(PacketCoding.CreateGamePacket(0, ActionResultMessage(false, Some(1))))
case CharacterRequestAction.Select => case CharacterRequestAction.Select =>
LivePlayerList.Add(sessionId, player) LivePlayerList.Add(sessionId, player)
//check can spawn on last continent/location from player //TODO check if can spawn on last continent/location from player?
//if yes, get continent guid accessors //TODO if yes, get continent guid accessors
//if no, get sanctuary guid accessors and reset the player's expectations //TODO if no, get sanctuary guid accessors and reset the player's expectations
galaxy ! IntergalacticCluster.GetWorld("home3") galaxy ! IntergalacticCluster.GetWorld("home3")
import scala.concurrent.duration._ import scala.concurrent.duration._
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
clientKeepAlive.cancel
clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient()) clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient())
case default => case default =>
log.error("Unsupported " + default + " in " + msg) log.error("Unsupported " + default + " in " + msg)
@ -701,27 +708,25 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ BeginZoningMessage() => case msg @ BeginZoningMessage() =>
log.info("Reticulating splines ...") log.info("Reticulating splines ...")
//map-specific initializations (VS sanctuary) //map-specific initializations
//TODO continent.ZoneConfiguration()
sendResponse(PacketCoding.CreateGamePacket(0, SetEmpireMessage(PlanetSideGUID(2), PlanetSideEmpire.VS))) //HART building C sendResponse(PacketCoding.CreateGamePacket(0, SetEmpireMessage(PlanetSideGUID(2), PlanetSideEmpire.VS))) //HART building C
sendResponse(PacketCoding.CreateGamePacket(0, SetEmpireMessage(PlanetSideGUID(29), PlanetSideEmpire.NC))) //South Villa Gun Tower sendResponse(PacketCoding.CreateGamePacket(0, SetEmpireMessage(PlanetSideGUID(29), PlanetSideEmpire.NC))) //South Villa Gun Tower
sendResponse(PacketCoding.CreateGamePacket(0, object2Hex)) //sendResponse(PacketCoding.CreateGamePacket(0, object2Hex))
//sendResponse(PacketCoding.CreateGamePacket(0, furyHex)) //sendResponse(PacketCoding.CreateGamePacket(0, furyHex))
sendResponse(PacketCoding.CreateGamePacket(0, ZonePopulationUpdateMessage(PlanetSideGUID(13), 414, 138, 0, 138, 0, 138, 0, 138, 0)))
sendResponse(PacketCoding.CreateGamePacket(0, TimeOfDayMessage(1191182336))) sendResponse(PacketCoding.CreateGamePacket(0, TimeOfDayMessage(1191182336)))
sendResponse(PacketCoding.CreateGamePacket(0, ReplicationStreamMessage(5, Some(6), Vector(SquadListing())))) //clear squad list sendResponse(PacketCoding.CreateGamePacket(0, ReplicationStreamMessage(5, Some(6), Vector(SquadListing())))) //clear squad list
//all players are part of the same zone right now, so don't expect much //load active players in zone
val playerContinent = player.Continent LivePlayerList.ZonePopulation(continent.ZoneNumber, _ => true).foreach(char => {
val player_guid = player.GUID
LivePlayerList.WorldPopulation({ case (_, char : Player) => char.Continent == playerContinent && char.HasGUID && char.GUID != player_guid}).foreach(char => {
sendResponse( sendResponse(
PacketCoding.CreateGamePacket(0, PacketCoding.CreateGamePacket(0,
ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get) ObjectCreateMessage(ObjectClass.avatar, char.GUID, char.Definition.Packet.ConstructorData(char).get)
) )
) )
}) })
//render Equipment that was dropped into world before player arrived //render Equipment that was dropped into zone before the player arrived
continent.EquipmentOnGround.toList.foreach(item => { continent.EquipmentOnGround.toList.foreach(item => {
val definition = item.Definition val definition = item.Definition
sendResponse( sendResponse(
@ -735,7 +740,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
) )
}) })
avatarService ! Join(playerContinent) avatarService ! Join(player.Continent)
self ! SetCurrentAvatar(player) self ! SetCurrentAvatar(player)
case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) => case msg @ PlayerStateMessageUpstream(avatar_guid, pos, vel, yaw, pitch, yaw_upper, seq_time, unk3, is_crouching, is_jumping, unk4, is_cloaking, unk5, unk6) =>
@ -809,7 +814,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(item.GUID == item_guid) { if(item.GUID == item_guid) {
val orient : Vector3 = Vector3(0f, 0f, player.Orientation.z) val orient : Vector3 = Vector3(0f, 0f, player.Orientation.z)
player.FreeHand.Equipment = None player.FreeHand.Equipment = None
continent.Actor ! Continent.DropItemOnGround(item, player.Position, orient) //TODO do I need to wait for callback? continent.Actor ! Zone.DropItemOnGround(item, player.Position, orient) //TODO do I need to wait for callback?
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item.GUID, player.Position, 0f, 0f, player.Orientation.z))) 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)) avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, player.Position, orient, item))
} }
@ -822,7 +827,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
case msg @ PickupItemMessage(item_guid, player_guid, unk1, unk2) => case msg @ PickupItemMessage(item_guid, player_guid, unk1, unk2) =>
log.info("PickupItem: " + msg) log.info("PickupItem: " + msg)
continent.Actor ! Continent.GetItemOnGround(player, item_guid) continent.Actor ! Zone.GetItemOnGround(player, item_guid)
case msg @ ReloadMessage(item_guid, ammo_clip, unk1) => case msg @ ReloadMessage(item_guid, ammo_clip, unk1) =>
log.info("Reload: " + msg) log.info("Reload: " + msg)
@ -939,7 +944,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
val pos = player.Position val pos = player.Position
val playerOrient = player.Orientation val playerOrient = player.Orientation
val orient : Vector3 = Vector3(0f, 0f, playerOrient.z) val orient : Vector3 = Vector3(0f, 0f, playerOrient.z)
continent.Actor ! Continent.DropItemOnGround(item2, pos, orient) continent.Actor ! Zone.DropItemOnGround(item2, pos, orient)
sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item2.GUID, pos, 0f, 0f, playerOrient.z))) //ground sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item2.GUID, pos, 0f, 0f, playerOrient.z))) //ground
avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, pos, orient, item2)) avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, pos, orient, item2))
} }
@ -1324,6 +1329,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
localAnnounce ! PlayerLoaded(localPlayer) //alerts WSA localAnnounce ! PlayerLoaded(localPlayer) //alerts WSA
resolver ! scala.util.Success(localPlayer) resolver ! scala.util.Success(localPlayer)
} }
override def onFailure(ex : Throwable) : Unit = {
localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA
}
}, RegisterObjectTask(tplayer) +: (holsterTasks ++ fifthHolsterTask ++ inventoryTasks) }, RegisterObjectTask(tplayer) +: (holsterTasks ++ fifthHolsterTask ++ inventoryTasks)
) )
} }
@ -1547,9 +1556,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
} }
} }
final case class ResponseToSelf(pkt : GamePacket)
object WorldSessionActor { object WorldSessionActor {
final case class ResponseToSelf(pkt : GamePacket)
private final case class PokeClient() private final case class PokeClient()
private final case class ServerLoaded() private final case class ServerLoaded()
private final case class PlayerLoaded(tplayer : Player) private final case class PlayerLoaded(tplayer : Player)