mirror of
https://github.com/2revoemag/PSF-BotServer.git
synced 2026-03-04 04:30:21 +00:00
Stripped down LivePlayerList functionality, moving player lists onto the appropriate corresponding zones. Corpses are now created, stored, and deleted.
This commit is contained in:
parent
8a21df429b
commit
20b7726653
10 changed files with 526 additions and 264 deletions
|
|
@ -1,9 +1,6 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects
|
||||
|
||||
import net.psforever.packet.game.PlanetSideGUID
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.concurrent.{Map, TrieMap}
|
||||
|
||||
/**
|
||||
|
|
@ -12,157 +9,42 @@ 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]
|
||||
/** 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])
|
||||
private val sessionMap : Map[Long, Avatar] = new TrieMap[Long, Avatar]
|
||||
|
||||
def WorldPopulation(predicate : ((_, Player)) => Boolean) : List[Player] = {
|
||||
def WorldPopulation(predicate : ((_, Avatar)) => Boolean) : List[Avatar] = {
|
||||
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
|
||||
def Add(sessionId : Long, avatar : Avatar) : Boolean = {
|
||||
sessionMap.values.find(char => char.equals(avatar)) match {
|
||||
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
|
||||
sessionMap.putIfAbsent(sessionId, avatar).isEmpty
|
||||
case Some(_) =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
def Remove(sessionId : Long) : Option[Player] = {
|
||||
sessionMap.remove(sessionId) match {
|
||||
case Some(char) =>
|
||||
zoneMap.foreach(zone => {
|
||||
recursiveRemoveSession(zone.iterator, sessionId) match {
|
||||
case Some(guid) =>
|
||||
zone.remove(guid)
|
||||
case None => ;
|
||||
}
|
||||
})
|
||||
Some(char)
|
||||
case None =>
|
||||
None
|
||||
}
|
||||
def Remove(sessionId : Long) : Option[Avatar] = {
|
||||
sessionMap.remove(sessionId)
|
||||
}
|
||||
|
||||
@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(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(zone: Int, sessionId : Long, guid : PlanetSideGUID) : Boolean = Assign(zone, sessionId, guid.guid)
|
||||
|
||||
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 =>
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
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[Avatar] = {
|
||||
val list = sessionMap.values.toList
|
||||
sessionMap.clear
|
||||
zoneMap.foreach(map => map.clear())
|
||||
list
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
* 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>
|
||||
* The mapping system is tightly coupled between the `Avatar` class and to an instance of `WorldSessionActor`.
|
||||
* <br>
|
||||
* Use:<br>
|
||||
* 1) When a users logs in during `WorldSessionActor`, associate that user's session id and the character.<br>
|
||||
* `LivePlayerList.Add(session, player)`<br>
|
||||
* 2) When that user's chosen character is declared his avatar using `SetCurrentAvatarMessage`,
|
||||
* also associate the user's session with their current GUID.<br>
|
||||
* `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>
|
||||
* 4a) In between the previous two steps, a user's character may be referenced by their current GUID.<br>
|
||||
* `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>
|
||||
* 1) When a users logs in during `WorldSessionActor`, associate that user's session id and their character (avatar).<br>
|
||||
* `LivePlayerList.Add(session, avatar)`<br>
|
||||
* 2) In between the previous two steps, a range of characters may be queried based on provided statistics.<br>
|
||||
* `LivePlayerList.WorldPopulation(...)`<br>
|
||||
* `LivePlayerList.ZonePopulation(zone, ...)`<br>
|
||||
* 5) When the user navigates away from a region completely, their entry is forgotten.<br>
|
||||
* `LivePlayerList.Drop(zone, guid)`<br>
|
||||
* 6) When the user leaves the game entirely, his character's entries are removed from the mappings.<br>
|
||||
* 3) When the user leaves the game entirely, his character's entry is removed from the mapping.<br>
|
||||
* `LivePlayerList.Remove(session)`
|
||||
*/
|
||||
object LivePlayerList {
|
||||
|
|
@ -179,100 +61,28 @@ object LivePlayerList {
|
|||
* @param predicate the conditions for filtering the live `Player`s
|
||||
* @return a list of users's `Player`s that fit the criteria
|
||||
*/
|
||||
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)
|
||||
def WorldPopulation(predicate : ((_, Avatar)) => Boolean) : List[Avatar] = Instance.WorldPopulation(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.
|
||||
* @param sessionId the session
|
||||
* @param player the character
|
||||
* @param avatar the character
|
||||
* @return `true`, if the session was association was made; `false`, otherwise
|
||||
*/
|
||||
def Add(sessionId : Long, player : Player) : Boolean = Instance.Add(sessionId, player)
|
||||
def Add(sessionId : Long, avatar : Avatar) : Boolean = Instance.Add(sessionId, avatar)
|
||||
|
||||
/**
|
||||
* Remove all entries related to the given session identifier from the mappings.
|
||||
* The player no longer counts as "online."
|
||||
* This function cleans up __all__ associations - those created by `Add`, and those created by `Assign`.
|
||||
* The character no longer counts as "online."
|
||||
* @param sessionId the session
|
||||
* @return any character that was afffected by the mapping removal
|
||||
*/
|
||||
def Remove(sessionId : Long) : Option[Player] = Instance.Remove(sessionId)
|
||||
|
||||
/**
|
||||
* 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(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(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(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(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)
|
||||
def Remove(sessionId : Long) : Option[Avatar] = Instance.Remove(sessionId)
|
||||
|
||||
/**
|
||||
* Hastily remove all mappings and ids.
|
||||
* @return an unsorted list of the characters that were still online
|
||||
*/
|
||||
def Shutdown : List[Player] = Instance.Shutdown
|
||||
def Shutdown : List[Avatar] = Instance.Shutdown
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.definition.converter
|
||||
|
||||
import net.psforever.objects.{EquipmentSlot, Player}
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, DetailedCharacterData, DrawnSlot, InternalSlot, InventoryData, PlacementData, RibbonBars}
|
||||
import net.psforever.types.{CharacterGender, GrenadeState}
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
class CorpseConverter extends AvatarConverter {
|
||||
override def ConstructorData(obj : Player) : Try[CharacterData] =
|
||||
Failure(new Exception("CorpseConverter should not be used to generate CharacterData"))
|
||||
|
||||
override def DetailedConstructorData(obj : Player) : Try[DetailedCharacterData] = {
|
||||
Success(
|
||||
DetailedCharacterData(
|
||||
MakeAppearanceData(obj),
|
||||
0, 0, 0, 0, 0, 0, 0,
|
||||
Nil, Nil, Nil, Nil,
|
||||
None,
|
||||
InventoryData((MakeHolsters(obj) ++ MakeInventory(obj)).sortBy(_.parentSlot)),
|
||||
DrawnSlot.None
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Compose some data from a `Player` into a representation common to both `CharacterData` and `DetailedCharacterData`.
|
||||
* @param obj the `Player` game object
|
||||
* @return the resulting `CharacterAppearanceData`
|
||||
*/
|
||||
private def MakeAppearanceData(obj : Player) : CharacterAppearanceData = {
|
||||
CharacterAppearanceData(
|
||||
PlacementData(obj.Position, obj.Orientation),
|
||||
BasicCharacterData(obj.Name, obj.Faction, CharacterGender.Male, 0, 0),
|
||||
0,
|
||||
false,
|
||||
false,
|
||||
obj.ExoSuit,
|
||||
"",
|
||||
0,
|
||||
true,
|
||||
obj.Orientation.y, //TODO is this important?
|
||||
0,
|
||||
true,
|
||||
GrenadeState.None,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
RibbonBars()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a player with an inventory, convert the contents of that inventory into converted-decoded packet data.
|
||||
* The inventory is not represented in a `0x17` `Player`, so the conversion is only valid for `0x18` avatars.
|
||||
* It will always be "`Detailed`".
|
||||
* @param obj the `Player` game object
|
||||
* @return a list of all items that were in the inventory in decoded packet form
|
||||
*/
|
||||
private def MakeInventory(obj : Player) : List[InternalSlot] = {
|
||||
obj.Inventory.Items
|
||||
.map({
|
||||
case(_, item) =>
|
||||
val equip : Equipment = item.obj
|
||||
BuildEquipment(item.start, equip)
|
||||
}).toList
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a player with equipment holsters, convert the contents of those holsters into converted-decoded packet data.
|
||||
* The decoded packet form is determined by the function in the parameters as both `0x17` and `0x18` conversions are available,
|
||||
* with exception to the contents of the fifth slot.
|
||||
* The fifth slot is only represented if the `Player` is an `0x18` type.
|
||||
* @param obj the `Player` game object
|
||||
* @return a list of all items that were in the holsters in decoded packet form
|
||||
*/
|
||||
private def MakeHolsters(obj : Player) : List[InternalSlot] = {
|
||||
recursiveMakeHolsters(obj.Holsters().iterator)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given some equipment holsters, convert the contents of those holsters into converted-decoded packet data.
|
||||
* @param iter an `Iterator` of `EquipmentSlot` objects that are a part of the player's holsters
|
||||
* @param list the current `List` of transformed data
|
||||
* @param index which holster is currently being explored
|
||||
* @return the `List` of inventory data created from the holsters
|
||||
*/
|
||||
@tailrec private def recursiveMakeHolsters(iter : Iterator[EquipmentSlot], list : List[InternalSlot] = Nil, index : Int = 0) : List[InternalSlot] = {
|
||||
if(!iter.hasNext) {
|
||||
list
|
||||
}
|
||||
else {
|
||||
val slot : EquipmentSlot = iter.next
|
||||
if(slot.Equipment.isDefined) {
|
||||
val equip : Equipment = slot.Equipment.get
|
||||
recursiveMakeHolsters(
|
||||
iter,
|
||||
list :+ BuildEquipment(index, equip),
|
||||
index + 1
|
||||
)
|
||||
}
|
||||
else {
|
||||
recursiveMakeHolsters(iter, list, index + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder method for turning an object into `0x17` decoded packet form.
|
||||
* @param index the position of the object
|
||||
* @param equip the game object
|
||||
* @return the game object in decoded packet form
|
||||
*/
|
||||
private def BuildEquipment(index : Int, equip : Equipment) : InternalSlot = {
|
||||
InternalSlot(equip.Definition.ObjectId, equip.GUID, index, equip.Definition.Packet.DetailedConstructorData(equip).get)
|
||||
}
|
||||
}
|
||||
|
||||
object CorpseConverter {
|
||||
val converter = new CorpseConverter
|
||||
}
|
||||
|
|
@ -22,7 +22,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() {
|
|||
PlanetSideGUID(0) //if(obj.Owner.isDefined) { obj.Owner.get } else { PlanetSideGUID(0) } //TODO is this really Owner?
|
||||
),
|
||||
0,
|
||||
obj.Health / obj.MaxHealth * 255, //TODO not precise
|
||||
255 * obj.Health / obj.MaxHealth, //TODO not precise
|
||||
false, false,
|
||||
obj.DeploymentState,
|
||||
false,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package net.psforever.objects.zones
|
|||
|
||||
import akka.actor.{ActorContext, ActorRef, Props}
|
||||
import akka.routing.RandomPool
|
||||
import net.psforever.objects.{PlanetSideGameObject, Player, Vehicle}
|
||||
import net.psforever.objects.{Avatar, PlanetSideGameObject, Player, Vehicle}
|
||||
import net.psforever.objects.equipment.Equipment
|
||||
import net.psforever.objects.guid.NumberPoolHub
|
||||
import net.psforever.objects.guid.actor.UniqueNumberSystem
|
||||
|
|
@ -14,6 +14,7 @@ import net.psforever.packet.game.PlanetSideGUID
|
|||
import net.psforever.types.Vector3
|
||||
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.concurrent.TrieMap
|
||||
import scala.collection.mutable.ListBuffer
|
||||
import scala.collection.immutable.{Map => PairMap}
|
||||
|
||||
|
|
@ -54,6 +55,12 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
|
|||
private var vehicles : List[Vehicle] = List[Vehicle]()
|
||||
/** */
|
||||
private var transport : ActorRef = ActorRef.noSender
|
||||
/** */
|
||||
private val players : TrieMap[Avatar, Option[Player]] = TrieMap[Avatar, Option[Player]]()
|
||||
/** */
|
||||
private var corpses : List[Player] = List[Player]()
|
||||
/** */
|
||||
private var population : ActorRef = ActorRef.noSender
|
||||
|
||||
private var buildings : PairMap[Int, Building] = PairMap.empty[Int, Building]
|
||||
|
||||
|
|
@ -80,6 +87,7 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
|
|||
accessor = context.actorOf(RandomPool(25).props(Props(classOf[UniqueNumberSystem], guid, UniqueNumberSystem.AllocateNumberPoolActors(guid))), s"$Id-uns")
|
||||
ground = context.actorOf(Props(classOf[ZoneGroundActor], equipmentOnGround), s"$Id-ground")
|
||||
transport = context.actorOf(Props(classOf[ZoneVehicleActor], this), s"$Id-vehicles")
|
||||
population = context.actorOf(Props(classOf[ZonePopulationActor], this), s"$Id-players")
|
||||
|
||||
Map.LocalObjects.foreach({ builderObject => builderObject.Build })
|
||||
MakeBuildings(context)
|
||||
|
|
@ -179,6 +187,12 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
|
|||
|
||||
def Vehicles : List[Vehicle] = vehicles
|
||||
|
||||
def Players : List[Avatar] = players.keys.toList
|
||||
|
||||
def LivePlayers : List[Player] = players.values.collect( { case Some(tplayer) => tplayer }).toList
|
||||
|
||||
def Corpses : List[Player] = corpses
|
||||
|
||||
def AddVehicle(vehicle : Vehicle) : List[Vehicle] = {
|
||||
vehicles = vehicles :+ vehicle
|
||||
Vehicles
|
||||
|
|
@ -208,6 +222,78 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
|
|||
}
|
||||
}
|
||||
|
||||
def PopulationJoin(avatar : Avatar) : Boolean = {
|
||||
players.get(avatar) match {
|
||||
case Some(_) =>
|
||||
false
|
||||
case None =>
|
||||
players += avatar -> None
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
def PopulationLeave(avatar : Avatar) : Option[Player] = {
|
||||
players.remove(avatar) match {
|
||||
case None =>
|
||||
None
|
||||
case Some(tplayer) =>
|
||||
tplayer
|
||||
}
|
||||
}
|
||||
|
||||
def PopulationSpawn(avatar : Avatar, player : Player) : Option[Player] = {
|
||||
players.get(avatar) match {
|
||||
case None =>
|
||||
None
|
||||
case Some(tplayer) =>
|
||||
tplayer match {
|
||||
case Some(aplayer) =>
|
||||
Some(aplayer)
|
||||
case None =>
|
||||
players(avatar) = Some(player)
|
||||
Some(player)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def PopulationRelease(avatar : Avatar) : Option[Player] = {
|
||||
players.get(avatar) match {
|
||||
case None =>
|
||||
None
|
||||
case Some(tplayer) =>
|
||||
players(avatar) = None
|
||||
tplayer
|
||||
}
|
||||
}
|
||||
|
||||
def CorpseAdd(player : Player) : Unit = {
|
||||
if(player.isBackpack && recursiveFindCorpse(players.values.filter(_.nonEmpty).map(_.get).iterator, player.GUID).isEmpty) {
|
||||
corpses = corpses :+ player
|
||||
}
|
||||
}
|
||||
|
||||
def CorpseRemove(player : Player) : Unit = {
|
||||
recursiveFindCorpse(corpses.iterator, player.GUID) match {
|
||||
case Some(index) =>
|
||||
corpses = corpses.take(index-1) ++ corpses.drop(index)
|
||||
case None => ;
|
||||
}
|
||||
}
|
||||
|
||||
@tailrec final def recursiveFindCorpse(iter : Iterator[Player], guid : PlanetSideGUID, index : Int = 0) : Option[Int] = {
|
||||
if(!iter.hasNext) {
|
||||
None
|
||||
}
|
||||
else {
|
||||
if(iter.next.GUID == guid) {
|
||||
Some(index)
|
||||
}
|
||||
else {
|
||||
recursiveFindCorpse(iter, guid, index + 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinate `Equipment` that has been dropped on the ground or to-be-dropped on the ground.
|
||||
* @return synchronized reference to the ground
|
||||
|
|
@ -220,6 +306,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
|
|||
|
||||
def Transport : ActorRef = transport
|
||||
|
||||
def Population : ActorRef = population
|
||||
|
||||
def Buildings : Map[Int, Building] = buildings
|
||||
|
||||
def Building(id : Int) : Option[Building] = {
|
||||
|
|
@ -266,6 +354,21 @@ object Zone {
|
|||
*/
|
||||
final case class Init()
|
||||
|
||||
object Population {
|
||||
final case class Join(avatar : Avatar)
|
||||
final case class Leave(avatar : Avatar)
|
||||
final case class Spawn(avatar : Avatar, player : Player)
|
||||
final case class Release(avatar : Avatar)
|
||||
final case class PlayerHasLeft(zone : Zone, player : Option[Player]) //Leave(avatar), but still has a player
|
||||
final case class PlayerAlreadySpawned(player : Player) //Spawn(avatar, player), but avatar already has a player
|
||||
final case class PlayerCanNotSpawn(zone : Zone, player : Player) //Spawn(avatar, player), but avatar did not initially Join(avatar)
|
||||
}
|
||||
|
||||
object Corpse {
|
||||
final case class Add(player : Player)
|
||||
final case class Remove(player : Player)
|
||||
}
|
||||
|
||||
/**
|
||||
* Message to relinguish an item and place in on the ground.
|
||||
* @param item the piece of `Equipment`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) 2017 PSForever
|
||||
package net.psforever.objects.zones
|
||||
|
||||
import akka.actor.Actor
|
||||
|
||||
class ZonePopulationActor(zone : Zone) extends Actor {
|
||||
def receive : Receive = {
|
||||
case Zone.Population.Join(avatar) =>
|
||||
zone.PopulationJoin(avatar)
|
||||
|
||||
case Zone.Population.Leave(avatar) =>
|
||||
zone.PopulationLeave(avatar) match {
|
||||
case None => ;
|
||||
case player @ Some(_) =>
|
||||
sender ! Zone.Population.PlayerHasLeft(zone, player)
|
||||
}
|
||||
|
||||
case Zone.Population.Spawn(avatar, player) =>
|
||||
zone.PopulationSpawn(avatar, player) match {
|
||||
case Some(tplayer) =>
|
||||
if(tplayer != player) {
|
||||
sender ! Zone.Population.PlayerCanNotSpawn(zone, player)
|
||||
}
|
||||
case None =>
|
||||
sender ! Zone.Population.PlayerCanNotSpawn(zone, player)
|
||||
}
|
||||
|
||||
case Zone.Population.Release(avatar) =>
|
||||
zone.PopulationRelease(avatar) match {
|
||||
case Some(_) => ;
|
||||
case None =>
|
||||
sender ! Zone.Population.PlayerHasLeft(zone, None)
|
||||
}
|
||||
|
||||
case Zone.Corpse.Add(player) =>
|
||||
zone.CorpseAdd(player)
|
||||
|
||||
case Zone.Corpse.Remove(player) =>
|
||||
zone.CorpseRemove(player)
|
||||
|
||||
case _ => ;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue