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)))