diff --git a/common/src/main/scala/net/psforever/objects/LivePlayerList.scala b/common/src/main/scala/net/psforever/objects/LivePlayerList.scala index c0ad0e8f3..c0e398568 100644 --- a/common/src/main/scala/net/psforever/objects/LivePlayerList.scala +++ b/common/src/main/scala/net/psforever/objects/LivePlayerList.scala @@ -3,6 +3,7 @@ package net.psforever.objects import net.psforever.packet.game.PlanetSideGUID +import scala.annotation.tailrec import scala.collection.concurrent.{Map, TrieMap} /** @@ -12,18 +13,28 @@ import scala.collection.concurrent.{Map, TrieMap} private class LivePlayerList { /** key - the session id; value - a `Player` object */ private val sessionMap : Map[Long, Player] = new TrieMap[Long, Player] - /** key - the global unique identifier; value - the session id */ - private val playerMap : Map[Int, Long] = new TrieMap[Int, Long] + /** the index of the List corresponds to zone number 1-32 with 0 being "Nowhere" */ + /** 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] = { - 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 = { sessionMap.values.find(char => char.equals(player)) match { case None => sessionMap.putIfAbsent(sessionId, player).isEmpty - true case Some(_) => false } @@ -32,46 +43,62 @@ private class LivePlayerList { def Remove(sessionId : Long) : Option[Player] = { sessionMap.remove(sessionId) match { case Some(char) => - playerMap.find({ case(_, sess) => sess == sessionId }) match { - case Some((guid, _)) => - playerMap.remove(guid) - case None => ; - } + zoneMap.foreach(zone => { + recursiveRemoveSession(zone.iterator, sessionId) match { + case Some(guid) => + zone.remove(guid) + case None => ; + } + }) Some(char) case None => None } } - def Get(guid : PlanetSideGUID) : Option[Player] = { - Get(guid.guid) + @tailrec private def recursiveRemoveSession(iter : Iterator[(Int, Long)], sessionId : Long) : Option[Int] = { + 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] = { - playerMap.get(guid) match { - case Some(sess) => - sessionMap.get(sess) - case _ => + def Get(zone : Int, guid : PlanetSideGUID) : Option[Player] = { + Get(zone, guid.guid) + } + + 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 } } - 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 = { - sessionMap.find({ case(sess, _) => sess == sessionId}) match { - case Some((_, char)) => - if(char.GUID.guid == guid) { - playerMap.find({ case(_, sess) => sess == sessionId }) match { - case Some((id, _)) => - playerMap.remove(id) - case None => ; - } - playerMap.put(guid, sessionId) - true - } - else { - false + def Assign(zone : Int, sessionId : Long, guid : Int) : Boolean = { + sessionMap.get(sessionId) match { + case Some(_) => + zoneMap.lift(zone) match { + case Some(zn) => + AssignToZone(zn, sessionId, guid) + case None => + false } 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] = { val list = sessionMap.values.toList sessionMap.clear - playerMap.clear + zoneMap.foreach(map => map.clear()) list } } @@ -90,20 +143,26 @@ private class LivePlayerList { /** * 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`. - * A loose coupling between the current globally unique identifier (GUID) and the user is also present.
+ * 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.
*
* Use:
* 1) When a users logs in during `WorldSessionActor`, associate that user's session id and the character.
*        `LivePlayerList.Add(session, player)`
* 2) When that user's chosen character is declared his avatar using `SetCurrentAvatarMessage`, * also associate the user's session with their current GUID.
- *        `LivePlayerList.Assign(session, guid)`
+ *        `LivePlayerList.Assign(zone, session, guid)`
* 3) Repeat the previous step for as many times the user's GUID changes, especially during the aforementioned condition.
* 4a) In between the previous two steps, a user's character may be referenced by their current GUID.
- *        `LivePlayerList.Get(guid)`
+ *        `LivePlayerList.Get(zone, guid)`
* 4b) Also in between those same previous steps, a range of characters may be queried based on provided statistics.
*        `LivePlayerList.WorldPopulation(...)`
- * 5) When the user leaves the game, his character's entries are removed from the mappings.
+ *        `LivePlayerList.ZonePopulation(zone, ...)`
+ * 5) When the user navigates away from a region completely, their entry is forgotten.
+ *        `LivePlayerList.Drop(zone, guid)`
+ * 6) When the user leaves the game entirely, his character's entries are removed from the mappings.
*        `LivePlayerList.Remove(session)` */ 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.
*
* 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. * 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 @@ -122,6 +181,19 @@ object LivePlayerList { */ 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.
+ *
+ * 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. * 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. + * @param zone the number of the zone * @param guid the current GUID of the character * @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. + * @param zone the number of the zone * @param guid the current GUID of the character * @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. * 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 guid the GUID to associate with the character; * technically, it has already been assigned and should be findable using `{character}.GUID.guid` * @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 */ - 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. * 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 guid the GUID to associate with the character; * technically, it has already been assigned and should be findable using `{character}.GUID.guid` * @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 */ - 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. diff --git a/common/src/main/scala/net/psforever/objects/continent/IntergalacticCluster.scala b/common/src/main/scala/net/psforever/objects/continent/IntergalacticCluster.scala index 6c4a880a9..bd8dc83ae 100644 --- a/common/src/main/scala/net/psforever/objects/continent/IntergalacticCluster.scala +++ b/common/src/main/scala/net/psforever/objects/continent/IntergalacticCluster.scala @@ -2,10 +2,11 @@ package net.psforever.objects.continent import akka.actor.Actor +import net.psforever.objects.Player 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 for(continent <- continents) { continent.Actor //seed context @@ -17,13 +18,19 @@ class IntergalacticCluster(continents : List[Continent]) extends Actor { case Some(continent) => sender ! IntergalacticCluster.GiveWorld(zoneId, continent) 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 _ => ; } - @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) { None } @@ -42,5 +49,9 @@ class IntergalacticCluster(continents : List[Continent]) extends Actor { object IntergalacticCluster { 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) } diff --git a/common/src/main/scala/net/psforever/objects/continent/Continent.scala b/common/src/main/scala/net/psforever/objects/continent/Zone.scala similarity index 63% rename from common/src/main/scala/net/psforever/objects/continent/Continent.scala rename to common/src/main/scala/net/psforever/objects/continent/Zone.scala index 87b8b9431..f6ff22442 100644 --- a/common/src/main/scala/net/psforever/objects/continent/Continent.scala +++ b/common/src/main/scala/net/psforever/objects/continent/Zone.scala @@ -9,21 +9,22 @@ 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 Continent(zoneId : String, map : String) { +class Zone(id : String, zoneNumber : Int, map : String) { 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 def Actor(implicit context : ActorContext) : ActorRef = { 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") pool.Selector = new RandomSelector 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]() - def ZoneId : String = zoneId + def ZoneId : String = id + + def ZoneNumber : Int = zoneNumber def Map : String = map 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 EquipmentOnGround : ListBuffer[Equipment] = equipmentOnGround + + def ZoneInitialization() : List[GamePacket] = { + List.empty[GamePacket] + } + + def ZoneConfiguration() : List[GamePacket] = { + List.empty[GamePacket] + } } -object Continent { - final def Nowhere : Continent = { Continent("", "") } //TODO needs overrides +object Zone { + final def Nowhere : Zone = { Zone("nowhere", 0, "nowhere") } //TODO needs overrides final case class DropItemOnGround(item : Equipment, pos : Vector3, orient : Vector3) @@ -53,7 +71,9 @@ object Continent { final case class ItemFromGround(player : Player, item : Equipment) - def apply(zoneId : String, map : String) : Continent = { - new Continent(zoneId, map) + final case class ZoneInitialization(list : List[GamePacket]) + + def apply(zoneId : String, zoneNumber : Int, map : String) : Zone = { + new Zone(zoneId, zoneNumber, map) } } diff --git a/common/src/main/scala/net/psforever/objects/continent/ContinentActor.scala b/common/src/main/scala/net/psforever/objects/continent/ZoneActor.scala similarity index 95% rename from common/src/main/scala/net/psforever/objects/continent/ContinentActor.scala rename to common/src/main/scala/net/psforever/objects/continent/ZoneActor.scala index ff771560a..309c129d6 100644 --- a/common/src/main/scala/net/psforever/objects/continent/ContinentActor.scala +++ b/common/src/main/scala/net/psforever/objects/continent/ZoneActor.scala @@ -7,9 +7,9 @@ import net.psforever.packet.game.PlanetSideGUID import scala.annotation.tailrec -class ContinentActor(continent : Continent) extends Actor { +class ZoneActor(continent : Zone) extends Actor { private[this] val log = org.log4s.getLogger - import Continent._ + import Zone._ def receive : Receive = { case DropItemOnGround(item, pos, orient) => diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala index e83768959..178fc6457 100644 --- a/pslogin/src/main/scala/PsLogin.scala +++ b/pslogin/src/main/scala/PsLogin.scala @@ -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.{Continent, IntergalacticCluster} +import net.psforever.objects.continent.{Zone, IntergalacticCluster} import net.psforever.objects.guid.TaskResolver import org.slf4j import org.fusesource.jansi.Ansi._ @@ -219,8 +219,8 @@ object PsLogin { } } - def createContinents() : List[Continent] = { - Continent("home3","map13") :: Nil + def createContinents() : List[Zone] = { + Zone("home3",13,"map13") :: Nil } def main(args : Array[String]) : Unit = { diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 6433fd4fb..ee1c62892 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.{Continent, IntergalacticCluster} +import net.psforever.objects.continent.{Zone, IntergalacticCluster} import net.psforever.objects.entity.IdentifiableEntity import net.psforever.objects.equipment._ import net.psforever.objects.guid.{Task, TaskResolver} @@ -34,7 +34,7 @@ class WorldSessionActor extends Actor with MDCContextAware { var avatarService = Actor.noSender var taskResolver = Actor.noSender var galaxy = Actor.noSender - var continent : Continent = Continent.Nowhere + var continent : Zone = Zone.Nowhere var clientKeepAlive : Cancellable = WorldSessionActor.DefaultCancellable @@ -45,10 +45,12 @@ class WorldSessionActor extends Actor with MDCContextAware { avatarService ! Leave() LivePlayerList.Remove(sessionId) match { case Some(tplayer) => - val guid = tplayer.GUID - avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid)) - taskResolver ! UnregisterAvatar(tplayer) - //TODO normally, the actual player avatar persists a minute or so after the user disconnects + if(tplayer.HasGUID) { + val guid = tplayer.GUID + avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ObjectDelete(guid, guid)) + taskResolver ! UnregisterAvatar(tplayer) + //TODO normally, the actual player avatar persists a minute or so after the user disconnects + } case None => ; } } @@ -417,7 +419,23 @@ 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) + + 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( PacketCoding.CreateGamePacket(0, 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, BroadcastWarpgateUpdateMessage(PlanetSideGUID(13), PlanetSideGUID(1), false, false, true))) // VS Sanctuary: Inactive Warpgate -> Broadcast Warpgate - //LoadMapMessage -> BeginZoningMessage - sendResponse(PacketCoding.CreateGamePacket(0, LoadMapMessage("map13","home3",40100,25,true,3770441820L))) //VS Sanctuary + sendResponse(PacketCoding.CreateGamePacket(0, ZonePopulationUpdateMessage(PlanetSideGUID(13), 414, 138, 0, 138, 0, 138, 0, 138, 0))) + + 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 tplayer.Spawn 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)) 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) => - //avatar-specific 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, CreateShortcutMessage(guid, 1, 0, true, Shortcut.MEDKIT))) - //temporary location - case Continent.ItemFromGround(tplayer, item) => + case Zone.ItemFromGround(tplayer, item) => val obj_guid = item.GUID val player_guid = tplayer.GUID tplayer.Fit(item) match { case Some(slot) => 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)) val definition = item.Definition sendResponse( @@ -500,7 +506,7 @@ class WorldSessionActor extends Actor with MDCContextAware { avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentInHand(player_guid, slot, item)) } 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) => @@ -603,7 +609,7 @@ class WorldSessionActor extends Actor with MDCContextAware { player = Player("IlllIIIlllIlIllIlllIllI", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) player.Position = Vector3(3674.8438f, 2726.789f, 91.15625f) player.Orientation = Vector3(0f, 0f, 90f) - player.Continent = "home3" + //player.Continent = "home3" player.Slot(0).Equipment = beamer1 player.Slot(2).Equipment = suppressor1 player.Slot(4).Equipment = forceblade1 @@ -684,13 +690,14 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, ActionResultMessage(false, Some(1)))) case CharacterRequestAction.Select => LivePlayerList.Add(sessionId, player) - //check can spawn on last continent/location from player - //if yes, get continent guid accessors - //if no, get sanctuary guid accessors and reset the player's expectations + //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") import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global + clientKeepAlive.cancel clientKeepAlive = context.system.scheduler.schedule(0 seconds, 500 milliseconds, self, PokeClient()) case default => log.error("Unsupported " + default + " in " + msg) @@ -701,27 +708,25 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ BeginZoningMessage() => 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(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, ZonePopulationUpdateMessage(PlanetSideGUID(13), 414, 138, 0, 138, 0, 138, 0, 138, 0))) sendResponse(PacketCoding.CreateGamePacket(0, TimeOfDayMessage(1191182336))) 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 - val playerContinent = player.Continent - val player_guid = player.GUID - LivePlayerList.WorldPopulation({ case (_, char : Player) => char.Continent == playerContinent && char.HasGUID && char.GUID != player_guid}).foreach(char => { + //load active players in zone + LivePlayerList.ZonePopulation(continent.ZoneNumber, _ => true).foreach(char => { sendResponse( PacketCoding.CreateGamePacket(0, 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 => { val definition = item.Definition sendResponse( @@ -735,7 +740,7 @@ class WorldSessionActor extends Actor with MDCContextAware { ) }) - avatarService ! Join(playerContinent) + avatarService ! Join(player.Continent) 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) => @@ -809,7 +814,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 ! 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))) 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) => 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) => log.info("Reload: " + msg) @@ -939,7 +944,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val pos = player.Position val playerOrient = player.Orientation 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 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 resolver ! scala.util.Success(localPlayer) } + + override def onFailure(ex : Throwable) : Unit = { + localAnnounce ! PlayerFailedToLoad(localPlayer) //alerts WSA + } }, RegisterObjectTask(tplayer) +: (holsterTasks ++ fifthHolsterTask ++ inventoryTasks) ) } @@ -1547,9 +1556,9 @@ class WorldSessionActor extends Actor with MDCContextAware { } } -final case class ResponseToSelf(pkt : GamePacket) - object WorldSessionActor { + final case class ResponseToSelf(pkt : GamePacket) + private final case class PokeClient() private final case class ServerLoaded() private final case class PlayerLoaded(tplayer : Player)