diff --git a/common/src/main/scala/net/psforever/objects/continent/Continent.scala b/common/src/main/scala/net/psforever/objects/continent/Continent.scala new file mode 100644 index 00000000..e084babb --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/continent/Continent.scala @@ -0,0 +1,59 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.continent + +import akka.actor.{ActorContext, ActorRef, Props} +import net.psforever.objects.Player +import net.psforever.objects.entity.IdentifiableEntity +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.game.PlanetSideGUID +import net.psforever.types.Vector3 + +import scala.collection.mutable.ListBuffer + +class Continent(zoneId : String, map : String) { + private var actor = ActorRef.noSender + private val guid : NumberPoolHub = new NumberPoolHub(new LimitedNumberSource(65536)) + 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") + + val pool = guid.AddPool("pool", (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") + } + actor + } + + private val equipmentOnGround : ListBuffer[Equipment] = ListBuffer[Equipment]() + + def ZoneId : String = zoneId + + def Map : String = map + + def GUID : ActorRef = accessor + + def GUID(object_guid : PlanetSideGUID) : Option[IdentifiableEntity] = guid(object_guid.guid) + + def EquipmentOnGround : ListBuffer[Equipment] = equipmentOnGround +} + +object Continent { + final def Nowhere : Continent = { Continent("", "") } //TODO needs overrides + + final case class DropItemOnGround(item : Equipment, pos : Vector3, orient : Vector3) + + final case class GetItemOnGround(player : Player, item_guid : PlanetSideGUID) + + final case class GiveItemFromGround(player : Player, item : Equipment) + + def apply(zoneId : String, map : String) : Continent = { + new Continent(zoneId, map) + } +} diff --git a/common/src/main/scala/net/psforever/objects/continent/ContinentActor.scala b/common/src/main/scala/net/psforever/objects/continent/ContinentActor.scala new file mode 100644 index 00000000..c81bf90d --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/continent/ContinentActor.scala @@ -0,0 +1,62 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.continent + +import akka.actor.Actor +import net.psforever.objects.equipment.Equipment +import net.psforever.packet.game.PlanetSideGUID + +import scala.annotation.tailrec + +class ContinentActor(continent : Continent) extends Actor { + private[this] val log = org.log4s.getLogger + import Continent._ + + def receive : Receive = { + case DropItemOnGround(item, pos, orient) => + item.Position = pos + item.Orientation = orient + continent.EquipmentOnGround += item + + case GetItemOnGround(player, item_guid) => + FindItemOnGround(item_guid) match { + case Some(item) => + sender ! GiveItemFromGround(player, item) + case None => + log.warn(s"item on ground $item_guid was requested by $player for pickup but was not found") + } + + case _ => ; + } + + private def FindItemOnGround(item_guid : PlanetSideGUID) : Option[Equipment] = { + recursiveFindItemOnGround(continent.EquipmentOnGround.iterator, item_guid) match { + case Some(index) => + Some(continent.EquipmentOnGround.remove(index)) + case None => + None + } + } + + /** + * Shift through objects on the ground to find the location of a specific item. + * @param iter an `Iterator` of `Equipment` + * @param item_guid the global unique identifier of the piece of `Equipment` being sought + * @param index the current position in the array-list structure used to create the `Iterator` + * @return the index of the object matching `item_guid`, if found; + * `None`, otherwise + */ + @tailrec private def recursiveFindItemOnGround(iter : Iterator[Equipment], item_guid : PlanetSideGUID, index : Int = 0) : Option[Int] = { + if(!iter.hasNext) { + None + } + else { + val item : Equipment = iter.next + if(item.GUID == item_guid) { + Some(index) + } + else { + recursiveFindItemOnGround(iter, item_guid, index + 1) + } + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/continent/IntergalacticCluster.scala b/common/src/main/scala/net/psforever/objects/continent/IntergalacticCluster.scala new file mode 100644 index 00000000..6c4a880a --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/continent/IntergalacticCluster.scala @@ -0,0 +1,46 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.continent + +import akka.actor.Actor + +import scala.annotation.tailrec + +class IntergalacticCluster(continents : List[Continent]) extends Actor { + //private[this] val log = org.log4s.getLogger + for(continent <- continents) { + continent.Actor //seed context + } + + def receive : Receive = { + case IntergalacticCluster.GetWorld(zoneId) => + findWorldInCluster(continents.iterator, zoneId) match { + case Some(continent) => + sender ! IntergalacticCluster.GiveWorld(zoneId, continent) + case None => + sender ! IntergalacticCluster.GiveWorld(zoneId, Continent.Nowhere) + } + + case _ => ; + } + + @tailrec private def findWorldInCluster(iter : Iterator[Continent], zoneId : String) : Option[Continent] = { + 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 : Continent) +} diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala index fab1d3e7..e8376895 100644 --- a/pslogin/src/main/scala/PsLogin.scala +++ b/pslogin/src/main/scala/PsLogin.scala @@ -12,10 +12,8 @@ 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.guid.{NumberPoolHub, TaskResolver} -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.objects.continent.{Continent, IntergalacticCluster} +import net.psforever.objects.guid.TaskResolver import org.slf4j import org.fusesource.jansi.Ansi._ import org.fusesource.jansi.Ansi.Color._ @@ -89,7 +87,7 @@ object PsLogin { configurator.doConfigure(logfile) } catch { - case je : JoranException => ; + case _ : JoranException => ; } if(loggerHasErrors(lc)) { @@ -202,23 +200,9 @@ object PsLogin { */ val serviceManager = ServiceManager.boot - - //experimental guid code - val hub = new NumberPoolHub(new LimitedNumberSource(65536)) - val pool1 = hub.AddPool("test1", (400 to 599).toList) - val poolActor1 = system.actorOf(Props(classOf[NumberPoolActor], pool1), name = "poolActor1") - pool1.Selector = new RandomSelector - val pool2 = hub.AddPool("test2", (600 to 799).toList) - val poolActor2 = system.actorOf(Props(classOf[NumberPoolActor], pool2), name = "poolActor2") - pool2.Selector = new RandomSelector - - serviceManager ! ServiceManager.Register(Props(classOf[NumberPoolAccessorActor], hub, pool1, poolActor1), "accessor1") - serviceManager ! ServiceManager.Register(Props(classOf[NumberPoolAccessorActor], hub, pool2, poolActor2), "accessor2") - - //task resolver serviceManager ! ServiceManager.Register(RandomPool(50).props(Props[TaskResolver]), "taskResolver") - serviceManager ! ServiceManager.Register(Props[AvatarService], "avatar") + serviceManager ! ServiceManager.Register(Props(classOf[IntergalacticCluster], createContinents()), "galaxy") /** Create two actors for handling the login and world server endpoints */ loginRouter = Props(new SessionRouter("Login", loginTemplate)) @@ -235,6 +219,10 @@ object PsLogin { } } + def createContinents() : List[Continent] = { + Continent("home3","map13") :: Nil + } + def main(args : Array[String]) : Unit = { Locale.setDefault(Locale.US); // to have floats with dots, not comma... this.args = args diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 9a3e3931..4aae3f2a 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -11,6 +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.entity.IdentifiableEntity import net.psforever.objects.equipment._ import net.psforever.objects.guid.{Task, TaskResolver} @@ -24,21 +25,16 @@ import scala.annotation.tailrec import scala.util.Success class WorldSessionActor extends Actor with MDCContextAware { + import WorldSessionActor._ private[this] val log = org.log4s.getLogger - private final case class PokeClient() - private final case class ServerLoaded() - private final case class PlayerLoaded(tplayer : Player) - private final case class ListAccountCharacters() - private final case class SetCurrentAvatar(tplayer : Player) - private final case class Continent_GiveItemFromGround(tplyaer : Player, item : Option[Equipment]) //TODO wrong place, move later - var sessionId : Long = 0 var leftRef : ActorRef = ActorRef.noSender var rightRef : ActorRef = ActorRef.noSender var avatarService = Actor.noSender - var accessor = Actor.noSender var taskResolver = Actor.noSender + var galaxy = Actor.noSender + var continent : Continent = Continent.Nowhere var clientKeepAlive : Cancellable = WorldSessionActor.DefaultCancellable @@ -71,8 +67,8 @@ class WorldSessionActor extends Actor with MDCContextAware { } context.become(Started) ServiceManager.serviceManager ! Lookup("avatar") - ServiceManager.serviceManager ! Lookup("accessor1") ServiceManager.serviceManager ! Lookup("taskResolver") + ServiceManager.serviceManager ! Lookup("galaxy") case _ => log.error("Unknown message") @@ -83,12 +79,12 @@ class WorldSessionActor extends Actor with MDCContextAware { case ServiceManager.LookupResult("avatar", endpoint) => avatarService = endpoint log.info("ID: " + sessionId + " Got avatar service " + endpoint) - case ServiceManager.LookupResult("accessor1", endpoint) => - accessor = endpoint - log.info("ID: " + sessionId + " Got guid service " + endpoint) case ServiceManager.LookupResult("taskResolver", endpoint) => taskResolver = endpoint log.info("ID: " + sessionId + " Got task resolver service " + endpoint) + case ServiceManager.LookupResult("galaxy", endpoint) => + galaxy = endpoint + log.info("ID: " + sessionId + " Got galaxy service " + endpoint) case ctrl @ ControlPacket(_, _) => handlePktContainer(ctrl) @@ -413,6 +409,11 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, CharacterInfoMessage(0, PlanetSideZoneID(1), 0, PlanetSideGUID(0), true, 0))) + case IntergalacticCluster.GiveWorld(zoneId, zone) => + player.Continent = zoneId + continent = zone + taskResolver ! RegisterAvatar(player) + case PlayerLoaded(tplayer) => log.info(s"Player $tplayer has been loaded") //init for whole server @@ -456,6 +457,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 @@ -488,7 +501,7 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(pkt) case default => - failWithError(s"Invalid packet class received: $default") + log.warn(s"Invalid packet class received: $default") } def handlePkt(pkt : PlanetSidePacket) : Unit = pkt match { @@ -496,7 +509,7 @@ class WorldSessionActor extends Actor with MDCContextAware { handleControlPkt(ctrl) case game : PlanetSideGamePacket => handleGamePkt(game) - case default => failWithError(s"Invalid packet class received: $default") + case default => log.error(s"Invalid packet class received: $default") } def handlePktContainer(pkt : PlanetSidePacketContainer) : Unit = pkt match { @@ -504,7 +517,7 @@ class WorldSessionActor extends Actor with MDCContextAware { handleControlPkt(ctrlPkt) case game @ GamePacket(opcode, seq, gamePkt) => handleGamePkt(gamePkt) - case default => failWithError(s"Invalid packet container class received: $default") + case default => log.warn(s"Invalid packet container class received: $default") } def handleControlPkt(pkt : PlanetSideControlPacket) = { @@ -667,7 +680,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //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 - taskResolver ! RegisterAvatar(player) + galaxy ! IntergalacticCluster.GetWorld("home3") import scala.concurrent.duration._ import scala.concurrent.ExecutionContext.Implicits.global @@ -1181,7 +1194,7 @@ class WorldSessionActor extends Actor with MDCContextAware { TaskResolver.GiveTask( new Task() { private val localObject = obj - private val localAccessor = accessor + private val localAccessor = continent.GUID override def isComplete : Task.Resolution.Value = { try { @@ -1316,7 +1329,7 @@ class WorldSessionActor extends Actor with MDCContextAware { TaskResolver.GiveTask( new Task() { private val localObject = obj - private val localAccessor = accessor + private val localAccessor = continent.GUID override def isComplete : Task.Resolution.Value = { try { @@ -1561,7 +1574,7 @@ class WorldSessionActor extends Actor with MDCContextAware { def failWithError(error : String) = { log.error(error) - //sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) + sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) } def sendResponse(cont : PlanetSidePacketContainer) : Unit = { @@ -1581,7 +1594,14 @@ class WorldSessionActor extends Actor with MDCContextAware { } object WorldSessionActor { - final case class ResponseToSelf(pkt : GamePacket) + private final case class ResponseToSelf(pkt : GamePacket) + private final case class PokeClient() + private final case class ServerLoaded() + private final case class PlayerLoaded(tplayer : Player) + private final case class PlayerFailedToLoad(tplayer : Player) + private final case class ListAccountCharacters() + private final case class SetCurrentAvatar(tplayer : Player) + private final case class Continent_GiveItemFromGround(tplyaer : Player, item : Option[Equipment]) //TODO wrong place, move later /** * A placeholder `Cancellable` object.