From f5182030da5788b4c20075c99211b8e591f7ff94 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 22 Jan 2018 20:45:05 -0500 Subject: [PATCH] added support for Lockers and access to Locker contents; modified RequestDelete to handle Locker, as well as previously neglected vehicle trunk contents; added Lockers to Hart C shuttle observation room --- .../psforever/objects/GlobalDefinitions.scala | 2 + .../psforever/objects/LockerContainer.scala | 53 +++++++++++++++---- .../scala/net/psforever/objects/Player.scala | 2 + .../serverobject/mblocker/Locker.scala | 33 ++++++++++++ .../objects/ServerObjectBuilderTest.scala | 20 ++++++- pslogin/src/main/scala/Maps.scala | 27 ++++++---- .../src/main/scala/WorldSessionActor.scala | 51 +++++++++++++++--- 7 files changed, 161 insertions(+), 27 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index c670a868..4c9b542b 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -500,6 +500,8 @@ object GlobalDefinitions { val spawn_pad = new ObjectDefinition(800) { Name = "spawn_pad" } + val mb_locker = new ObjectDefinition(524) { Name = "mb_locker" } + val lock_external = new IFFLockDefinition val door = new DoorDefinition diff --git a/common/src/main/scala/net/psforever/objects/LockerContainer.scala b/common/src/main/scala/net/psforever/objects/LockerContainer.scala index 6c79c604..91f13590 100644 --- a/common/src/main/scala/net/psforever/objects/LockerContainer.scala +++ b/common/src/main/scala/net/psforever/objects/LockerContainer.scala @@ -2,17 +2,55 @@ package net.psforever.objects import net.psforever.objects.definition.EquipmentDefinition -import net.psforever.objects.definition.converter.LockerContainerConverter -import net.psforever.objects.equipment.{Equipment, EquipmentSize} -import net.psforever.objects.inventory.GridInventory +import net.psforever.objects.equipment.Equipment +import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} +import net.psforever.packet.game.PlanetSideGUID -class LockerContainer extends Equipment { +import scala.annotation.tailrec + +class LockerContainer extends Equipment with Container { private val inventory = GridInventory(30, 20) def Inventory : GridInventory = inventory + def VisibleSlots : Set[Int] = Set.empty[Int] + + override def Slot(slot : Int) : EquipmentSlot = { + if(inventory.Offset <= slot && slot <= inventory.LastIndex) { + inventory.Slot(slot) + } + else { + OffhandEquipmentSlot.BlockedSlot + } + } + + def Fit(obj : Equipment) : Option[Int] = inventory.Fit(obj.Definition.Tile) + def Find(guid : PlanetSideGUID) : Option[Int] = { + findInInventory(inventory.Items.values.iterator, guid) match { + case Some(index) => + Some(index) + case None => + None + } + } + + @tailrec private def findInInventory(iter : Iterator[InventoryItem], guid : PlanetSideGUID) : Option[Int] = { + if(!iter.hasNext) { + None + } + else { + val item = iter.next + if(item.obj.GUID == guid) { + Some(item.start) + } + else { + findInInventory(iter, guid) + } + } + } + def Definition : EquipmentDefinition = GlobalDefinitions.locker_container } @@ -20,11 +58,4 @@ object LockerContainer { def apply() : LockerContainer = { new LockerContainer() } - - import net.psforever.packet.game.PlanetSideGUID - def apply(guid : PlanetSideGUID) : LockerContainer = { - val obj = new LockerContainer() - obj.GUID = guid - obj - } } diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 52425265..710a3845 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -188,6 +188,8 @@ class Player(private val name : String, def Inventory : GridInventory = inventory + def Locker : LockerContainer = fifthSlot.Equipment.get.asInstanceOf[LockerContainer] + def Fit(obj : Equipment) : Option[Int] = { recursiveHolsterFit(holsters.iterator, obj.Size) match { case Some(index) => diff --git a/common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala new file mode 100644 index 00000000..60ee7979 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/mblocker/Locker.scala @@ -0,0 +1,33 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.mblocker + +import akka.actor.ActorContext +import net.psforever.objects.GlobalDefinitions +import net.psforever.objects.definition.ObjectDefinition +import net.psforever.objects.serverobject.PlanetSideServerObject + +class Locker extends PlanetSideServerObject { + def Definition : ObjectDefinition = GlobalDefinitions.mb_locker +} + +object Locker { + /** + * Overloaded constructor. + * @return a `VehicleSpawnPad` object + */ + def apply() : Locker = { + new Locker() + } + + /** + * Instantiate an configure a `Locker` object + * @param id the unique id that will be assigned to this entity + * @param context a context to allow the object to properly set up `ActorSystem` functionality; + * not necessary for this object, but required by signature + * @return the `Locker` object + */ + def Constructor(id : Int, context : ActorContext) : Locker = { + val obj = Locker() + obj + } +} diff --git a/common/src/test/scala/objects/ServerObjectBuilderTest.scala b/common/src/test/scala/objects/ServerObjectBuilderTest.scala index 9e9a1bc3..8dc4528f 100644 --- a/common/src/test/scala/objects/ServerObjectBuilderTest.scala +++ b/common/src/test/scala/objects/ServerObjectBuilderTest.scala @@ -2,7 +2,6 @@ package objects import akka.actor.{Actor, Props} -import net.psforever.objects.GlobalDefinitions.order_terminal import net.psforever.objects.guid.NumberPoolHub import net.psforever.packet.game.PlanetSideGUID import net.psforever.objects.serverobject.ServerObjectBuilder @@ -62,6 +61,7 @@ class ImplantTerminalMechObjectBuilderTest extends ActorTest { } class TerminalObjectBuilderTest extends ActorTest { + import net.psforever.objects.GlobalDefinitions.order_terminal import net.psforever.objects.serverobject.terminals.Terminal "TerminalObjectBuilder" should { "build" in { @@ -99,6 +99,24 @@ class VehicleSpawnPadObjectBuilderTest extends ActorTest { } } +class LockerObjectBuilderTest extends ActorTest { + import net.psforever.objects.serverobject.mblocker.Locker + "LockerObjectBuilder" should { + "build" in { + val hub = ServerObjectBuilderTest.NumberPoolHub + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, + Locker.Constructor), hub), "locker") + actor ! "!" + + val reply = receiveOne(Duration.create(1000, "ms")) + assert(reply.isInstanceOf[Locker]) + assert(reply.asInstanceOf[Locker].HasGUID) + assert(reply.asInstanceOf[Locker].GUID == PlanetSideGUID(1)) + assert(reply == hub(1).get) + } + } +} + object ServerObjectBuilderTest { import net.psforever.objects.guid.source.LimitedNumberSource def NumberPoolHub : NumberPoolHub = { diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index 6c901153..97ec1139 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -5,6 +5,7 @@ import net.psforever.objects.serverobject.ServerObjectBuilder import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock +import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.types.Vector3 @@ -62,6 +63,14 @@ object Maps { LocalObject(ServerObjectBuilder(529, ImplantTerminalMech.Constructor)) //Hart C LocalObject(ServerObjectBuilder(556, IFFLock.Constructor)) LocalObject(ServerObjectBuilder(558, IFFLock.Constructor)) + LocalObject(ServerObjectBuilder(686, Locker.Constructor)) + LocalObject(ServerObjectBuilder(687, Locker.Constructor)) + LocalObject(ServerObjectBuilder(688, Locker.Constructor)) + LocalObject(ServerObjectBuilder(689, Locker.Constructor)) + LocalObject(ServerObjectBuilder(690, Locker.Constructor)) + LocalObject(ServerObjectBuilder(691, Locker.Constructor)) + LocalObject(ServerObjectBuilder(692, Locker.Constructor)) + LocalObject(ServerObjectBuilder(693, Locker.Constructor)) LocalObject(ServerObjectBuilder(186, Terminal.Constructor(cert_terminal))) LocalObject(ServerObjectBuilder(187, Terminal.Constructor(cert_terminal))) LocalObject(ServerObjectBuilder(188, Terminal.Constructor(cert_terminal))) @@ -95,17 +104,17 @@ object Maps { ObjectToBase(330, 29) ObjectToBase(332, 29) //ObjectToBase(520, 29) - ObjectToBase(522, 29) - ObjectToBase(523, 29) - ObjectToBase(524, 29) - ObjectToBase(525, 29) - ObjectToBase(526, 29) - ObjectToBase(527, 29) - ObjectToBase(528, 29) - ObjectToBase(529, 29) + ObjectToBase(522, 2) + ObjectToBase(523, 2) + ObjectToBase(524, 2) + ObjectToBase(525, 2) + ObjectToBase(526, 2) + ObjectToBase(527, 2) + ObjectToBase(528, 2) + ObjectToBase(529, 2) ObjectToBase(556, 29) ObjectToBase(558, 29) - ObjectToBase(1081, 29) + ObjectToBase(1081, 2) ObjectToBase(1063, 2) //TODO unowned courtyard terminal? ObjectToBase(500, 2) //TODO unowned courtyard spawnpad? ObjectToBase(304, 2) //TODO unowned courtyard terminal? diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index e6d2e584..3125f762 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -19,6 +19,7 @@ import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObjec import net.psforever.objects.serverobject.doors.Door import net.psforever.objects.serverobject.implantmech.ImplantTerminalMech import net.psforever.objects.serverobject.locks.IFFLock +import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.VehicleSpawnPad import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.vehicles.{AccessPermissionGroup, VehicleLockState} @@ -48,6 +49,7 @@ class WorldSessionActor extends Actor with MDCContextAware { var continent : Zone = null var progressBarValue : Option[Float] = None var shooting : Option[PlanetSideGUID] = None + var accessedContainer : Option[PlanetSideGameObject with Container] = None var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj @@ -1460,7 +1462,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } previousBox.Capacity = capacity } - + if(previousBox.Capacity > 0) { //TODO split previousBox into AmmoBox objects of appropriate max capacity, e.g., 100 9mm -> 2 x 50 9mm obj.Inventory.Fit(previousBox.Definition.Tile) match { @@ -1677,13 +1679,24 @@ class WorldSessionActor extends Actor with MDCContextAware { } case Some(obj : Equipment) => - player.Find(object_guid) match { //player should be holding it - case Some(slot) => - taskResolver ! RemoveEquipmentFromSlot(player, player.Slot(slot).Equipment.get, slot) + val findFunc : PlanetSideGameObject with Container => Option[(PlanetSideGameObject with Container, Option[Int])] = FindInLocalContainer(object_guid) + + findFunc(player) + .orElse(findFunc(player.Locker)) + .orElse(accessedContainer match { + case Some(parent) => + findFunc(parent) + case None => + None + }) match { + case Some((parent, Some(slot))) => + taskResolver ! RemoveEquipmentFromSlot(parent, obj, slot) log.info(s"RequestDestroy: equipment $object_guid") - case None => + + case _ => + //TODO search for item on ground sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(object_guid, 0))) - log.warn(s"RequestDestroy: object $object_guid not found in player hands") + log.warn(s"RequestDestroy: object $object_guid not found") } case None => @@ -1698,6 +1711,7 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("ObjectDelete: " + msg) case msg @ MoveItemMessage(item_guid, source_guid, destination_guid, dest, unk1) => + log.info(s"MoveItem: $msg") (continent.GUID(source_guid), continent.GUID(destination_guid), continent.GUID(item_guid)) match { case (Some(source : Container), Some(destination : Container), Some(item : Equipment)) => source.Find(item_guid) match { @@ -1842,6 +1856,11 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => ; } + case Some(obj : Locker) => + val container = player.Locker + accessedContainer = Some(container) + sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, container.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, 456))) + case Some(obj : Vehicle) => if(obj.Faction == player.Faction) { val equipment = player.Slot(player.DrawnSlot).Equipment @@ -1856,6 +1875,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //access to trunk if(obj.AccessingTrunk.isEmpty) { obj.AccessingTrunk = player.GUID + accessedContainer = Some(obj) AccessContents(obj) sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))) } @@ -1900,6 +1920,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ =>; } + accessedContainer = None case msg @ DeployObjectMessage(guid, unk1, pos, roll, pitch, yaw, unk2) => log.info("DeployObject: " + msg) @@ -2891,6 +2912,24 @@ class WorldSessionActor extends Actor with MDCContextAware { GlobalDefinitions.isCavernEquipment(objDef) || (faction != tplayer.Faction && faction != PlanetSideEmpire.NEUTRAL) } + /** + * Given an object globally unique identifier, search in a given location for it. + * @param object_guid the object + * @param parent a `Container` object wherein to search + * @return an optional tuple that contains two values; + * the first value is the container that matched correctly with the object's GUID; + * the second value is the slot position of the object + */ + def FindInLocalContainer(object_guid : PlanetSideGUID)(parent : PlanetSideGameObject with Container) : Option[(PlanetSideGameObject with Container, Option[Int])] = { + val slot : Option[Int] = parent.Find(object_guid) + slot match { + case place @ Some(_) => + Some(parent, slot) + case None => + None + } + } + def failWithError(error : String) = { log.error(error) sendResponse(PacketCoding.CreateControlPacket(ConnectionClose()))