diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
index 4fccc47f..1326f447 100644
--- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
+++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala
@@ -3,11 +3,16 @@ package net.psforever.objects
import net.psforever.objects.definition._
import net.psforever.objects.definition.converter.{CommandDetonaterConverter, LockerContainerConverter, REKConverter}
+import net.psforever.objects.doors.{DoorDefinition, IFFLockDefinition}
import net.psforever.objects.equipment.CItem.DeployedItem
import net.psforever.objects.equipment._
import net.psforever.objects.inventory.InventoryTile
+<<<<<<< c5ae9e477ccade5759eac2e9526ba898d0e8f16b
import net.psforever.objects.terminals.{CertTerminalDefinition, OrderTerminalDefinition}
import net.psforever.packet.game.objectcreate.ObjectClass
+=======
+import net.psforever.objects.terminals.OrderTerminalDefinition
+>>>>>>> automated doors, IFF locks, and bases thus that only permissible doors can be opened by players of correct faction alignment; Base is just a prototype example, hastily created for this functionality; LocalService will eventually be used for doors messages (and other things)
import net.psforever.types.PlanetSideEmpire
object GlobalDefinitions {
@@ -1242,4 +1247,9 @@ object GlobalDefinitions {
order_terminal = new OrderTerminalDefinition
val
cert_terminal = new CertTerminalDefinition
+
+ val
+ external_lock = new IFFLockDefinition
+ val
+ door = new DoorDefinition
}
diff --git a/common/src/main/scala/net/psforever/objects/doors/Base.scala b/common/src/main/scala/net/psforever/objects/doors/Base.scala
new file mode 100644
index 00000000..9fa8b3d9
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/doors/Base.scala
@@ -0,0 +1,17 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.doors
+
+import net.psforever.types.PlanetSideEmpire
+
+class Base(private val id : Int) {
+ private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
+
+ def Id : Int = id
+
+ def Faction : PlanetSideEmpire.Value = faction
+
+ def Faction_=(emp : PlanetSideEmpire.Value) : PlanetSideEmpire.Value = {
+ faction = emp
+ Faction
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/doors/Door.scala b/common/src/main/scala/net/psforever/objects/doors/Door.scala
index 623bcf42..179256f9 100644
--- a/common/src/main/scala/net/psforever/objects/doors/Door.scala
+++ b/common/src/main/scala/net/psforever/objects/doors/Door.scala
@@ -4,7 +4,6 @@ package net.psforever.objects.doors
import akka.actor.{ActorContext, ActorRef, Props}
import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.packet.game.UseItemMessage
-import net.psforever.types.PlanetSideEmpire
/**
* na
@@ -22,26 +21,29 @@ class Door(ddef : DoorDefinition) extends PlanetSideGameObject {
}
private var openState : Boolean = false
- private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.NEUTRAL
- private var hackedBy : Option[PlanetSideEmpire.Value] = None
+ private var lockState : Boolean = false
def Open : Boolean = openState
- def Faction : PlanetSideEmpire.Value = faction
-
- def Convert(toFaction : PlanetSideEmpire.Value) : Unit = {
- hackedBy = None
- faction = toFaction
+ def Open_=(open : Boolean) : Boolean = {
+ openState = open
+ Open
}
- def Request(player : Player, msg : UseItemMessage) : Door.Exchange = {
- if(!openState) {
- if(faction == PlanetSideEmpire.NEUTRAL || player.Faction == faction) {
- Door.OpenEvent()
- }
- else {
- Door.NoEvent()
- }
+ def Locked : Boolean = lockState
+
+ def Locked_=(lock : Boolean) : Boolean = {
+ lockState = lock
+ Locked
+ }
+
+ def Use(player : Player, msg : UseItemMessage) : Door.Exchange = {
+ if(!lockState && !openState) {
+ openState = true
+ Door.OpenEvent()
+ }
+ else if(openState) {
+ Door.CloseEvent()
}
else {
Door.NoEvent()
@@ -52,7 +54,7 @@ class Door(ddef : DoorDefinition) extends PlanetSideGameObject {
}
object Door {
- final case class Request(player : Player, msg : UseItemMessage)
+ final case class Use(player : Player, msg : UseItemMessage)
sealed trait Exchange
diff --git a/common/src/main/scala/net/psforever/objects/doors/DoorControl.scala b/common/src/main/scala/net/psforever/objects/doors/DoorControl.scala
index 0491a59a..e98fb9b7 100644
--- a/common/src/main/scala/net/psforever/objects/doors/DoorControl.scala
+++ b/common/src/main/scala/net/psforever/objects/doors/DoorControl.scala
@@ -11,8 +11,8 @@ class DoorControl(door : Door) extends Actor {
private var doorCloser : Cancellable = DoorControl.DefaultCloser
def receive : Receive = {
- case Door.Request(player, msg) =>
- sender ! Door.DoorMessage(player, msg, door.Request(player, msg))
+ case Door.Use(player, msg) =>
+ sender ! Door.DoorMessage(player, msg, door.Use(player, msg))
//doorCloser = context.system.scheduler.scheduleOnce(5000L, sender, Door.DoorMessage())
case _ =>
sender ! Door.NoEvent()
diff --git a/common/src/main/scala/net/psforever/objects/doors/DoorDefinition.scala b/common/src/main/scala/net/psforever/objects/doors/DoorDefinition.scala
index 777cccf5..d18c50c4 100644
--- a/common/src/main/scala/net/psforever/objects/doors/DoorDefinition.scala
+++ b/common/src/main/scala/net/psforever/objects/doors/DoorDefinition.scala
@@ -5,9 +5,8 @@ import net.psforever.objects.definition.ObjectDefinition
/**
* The definition for any `door`.
- * @param objectId the object's identifier number
+ * Object Id 242 is a generic door.
*/
-abstract class DoorDefinition(objectId : Int) extends ObjectDefinition(objectId) {
+class DoorDefinition extends ObjectDefinition(242) {
Name = "door"
}
-
diff --git a/common/src/main/scala/net/psforever/objects/doors/IFFLock.scala b/common/src/main/scala/net/psforever/objects/doors/IFFLock.scala
new file mode 100644
index 00000000..81554a0b
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/doors/IFFLock.scala
@@ -0,0 +1,28 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.doors
+
+import net.psforever.objects.{GlobalDefinitions, PlanetSideGameObject}
+import net.psforever.types.PlanetSideEmpire
+
+class IFFLock extends PlanetSideGameObject {
+ private var hackedBy : Option[PlanetSideEmpire.Value] = None
+
+ def Hacker : Option[PlanetSideEmpire.Value] = hackedBy
+
+ def Hacker_=(hacker : PlanetSideEmpire.Value) : Option[PlanetSideEmpire.Value] = {
+ Hacker = Some(hacker)
+ }
+
+ def Hacker_=(hacker : Option[PlanetSideEmpire.Value]) : Option[PlanetSideEmpire.Value] = {
+ hackedBy = hacker
+ Hacker
+ }
+
+ def Definition : IFFLockDefinition = GlobalDefinitions.external_lock
+}
+
+object IFFLock {
+ def apply() : IFFLock = {
+ new IFFLock
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/doors/IFFLockDefinition.scala b/common/src/main/scala/net/psforever/objects/doors/IFFLockDefinition.scala
new file mode 100644
index 00000000..afd6a2a7
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/doors/IFFLockDefinition.scala
@@ -0,0 +1,8 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.doors
+
+import net.psforever.objects.definition.ObjectDefinition
+
+class IFFLockDefinition extends ObjectDefinition(0) {
+ Name = "iff_lock"
+}
diff --git a/common/src/main/scala/net/psforever/objects/zones/IFFLockObjectBuilder.scala b/common/src/main/scala/net/psforever/objects/zones/IFFLockObjectBuilder.scala
new file mode 100644
index 00000000..e4e55011
--- /dev/null
+++ b/common/src/main/scala/net/psforever/objects/zones/IFFLockObjectBuilder.scala
@@ -0,0 +1,32 @@
+// Copyright (c) 2017 PSForever
+package net.psforever.objects.zones
+
+import net.psforever.objects.doors.{IFFLock, IFFLockDefinition}
+
+/**
+ * Wrapper `Class` designed to instantiate a `Door` server object.
+ * @param idef a `IFFLockDefinition` object, indicating the specific functionality of the resulting `Door`
+ * @param id the globally unique identifier to which this `IFFLock` will be registered
+ */
+class IFFLockObjectBuilder(private val idef : IFFLockDefinition, private val id : Int) extends ServerObjectBuilder[IFFLock] {
+ import akka.actor.ActorContext
+ import net.psforever.objects.guid.NumberPoolHub
+
+ def Build(implicit context : ActorContext, guid : NumberPoolHub) : IFFLock = {
+ val obj = IFFLock()
+ guid.register(obj, id) //non-Actor GUID registration
+ obj
+ }
+}
+
+object IFFLockObjectBuilder {
+ /**
+ * Overloaded constructor for a `IFFLockObjectBuilder`.
+ * @param idef an `IFFLock` object
+ * @param id a globally unique identifier
+ * @return an `IFFLockObjectBuilder` object
+ */
+ def apply(idef : IFFLockDefinition, id : Int) : IFFLockObjectBuilder = {
+ new IFFLockObjectBuilder(idef, id)
+ }
+}
diff --git a/common/src/main/scala/net/psforever/objects/zones/Zone.scala b/common/src/main/scala/net/psforever/objects/zones/Zone.scala
index 617fbb59..ae3cf6fa 100644
--- a/common/src/main/scala/net/psforever/objects/zones/Zone.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/Zone.scala
@@ -2,6 +2,7 @@
package net.psforever.objects.zones
import akka.actor.{ActorContext, ActorRef, Props}
+import net.psforever.objects.doors.Base
import net.psforever.objects.{PlanetSideGameObject, Player}
import net.psforever.objects.equipment.Equipment
import net.psforever.objects.guid.NumberPoolHub
@@ -48,6 +49,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
/** Used by the `Zone` to coordinate `Equipment` dropping and collection requests. */
private var ground : ActorRef = ActorRef.noSender
+ private var bases : List[Base] = List()
+
/**
* Establish the basic accessible conditions necessary for a functional `Zone`.
*
@@ -69,6 +72,8 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
Map.LocalObjects.foreach({ builderObject =>
builderObject.Build
})
+
+ MakeBases(Map.LocalBases)
}
}
@@ -169,6 +174,15 @@ class Zone(private val zoneId : String, zoneMap : ZoneMap, zoneNumber : Int) {
*/
def Ground : ActorRef = ground
+ def MakeBases(num : Int) : List[Base] = {
+ bases = (0 to num).map(id => new Base(id)).toList
+ bases
+ }
+
+ def Base(id : Int) : Option[Base] = {
+ bases.lift(id)
+ }
+
/**
* Provide bulk correspondence on all map entities that can be composed into packet messages and reported to a client.
* These messages are sent in this fashion at the time of joining the server:
diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala
index 811b6ef9..ba34619c 100644
--- a/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/ZoneActor.scala
@@ -13,8 +13,48 @@ class ZoneActor(zone : Zone) extends Actor {
def receive : Receive = {
case Zone.Init() =>
zone.Init
+ ZoneSetupCheck()
case msg =>
log.warn(s"Received unexpected message - $msg")
}
+
+ def ZoneSetupCheck(): Unit = {
+ def guid(id : Int) = zone.GUID(id)
+ val map = zone.Map
+ val slog = org.log4s.getLogger(s"zone/${zone.Id}/sanity")
+
+ //check base to object associations
+ map.ObjectToBase.foreach({ case((object_guid, base_id)) =>
+ if(zone.Base(base_id).isEmpty) {
+ slog.error(s"expected a base #$base_id")
+ }
+ if(guid(object_guid).isEmpty) {
+ slog.error(s"expected object id $object_guid to exist, but it did not")
+ }
+ })
+
+ //check door to lock association
+ import net.psforever.objects.doors.{Door, IFFLock}
+ map.DoorToLock.foreach({ case((door_guid, lock_guid)) =>
+ try {
+ if(!guid(door_guid).get.isInstanceOf[Door]) {
+ slog.error(s"expected id $door_guid to be a door, but it was not")
+ }
+ }
+ catch {
+ case _ : Exception =>
+ slog.error(s"expected a door, but looking for uninitialized object $door_guid")
+ }
+ try {
+ if(!guid(lock_guid).get.isInstanceOf[IFFLock]) {
+ slog.error(s"expected id $lock_guid to be an IFF lock, but it was not")
+ }
+ }
+ catch {
+ case _ : Exception =>
+ slog.error(s"expected an IFF lock, but looking for uninitialized object $lock_guid")
+ }
+ })
+ }
}
diff --git a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
index 05ae29e0..7c56a131 100644
--- a/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
+++ b/common/src/main/scala/net/psforever/objects/zones/ZoneMap.scala
@@ -12,19 +12,31 @@ package net.psforever.objects.zones
* Use it as a blueprint.
*
* The "training zones" are the best example of the difference between a `ZoneMap` and a `Zone.`
+ * ("Course" will be used as an unofficial location and layout descriptor.)
* `tzdrtr` is the Terran Republic driving course.
* `tzdrvs` is the Vanu Sovereignty driving course.
- * While each course can have different objects and object states (`Zone`),
- * both courses have the same basic server objects because they are built from the same blueprint (`ZoneMap`).
+ * While each course can have different objects and object states, i.e., a `Zone`,
+ * both of these courses utilize the same basic server object layout because they are built from the same blueprint, i.e., a `ZoneMap`.
* @param name the privileged name that can be used as the first parameter in the packet `LoadMapMessage`
* @see `ServerObjectBuilder`
* `LoadMapMessage`
*/
class ZoneMap(private val name : String) {
private var localObjects : List[ServerObjectBuilder[_]] = List()
+ private var linkDoorLock : Map[Int, Int] = Map()
+ private var linkObjectBase : Map[Int, Int] = Map()
+ private var numBases : Int = 0
def Name : String = name
+ /**
+ * The list of all server object builder wrappers that have been assigned to this `ZoneMap`.
+ * @return the `List` of all `ServerObjectBuilders` known to this `ZoneMap`
+ */
+ def LocalObjects : List[ServerObjectBuilder[_]] = {
+ localObjects
+ }
+
/**
* Append the builder for a server object to the list of builders known to this `ZoneMap`.
* @param obj the builder for a server object
@@ -33,11 +45,22 @@ class ZoneMap(private val name : String) {
localObjects = localObjects :+ obj
}
- /**
- * The list of all server object builder wrappers that have been assigned to this `ZoneMap`.
- * @return the `List` of all `ServerObjectBuilders` known to this `ZoneMap`
- */
- def LocalObjects : List[ServerObjectBuilder[_]] = {
- localObjects
+ def LocalBases : Int = numBases
+
+ def LocalBases_=(num : Int) : Int = {
+ numBases = math.max(0, num)
+ LocalBases
+ }
+
+ def ObjectToBase : Map[Int, Int] = linkObjectBase
+
+ def ObjectToBase(object_guid : Int, base_id : Int) : Unit = {
+ linkObjectBase = linkObjectBase ++ Map(object_guid -> base_id)
+ }
+
+ def DoorToLock : Map[Int, Int] = linkDoorLock
+
+ def DoorToLock(door_guid : Int, lock_guid : Int) = {
+ linkDoorLock = linkDoorLock ++ Map(door_guid -> lock_guid)
}
}
diff --git a/pslogin/src/main/scala/AvatarService.scala b/pslogin/src/main/scala/AvatarService.scala
index 377a9249..5905366f 100644
--- a/pslogin/src/main/scala/AvatarService.scala
+++ b/pslogin/src/main/scala/AvatarService.scala
@@ -1,22 +1,14 @@
-// Copyright (c) 2016 PSForever.net to present
+// Copyright (c) 2017 PSForever
import akka.actor.Actor
-import akka.event.{ActorEventBus, SubchannelClassification}
-import akka.util.Subclassification
import net.psforever.objects.equipment.Equipment
import net.psforever.packet.game.objectcreate.ConstructorData
import net.psforever.types.ExoSuitType
import net.psforever.packet.game.{PlanetSideGUID, PlayerStateMessageUpstream}
import net.psforever.types.Vector3
-sealed trait Action
-
-sealed trait Response
-
-final case class Join(channel : String)
-final case class Leave()
-final case class LeaveAll()
-
object AvatarAction {
+ trait Action
+
final case class ArmorChanged(player_guid : PlanetSideGUID, suit : ExoSuitType.Value, subtype : Int) extends Action
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Action
final case class EquipmentInHand(player_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action
@@ -36,6 +28,8 @@ object AvatarAction {
}
object AvatarServiceResponse {
+ trait Response
+
final case class ArmorChanged(suit : ExoSuitType.Value, subtype : Int) extends Response
//final case class DropItem(pos : Vector3, orient : Vector3, item : PlanetSideGUID) extends Response
final case class EquipmentInHand(slot : Int, item : Equipment) extends Response
@@ -54,30 +48,14 @@ object AvatarServiceResponse {
// final case class ChangeWeapon(facingYaw : Int) extends Response
}
-final case class AvatarServiceMessage(forChannel : String, actionMessage : Action)
+final case class AvatarServiceMessage(forChannel : String, actionMessage : AvatarAction.Action)
-final case class AvatarServiceResponse(toChannel : String, avatar_guid : PlanetSideGUID, replyMessage : Response)
+final case class AvatarServiceResponse(toChannel : String, avatar_guid : PlanetSideGUID, replyMessage : AvatarServiceResponse.Response) extends GenericEventBusMsg
/*
- /avatar/
+ /Avatar/
*/
-class AvatarEventBus extends ActorEventBus with SubchannelClassification {
- type Event = AvatarServiceResponse
- type Classifier = String
-
- protected def classify(event: Event): Classifier = event.toChannel
-
- protected def subclassification = new Subclassification[Classifier] {
- def isEqual(x: Classifier, y: Classifier) = x == y
- def isSubclass(x: Classifier, y: Classifier) = x.startsWith(y)
- }
-
- protected def publish(event: Event, subscriber: Subscriber): Unit = {
- subscriber ! event
- }
-}
-
class AvatarService extends Actor {
//import AvatarServiceResponse._
private [this] val log = org.log4s.getLogger
@@ -86,62 +64,58 @@ class AvatarService extends Actor {
log.info("Starting...")
}
- val AvatarEvents = new AvatarEventBus
-
- /*val channelMap = Map(
- AvatarMessageType.CMT_OPEN -> AvatarPath("local")
- )*/
+ val AvatarEvents = new GenericEventBus[AvatarServiceResponse] //AvatarEventBus
def receive = {
- case Join(channel) =>
- val path = "/Avatar/" + channel
+ case Service.Join(channel) =>
+ val path = s"/$channel/Avatar"
val who = sender()
log.info(s"$who has joined $path")
AvatarEvents.subscribe(who, path)
- case Leave() =>
+ case Service.Leave() =>
AvatarEvents.unsubscribe(sender())
- case LeaveAll() =>
+ case Service.LeaveAll() =>
AvatarEvents.unsubscribe(sender())
case AvatarServiceMessage(forChannel, action) =>
action match {
case AvatarAction.ArmorChanged(player_guid, suit, subtype) =>
AvatarEvents.publish(
- AvatarServiceResponse("/Avatar/" + forChannel, player_guid, AvatarServiceResponse.ArmorChanged(suit, subtype))
+ AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.ArmorChanged(suit, subtype))
)
case AvatarAction.EquipmentInHand(player_guid, slot, obj) =>
AvatarEvents.publish(
- AvatarServiceResponse("/Avatar/" + forChannel, player_guid, AvatarServiceResponse.EquipmentInHand(slot, obj))
+ AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.EquipmentInHand(slot, obj))
)
case AvatarAction.EquipmentOnGround(player_guid, pos, orient, obj) =>
AvatarEvents.publish(
- AvatarServiceResponse("/Avatar/" + forChannel, player_guid, AvatarServiceResponse.EquipmentOnGround(pos, orient, obj))
+ AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.EquipmentOnGround(pos, orient, obj))
)
case AvatarAction.LoadPlayer(player_guid, pdata) =>
AvatarEvents.publish(
- AvatarServiceResponse("/Avatar/" + forChannel, player_guid, AvatarServiceResponse.LoadPlayer(pdata))
+ AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.LoadPlayer(pdata))
)
case AvatarAction.ObjectDelete(player_guid, item_guid, unk) =>
AvatarEvents.publish(
- AvatarServiceResponse("/Avatar/" + forChannel, player_guid, AvatarServiceResponse.ObjectDelete(item_guid, unk))
+ AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.ObjectDelete(item_guid, unk))
)
case AvatarAction.ObjectHeld(player_guid, slot) =>
AvatarEvents.publish(
- AvatarServiceResponse("/Avatar/" + forChannel, player_guid, AvatarServiceResponse.ObjectHeld(slot))
+ AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.ObjectHeld(slot))
)
case AvatarAction.PlanetsideAttribute(guid, attribute_type, attribute_value) =>
AvatarEvents.publish(
- AvatarServiceResponse("/Avatar/" + forChannel, guid, AvatarServiceResponse.PlanetSideAttribute(attribute_type, attribute_value))
+ AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarServiceResponse.PlanetSideAttribute(attribute_type, attribute_value))
)
case AvatarAction.PlayerState(guid, msg, spectator, weapon) =>
AvatarEvents.publish(
- AvatarServiceResponse("/Avatar/" + forChannel, guid, AvatarServiceResponse.PlayerState(msg, spectator, weapon))
+ AvatarServiceResponse(s"/$forChannel/Avatar", guid, AvatarServiceResponse.PlayerState(msg, spectator, weapon))
)
case AvatarAction.Reload(player_guid, mag) =>
AvatarEvents.publish(
- AvatarServiceResponse("/Avatar/" + forChannel, player_guid, AvatarServiceResponse.Reload(mag))
+ AvatarServiceResponse(s"/$forChannel/Avatar", player_guid, AvatarServiceResponse.Reload(mag))
)
case _ => ;
}
diff --git a/pslogin/src/main/scala/LocalService.scala b/pslogin/src/main/scala/LocalService.scala
new file mode 100644
index 00000000..5337b074
--- /dev/null
+++ b/pslogin/src/main/scala/LocalService.scala
@@ -0,0 +1,58 @@
+// Copyright (c) 2017 PSForever
+import akka.actor.Actor
+import net.psforever.packet.game.PlanetSideGUID
+
+object LocalAction {
+ trait Action
+
+ final case class Door(player_guid : PlanetSideGUID) extends Action
+}
+
+object LocalServiceResponse {
+ trait Response
+
+ final case class Door(player_guid : PlanetSideGUID) extends Response
+}
+
+final case class LocalServiceMessage(forChannel : String, actionMessage : LocalAction.Action)
+
+final case class LocalServiceResponse(toChannel : String, avatar_guid : PlanetSideGUID, replyMessage : LocalServiceResponse.Response) extends GenericEventBusMsg
+
+/*
+ /LocalEnvironment/
+ */
+
+class LocalService extends Actor {
+ //import LocalService._
+ private [this] val log = org.log4s.getLogger
+
+ override def preStart = {
+ log.info("Starting...")
+ }
+
+ val LocalEvents = new GenericEventBus[LocalServiceResponse]
+
+ def receive = {
+ case Service.Join(channel) =>
+ val path = s"/$channel/LocalEnvironment"
+ val who = sender()
+ log.info(s"$who has joined $path")
+ LocalEvents.subscribe(who, path)
+ case Service.Leave() =>
+ LocalEvents.unsubscribe(sender())
+ case Service.LeaveAll() =>
+ LocalEvents.unsubscribe(sender())
+
+ case LocalServiceMessage(forChannel, action) =>
+ action match {
+ case LocalAction.Door(player_guid) =>
+ LocalEvents.publish(
+ LocalServiceResponse(s"/$forChannel/LocalEnvironment" + forChannel, player_guid, LocalServiceResponse.Door(player_guid))
+ )
+ case _ => ;
+ }
+
+ case msg =>
+ log.info(s"Unhandled message $msg from $sender")
+ }
+}
diff --git a/pslogin/src/main/scala/PsLogin.scala b/pslogin/src/main/scala/PsLogin.scala
index d0c22dc4..59edb2f7 100644
--- a/pslogin/src/main/scala/PsLogin.scala
+++ b/pslogin/src/main/scala/PsLogin.scala
@@ -3,7 +3,7 @@ import java.net.InetAddress
import java.io.File
import java.util.Locale
-import akka.actor.{ActorRef, ActorSystem, Props}
+import akka.actor.{ActorContext, ActorRef, ActorSystem, Props}
import akka.routing.RandomPool
import ch.qos.logback.classic.LoggerContext
import ch.qos.logback.classic.joran.JoranConfigurator
@@ -202,6 +202,7 @@ object PsLogin {
val serviceManager = ServiceManager.boot
serviceManager ! ServiceManager.Register(RandomPool(50).props(Props[TaskResolver]), "taskResolver")
serviceManager ! ServiceManager.Register(Props[AvatarService], "avatar")
+ serviceManager ! ServiceManager.Register(Props[LocalService], "local")
serviceManager ! ServiceManager.Register(Props(classOf[InterstellarCluster], createContinents()), "galaxy")
/** Create two actors for handling the login and world server endpoints */
@@ -222,20 +223,38 @@ object PsLogin {
def createContinents() : List[Zone] = {
val map13 = new ZoneMap("map13") {
import net.psforever.objects.GlobalDefinitions._
- val ddef = new net.psforever.objects.doors.DoorDefinition(242) {} //generic door
- LocalObject(DoorObjectBuilder(ddef, 330))
- LocalObject(DoorObjectBuilder(ddef, 332))
- LocalObject(DoorObjectBuilder(ddef, 372))
- LocalObject(DoorObjectBuilder(ddef, 373))
+ LocalObject(DoorObjectBuilder(door, 330))
+ LocalObject(DoorObjectBuilder(door, 332))
+ LocalObject(DoorObjectBuilder(door, 372))
+ LocalObject(DoorObjectBuilder(door, 373))
+ LocalObject(IFFLockObjectBuilder(external_lock, 556))
+ LocalObject(IFFLockObjectBuilder(external_lock, 558))
LocalObject(TerminalObjectBuilder(cert_terminal, 186))
LocalObject(TerminalObjectBuilder(cert_terminal, 187))
LocalObject(TerminalObjectBuilder(cert_terminal, 188))
LocalObject(TerminalObjectBuilder(order_terminal, 853))
LocalObject(TerminalObjectBuilder(order_terminal, 855))
LocalObject(TerminalObjectBuilder(order_terminal, 860))
+
+ LocalBases = 30
+
+ ObjectToBase(330, 29)
+ ObjectToBase(332, 29)
+ ObjectToBase(556, 29)
+ ObjectToBase(558, 29)
+ DoorToLock(330, 558)
+ DoorToLock(332, 556)
+ }
+ val home3 = new Zone("home3", map13, 13) {
+ override def Init(implicit context : ActorContext) : Unit = {
+ super.Init(context)
+
+ import net.psforever.types.PlanetSideEmpire
+ Base(2).get.Faction = PlanetSideEmpire.VS //HART building C
+ Base(29).get.Faction = PlanetSideEmpire.NC //South Villa Gun Tower
+ }
}
- val home3 = Zone("home3", map13, 13)
home3 ::
Nil
diff --git a/pslogin/src/main/scala/Service.scala b/pslogin/src/main/scala/Service.scala
new file mode 100644
index 00000000..37a9c87c
--- /dev/null
+++ b/pslogin/src/main/scala/Service.scala
@@ -0,0 +1,29 @@
+// Copyright (c) 2017 PSForever
+import akka.event.{ActorEventBus, SubchannelClassification}
+import akka.util.Subclassification
+
+object Service {
+ final case class Join(channel : String)
+ final case class Leave()
+ final case class LeaveAll()
+}
+
+trait GenericEventBusMsg {
+ def toChannel : String
+}
+
+class GenericEventBus[A <: GenericEventBusMsg] extends ActorEventBus with SubchannelClassification {
+ type Event = A
+ type Classifier = String
+
+ protected def classify(event: Event): Classifier = event.toChannel
+
+ protected def subclassification = new Subclassification[Classifier] {
+ def isEqual(x: Classifier, y: Classifier) = x == y
+ def isSubclass(x: Classifier, y: Classifier) = x.startsWith(y)
+ }
+
+ protected def publish(event: Event, subscriber: Subscriber): Unit = {
+ subscriber ! event
+ }
+}
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala
index 719c1cd1..76ec1a7b 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.doors.Door
+import net.psforever.objects.doors.{Door, IFFLock}
import net.psforever.objects.zones.{InterstellarCluster, Zone}
import net.psforever.objects.entity.IdentifiableEntity
import net.psforever.objects.equipment._
@@ -32,9 +32,10 @@ class WorldSessionActor extends Actor with MDCContextAware {
var sessionId : Long = 0
var leftRef : ActorRef = ActorRef.noSender
var rightRef : ActorRef = ActorRef.noSender
- var avatarService = Actor.noSender
- var taskResolver = Actor.noSender
- var galaxy = Actor.noSender
+ var avatarService : ActorRef = ActorRef.noSender
+ var localService : ActorRef = ActorRef.noSender
+ var taskResolver : ActorRef = Actor.noSender
+ var galaxy : ActorRef = Actor.noSender
var continent : Zone = null
var clientKeepAlive : Cancellable = WorldSessionActor.DefaultCancellable
@@ -43,7 +44,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
if(clientKeepAlive != null)
clientKeepAlive.cancel()
- avatarService ! Leave()
+ avatarService ! Service.Leave()
LivePlayerList.Remove(sessionId) match {
case Some(tplayer) =>
if(tplayer.HasGUID) {
@@ -70,6 +71,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
}
context.become(Started)
ServiceManager.serviceManager ! Lookup("avatar")
+ ServiceManager.serviceManager ! Lookup("local")
ServiceManager.serviceManager ! Lookup("taskResolver")
ServiceManager.serviceManager ! Lookup("galaxy")
@@ -82,6 +84,9 @@ class WorldSessionActor extends Actor with MDCContextAware {
case ServiceManager.LookupResult("avatar", endpoint) =>
avatarService = endpoint
log.info("ID: " + sessionId + " Got avatar service " + endpoint)
+ case ServiceManager.LookupResult("local", endpoint) =>
+ localService = endpoint
+ log.info("ID: " + sessionId + " Got local service " + endpoint)
case ServiceManager.LookupResult("taskResolver", endpoint) =>
taskResolver = endpoint
log.info("ID: " + sessionId + " Got task resolver service " + endpoint)
@@ -724,7 +729,7 @@ class WorldSessionActor extends Actor with MDCContextAware {
)
})
- avatarService ! Join(player.Continent)
+ avatarService ! Service.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) =>
@@ -957,8 +962,23 @@ class WorldSessionActor extends Actor with MDCContextAware {
// TODO: Not all incoming UseItemMessage's respond with another UseItemMessage (i.e. doors only send out GenericObjectStateMsg)
continent.GUID(object_guid) match {
case Some(door : Door) =>
- log.info("Door action!")
- door.Actor ! Door.Request(player, msg)
+ continent.Map.DoorToLock.get(object_guid.guid) match { //check for IFFLock
+ case Some(lock_guid) =>
+ val lock_hacked = continent.GUID(lock_guid).get.asInstanceOf[IFFLock].Hacker.contains(player.Faction)
+ continent.Map.ObjectToBase.get(lock_guid) match { //check for associated base
+ case Some(base_id) =>
+ if(continent.Base(base_id).get.Faction == player.Faction || lock_hacked) { //either base allegiance aligns or lock is hacked
+ door.Actor ! Door.Use(player, msg)
+ }
+ case None =>
+ if(lock_hacked) { //is lock hacked?
+ door.Actor ! Door.Use(player, msg)
+ }
+ }
+ case None =>
+ door.Actor ! Door.Use(player, msg) //let door open freely
+ }
+
case Some(obj : PlanetSideGameObject) =>
if(itemType != 121) {
sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)))