From 0200a688e7b0794b8349ea5bab00d7db4eb735ec Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 12 May 2018 02:16:41 -0400 Subject: [PATCH 01/13] mechanism for seamless bundling of packet and then emptying it towards the network --- .../src/main/scala/WorldSessionActor.scala | 96 ++++++++++++++++++- 1 file changed, 92 insertions(+), 4 deletions(-) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index ca46235b..f2bec225 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1136,6 +1136,7 @@ class WorldSessionActor extends Actor with MDCContextAware { val popNC = poplist.count(_.faction == PlanetSideEmpire.NC) val popVS = poplist.count(_.faction == PlanetSideEmpire.VS) + StartBundlingPackets() zone.Buildings.foreach({ case(id, building) => initBuilding(continentNumber, id, building) }) sendResponse(ZonePopulationUpdateMessage(continentNumber, 414, 138, popTR, 138, popNC, 138, popVS, 138, popBO)) sendResponse(ContinentalLockUpdateMessage(continentNumber, PlanetSideEmpire.NEUTRAL)) @@ -1220,6 +1221,7 @@ class WorldSessionActor extends Actor with MDCContextAware { RequestSanctuaryZoneSpawn(player, zone_number) case InterstellarCluster.ClientInitializationComplete() => + StopBundlingPackets() LivePlayerList.Add(sessionId, avatar) //PropertyOverrideMessage sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) @@ -1269,6 +1271,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case SetCurrentAvatar(tplayer) => player = tplayer val guid = tplayer.GUID + StartBundlingPackets() sendResponse(SetCurrentAvatarMessage(guid,0,0)) sendResponse(PlayerStateShiftMessage(ShiftState(1, tplayer.Position, tplayer.Orientation.z))) if(spectator) { @@ -1301,6 +1304,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //SquadDefinitionActionMessage and SquadDetailDefinitionUpdateMessage //MapObjectStateBlockMessage and ObjectCreateMessage //TacticsMessage + StopBundlingPackets() sendResponse(ChatMsg(ChatMessageType.CMT_EXPANSIONS, true, "", "1 on", None)) //CC on @@ -1466,6 +1470,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ BeginZoningMessage() => log.info("Reticulating splines ...") + StartBundlingPackets() configZone(continent) sendResponse(TimeOfDayMessage(1191182336)) @@ -1539,6 +1544,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case _ => ; } }) + StopBundlingPackets() avatarService ! Service.Join(player.Continent) localService ! Service.Join(player.Continent) vehicleService ! Service.Join(player.Continent) @@ -4139,12 +4145,95 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ConnectionClose()) } - def sendResponse(cont : PlanetSideControlPacket) : Unit = { - sendResponse(PacketCoding.CreateControlPacket(cont)) + /** + * Persistent collector that intercepts `GamePacket` and `ControlPacket` messages that are being sent towards the network. + */ + private val packetBundlingCollector : MultiPacketCollector = new MultiPacketCollector() + /** + * Re-assigned function used to direct/intercept packets being sent towards the network. + * Defaults to directing the packets. + */ + private var packetBundlingFunc : (PlanetSidePacket)=>Option[PlanetSidePacket] = NoBundlingAction + + /** + * Start packet bundling by assigning the appropriate function. + * @see `sendResponse(PlanetSidePacket) : Unit` + */ + def StartBundlingPackets() : Unit = { + log.trace("WORLD SEND: STARTED BUNDLING PACKETS") + packetBundlingFunc = PerformBundlingAction } - def sendResponse(cont : PlanetSideGamePacket) : Unit = { + /** + * Stop packet bundling by assigning the appropriate function. + * If any bundles are in the collector's buffer, push that bundle out towards the network. + * @see `sendResponse(PlanetSidePacket) : Unit` + */ + def StopBundlingPackets() : Unit = { + log.trace("WORLD SEND: PACKET BUNDLING SUSPENDED") + packetBundlingFunc = NoBundlingAction + packetBundlingCollector.BundleOption match { + case Some(bundle) => + sendResponse(bundle) + case None => ; + } + } + + /** + * Transform the packet into either a `PlanetSideGamePacket` or a `PlanetSideControlPacket` and push it towards the network. + * @param cont the packet + * @return the same packet, to indicate it was sent + */ + private def NoBundlingAction(cont : PlanetSidePacket) : Option[PlanetSidePacket] = { + cont match { + case game : PlanetSideGamePacket => + sendResponse(PacketCoding.CreateGamePacket(0, game)) + case control : PlanetSideControlPacket => + sendResponse(PacketCoding.CreateControlPacket(control)) + case _ => ; + } + Some(cont) + } + + /** + * Intercept the packet being sent towards the network and + * add it to a bundle that will eventually be sent to the network itself. + * @param cont the packet + * @return always `None`, to indicate the packet was not sent + */ + private def PerformBundlingAction(cont : PlanetSidePacket) : Option[PlanetSidePacket] = { + log.trace("WORLD SEND, BUNDLED: " + cont) + packetBundlingCollector.Add(cont) + None + } + + /** + * Common entry point for transmitting packets to the network. + * Alternately, catch those packets and retain them to send out a bundled message. + * @param cont the packet + */ + def sendResponse(cont : PlanetSidePacket) : Unit = packetBundlingFunc(cont) + + /** + * `KeepAliveMessage` is a special `PlanetSideGamePacket` that is excluded from being bundled when it is sent to the network.
+ *
+ * The risk of the server getting caught in a state where the packets dispatched to the client are alwaysd bundled is posible. + * Starting the bundling functionality but forgetting to transition into a state where it is deactivated can lead to this problem. + * No packets except for `KeepAliveMessage` will ever be sent until the ever-accumulating packets overflow. + * To avoid this state, whenever a `KeepAliveMessage` is sent, the packet collector empties its current contents to the network. + * @see `StartBundlingPackets`
+ * `StopBundlingPackets`
+ * `clientKeepAlive` + * @param cont a `KeepAliveMessage` packet + */ + def sendResponse(cont : KeepAliveMessage) : Unit = { sendResponse(PacketCoding.CreateGamePacket(0, cont)) + packetBundlingCollector.BundleOption match { + case Some(bundle) => + log.trace("WORLD SEND: INTERMITTENT PACKET BUNDLE") + sendResponse(bundle) + case None => ; + } } def sendResponse(cont : PlanetSidePacketContainer) : Unit = { @@ -4153,7 +4242,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } def sendResponse(cont : MultiPacketBundle) : Unit = { - log.trace("WORLD SEND: " + cont) sendResponse(cont.asInstanceOf[Any]) } From cfdc2c9ec77e4d8c771528fa1502421c4c5718f2 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sat, 12 May 2018 20:26:46 -0400 Subject: [PATCH 02/13] swapped arm lowering counteraction to target action: boarding the vehicle while it is being spawned --- pslogin/src/main/scala/WorldSessionActor.scala | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index bfbfe5cf..95ff93e9 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1043,6 +1043,12 @@ class WorldSessionActor extends Actor with MDCContextAware { case VehicleSpawnPad.StartPlayerSeatedInVehicle(vehicle, pad) => val vehicle_guid = vehicle.GUID + PlayerActionsToCancel() + if(player.VisibleSlots.contains(player.DrawnSlot)) { + player.DrawnSlot = Player.HandsDownSlot + sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, true)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot)) + } sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 1L)) //mount points off? sendResponse(PlanetsideAttributeMessage(vehicle_guid, 21, player.GUID.guid)) //fte and ownership? @@ -3841,11 +3847,6 @@ class WorldSessionActor extends Actor with MDCContextAware { shooting = None case None => ; } - if(player != null && player.isAlive && player.VisibleSlots.contains(player.DrawnSlot)) { - player.DrawnSlot = Player.HandsDownSlot - sendResponse(ObjectHeldMessage(player.GUID, Player.HandsDownSlot, true)) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectHeld(player.GUID, player.LastDrawnSlot)) - } if(flying) { sendResponse(ChatMsg(ChatMessageType.CMT_FLY, false, "", "off", None)) flying = false From 7673c8941bccf6449968d469cd38ab877edbb100 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sun, 13 May 2018 21:57:14 -0400 Subject: [PATCH 03/13] begun work on repair and rearm silos by dividing up proximity terminal behavior; terminals at Anguta wired --- .../psforever/objects/GlobalDefinitions.scala | 2 + .../terminals/ProximityTerminal.scala | 18 +----- .../terminals/ProximityTerminalControl.scala | 26 +++------ .../terminals/ProximityUnit.scala | 55 +++++++++++++++++++ .../terminals/RepairRearmControl.scala | 22 ++++++++ .../terminals/RepairRearmSilo.scala | 38 +++++++++++++ .../terminals/RepairRearmSiloDefinition.scala | 27 +++++++++ .../serverobject/terminals/Terminal.scala | 6 +- .../terminals/TerminalControl.scala | 3 +- pslogin/src/main/scala/Maps.scala | 6 +- .../src/main/scala/WorldSessionActor.scala | 36 ++++++++---- 11 files changed, 186 insertions(+), 53 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index fc511e8d..d39790ef 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -524,6 +524,8 @@ object GlobalDefinitions { val medical_terminal = new MedicalTerminalDefinition(529) + val repair_silo = new RepairRearmSiloDefinition(729) + val spawn_pad = new VehicleSpawnPadDefinition val mb_locker = new LockerDefinition diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala index c98e7da5..4eafb142 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala @@ -1,8 +1,6 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.terminals -import net.psforever.packet.game.PlanetSideGUID - /** * A server object that is a "terminal" that can be accessed for amenities and services, * triggered when a certain distance from the unit itself (proximity-based).
@@ -11,21 +9,7 @@ import net.psforever.packet.game.PlanetSideGUID * For example, the cavern crystals are considered owner-neutral elements that are not attached to a `Building` object. * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ -class ProximityTerminal(tdef : MedicalTerminalDefinition) extends Terminal(tdef) { - private var users : Set[PlanetSideGUID] = Set.empty - - def NumberUsers : Int = users.size - - def AddUser(player_guid : PlanetSideGUID) : Int = { - users += player_guid - NumberUsers - } - - def RemoveUser(player_guid : PlanetSideGUID) : Int = { - users -= player_guid - NumberUsers - } -} +class ProximityTerminal(tdef : MedicalTerminalDefinition) extends Terminal(tdef) with ProximityUnit object ProximityTerminal { /** diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala index 407fd0cb..753cdb65 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminalControl.scala @@ -2,36 +2,24 @@ package net.psforever.objects.serverobject.terminals import akka.actor.Actor -import net.psforever.objects.serverobject.CommonMessages import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} -import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage /** - * * An `Actor` that handles messages being dispatched to a specific `ProximityTerminal`. * Although this "terminal" itself does not accept the same messages as a normal `Terminal` object, * it returns the same type of messages - wrapped in a `TerminalMessage` - to the `sender`. * @param term the proximity unit (terminal) */ -class ProximityTerminalControl(term : ProximityTerminal) extends Actor with FactionAffinityBehavior.Check { +class ProximityTerminalControl(term : Terminal with ProximityUnit) extends Actor with FactionAffinityBehavior.Check with ProximityUnit.Use { def FactionObject : FactionAffinity = term - def receive : Receive = checkBehavior.orElse { - case CommonMessages.Use(player) => - val hadNoUsers = term.NumberUsers == 0 - if(term.AddUser(player.GUID) == 1 && hadNoUsers) { - sender ! TerminalMessage(player, null, Terminal.StartProximityEffect(term)) - } + def TerminalObject : Terminal with ProximityUnit = term - case CommonMessages.Unuse(player) => - val hadUsers = term.NumberUsers > 0 - if(term.RemoveUser(player.GUID) == 0 && hadUsers) { - sender ! TerminalMessage(player, null, Terminal.StopProximityEffect(term)) - } - - case _ => - sender ! Terminal.NoDeal() - } + def receive : Receive = checkBehavior + .orElse(proximityBehavior) + .orElse { + case _ => ; + } override def toString : String = term.Definition.Name } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala new file mode 100644 index 00000000..8339e51e --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala @@ -0,0 +1,55 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +import net.psforever.objects.serverobject.CommonMessages +import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage +import net.psforever.packet.game.PlanetSideGUID + +/** + * A server object that is a "terminal" that can be accessed for amenities and services, + * triggered when a certain distance from the unit itself (proximity-based).
+ *
+ * Unlike conventional terminals, this structure is not necessarily structure-owned. + * For example, the cavern crystals are considered owner-neutral elements that are not attached to a `Building` object. + */ +trait ProximityUnit { + this : Terminal => + + private var users : Set[PlanetSideGUID] = Set.empty + + def NumberUsers : Int = users.size + + def AddUser(player_guid : PlanetSideGUID) : Int = { + users += player_guid + NumberUsers + } + + def RemoveUser(player_guid : PlanetSideGUID) : Int = { + users -= player_guid + NumberUsers + } +} + +object ProximityUnit { + import akka.actor.Actor + + trait Use { + this : Actor => + + def TerminalObject : Terminal with ProximityUnit + + val proximityBehavior : Receive = { + case CommonMessages.Use(player) => + val hadNoUsers = TerminalObject.NumberUsers == 0 + if(TerminalObject.AddUser(player.GUID) == 1 && hadNoUsers) { + sender ! TerminalMessage(player, null, Terminal.StartProximityEffect(TerminalObject)) + } + + case CommonMessages.Unuse(player) => + val hadUsers = TerminalObject.NumberUsers > 0 + if(TerminalObject.RemoveUser(player.GUID) == 0 && hadUsers) { + sender ! TerminalMessage(player, null, Terminal.StopProximityEffect(TerminalObject)) + } + } + } +} \ No newline at end of file diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala new file mode 100644 index 00000000..b5514305 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala @@ -0,0 +1,22 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +import akka.actor.Actor +import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} + +class RepairRearmControl(term : RepairRearmSilo) extends Actor with FactionAffinityBehavior.Check with ProximityUnit.Use { + def FactionObject : FactionAffinity = term + + def TerminalObject : Terminal with ProximityUnit = term + + def receive : Receive = checkBehavior + .orElse(proximityBehavior) + .orElse { + case Terminal.Request(player, msg) => + sender ! Terminal.TerminalMessage(player, msg, term.Request(player, msg)) + + case _ => ; + } + + override def toString : String = term.Definition.Name +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala new file mode 100644 index 00000000..45a01302 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala @@ -0,0 +1,38 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +/** + * A server object that is a "terminal" that can be accessed for amenities and services, + * triggered when a certain distance from the unit itself (proximity-based).
+ *
+ * Unlike conventional terminals, this structure is not necessarily structure-owned. + * For example, the cavern crystals are considered owner-neutral elements that are not attached to a `Building` object. + * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ +class RepairRearmSilo(tdef : RepairRearmSiloDefinition) extends Terminal(tdef) with ProximityUnit + +object RepairRearmSilo { + /** + * Overloaded constructor. + * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + */ + def apply(tdef : RepairRearmSiloDefinition) : RepairRearmSilo = { + new RepairRearmSilo(tdef) + } + + import akka.actor.ActorContext + + /** + * Instantiate an configure a `Terminal` object + * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields + * @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 + * @return the `Terminal` object + */ + def Constructor(tdef : RepairRearmSiloDefinition)(id : Int, context : ActorContext) : RepairRearmSilo = { + import akka.actor.Props + val obj = RepairRearmSilo(tdef) + obj.Actor = context.actorOf(Props(classOf[RepairRearmControl], obj), s"${tdef.Name}_$id") + obj + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala new file mode 100644 index 00000000..b0daa854 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala @@ -0,0 +1,27 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +import net.psforever.objects.Player +import net.psforever.packet.game.ItemTransactionMessage + +class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinition(objectId) { + Name = "repair_silo" + + private val buyFunc : (Player, ItemTransactionMessage)=>Terminal.Exchange = EquipmentTerminalDefinition.Buy(Map.empty, Map.empty, Map.empty) + + override def Buy(player: Player, msg : ItemTransactionMessage) : Terminal.Exchange = buyFunc(player, msg) + + override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { + if(msg.item_page == 4) { //Favorites tab + player.LoadLoadout(msg.unk1) match { + case Some(loadout) => + Terminal.VehicleLoadout(Nil, Nil) + case None => + Terminal.NoDeal() + } + } + else { + Terminal.NoDeal() + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala index b031a990..8ee17814 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala @@ -190,17 +190,19 @@ object Terminal { */ final case class InfantryLoadout(exosuit : ExoSuitType.Value, subtype : Int = 0, holsters : List[InventoryItem], inventory : List[InventoryItem]) extends Exchange + final case class VehicleLoadout(weapons : List[InventoryItem], inventory : List[InventoryItem]) extends Exchange + /** * Start the special effects caused by a proximity-base service. * @param terminal the proximity-based unit */ - final case class StartProximityEffect(terminal : ProximityTerminal) extends Exchange + final case class StartProximityEffect(terminal : Terminal with ProximityUnit) extends Exchange /** * Stop the special effects caused by a proximity-base service. * @param terminal the proximity-based unit */ - final case class StopProximityEffect(terminal : ProximityTerminal) extends Exchange + final case class StopProximityEffect(terminal : Terminal with ProximityUnit) extends Exchange /** * Overloaded constructor. diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala index ad63babf..4efa324e 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalControl.scala @@ -15,8 +15,7 @@ class TerminalControl(term : Terminal) extends Actor with FactionAffinityBehavio case Terminal.Request(player, msg) => sender ! Terminal.TerminalMessage(player, msg, term.Request(player, msg)) - case _ => - sender ! Terminal.NoDeal() + case _ => ; } override def toString : String = term.Definition.Name diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index 4051208c..7d0494dd 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -7,7 +7,7 @@ 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.structures.{Building, FoundationBuilder, StructureType, WarpGate} -import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal} +import net.psforever.objects.serverobject.terminals.{ProximityTerminal, RepairRearmSilo, Terminal} import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.types.Vector3 @@ -113,6 +113,8 @@ object Maps { LocalObject(2145, SpawnTube.Constructor(Vector3(3980.4062f, 4252.7656f, 257.5625f), Vector3(0, 0, 90))) LocalObject(2146, SpawnTube.Constructor(Vector3(3980.4062f, 4259.992f, 257.5625f), Vector3(0, 0, 90))) LocalObject(2147, SpawnTube.Constructor(Vector3(3980.4062f, 4267.3047f, 257.5625f), Vector3(0, 0, 90))) + LocalObject(2050, RepairRearmSilo.Constructor(repair_silo)) + LocalObject(2062, RepairRearmSilo.Constructor(repair_silo)) LocalObject(2239, Terminal.Constructor(spawn_terminal)) LocalObject(2244, Terminal.Constructor(spawn_terminal)) LocalObject(2245, Terminal.Constructor(spawn_terminal)) @@ -214,6 +216,8 @@ object Maps { ObjectToBuilding(1576, 2) ObjectToBuilding(1577, 2) ObjectToBuilding(1578, 2) + ObjectToBuilding(2050, 2) + ObjectToBuilding(2062, 2) ObjectToBuilding(2145, 2) ObjectToBuilding(2146, 2) ObjectToBuilding(2147, 2) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index bfbfe5cf..33514522 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -28,8 +28,7 @@ import net.psforever.objects.serverobject.mblocker.Locker import net.psforever.objects.serverobject.pad.{VehicleSpawnControl, VehicleSpawnPad} import net.psforever.objects.serverobject.pad.process.{AutoDriveControls, VehicleSpawnControlGuided} import net.psforever.objects.serverobject.structures.{Building, StructureType, WarpGate} -import net.psforever.objects.serverobject.terminals.{MatrixTerminalDefinition, ProximityTerminal, Terminal} -import net.psforever.objects.serverobject.terminals.Terminal +import net.psforever.objects.serverobject.terminals._ import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.objects.vehicles.{AccessPermissionGroup, Utility, VehicleLockState} @@ -895,6 +894,9 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false)) } + case Terminal.VehicleLoadout(weapons, inventory) => + log.info(s"$tplayer wants to change their vehicle equipment loadout to their option #${msg.unk1 + 1}") + case Terminal.SellCertification(cert, cost) => if(tplayer.Certifications.contains(cert)) { log.info(s"$tplayer is forgetting the $cert certification for $cost points") @@ -2267,6 +2269,16 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + case Some(obj : RepairRearmSilo) => + player.VehicleSeated match { + case Some(vehicle_guid) => + val vehicle = continent.GUID(vehicle_guid).get.asInstanceOf[Vehicle] + sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + sendResponse(UseItemMessage(avatar_guid, unk1, vehicle_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, vehicle.Definition.ObjectId)) + case None => + log.error("UseItem: expected seated vehicle, but found none") + } + case Some(obj : Terminal) => if(obj.Definition.isInstanceOf[MatrixTerminalDefinition]) { //TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks) @@ -2300,7 +2312,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ ProximityTerminalUseMessage(player_guid, object_guid, _) => log.info(s"ProximityTerminal: $msg") continent.GUID(object_guid) match { - case Some(obj : ProximityTerminal) => + case Some(obj : Terminal with ProximityUnit) => if(usingProximityTerminal.contains(object_guid)) { SelectProximityUnit(obj) } @@ -2308,9 +2320,9 @@ class WorldSessionActor extends Actor with MDCContextAware { StartUsingProximityUnit(obj) } case Some(obj) => ; - log.warn(s"ProximityTerminal: object is not a terminal - $obj") + log.warn(s"ProximityTerminalUse: object is not a proximity terminal - $obj") case None => - log.warn(s"ProximityTerminal: no object with guid $object_guid found") + log.warn(s"ProximityTerminalUse: no object with guid $object_guid found") } case msg @ UnuseItemMessage(player_guid, object_guid) => @@ -3985,7 +3997,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * Special note is warranted in the case of a medical terminal or an advanced medical terminal. * @param terminal the proximity-based unit */ - def StartUsingProximityUnit(terminal : ProximityTerminal) : Unit = { + def StartUsingProximityUnit(terminal : Terminal with ProximityUnit) : Unit = { val term_guid = terminal.GUID if(!usingProximityTerminal.contains(term_guid)) { usingProximityTerminal += term_guid @@ -4006,7 +4018,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * Other sorts of proximity-based units are put on a timer. * @param terminal the proximity-based unit */ - def StopUsingProximityUnit(terminal : ProximityTerminal) : Unit = { + def StopUsingProximityUnit(terminal : Terminal with ProximityUnit) : Unit = { val term_guid = terminal.GUID if(usingProximityTerminal.contains(term_guid)) { usingProximityTerminal -= term_guid @@ -4025,7 +4037,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * If this timer completes, a message will be sent that will attempt to disassociate from the target proximity unit. * @param terminal the proximity-based unit */ - def SetDelayedProximityUnitReset(terminal : ProximityTerminal) : Unit = { + def SetDelayedProximityUnitReset(terminal : Terminal with ProximityUnit) : Unit = { val terminal_guid = terminal.GUID ClearDelayedProximityUnitReset(terminal_guid) import scala.concurrent.duration._ @@ -4070,7 +4082,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * and determinig which kind of unit is being utilized. * @param terminal the proximity-based unit */ - def SelectProximityUnit(terminal : ProximityTerminal) : Unit = { + def SelectProximityUnit(terminal : Terminal with ProximityUnit) : Unit = { terminal.Definition match { case GlobalDefinitions.adv_med_terminal | GlobalDefinitions.medical_terminal => ProximityMedicalTerminal(terminal) @@ -4089,7 +4101,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * If the player is both fully healed and fully repaired, stop using the terminal. * @param unit the medical terminal */ - def ProximityMedicalTerminal(unit : ProximityTerminal) : Unit = { + def ProximityMedicalTerminal(unit : Terminal with ProximityUnit) : Unit = { val healthFull : Boolean = if(player.Health < player.MaxHealth) { HealAction(player) } @@ -4113,7 +4125,7 @@ class WorldSessionActor extends Actor with MDCContextAware { * If the player is fully healed, stop using the crystal. * @param unit the healing crystal */ - def ProximityHealCrystal(unit : ProximityTerminal) : Unit = { + def ProximityHealCrystal(unit : Terminal with ProximityUnit) : Unit = { val healthFull : Boolean = if(player.Health < player.MaxHealth) { HealAction(player) } @@ -4253,7 +4265,7 @@ object WorldSessionActor { private final case class ListAccountCharacters() private final case class SetCurrentAvatar(tplayer : Player) private final case class VehicleLoaded(vehicle : Vehicle) - private final case class DelayedProximityUnitStop(unit : ProximityTerminal) + private final case class DelayedProximityUnitStop(unit : Terminal with ProximityUnit) private final case class UnregisterCorpseOnVehicleDisembark(corpse : Player) /** From 9d7d1b0456de02c57632a85963a47379533f6ed4 Mon Sep 17 00:00:00 2001 From: FateJH Date: Sun, 13 May 2018 23:06:01 -0400 Subject: [PATCH 04/13] split previous Loadout into separate InfantryLoadout and VehicleLoadout classes; moved new Loadout code into its own package and corrected import statements; corrected Terminal code and tests --- .../scala/net/psforever/objects/Avatar.scala | 1 + .../scala/net/psforever/objects/Player.scala | 1 + .../objects/loadouts/InfantryLoadout.scala | 28 ++++ .../objects/{ => loadouts}/Loadout.scala | 54 +++---- .../objects/loadouts/VehicleLoadout.scala | 11 ++ .../EquipmentTerminalDefinition.scala | 3 +- .../terminals/OrderTerminalABDefinition.scala | 5 +- .../terminals/OrderTerminalDefinition.scala | 5 +- .../terminals/VehicleTerminalDefinition.scala | 144 +++++++++++------- .../src/test/scala/objects/AvatarTest.scala | 13 +- .../src/test/scala/objects/LoadoutTest.scala | 11 +- .../src/main/scala/WorldSessionActor.scala | 3 +- 12 files changed, 166 insertions(+), 113 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala rename common/src/main/scala/net/psforever/objects/{ => loadouts}/Loadout.scala (86%) create mode 100644 common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala diff --git a/common/src/main/scala/net/psforever/objects/Avatar.scala b/common/src/main/scala/net/psforever/objects/Avatar.scala index 067dafec..8b704a6d 100644 --- a/common/src/main/scala/net/psforever/objects/Avatar.scala +++ b/common/src/main/scala/net/psforever/objects/Avatar.scala @@ -3,6 +3,7 @@ package net.psforever.objects import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition} import net.psforever.objects.equipment.EquipmentSize +import net.psforever.objects.loadouts.Loadout import net.psforever.types.{CertificationType, CharacterGender, ImplantType, PlanetSideEmpire} import scala.annotation.tailrec diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index f5642611..72246fb4 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -4,6 +4,7 @@ package net.psforever.objects import net.psforever.objects.definition.AvatarDefinition import net.psforever.objects.equipment.{Equipment, EquipmentSize} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} +import net.psforever.objects.loadouts.Loadout import net.psforever.objects.serverobject.affinity.FactionAffinity import net.psforever.packet.game.PlanetSideGUID import net.psforever.types._ diff --git a/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala new file mode 100644 index 00000000..72100a61 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala @@ -0,0 +1,28 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.loadouts + +import net.psforever.types.ExoSuitType + +final case class InfantryLoadout(label : String, + visible_slots : List[Loadout.SimplifiedEntry], + inventory : List[Loadout.SimplifiedEntry], + exosuit : ExoSuitType.Value, + subtype : Int) extends Loadout(label, visible_slots, inventory) { + /** + * The exo-suit in which the avatar will be dressed. + * Might be restricted and, thus, restrict the rest of the `Equipment` from being constructed and given. + * @return the exo-suit + */ + def ExoSuit : ExoSuitType.Value = exosuit + + /** + * The mechanized assault exo-suit specialization number that indicates whether the MAX performs: + * anti-infantry (1), + * anti-vehicular (2), + * or anti-air work (3). + * The major distinction is the type of arm weapons that MAX is equipped. + * When the blueprint doesn't call for a MAX, the number will be 0. + * @return the specialization number + */ + def Subtype : Int = subtype +} diff --git a/common/src/main/scala/net/psforever/objects/Loadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala similarity index 86% rename from common/src/main/scala/net/psforever/objects/Loadout.scala rename to common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala index ec997d0a..89d2261c 100644 --- a/common/src/main/scala/net/psforever/objects/Loadout.scala +++ b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala @@ -1,6 +1,7 @@ // Copyright (c) 2017 PSForever -package net.psforever.objects +package net.psforever.objects.loadouts +import net.psforever.objects._ import net.psforever.objects.definition._ import net.psforever.objects.equipment.Equipment import net.psforever.objects.inventory.InventoryItem @@ -34,38 +35,16 @@ import scala.annotation.tailrec * @param label the name by which this inventory will be known when displayed in a Favorites list * @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target * @param inventory simplified representation of the `Equipment` in the target's inventory or trunk - * @param exosuit na - * @param subtype na */ -final case class Loadout(private val label : String, - private val visible_slots : List[Loadout.SimplifiedEntry], - private val inventory : List[Loadout.SimplifiedEntry], - private val exosuit : ExoSuitType.Value, - private val subtype : Int) { +abstract class Loadout(label : String, + visible_slots : List[Loadout.SimplifiedEntry], + inventory : List[Loadout.SimplifiedEntry]) { /** * The label by which this `Loadout` is called. * @return the label */ def Label : String = label - /** - * The exo-suit in which the avatar will be dressed. - * Might be restricted and, thus, restrict the rest of the `Equipment` from being constructed and given. - * @return the exo-suit - */ - def ExoSuit : ExoSuitType.Value = exosuit - - /** - * The mechanized assault exo-suit specialization number that indicates whether the MAX performs: - * anti-infantry (1), - * anti-vehicular (2), - * or anti-air work (3). - * The major distinction is the type of arm weapons that MAX is equipped. - * When the blueprint doesn't call for a MAX, the number will be 0. - * @return the specialization number - */ - def Subtype : Int = subtype - /** * The `Equipment` in the `Player`'s holster slots when this `Loadout` is created. * @return a `List` of the holster item blueprints @@ -80,12 +59,8 @@ final case class Loadout(private val label : String, } object Loadout { - def apply(label : String, visible : List[SimplifiedEntry], inventory : List[SimplifiedEntry]) : Loadout = { - new Loadout(label, visible, inventory, ExoSuitType.Standard, 0) - } - def Create(player : Player, label : String) : Loadout = { - new Loadout( + InfantryLoadout( label, packageSimplifications(player.Holsters()), packageSimplifications(player.Inventory.Items.values.toList), @@ -95,10 +70,11 @@ object Loadout { } def Create(vehicle : Vehicle, label : String) : Loadout = { - Loadout( + VehicleLoadout( label, packageSimplifications(vehicle.Weapons.map({ case ((index, weapon)) => InventoryItem(weapon.Equipment.get, index) }).toList), - packageSimplifications(vehicle.Trunk.Items.values.toList) + packageSimplifications(vehicle.Trunk.Items.values.toList), + vehicle.Definition ) } @@ -153,8 +129,12 @@ object Loadout { final case class ShorthandKit(definition : KitDefinition) extends Simplification def DetermineSubtype(player : Player) : Int = { - if(player.ExoSuit == ExoSuitType.MAX) { - player.Slot(0).Equipment match { + DetermineSubtype(player.ExoSuit, player.Slot(0).Equipment) + } + + def DetermineSubtype(suit : ExoSuitType.Value, weapon : Option[Equipment]) : Int = { + if(suit == ExoSuitType.MAX) { + weapon match { case Some(item) => item.Definition match { case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.nchev_scattercannon | GlobalDefinitions.vshev_quasar => @@ -180,7 +160,7 @@ object Loadout { * @param equipment the holster slots * @return a `List` of simplified `Equipment` */ - private def packageSimplifications(equipment : Array[EquipmentSlot]) : List[SimplifiedEntry] = { + protected def packageSimplifications(equipment : Array[EquipmentSlot]) : List[SimplifiedEntry] = { recursiveHolsterSimplifications(equipment.iterator) } @@ -189,7 +169,7 @@ object Loadout { * @param equipment the enumerated contents of the inventory * @return a `List` of simplified `Equipment` */ - private def packageSimplifications(equipment : List[InventoryItem]) : List[SimplifiedEntry] = { + protected def packageSimplifications(equipment : List[InventoryItem]) : List[SimplifiedEntry] = { equipment.map(entry => { SimplifiedEntry(buildSimplification(entry.obj), entry.start) }) } diff --git a/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala new file mode 100644 index 00000000..b4713d64 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala @@ -0,0 +1,11 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.loadouts + +import net.psforever.objects.definition._ + +final case class VehicleLoadout(label : String, + visible_slots : List[Loadout.SimplifiedEntry], + inventory : List[Loadout.SimplifiedEntry], + vehicle_definition : VehicleDefinition) extends Loadout(label, visible_slots, inventory) { + def Definition : VehicleDefinition = vehicle_definition +} \ No newline at end of file diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala index 2c062abb..9067ae34 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala @@ -4,6 +4,7 @@ package net.psforever.objects.serverobject.terminals import net.psforever.objects._ import net.psforever.objects.definition._ import net.psforever.objects.equipment.Equipment +import net.psforever.objects.loadouts.Loadout import net.psforever.packet.game.ItemTransactionMessage import net.psforever.types.ExoSuitType @@ -336,7 +337,7 @@ object EquipmentTerminalDefinition { * `TerminalDefinition.MakeKit` */ def BuildSimplifiedPattern(entry : Loadout.Simplification) : Equipment = { - import net.psforever.objects.Loadout._ + import net.psforever.objects.loadouts.Loadout._ entry match { case obj : ShorthandTool => val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.definition }) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala index 192d87dc..8060db3c 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala @@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.terminals import akka.actor.ActorContext import net.psforever.objects.Player +import net.psforever.objects.loadouts.InfantryLoadout import net.psforever.objects.inventory.InventoryItem import net.psforever.objects.serverobject.structures.Amenity import net.psforever.packet.game.ItemTransactionMessage @@ -52,7 +53,7 @@ class OrderTerminalABDefinition(object_id : Int) extends EquipmentTerminalDefini override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { if(msg.item_page == 4) { //Favorites tab player.LoadLoadout(msg.unk1) match { - case Some(loadout) => + case Some(loadout : InfantryLoadout) => if(loadout.ExoSuit != ExoSuitType.MAX) { val holsters = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) val inventory = loadout.Inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) @@ -61,7 +62,7 @@ class OrderTerminalABDefinition(object_id : Int) extends EquipmentTerminalDefini else { Terminal.NoDeal() } - case None => + case Some(_) | None => Terminal.NoDeal() } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala index a1bfca86..c5ab86aa 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala @@ -2,6 +2,7 @@ package net.psforever.objects.serverobject.terminals import net.psforever.objects.Player +import net.psforever.objects.loadouts.InfantryLoadout import net.psforever.objects.inventory.InventoryItem import net.psforever.packet.game.ItemTransactionMessage import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition._ @@ -37,11 +38,11 @@ class OrderTerminalDefinition extends EquipmentTerminalDefinition(612) { override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { if(msg.item_page == 4) { //Favorites tab player.LoadLoadout(msg.unk1) match { - case Some(loadout) => + case Some(loadout : InfantryLoadout) => val holsters = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) val inventory = loadout.Inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) Terminal.InfantryLoadout(loadout.ExoSuit, loadout.Subtype, holsters, inventory) - case None => + case Some(_) | None => Terminal.NoDeal() } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala index a64fad15..bff5fe26 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala @@ -3,6 +3,7 @@ package net.psforever.objects.serverobject.terminals import net.psforever.objects.definition.VehicleDefinition import net.psforever.objects.{Player, Vehicle} +import net.psforever.objects.loadouts.VehicleLoadout import net.psforever.objects.inventory.InventoryItem import net.psforever.packet.game.ItemTransactionMessage @@ -83,7 +84,7 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition // "aphelion_flight" -> (()=>Unit) ) - import net.psforever.objects.{Loadout => _Loadout} //distinguish from Terminal.Loadout message + import net.psforever.objects.loadouts.{Loadout => _Loadout} //distinguish from Terminal.Loadout message import _Loadout._ /** * A `Map` of the default contents of a `Vehicle` inventory, called the trunk. @@ -101,29 +102,31 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition val ammo_flux = ShorthandAmmoBox(flux_cannon_thresher_battery, flux_cannon_thresher_battery.Capacity) val ammo_bomb = ShorthandAmmoBox(liberator_bomb, liberator_bomb.Capacity) Map( - //"quadstealth" -> _Loadout("default_quadstealth", List(), List()), - "quadassault" -> _Loadout("default_quadassault", List(), + //"quadstealth" -> VehicleLoadout("default_quadstealth", List(), List(), quadstealth), + "quadassault" -> VehicleLoadout("default_quadassault", List(), List( SimplifiedEntry(ammo_12mm, 30), SimplifiedEntry(ammo_12mm, 34), SimplifiedEntry(ammo_12mm, 74), SimplifiedEntry(ammo_12mm, 78) - ) + ), + quadassault ), { val ammo = ShorthandAmmoBox(hellfire_ammo, hellfire_ammo.Capacity) - "fury" -> _Loadout("default_fury", List(), + "fury" -> VehicleLoadout("default_fury", List(), List( SimplifiedEntry(ammo, 30), SimplifiedEntry(ammo, 34), SimplifiedEntry(ammo, 74), SimplifiedEntry(ammo, 78) - ) + ), + fury ) }, - //"ant" -> _Loadout("default_ant", List(), List()), - //"ams" -> _Loadout("default_ams", List(), List()), - "two_man_assault_buggy" -> _Loadout("default_two_man_assault_buggy", List(), + //"ant" -> VehicleLoadout("default_ant", List(), List(), ant), + //"ams" -> VehicleLoadout("default_ams", List(), List(), ams), + "two_man_assault_buggy" -> VehicleLoadout("default_two_man_assault_buggy", List(), List( SimplifiedEntry(ammo_12mm, 30), SimplifiedEntry(ammo_12mm, 34), @@ -131,11 +134,12 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_12mm, 90), SimplifiedEntry(ammo_12mm, 94), SimplifiedEntry(ammo_12mm, 98) - ) + ), + two_man_assault_buggy ), { val ammo = ShorthandAmmoBox(skyguard_flak_cannon_ammo, skyguard_flak_cannon_ammo.Capacity) - "skyguard" -> _Loadout("default_skyguard", List(), + "skyguard" -> VehicleLoadout("default_skyguard", List(), List( SimplifiedEntry(ammo_12mm, 30), SimplifiedEntry(ammo_12mm, 34), @@ -143,10 +147,11 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo, 90), SimplifiedEntry(ammo, 94), SimplifiedEntry(ammo, 98) - ) + ), + skyguard ) }, - "threemanheavybuggy" -> _Loadout("default_threemanheavybuggy", List(), + "threemanheavybuggy" -> VehicleLoadout("default_threemanheavybuggy", List(), List( SimplifiedEntry(ammo_12mm, 30), SimplifiedEntry(ammo_12mm, 34), @@ -154,11 +159,12 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_mortar, 90), SimplifiedEntry(ammo_mortar, 94), SimplifiedEntry(ammo_mortar, 98) - ) + ), + threemanheavybuggy ), { val ammo = ShorthandAmmoBox(firebird_missile, firebird_missile.Capacity) - "twomanheavybuggy" -> _Loadout("default_twomanheavybuggy", List(), + "twomanheavybuggy" -> VehicleLoadout("default_twomanheavybuggy", List(), List( SimplifiedEntry(ammo, 30), SimplifiedEntry(ammo, 34), @@ -166,10 +172,11 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo, 90), SimplifiedEntry(ammo, 94), SimplifiedEntry(ammo, 98) - ) + ), + twomanheavybuggy ) }, - "twomanhoverbuggy" -> _Loadout("default_twomanhoverbuggy", List(), + "twomanhoverbuggy" -> VehicleLoadout("default_twomanhoverbuggy", List(), List( SimplifiedEntry(ammo_flux, 30), SimplifiedEntry(ammo_flux, 34), @@ -177,9 +184,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_flux, 90), SimplifiedEntry(ammo_flux, 94), SimplifiedEntry(ammo_flux, 98) - ) + ), + twomanhoverbuggy ), - "mediumtransport" -> _Loadout("default_mediumtransport", List(), + "mediumtransport" -> VehicleLoadout("default_mediumtransport", List(), List( SimplifiedEntry(ammo_20mm, 30), SimplifiedEntry(ammo_20mm, 34), @@ -190,9 +198,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_20mm, 150), SimplifiedEntry(ammo_20mm, 154), SimplifiedEntry(ammo_20mm, 158) - ) + ), + mediumtransport ), - "battlewagon" -> _Loadout("default_battlewagon", List(), + "battlewagon" -> VehicleLoadout("default_battlewagon", List(), List( SimplifiedEntry(ammo_15mm, 30), SimplifiedEntry(ammo_15mm, 34), @@ -203,11 +212,12 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_15mm, 150), SimplifiedEntry(ammo_15mm, 154), SimplifiedEntry(ammo_15mm, 158) - ) + ), + battlewagon ), { val ammo = ShorthandAmmoBox(gauss_cannon_ammo, gauss_cannon_ammo.Capacity) - "thunderer" -> _Loadout("default_thunderer", List(), + "thunderer" -> VehicleLoadout("default_thunderer", List(), List( SimplifiedEntry(ammo, 30), SimplifiedEntry(ammo, 34), @@ -218,12 +228,13 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo, 150), SimplifiedEntry(ammo, 154), SimplifiedEntry(ammo, 158) - ) + ), + thunderer ) }, { val ammo = ShorthandAmmoBox(fluxpod_ammo, fluxpod_ammo.Capacity) - "aurora" -> _Loadout("default_aurora", List(), + "aurora" -> VehicleLoadout("default_aurora", List(), List( SimplifiedEntry(ammo, 30), SimplifiedEntry(ammo, 34), @@ -234,10 +245,11 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo, 150), SimplifiedEntry(ammo, 154), SimplifiedEntry(ammo, 158) - ) + ), + aurora ) }, - "apc_tr" -> _Loadout("default_apc_tr", List(), + "apc_tr" -> VehicleLoadout("default_apc_tr", List(), List( SimplifiedEntry(ammo_75mm, 30), SimplifiedEntry(ammo_75mm, 34), @@ -259,9 +271,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_15mm, 278), SimplifiedEntry(ammo_15mm, 282), SimplifiedEntry(ammo_15mm, 286) - ) + ), + apc_tr ), - "apc_nc" -> _Loadout("default_apc_nc", List(), + "apc_nc" -> VehicleLoadout("default_apc_nc", List(), List( SimplifiedEntry(ammo_75mm, 30), SimplifiedEntry(ammo_75mm, 34), @@ -283,9 +296,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_20mm, 278), SimplifiedEntry(ammo_20mm, 282), SimplifiedEntry(ammo_20mm, 286) - ) + ), + apc_nc ), - "apc_vs" -> _Loadout("default_apc_vs", List(), + "apc_vs" -> VehicleLoadout("default_apc_vs", List(), List( SimplifiedEntry(ammo_75mm, 30), SimplifiedEntry(ammo_75mm, 34), @@ -307,9 +321,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_flux, 278), SimplifiedEntry(ammo_flux, 282), SimplifiedEntry(ammo_flux, 286) - ) + ), + apc_vs ), - "lightning" -> _Loadout("default_lightning", List(), + "lightning" -> VehicleLoadout("default_lightning", List(), List( SimplifiedEntry(ammo_25mm, 30), SimplifiedEntry(ammo_25mm, 34), @@ -317,11 +332,12 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_75mm, 90), SimplifiedEntry(ammo_75mm, 94), SimplifiedEntry(ammo_75mm, 98) - ) + ), + lightning ), { val ammo = ShorthandAmmoBox(bullet_105mm, bullet_105mm.Capacity) - "prowler" -> _Loadout("default_prowler", List(), + "prowler" -> VehicleLoadout("default_prowler", List(), List( SimplifiedEntry(ammo_15mm, 30), SimplifiedEntry(ammo_15mm, 34), @@ -329,12 +345,13 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo, 90), SimplifiedEntry(ammo, 94), SimplifiedEntry(ammo, 98) - ) + ), + prowler ) }, { val ammo = ShorthandAmmoBox(bullet_150mm, bullet_150mm.Capacity) - "vanguard" -> _Loadout("default_vanguard", List(), + "vanguard" -> VehicleLoadout("default_vanguard", List(), List( SimplifiedEntry(ammo_20mm, 30), SimplifiedEntry(ammo_20mm, 34), @@ -342,13 +359,14 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo, 90), SimplifiedEntry(ammo, 94), SimplifiedEntry(ammo, 98) - ) + ), + vanguard ) }, { val ammo1 = ShorthandAmmoBox(pulse_battery, pulse_battery.Capacity) val ammo2 = ShorthandAmmoBox(heavy_rail_beam_battery, heavy_rail_beam_battery.Capacity) - "magrider" -> _Loadout("default_magrider", List(), + "magrider" -> VehicleLoadout("default_magrider", List(), List( SimplifiedEntry(ammo1, 30), SimplifiedEntry(ammo1, 34), @@ -356,23 +374,25 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo2, 90), SimplifiedEntry(ammo2, 94), SimplifiedEntry(ammo2, 98) - ) + ), + magrider ) }, - //"flail" -> _Loadout("default_flail", List(), List()), - //"switchblade" -> _Loadout("default_switchblade", List(), List()), - //"router" -> _Loadout("default_router", List(), List()), - "mosquito" -> _Loadout("default_mosquito", List(), + //"flail" -> VehicleLoadout("default_flail", List(), List(), flail), + //"switchblade" -> VehicleLoadout("default_switchblade", List(), List(), switchblade), + //"router" -> VehicleLoadout("default_router", List(), List(), router), + "mosquito" -> VehicleLoadout("default_mosquito", List(), List( SimplifiedEntry(ammo_12mm, 30), SimplifiedEntry(ammo_12mm, 34), SimplifiedEntry(ammo_12mm, 74), SimplifiedEntry(ammo_12mm, 78) - ) + ), + mosquito ), { val ammo = ShorthandAmmoBox(reaver_rocket, reaver_rocket.Capacity) - "lightgunship" -> _Loadout("default_lightgunship", List(), + "lightgunship" -> VehicleLoadout("default_lightgunship", List(), List( SimplifiedEntry(ammo, 30), SimplifiedEntry(ammo, 34), @@ -380,22 +400,24 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo, 90), SimplifiedEntry(ammo_20mm, 94), SimplifiedEntry(ammo_20mm, 98) - ) + ), + lightgunship ) }, { val ammo1 = ShorthandAmmoBox(wasp_rocket_ammo, wasp_rocket_ammo.Capacity) val ammo2 = ShorthandAmmoBox(wasp_gun_ammo, wasp_gun_ammo.Capacity) - "wasp" -> _Loadout("default_wasp", List(), + "wasp" -> VehicleLoadout("default_wasp", List(), List( SimplifiedEntry(ammo1, 30), SimplifiedEntry(ammo1, 34), SimplifiedEntry(ammo2, 74), SimplifiedEntry(ammo2, 78) - ) + ), + wasp ) }, - "liberator" -> _Loadout("default_liberator", List(), + "liberator" -> VehicleLoadout("default_liberator", List(), List( SimplifiedEntry(ammo_35mm, 30), SimplifiedEntry(ammo_35mm, 34), @@ -406,9 +428,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_bomb, 150), SimplifiedEntry(ammo_bomb, 154), SimplifiedEntry(ammo_bomb, 158) - ) + ), + liberator ), - "vulture" -> _Loadout("default_vulture", List(), + "vulture" -> VehicleLoadout("default_vulture", List(), List( SimplifiedEntry(ammo_35mm, 30), SimplifiedEntry(ammo_35mm, 34), @@ -418,9 +441,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_bomb, 98), SimplifiedEntry(ammo_bomb, 102), SimplifiedEntry(ammo_bomb, 106) - ) //TODO confirm + ), //TODO confirm + vulture ), - "dropship" -> _Loadout("default_dropship", List(), + "dropship" -> VehicleLoadout("default_dropship", List(), List( SimplifiedEntry(ammo_20mm, 30), SimplifiedEntry(ammo_20mm, 34), @@ -434,9 +458,10 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_20mm, 162), SimplifiedEntry(ammo_20mm, 166), SimplifiedEntry(ammo_20mm, 170) - ) + ), + dropship ), - "galaxy_gunship" -> _Loadout("galaxy_gunship", List(), + "galaxy_gunship" -> VehicleLoadout("galaxy_gunship", List(), List( SimplifiedEntry(ammo_35mm, 30), SimplifiedEntry(ammo_35mm, 34), @@ -450,10 +475,11 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition SimplifiedEntry(ammo_mortar, 178), SimplifiedEntry(ammo_mortar, 182), SimplifiedEntry(ammo_mortar, 186) - ) + ), + galaxy_gunship ) - //"phantasm" -> _Loadout("default_phantasm", List(), List()), - //"lodestar" -> _Loadout("default_lodestar", List(), List()), + //"phantasm" -> VehicleLoadout("default_phantasm", List(), List(), phantasm), + //"lodestar" -> VehicleLoadout("default_lodestar", List(), List(), lodestar), ) } diff --git a/common/src/test/scala/objects/AvatarTest.scala b/common/src/test/scala/objects/AvatarTest.scala index 95fb698f..6a45225d 100644 --- a/common/src/test/scala/objects/AvatarTest.scala +++ b/common/src/test/scala/objects/AvatarTest.scala @@ -3,6 +3,7 @@ package objects import net.psforever.objects.GlobalDefinitions._ import net.psforever.objects._ +import net.psforever.objects.loadouts._ import net.psforever.objects.definition.ImplantDefinition import net.psforever.types.{CharacterGender, ImplantType, PlanetSideEmpire} import org.specs2.mutable._ @@ -291,7 +292,7 @@ class AvatarTest extends Specification { avatar.SaveLoadout(obj, "test", 0) avatar.LoadLoadout(0) match { - case Some(items) => + case Some(items : InfantryLoadout) => items.Label mustEqual "test" items.ExoSuit mustEqual obj.ExoSuit items.Subtype mustEqual 0 @@ -321,7 +322,7 @@ class AvatarTest extends Specification { inventory(4).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual energy_cell inventory(5).index mustEqual 39 inventory(5).item.asInstanceOf[Loadout.ShorthandSimpleItem].definition mustEqual remote_electronics_kit - case None => + case _ => ko } } @@ -347,13 +348,13 @@ class AvatarTest extends Specification { avatar.SaveLoadout(obj, "test", 0) avatar.LoadLoadout(0) match { - case Some(items) => + case Some(items : InfantryLoadout) => items.Label mustEqual "test" items.ExoSuit mustEqual obj.ExoSuit items.Subtype mustEqual 0 items.VisibleSlots.length mustEqual 3 items.Inventory.length mustEqual 0 //empty - case None => + case _ => ko } } @@ -366,13 +367,13 @@ class AvatarTest extends Specification { avatar.SaveLoadout(obj, "test", 0) avatar.LoadLoadout(0) match { - case Some(items) => + case Some(items : InfantryLoadout) => items.Label mustEqual "test" items.ExoSuit mustEqual obj.ExoSuit items.Subtype mustEqual 0 items.VisibleSlots.length mustEqual 0 //empty items.Inventory.length mustEqual 6 - case None => + case _ => ko } } diff --git a/common/src/test/scala/objects/LoadoutTest.scala b/common/src/test/scala/objects/LoadoutTest.scala index fb01d696..208f5f6d 100644 --- a/common/src/test/scala/objects/LoadoutTest.scala +++ b/common/src/test/scala/objects/LoadoutTest.scala @@ -2,6 +2,7 @@ package objects import net.psforever.objects._ +import net.psforever.objects.loadouts._ import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire} import net.psforever.objects.GlobalDefinitions._ import org.specs2.mutable._ @@ -36,7 +37,7 @@ class LoadoutTest extends Specification { "create a loadout that contains a player's inventory" in { val player = CreatePlayer() - val obj = Loadout.Create(player, "test") + val obj = Loadout.Create(player, "test").asInstanceOf[InfantryLoadout] obj.Label mustEqual "test" obj.ExoSuit mustEqual obj.ExoSuit @@ -71,16 +72,16 @@ class LoadoutTest extends Specification { slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max) Player.SuitSetup(player, ExoSuitType.MAX) - val ldout1 = Loadout.Create(player, "weaponless") + val ldout1 = Loadout.Create(player, "weaponless").asInstanceOf[InfantryLoadout] slot.Equipment = None slot.Equipment = Tool(trhev_dualcycler) - val ldout2 = Loadout.Create(player, "cycler") + val ldout2 = Loadout.Create(player, "cycler").asInstanceOf[InfantryLoadout] slot.Equipment = None slot.Equipment = Tool(trhev_pounder) - val ldout3 = Loadout.Create(player, "pounder") + val ldout3 = Loadout.Create(player, "pounder").asInstanceOf[InfantryLoadout] slot.Equipment = None slot.Equipment = Tool(trhev_burster) - val ldout4 = Loadout.Create(player, "burster") + val ldout4 = Loadout.Create(player, "burster").asInstanceOf[InfantryLoadout] ldout1.Subtype mustEqual 0 ldout2.Subtype mustEqual 1 diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 33514522..fd9bd7d1 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -15,6 +15,7 @@ import net.psforever.objects._ import net.psforever.objects.definition.ToolDefinition import net.psforever.objects.definition.converter.CorpseConverter import net.psforever.objects.equipment._ +import net.psforever.objects.loadouts._ import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.serverobject.mount.Mountable @@ -2360,6 +2361,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case msg @ FavoritesRequest(player_guid, unk, action, line, label) => + log.info(s"FavoritesRequest: $msg") if(player.GUID == player_guid) { val name = label.getOrElse("missing_loadout_name") action match { @@ -2372,7 +2374,6 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(FavoritesMessage(0, player_guid, line, "")) } } - log.info("FavoritesRequest: " + msg) case msg @ WeaponDelayFireMessage(seq_time, weapon_guid) => log.info("WeaponDelayFire: " + msg) From 2a4fe4865e7090741d8cf33a30dbbfa49e0877b3 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 15 May 2018 08:05:33 -0400 Subject: [PATCH 05/13] partially-working vehicle favorites system; modifications to existing favorites system, identification of packet parameters --- .../scala/net/psforever/objects/Avatar.scala | 17 +- .../scala/net/psforever/objects/Vehicle.scala | 33 +++ .../terminals/OrderTerminalABDefinition.scala | 2 +- .../terminals/OrderTerminalDefinition.scala | 2 +- .../terminals/RepairRearmSiloDefinition.scala | 13 +- .../serverobject/terminals/Terminal.scala | 5 +- .../terminals/TerminalDefinition.scala | 2 +- .../packet/game/FavoritesMessage.scala | 29 +-- .../packet/game/FavoritesRequest.scala | 22 +- .../net/psforever/types/LoadoutType.scala | 16 ++ .../net/psforever/types/TransactionType.scala | 2 +- .../scala/game/FavoritesMessageTest.scala | 9 +- .../scala/game/FavoritesRequestTest.scala | 7 +- .../terminal/OrderTerminalABTest.scala | 4 +- .../src/main/scala/WorldSessionActor.scala | 220 ++++++++++++++---- 15 files changed, 295 insertions(+), 88 deletions(-) create mode 100644 common/src/main/scala/net/psforever/types/LoadoutType.scala diff --git a/common/src/main/scala/net/psforever/objects/Avatar.scala b/common/src/main/scala/net/psforever/objects/Avatar.scala index 8b704a6d..05caf2be 100644 --- a/common/src/main/scala/net/psforever/objects/Avatar.scala +++ b/common/src/main/scala/net/psforever/objects/Avatar.scala @@ -17,7 +17,7 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : /** Certifications */ private val certs : mutable.Set[CertificationType.Value] = mutable.Set[CertificationType.Value]() /** Implants
- * Unlike other objects, the maximum number of `ImplantSlots` are built into the `Avatar`. + * Unlike other objects, all `ImplantSlot` objects are already built into the `Avatar`. * Additionally, implants do not have tightly-coupled "`Definition` objects" that explain a formal implant object. * The `ImplantDefinition` objects themselves are moved around as if they were the implants. * The terms externally used for the states of process is "installed" and "uninstalled." @@ -25,9 +25,12 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : * @see `DetailedCharacterData.implants` */ private val implants : Array[ImplantSlot] = Array.fill[ImplantSlot](3)(new ImplantSlot) - /** Loadouts */ - private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](10)(None) - /** Locker (inventory slot number five) */ + /** Loadouts
+ * 0-9 are Infantry loadouts + * 10-14 are Vehicle loadouts + */ + private val loadouts : Array[Option[Loadout]] = Array.fill[Option[Loadout]](15)(None) + /** Locker */ private val locker : LockerContainer = new LockerContainer() { override def toString : String = { s"$name's ${Definition.Name}" @@ -154,6 +157,12 @@ class Avatar(val name : String, val faction : PlanetSideEmpire.Value, val sex : } } + def SaveLoadout(owner : Vehicle, label : String, line : Int) : Unit = { + if(line > 9 && line < loadouts.length) { + loadouts(line) = Some(Loadout.Create(owner, label)) + } + } + def LoadLoadout(line : Int) : Option[Loadout] = loadouts.lift(line).getOrElse(None) def DeleteLoadout(line : Int) : Unit = { diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index 3aaa32eb..6dabd99a 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -349,6 +349,39 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ def VisibleSlots : Set[Int] = weapons.keySet + override def Slot(slotNum : Int) : EquipmentSlot = { + weapons.get(slotNum) +// .orElse(utilities.get(slotNum) match { +// case Some(_) => +// //TODO what do now? +// None +// case None => ; +// None +// }) + .orElse(Some(Inventory.Slot(slotNum))).get + } + + override def Find(guid : PlanetSideGUID) : Option[Int] = { + weapons.find({ case (_, obj) => + obj.Equipment match { + case Some(item) => + if(item.HasGUID && item.GUID == guid) { + true + } + else { + false + } + case None => + false + } + }) match { + case Some((index, _)) => + Some(index) + case None => + Inventory.Find(guid) + } + } + /** * A reference to the `Vehicle` `Trunk` space. * @return this `Vehicle` `Trunk` diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala index 8060db3c..3aaac12a 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala @@ -42,7 +42,7 @@ class OrderTerminalABDefinition(object_id : Int) extends EquipmentTerminalDefini override def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = buyFunc(player, msg) /** - * Process a `TransactionType.InfantryLoadout` action by the user. + * Process a `TransactionType.Loadout` action by the user. * `Loadout` objects are blueprints composed of exo-suit specifications and simplified `Equipment`-to-slot mappings. * If a valid loadout is found, its data is transformed back into actual `Equipment` for return to the user. * Loadouts that would suit the player into a mechanized assault exo-suit are not permitted. diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala index c5ab86aa..8a270b34 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala @@ -28,7 +28,7 @@ class OrderTerminalDefinition extends EquipmentTerminalDefinition(612) { override def Buy(player: Player, msg : ItemTransactionMessage) : Terminal.Exchange = buyFunc(player, msg) /** - * Process a `TransactionType.InfantryLoadout` action by the user. + * Process a `TransactionType.Loadout` action by the user. * `Loadout` objects are blueprints composed of exo-suit specifications and simplified `Equipment`-to-slot mappings. * If a valid loadout is found, its data is transformed back into actual `Equipment` for return to the user. * @param player the player diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala index b0daa854..baf37bc4 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala @@ -2,6 +2,9 @@ package net.psforever.objects.serverobject.terminals import net.psforever.objects.Player +import net.psforever.objects.inventory.InventoryItem +import net.psforever.objects.loadouts.VehicleLoadout +import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition.BuildSimplifiedPattern import net.psforever.packet.game.ItemTransactionMessage class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinition(objectId) { @@ -13,10 +16,12 @@ class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinit override def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { if(msg.item_page == 4) { //Favorites tab - player.LoadLoadout(msg.unk1) match { - case Some(loadout) => - Terminal.VehicleLoadout(Nil, Nil) - case None => + player.LoadLoadout(msg.unk1 + 10) match { + case Some(loadout : VehicleLoadout) => + val weapons = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + val inventory = loadout.Inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + Terminal.VehicleLoadout(loadout.vehicle_definition, weapons, inventory) + case _ => Terminal.NoDeal() } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala index 8ee17814..7a8676bb 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/Terminal.scala @@ -2,6 +2,7 @@ package net.psforever.objects.serverobject.terminals import net.psforever.objects.Player +import net.psforever.objects.definition.VehicleDefinition import net.psforever.objects.serverobject.structures.Amenity import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types.{TransactionType, Vector3} @@ -73,7 +74,7 @@ class Terminal(tdef : TerminalDefinition) extends Amenity { case TransactionType.Sell => tdef.Sell(player, msg) - case TransactionType.InfantryLoadout => + case TransactionType.Loadout => tdef.Loadout(player, msg) case _ => @@ -190,7 +191,7 @@ object Terminal { */ final case class InfantryLoadout(exosuit : ExoSuitType.Value, subtype : Int = 0, holsters : List[InventoryItem], inventory : List[InventoryItem]) extends Exchange - final case class VehicleLoadout(weapons : List[InventoryItem], inventory : List[InventoryItem]) extends Exchange + final case class VehicleLoadout(vehicle_definition : VehicleDefinition, weapons : List[InventoryItem], inventory : List[InventoryItem]) extends Exchange /** * Start the special effects caused by a proximity-base service. diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala index be739f4c..562c6c91 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/TerminalDefinition.scala @@ -24,7 +24,7 @@ abstract class TerminalDefinition(objectId : Int) extends net.psforever.objects. def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() /** - * The unimplemented functionality for this `Terminal`'s `TransactionType.InfantryLoadout` activity. + * The unimplemented functionality for this `Terminal`'s `TransactionType.Loadout` activity. */ def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() } diff --git a/common/src/main/scala/net/psforever/packet/game/FavoritesMessage.scala b/common/src/main/scala/net/psforever/packet/game/FavoritesMessage.scala index 7fa3d32d..314986c4 100644 --- a/common/src/main/scala/net/psforever/packet/game/FavoritesMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/FavoritesMessage.scala @@ -2,6 +2,7 @@ package net.psforever.packet.game import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import net.psforever.types.LoadoutType import scodec.Codec import scodec.codecs._ import shapeless.{::, HNil} @@ -18,12 +19,6 @@ import shapeless.{::, HNil} * Infantry equipment favorites are appended with a code for the type of exo-suit that they will load on a player. * This does not match the same two field numbering system as in `ArmorChangedMessage` packets.
*
- * Lists:
- * ` - * 0 - Equipment Terminal (infantry)
- * 1 - Repair/Rearm Silo (standard vehicles)
- * ` - *
* Armors:
* ` * 1 - Agile
@@ -33,13 +28,7 @@ import shapeless.{::, HNil} * 6 - AV MAX
* ` *
- * Exploration 1:
- * The identifier for the list is two bits so four separated lists of `Favorites` are supportable. - * Two of the lists are common enough and we can assume one of the others is related to Battleframe Robotics. - * These lists also do not include `Squad Defintion...` presets. - * What are the unknown lists?
- *
- * Exploration 2:
+ * Exploration:
* There are three unaccounted exo-suit indices - 0, 3, and 7; * and, there are two specific kinds of exo-suit that are not defined - Infiltration and Standard. * It is possible that one of the indices also defines the generic MAX (see `ArmorChangedMessage`). @@ -50,11 +39,11 @@ import shapeless.{::, HNil} * @param label the identifier for this entry * @param armor the type of exo-suit, if an Infantry loadout */ -final case class FavoritesMessage(list : Int, +final case class FavoritesMessage(list : LoadoutType.Value, player_guid : PlanetSideGUID, line : Int, label : String, - armor : Option[Int] = None) + armor : Option[Int]) extends PlanetSideGamePacket { type Packet = FavoritesMessage def opcode = GamePacketOpcode.FavoritesMessage @@ -62,12 +51,16 @@ final case class FavoritesMessage(list : Int, } object FavoritesMessage extends Marshallable[FavoritesMessage] { + def apply(list : LoadoutType.Value, player_guid : PlanetSideGUID, line : Int, label : String) : FavoritesMessage = { + FavoritesMessage(list, player_guid, line, label, None) + } + implicit val codec : Codec[FavoritesMessage] = ( - ("list" | uint2L) >>:~ { value => + ("list" | LoadoutType.codec) >>:~ { value => ("player_guid" | PlanetSideGUID.codec) :: ("line" | uint4L) :: ("label" | PacketHelpers.encodedWideStringAligned(2)) :: - conditional(value == 0, "armor" | uintL(3)) + conditional(value == LoadoutType.Infantry, "armor" | uintL(3)) }).xmap[FavoritesMessage] ( { case lst :: guid :: ln :: str :: arm :: HNil => @@ -75,7 +68,7 @@ object FavoritesMessage extends Marshallable[FavoritesMessage] { }, { case FavoritesMessage(lst, guid, ln, str, arm) => - val armset : Option[Int] = if(lst == 0 && arm.isEmpty) { Some(0) } else { arm } + val armset : Option[Int] = if(lst == LoadoutType.Infantry && arm.isEmpty) { Some(0) } else { arm } lst :: guid :: ln :: str :: armset :: HNil } ) diff --git a/common/src/main/scala/net/psforever/packet/game/FavoritesRequest.scala b/common/src/main/scala/net/psforever/packet/game/FavoritesRequest.scala index 3d9af568..ac3ccb10 100644 --- a/common/src/main/scala/net/psforever/packet/game/FavoritesRequest.scala +++ b/common/src/main/scala/net/psforever/packet/game/FavoritesRequest.scala @@ -2,21 +2,33 @@ package net.psforever.packet.game import net.psforever.packet.{GamePacketOpcode, Marshallable, PacketHelpers, PlanetSideGamePacket} +import net.psforever.types.LoadoutType import scodec.Codec import scodec.codecs._ object FavoritesAction extends Enumeration { type Type = Value - val Unknown, - Save, - Delete = Value + val + Unknown, + Save, + Delete + = Value implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L) } +/** + * na + * @param player_guid the player + * @param list na + * @param action the behavior of this packet + * @param line what line of the applicable loadout ("Saved Favorites") list is modified + * @param label applicable when a load out is being saved; + * this is the string that will be displayed in the list of loadouts on that line + */ final case class FavoritesRequest(player_guid : PlanetSideGUID, - unk : Int, + list : LoadoutType.Value, action : FavoritesAction.Value, line : Int, label : Option[String]) @@ -29,7 +41,7 @@ final case class FavoritesRequest(player_guid : PlanetSideGUID, object FavoritesRequest extends Marshallable[FavoritesRequest] { implicit val codec : Codec[FavoritesRequest] = ( ("player_guid" | PlanetSideGUID.codec) :: - ("unk" | uint2L) :: + ("list" | LoadoutType.codec) :: (("action" | FavoritesAction.codec) >>:~ { action => ("line" | uint4L) :: conditional(action == FavoritesAction.Save, "label" | PacketHelpers.encodedWideString) diff --git a/common/src/main/scala/net/psforever/types/LoadoutType.scala b/common/src/main/scala/net/psforever/types/LoadoutType.scala new file mode 100644 index 00000000..ad180e77 --- /dev/null +++ b/common/src/main/scala/net/psforever/types/LoadoutType.scala @@ -0,0 +1,16 @@ +// Copyright (c) 2017 PSForever +package net.psforever.types + +import net.psforever.packet.PacketHelpers +import scodec.codecs.uint2L + +object LoadoutType extends Enumeration { + type Type = Value + + val + Infantry, + Vehicle + = Value + + implicit val codec = PacketHelpers.createEnumerationCodec(this, uint2L) +} diff --git a/common/src/main/scala/net/psforever/types/TransactionType.scala b/common/src/main/scala/net/psforever/types/TransactionType.scala index bf76e025..4fee2ec1 100644 --- a/common/src/main/scala/net/psforever/types/TransactionType.scala +++ b/common/src/main/scala/net/psforever/types/TransactionType.scala @@ -12,7 +12,7 @@ object TransactionType extends Enumeration { Sell, // or forget on certif term Unk4, Unk5, - InfantryLoadout, + Loadout, Unk7 = Value diff --git a/common/src/test/scala/game/FavoritesMessageTest.scala b/common/src/test/scala/game/FavoritesMessageTest.scala index 4033eb82..74e25392 100644 --- a/common/src/test/scala/game/FavoritesMessageTest.scala +++ b/common/src/test/scala/game/FavoritesMessageTest.scala @@ -4,6 +4,7 @@ package game import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ +import net.psforever.types.LoadoutType import scodec.bits._ class FavoritesMessageTest extends Specification { @@ -13,7 +14,7 @@ class FavoritesMessageTest extends Specification { "decode (for infantry)" in { PacketCoding.DecodePacket(stringInfantry).require match { case FavoritesMessage(list, player_guid, line, label, armor) => - list mustEqual 0 + list mustEqual LoadoutType.Infantry player_guid mustEqual PlanetSideGUID(3760) line mustEqual 0 label mustEqual "Agile (basic)" @@ -25,7 +26,7 @@ class FavoritesMessageTest extends Specification { } "encode (for infantry)" in { - val msg = FavoritesMessage(0, PlanetSideGUID(3760), 0, "Agile (basic)", Option(1)) + val msg = FavoritesMessage(LoadoutType.Infantry, PlanetSideGUID(3760), 0, "Agile (basic)", Option(1)) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringInfantry @@ -34,7 +35,7 @@ class FavoritesMessageTest extends Specification { "decode (for vehicles)" in { PacketCoding.DecodePacket(stringVehicles).require match { case FavoritesMessage(list, player_guid, line, label, armor) => - list mustEqual 1 + list mustEqual LoadoutType.Vehicle player_guid mustEqual PlanetSideGUID(4210) line mustEqual 0 label mustEqual "Skyguard" @@ -45,7 +46,7 @@ class FavoritesMessageTest extends Specification { } "encode (for vehicles)" in { - val msg = FavoritesMessage(1, PlanetSideGUID(4210), 0, "Skyguard") + val msg = FavoritesMessage(LoadoutType.Vehicle, PlanetSideGUID(4210), 0, "Skyguard") val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringVehicles diff --git a/common/src/test/scala/game/FavoritesRequestTest.scala b/common/src/test/scala/game/FavoritesRequestTest.scala index 23adfccc..ca66f047 100644 --- a/common/src/test/scala/game/FavoritesRequestTest.scala +++ b/common/src/test/scala/game/FavoritesRequestTest.scala @@ -4,6 +4,7 @@ package game import org.specs2.mutable._ import net.psforever.packet._ import net.psforever.packet.game._ +import net.psforever.types.LoadoutType import scodec.bits._ class FavoritesRequestTest extends Specification { @@ -11,9 +12,9 @@ class FavoritesRequestTest extends Specification { "decode (for infantry)" in { PacketCoding.DecodePacket(stringInfantry).require match { - case FavoritesRequest(player_guid, unk, action, line, label) => + case FavoritesRequest(player_guid, list, action, line, label) => player_guid mustEqual PlanetSideGUID(75) - unk mustEqual 0 + list mustEqual LoadoutType.Infantry action mustEqual FavoritesAction.Save line mustEqual 1 label.isDefined mustEqual true @@ -24,7 +25,7 @@ class FavoritesRequestTest extends Specification { } "encode (for infantry)" in { - val msg = FavoritesRequest(PlanetSideGUID(75), 0, FavoritesAction.Save, 1, Some("Example")) + val msg = FavoritesRequest(PlanetSideGUID(75), LoadoutType.Infantry, FavoritesAction.Save, 1, Some("Example")) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringInfantry diff --git a/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala index c4d44e14..ee4b4a47 100644 --- a/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala +++ b/common/src/test/scala/objects/terminal/OrderTerminalABTest.scala @@ -68,10 +68,10 @@ class OrderTerminalABTest extends Specification { player.ExoSuit = ExoSuitType.MAX avatar.SaveLoadout(player, "test2", 1) - val msg1 = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.InfantryLoadout, 4, "", 0, PlanetSideGUID(0)) + val msg1 = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Loadout, 4, "", 0, PlanetSideGUID(0)) terminal.Request(player, msg1) mustEqual Terminal.InfantryLoadout(ExoSuitType.Standard, 0, Nil, Nil) - val msg2 = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.InfantryLoadout, 4, "", 1, PlanetSideGUID(0)) + val msg2 = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Loadout, 4, "", 1, PlanetSideGUID(0)) terminal.Request(player, msg2) mustEqual Terminal.NoDeal() } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index fd9bd7d1..9b05f120 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -39,6 +39,7 @@ import net.psforever.types._ import services._ import services.avatar.{AvatarAction, AvatarResponse, AvatarServiceMessage, AvatarServiceResponse} import services.local.{LocalAction, LocalResponse, LocalServiceMessage, LocalServiceResponse} +import services.vehicle.VehicleAction.UnstowEquipment import services.vehicle.{VehicleAction, VehicleResponse, VehicleServiceMessage, VehicleServiceResponse} import scala.annotation.tailrec @@ -818,7 +819,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) => //TODO optimizations against replacing Equipment with the exact same Equipment and potentially for recycling existing Equipment log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}") - sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.InfantryLoadout, true)) + sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, true)) val dropPred = DropPredicate(tplayer) val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred) val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred) @@ -881,7 +882,59 @@ class WorldSessionActor extends Actor with MDCContextAware { val objDef = obj.Definition avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, objDef.ObjectId, obj.GUID, objDef.Packet.ConstructorData(obj).get)) }) - sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.InfantryLoadout, true)) + sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, true)) + + case Terminal.VehicleLoadout(definition, weapons, inventory) => + log.info(s"$tplayer wants to change their vehicle equipment loadout to their option #${msg.unk1 + 1}") + log.info(s"Vehicle: $definition") + log.info(s"Weapons (${weapons.size}): $weapons") + log.info(s"Inventory (${inventory.size}): $inventory") + LocalVehicle match { + case Some(vehicle) => + sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, true)) + val (_, afterInventory) = inventory.partition( DropPredicate(tplayer) ) //dropped items are lost + //common action - remove old inventory + val deleteEquipment : (Int,Equipment)=>Unit = DeleteEquipmentFromVehicle(vehicle) + vehicle.Inventory.Clear().foreach({ case InventoryItem(obj, index) => + deleteEquipment(index, obj) + taskResolver ! GUIDTask.UnregisterEquipment(obj)(continent.GUID) + }) + val stowEquipment : (Int,Equipment)=>TaskResolver.GiveTask = StowNewEquipmentInVehicle(vehicle) + (if(vehicle.Definition == definition) { + //vehicles are the same type; transfer over weapons + vehicle.Weapons + .filter({ case (_, slot) => slot.Equipment.nonEmpty }) + .foreach({ case (_, slot) => + val equipment = slot.Equipment.get + slot.Equipment = None + val equipment_guid = equipment.GUID + sendResponse(ObjectDeleteMessage(equipment_guid, 0)) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, equipment_guid)) + taskResolver ! GUIDTask.UnregisterEquipment(equipment)(continent.GUID) + }) + weapons.foreach({ case InventoryItem(obj, index) => + //create weapons and share with everyone + taskResolver ! PutNewWeaponInVehicleSlot(vehicle, obj.asInstanceOf[Tool], index) + }) + afterInventory + } + else { + //do not transfer over weapons + if(vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset) { + afterInventory + } + else { + //accommodate as much of inventory as possible + //TODO map x,y -> x,y rather than reorganize items + val (stow, _) = GridInventory.recoverInventory(afterInventory, vehicle.Inventory) //dropped items can be forgotten + stow + } + }).foreach({ case InventoryItem(obj, index) => + taskResolver ! stowEquipment(index, obj) + }) + case None => + log.error(s"can not apply the loadout - can not find a vehicle") + } case Terminal.LearnCertification(cert, cost) => if(!tplayer.Certifications.contains(cert)) { @@ -895,9 +948,6 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, false)) } - case Terminal.VehicleLoadout(weapons, inventory) => - log.info(s"$tplayer wants to change their vehicle equipment loadout to their option #${msg.unk1 + 1}") - case Terminal.SellCertification(cert, cost) => if(tplayer.Certifications.contains(cert)) { log.info(s"$tplayer is forgetting the $cert certification for $cost points") @@ -1424,9 +1474,10 @@ class WorldSessionActor extends Actor with MDCContextAware { AwardBattleExperiencePoints(avatar, 1000000L) player = new Player(avatar) //player.Position = Vector3(3561.0f, 2854.0f, 90.859375f) //home3, HART C - //player.Orientation = Vector3(0f, 0f, 90f) - player.Position = Vector3(4262.211f ,4067.0625f ,262.35938f) //z6, Akna.tower - player.Orientation = Vector3(0f, 0f, 132.1875f) + player.Position = Vector3(3940.3984f, 4343.625f, 266.45312f) + player.Orientation = Vector3(0f, 0f, 90f) + //player.Position = Vector3(4262.211f ,4067.0625f ,262.35938f) //z6, Akna.tower + //player.Orientation = Vector3(0f, 0f, 132.1875f) // player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting player.Slot(0).Equipment = SimpleItem(remote_electronics_kit) //Tool(GlobalDefinitions.StandardPistol(player.Faction)) player.Slot(2).Equipment = Tool(punisher) //suppressor @@ -1757,15 +1808,15 @@ class WorldSessionActor extends Actor with MDCContextAware { case x :: xs => val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match { case (veh : Vehicle) => - (DeleteAmmunitionInVehicle(veh), ModifyAmmunitionInVehicle(veh)) + (DeleteEquipmentFromVehicle(veh), ModifyAmmunitionInVehicle(veh)) case _ => - (DeleteAmmunition(obj), ModifyAmmunition(obj)) + (DeleteEquipment(obj), ModifyAmmunition(obj)) } val (stowFuncTask, stowFunc) : ((Int, AmmoBox)=>TaskResolver.GiveTask, (Int, AmmoBox)=>Unit) = obj match { case (veh : Vehicle) => - (StowNewAmmunitionInVehicles(veh), StowAmmunitionInVehicles(veh)) + (StowNewEquipmentInVehicle(veh), StowEquipmentInVehicles(veh)) case _ => - (StowNewAmmunition(obj), StowAmmunition(obj)) + (StowNewEquipment(obj), StowEquipment(obj)) } xs.foreach(item => { obj.Inventory -= x.start @@ -1977,9 +2028,9 @@ class WorldSessionActor extends Actor with MDCContextAware { case x :: xs => val (deleteFunc, modifyFunc) : ((Int, AmmoBox)=>Unit, (AmmoBox, Int)=>Unit) = obj match { case (veh : Vehicle) => - (DeleteAmmunitionInVehicle(veh), ModifyAmmunitionInVehicle(veh)) + (DeleteEquipmentFromVehicle(veh), ModifyAmmunitionInVehicle(veh)) case _ => - (DeleteAmmunition(obj), ModifyAmmunition(obj)) + (DeleteEquipment(obj), ModifyAmmunition(obj)) } xs.foreach(item => { deleteFunc(item.start, item.obj.asInstanceOf[AmmoBox]) @@ -2071,7 +2122,14 @@ class WorldSessionActor extends Actor with MDCContextAware { findFunc(parent) case None => None - }) match { + }) + .orElse(LocalVehicle 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") @@ -2360,18 +2418,42 @@ class WorldSessionActor extends Actor with MDCContextAware { log.error(s"ItemTransaction: $terminal_guid does not exist") } - case msg @ FavoritesRequest(player_guid, unk, action, line, label) => + case msg @ FavoritesRequest(player_guid, list, action, line, label) => log.info(s"FavoritesRequest: $msg") if(player.GUID == player_guid) { - val name = label.getOrElse("missing_loadout_name") + val lineno = if(list == LoadoutType.Vehicle) { line + 10 } else { line } + val name = label.getOrElse(s"missing_loadout_${line+1}") action match { - case FavoritesAction.Unknown => ; case FavoritesAction.Save => - avatar.SaveLoadout(player, name, line) - sendResponse(FavoritesMessage(0, player_guid, line, name)) + (if(list == LoadoutType.Infantry) { + Some(player) + } + else if(list == LoadoutType.Vehicle) { + player.VehicleSeated match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) + case None => + None + } + } + else { + None + }) match { + case Some(owner : Player) => + avatar.SaveLoadout(owner, name, lineno) + case Some(owner : Vehicle) => + avatar.SaveLoadout(owner, name, lineno) + case Some(_) | None => + log.error("FavoritesRequest: unexpected owner for favorites") + } + sendResponse(FavoritesMessage(list, player_guid, line, name)) + case FavoritesAction.Delete => - avatar.DeleteLoadout(line) - sendResponse(FavoritesMessage(0, player_guid, line, "")) + avatar.DeleteLoadout(lineno) + sendResponse(FavoritesMessage(list, player_guid, line, "")) + + case FavoritesAction.Unknown => + log.warn("FavoritesRequest: unknown favorites action") } } @@ -2653,6 +2735,46 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + def PutNewWeaponInVehicleSlot(target : Vehicle, obj : Tool, index : Int) : TaskResolver.GiveTask = { + TaskResolver.GiveTask( + new Task() { + private val localTarget = target + private val localIndex = index + private val localObject = obj + private val localAnnounce = self + private val localAvatarService = avatarService + private val localVehicleService = vehicleService + + override def isComplete : Task.Resolution.Value = { + if(localTarget.Slot(localIndex).Equipment.contains(localObject)) { + Task.Resolution.Success + } + else { + Task.Resolution.Incomplete + } + } + + def Execute(resolver : ActorRef) : Unit = { + localTarget.Slot(localIndex).Equipment = localObject + resolver ! scala.util.Success(this) + } + + override def onSuccess() : Unit = { + val definition = localObject.Definition + if(localTarget.VisibleSlots.contains(localIndex)) { + localAvatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(localTarget.GUID, localTarget.GUID, localIndex, localObject)) + } + val channel = s"${localTarget.Actor}" + (0 until localObject.MaxAmmoSlot).foreach({ index => + val box = localObject.AmmoSlots(index).Box + val boxDef = box.Definition + val boxdata = boxDef.Packet.DetailedConstructorData(box).get + localVehicleService ! VehicleServiceMessage(channel, VehicleAction.InventoryState(PlanetSideGUID(0), box, localObject.GUID, index, boxdata)) + }) + } + }, List(GUIDTask.RegisterTool(obj)(continent.GUID))) + } + /** * Construct tasking that coordinates the following:
* 1) Remove a new piece of `Equipment` from where it is currently stored.
@@ -3297,14 +3419,14 @@ class WorldSessionActor extends Actor with MDCContextAware { } /** - * Given an object that contains a box of amunition in its `Inventory` at a certain location, + * Given an object that contains an item (`Equipment`) in its `Inventory` at a certain location, * remove it permanently. * @param obj the `Container` - * @param start where the ammunition can be found - * @param item an object to unregister (should have been the ammunition that was removed); + * @param start where the item can be found + * @param item an object to unregister; * not explicitly checked */ - private def DeleteAmmunition(obj : PlanetSideGameObject with Container)(start : Int, item : AmmoBox) : Unit = { + private def DeleteEquipment(obj : PlanetSideGameObject with Container)(start : Int, item : Equipment) : Unit = { val item_guid = item.GUID obj.Inventory -= start taskResolver ! GUIDTask.UnregisterEquipment(item)(continent.GUID) @@ -3312,17 +3434,17 @@ class WorldSessionActor extends Actor with MDCContextAware { } /** - * Given a vehicle that contains a box of amunition in its `Trunk` at a certain location, + * Given a vehicle that contains an item (`Equipment`) in its `Trunk` at a certain location, * remove it permanently. - * @see `DeleteAmmunition` + * @see `DeleteEquipment` * @param obj the `Vehicle` - * @param start where the ammunition can be found - * @param item an object to unregister (should have been the ammunition that was removed); + * @param start where the item can be found + * @param item an object to unregister; * not explicitly checked */ - private def DeleteAmmunitionInVehicle(obj : Vehicle)(start : Int, item : AmmoBox) : Unit = { + private def DeleteEquipmentFromVehicle(obj : Vehicle)(start : Int, item : Equipment) : Unit = { val item_guid = item.GUID - DeleteAmmunition(obj)(start, item) + DeleteEquipment(obj)(start, item) vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player.GUID, item_guid)) } @@ -3355,27 +3477,27 @@ class WorldSessionActor extends Actor with MDCContextAware { /** * Announce that an already-registered `AmmoBox` object exists in a given position in some `Container` object's inventory. - * @see `StowAmmunitionInVehicles` + * @see `StowEquipmentInVehicles` * @see `ChangeAmmoMessage` * @param obj the `Container` object * @param index an index in `obj`'s inventory * @param item an `AmmoBox` */ - def StowAmmunition(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : Unit = { + def StowEquipment(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : Unit = { obj.Inventory += index -> item sendResponse(ObjectAttachMessage(obj.GUID, item.GUID, index)) } /** * Announce that an already-registered `AmmoBox` object exists in a given position in some vehicle's inventory. - * @see `StowAmmunition` + * @see `StowEquipment` * @see `ChangeAmmoMessage` * @param obj the `Vehicle` object * @param index an index in `obj`'s inventory * @param item an `AmmoBox` */ - def StowAmmunitionInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : Unit = { - StowAmmunition(obj)(index, item) + def StowEquipmentInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : Unit = { + StowEquipment(obj)(index, item) vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player.GUID, obj.GUID, index, item)) } @@ -3383,14 +3505,14 @@ class WorldSessionActor extends Actor with MDCContextAware { * Prepare tasking that registers an `AmmoBox` object * and announces that it exists in a given position in some `Container` object's inventory. * `PutEquipmentInSlot` is the fastest way to achieve these goals. - * @see `StowNewAmmunitionInVehicles` + * @see `StowNewEquipmentInVehicle` * @see `ChangeAmmoMessage` * @param obj the `Container` object * @param index an index in `obj`'s inventory * @param item an `AmmoBox` * @return a `TaskResolver.GiveTask` chain that executes the action */ - def StowNewAmmunition(obj : PlanetSideGameObject with Container)(index : Int, item : AmmoBox) : TaskResolver.GiveTask = { + def StowNewEquipment(obj : PlanetSideGameObject with Container)(index : Int, item : Equipment) : TaskResolver.GiveTask = { PutEquipmentInSlot(obj, item, index) } @@ -3398,14 +3520,14 @@ class WorldSessionActor extends Actor with MDCContextAware { * Prepare tasking that registers an `AmmoBox` object * and announces that it exists in a given position in some vehicle's inventory. * `PutEquipmentInSlot` is the fastest way to achieve these goals. - * @see `StowNewAmmunition` + * @see `StowNewEquipment` * @see `ChangeAmmoMessage` * @param obj the `Container` object * @param index an index in `obj`'s inventory * @param item an `AmmoBox` * @return a `TaskResolver.GiveTask` chain that executes the action */ - def StowNewAmmunitionInVehicles(obj : Vehicle)(index : Int, item : AmmoBox) : TaskResolver.GiveTask = { + def StowNewEquipmentInVehicle(obj : Vehicle)(index : Int, item : Equipment) : TaskResolver.GiveTask = { TaskResolver.GiveTask( new Task() { private val localService = vehicleService @@ -3424,7 +3546,7 @@ class WorldSessionActor extends Actor with MDCContextAware { resolver ! scala.util.Success(this) } }, - List(StowNewAmmunition(obj)(index, item)) + List(StowNewEquipment(obj)(index, item)) ) } @@ -3640,6 +3762,20 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + def LocalVehicle : Option[Vehicle] = { + player.VehicleSeated match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + Some(obj) + case _ => + None + } + case None => + None + } + } + /** * Perform specific operations depending on the target of deployment. * @param obj the object that has deployed From a513f4996ec37f2305797dfd6488591cb8c7b92a Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 16 May 2018 00:27:25 -0400 Subject: [PATCH 06/13] working vehicle loadouts from repair/rearm silos; code documentation and expanded tests --- .../objects/loadouts/InfantryLoadout.scala | 41 +++-- .../psforever/objects/loadouts/Loadout.scala | 80 +++++---- .../objects/loadouts/VehicleLoadout.scala | 22 ++- .../terminals/OrderTerminalABDefinition.scala | 8 +- .../terminals/OrderTerminalDefinition.scala | 6 +- .../terminals/ProximityUnit.scala | 25 +-- .../terminals/RepairRearmControl.scala | 5 + .../terminals/RepairRearmSilo.scala | 3 + .../terminals/RepairRearmSiloDefinition.scala | 8 +- .../terminals/VehicleTerminalDefinition.scala | 8 +- .../src/test/scala/objects/AvatarTest.scala | 34 ++-- .../src/test/scala/objects/LoadoutTest.scala | 46 +++-- .../objects/ServerObjectBuilderTest.scala | 19 ++ .../src/test/scala/objects/VehicleTest.scala | 32 ++++ .../objects/terminal/OrderTerminalTest.scala | 46 ++++- .../ProximityTerminalControlTest.scala | 3 +- .../objects/terminal/ProximityTest.scala | 170 ++++++++++++++++++ .../terminal/RepairRearmSiloTest.scala | 91 ++++++++++ .../terminal/TerminalControlTest.scala | 3 +- .../src/main/scala/WorldSessionActor.scala | 129 +++++-------- .../services/vehicle/VehicleAction.scala | 1 + .../services/vehicle/VehicleResponse.scala | 1 + .../services/vehicle/VehicleService.scala | 4 + .../src/test/scala/VehicleServiceTest.scala | 16 ++ 24 files changed, 601 insertions(+), 200 deletions(-) create mode 100644 common/src/test/scala/objects/terminal/ProximityTest.scala create mode 100644 common/src/test/scala/objects/terminal/RepairRearmSiloTest.scala diff --git a/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala index 72100a61..e7470546 100644 --- a/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala +++ b/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala @@ -3,26 +3,29 @@ package net.psforever.objects.loadouts import net.psforever.types.ExoSuitType +/** + * A blueprint of a player's uniform, their holster items, and their inventory items, saved in a specific state. + * This previous state can be restored on any given player template + * by reconstructing the items (if permitted) and re-assigning the uniform (if available).
+ *
+ * The fifth tab on an `order_terminal` window is occupied by the list of "Favorite" `Loadout` blueprints. + * The ten-long list is initialized with `FavoritesMessage` packets assigned to the "Infantry" list. + * Specific entries are added or removed using `FavoritesRequest` packets, + * re-established using other conventional game packets. + * @param label the name by which this inventory will be known when displayed in a Favorites list; + * field gets inherited + * @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target; + * field gets inherited + * @param inventory simplified representation of the `Equipment` in the target's inventory or trunk; + * field gets inherited + * @param exosuit the exo-suit in which the avatar will be dressed; + * may be restricted + * @param subtype the mechanized assault exo-suit specialization number that indicates whether the MAX performs: + * anti-infantry (1), anti-vehicular (2), or anti-air work (3); + * the default value is 0 + */ final case class InfantryLoadout(label : String, visible_slots : List[Loadout.SimplifiedEntry], inventory : List[Loadout.SimplifiedEntry], exosuit : ExoSuitType.Value, - subtype : Int) extends Loadout(label, visible_slots, inventory) { - /** - * The exo-suit in which the avatar will be dressed. - * Might be restricted and, thus, restrict the rest of the `Equipment` from being constructed and given. - * @return the exo-suit - */ - def ExoSuit : ExoSuitType.Value = exosuit - - /** - * The mechanized assault exo-suit specialization number that indicates whether the MAX performs: - * anti-infantry (1), - * anti-vehicular (2), - * or anti-air work (3). - * The major distinction is the type of arm weapons that MAX is equipped. - * When the blueprint doesn't call for a MAX, the number will be 0. - * @return the specialization number - */ - def Subtype : Int = subtype -} + subtype : Int) extends Loadout(label, visible_slots, inventory) diff --git a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala index 89d2261c..7035c89d 100644 --- a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala +++ b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala @@ -10,27 +10,16 @@ import net.psforever.types.ExoSuitType import scala.annotation.tailrec /** - * From a `Player` their current exo-suit and their `Equipment`, retain a set of instructions to reconstruct this arrangement.
+ * The base of all specific kinds of blueprint containers. + * This previous state can be restored on any appropriate template from which the loadout was copied + * by reconstructing the items (if permitted). + * The three fields are the name assigned to the loadout, + * the visible items that are created (which obey different rules depending on the source), + * and the concealed items that are created and added to the source's `Inventory`.
+ * For example, the `visible_slots` on a `Player`-borne loadout will transform into the form `Array[EquipmentSlot]`; + * `Vehicle`-originating loadouts transform into the form `Map[Int, Equipment]`. *
- * `Loadout` objects are composed of the following information, as if a blueprint:
- * - the avatar's current exo-suit
- * - the type of specialization, called a "subtype" (mechanized assault exo-suits only)
- * - the contents of the avatar's occupied holster slots
- * - the contents of the avatar's occupied inventory
- * `Equipment` contents of the holsters and of the formal inventory region will be condensed into a simplified form. - * These are also "blueprints." - * At its most basic, this simplification will merely comprise the former object's `EquipmentDefinition`. - * For items that are already simple - `Kit` objects and `SimpleItem` objects - this form will not be too far removed. - * For more complicated affairs like `Tool` objects and `AmmoBox` objects, only essential information will be retained.
- *
- * The deconstructed blueprint can be applied to any avatar. - * They are, however, typically tied to unique users and unique characters. - * For reasons of certifications, however, permissions on that avatar may affect what `Equipment` can be distributed. - * Even a whole blueprint can be denied if the user lacks the necessary exo-suit certification. - * A completely new piece of `Equipment` is constructed when the `Loadout` is regurgitated.
- *
- * The fifth tab on an `order_terminal` window is for "Favorite" blueprints for `Loadout` entries. - * The ten-long list is initialized with `FavoritesMessage` packets. + * The lists of user-specific loadouts are initialized with `FavoritesMessage` packets. * Specific entries are loaded or removed using `FavoritesRequest` packets. * @param label the name by which this inventory will be known when displayed in a Favorites list * @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target @@ -38,27 +27,15 @@ import scala.annotation.tailrec */ abstract class Loadout(label : String, visible_slots : List[Loadout.SimplifiedEntry], - inventory : List[Loadout.SimplifiedEntry]) { - /** - * The label by which this `Loadout` is called. - * @return the label - */ - def Label : String = label - - /** - * The `Equipment` in the `Player`'s holster slots when this `Loadout` is created. - * @return a `List` of the holster item blueprints - */ - def VisibleSlots : List[Loadout.SimplifiedEntry] = visible_slots - - /** - * The `Equipment` in the `Player`'s inventory region when this `Loadout` is created. - * @return a `List` of the inventory item blueprints - */ - def Inventory : List[Loadout.SimplifiedEntry] = inventory -} + inventory : List[Loadout.SimplifiedEntry]) object Loadout { + /** + * Produce the blueprint on a player. + * @param player the player + * @param label the name of this loadout + * @return an `InfantryLoadout` object populated with appropriate information about the current state of the player + */ def Create(player : Player, label : String) : Loadout = { InfantryLoadout( label, @@ -69,6 +46,12 @@ object Loadout { ) } + /** + * Produce the blueprint of a vehicle. + * @param vehicle the vehicle + * @param label the name of this loadout + * @return a `VehicleLoadout` object populated with appropriate information about the current state of the vehicle + */ def Create(vehicle : Vehicle, label : String) : Loadout = { VehicleLoadout( label, @@ -128,10 +111,29 @@ object Loadout { */ final case class ShorthandKit(definition : KitDefinition) extends Simplification + /** + * The sub-type of the player's uniform. + * Applicable to mechanized assault units, mainly. + * The subtype is reported as a number but indicates the specialization - anti-infantry, ani-vehicular, anti-air - of the suit + * as indicated by the arm weapon(s). + * @param player the player + * @return the numeric subtype + */ def DetermineSubtype(player : Player) : Int = { DetermineSubtype(player.ExoSuit, player.Slot(0).Equipment) } + /** + * The sub-type of the player's uniform. + * Applicable to mechanized assault units, mainly. + * The subtype is reported as a number but indicates the specialization - anti-infantry, ani-vehicular, anti-air - of the suit + * as indicated by the arm weapon(s). + * @param suit the player's uniform; + * the target is for MAX armors + * @param weapon any weapon the player may have it his "first pistol slot;" + * to a MAX, that is its "primary weapon slot" + * @return the numeric subtype + */ def DetermineSubtype(suit : ExoSuitType.Value, weapon : Option[Equipment]) : Int = { if(suit == ExoSuitType.MAX) { weapon match { diff --git a/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala index b4713d64..5f7e0b63 100644 --- a/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala +++ b/common/src/main/scala/net/psforever/objects/loadouts/VehicleLoadout.scala @@ -3,9 +3,25 @@ package net.psforever.objects.loadouts import net.psforever.objects.definition._ +/** + * A blueprint of a vehicle's mounted weapons and its inventory items, saved in a specific state. + * This previous state can be restored on an apporpriate vehicle template + * by reconstructing the items (if permitted). + * Mismatched vehicles may produce no loadout or an imperfect loadout depending on specifications.
+ *
+ * The second tab on an `repair_silo` window is occupied by the list of "Favorite" `Loadout` blueprints. + * The five-long list is initialized with `FavoritesMessage` packets assigned to the "Vehicle" list. + * Specific entries are added or removed using `FavoritesRequest` packets, + * re-established using other conventional game packets. + * @param label the name by which this inventory will be known when displayed in a Favorites list; + * field gets inherited + * @param visible_slots simplified representation of the `Equipment` that can see "seen" on the target; + * field gets inherited + * @param inventory simplified representation of the `Equipment` in the target's inventory or trunk; + * field gets inherited + * @param vehicle_definition the original type of vehicle whose state is being populated + */ final case class VehicleLoadout(label : String, visible_slots : List[Loadout.SimplifiedEntry], inventory : List[Loadout.SimplifiedEntry], - vehicle_definition : VehicleDefinition) extends Loadout(label, visible_slots, inventory) { - def Definition : VehicleDefinition = vehicle_definition -} \ No newline at end of file + vehicle_definition : VehicleDefinition) extends Loadout(label, visible_slots, inventory) diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala index 3aaac12a..c90aca31 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalABDefinition.scala @@ -54,10 +54,10 @@ class OrderTerminalABDefinition(object_id : Int) extends EquipmentTerminalDefini if(msg.item_page == 4) { //Favorites tab player.LoadLoadout(msg.unk1) match { case Some(loadout : InfantryLoadout) => - if(loadout.ExoSuit != ExoSuitType.MAX) { - val holsters = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) - val inventory = loadout.Inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) - Terminal.InfantryLoadout(loadout.ExoSuit, loadout.Subtype, holsters, inventory) + if(loadout.exosuit != ExoSuitType.MAX) { + val holsters = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + Terminal.InfantryLoadout(loadout.exosuit, loadout.subtype, holsters, inventory) } else { Terminal.NoDeal() diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala index 8a270b34..d9730bd6 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/OrderTerminalDefinition.scala @@ -39,9 +39,9 @@ class OrderTerminalDefinition extends EquipmentTerminalDefinition(612) { if(msg.item_page == 4) { //Favorites tab player.LoadLoadout(msg.unk1) match { case Some(loadout : InfantryLoadout) => - val holsters = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) - val inventory = loadout.Inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) - Terminal.InfantryLoadout(loadout.ExoSuit, loadout.Subtype, holsters, inventory) + val holsters = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + Terminal.InfantryLoadout(loadout.exosuit, loadout.subtype, holsters, inventory) case Some(_) | None => Terminal.NoDeal() } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala index 8339e51e..25843fa1 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityUnit.scala @@ -6,26 +6,27 @@ import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage import net.psforever.packet.game.PlanetSideGUID /** - * A server object that is a "terminal" that can be accessed for amenities and services, - * triggered when a certain distance from the unit itself (proximity-based).
- *
- * Unlike conventional terminals, this structure is not necessarily structure-owned. + * A server object that provides a service, triggered when a certain distance from the unit itself (proximity-based). + * Unlike conventional terminals, this one is not necessarily structure-owned. * For example, the cavern crystals are considered owner-neutral elements that are not attached to a `Building` object. */ trait ProximityUnit { this : Terminal => - private var users : Set[PlanetSideGUID] = Set.empty + /** + * A list of targets that are currently affected by this proximity unit. + */ + private var targets : Set[PlanetSideGUID] = Set.empty - def NumberUsers : Int = users.size + def NumberUsers : Int = targets.size def AddUser(player_guid : PlanetSideGUID) : Int = { - users += player_guid + targets += player_guid NumberUsers } def RemoveUser(player_guid : PlanetSideGUID) : Int = { - users -= player_guid + targets -= player_guid NumberUsers } } @@ -33,11 +34,15 @@ trait ProximityUnit { object ProximityUnit { import akka.actor.Actor + /** + * A mixin `trait` for an `Actor`'s `PartialFunction` that handles messages, + * in this case handling messages that controls the telegraphed state of the `ProximityUnit` object as the number of users changes. + */ trait Use { this : Actor => def TerminalObject : Terminal with ProximityUnit - + val proximityBehavior : Receive = { case CommonMessages.Use(player) => val hadNoUsers = TerminalObject.NumberUsers == 0 @@ -52,4 +57,4 @@ object ProximityUnit { } } } -} \ No newline at end of file +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala index b5514305..2451fbb3 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala @@ -4,6 +4,11 @@ package net.psforever.objects.serverobject.terminals import akka.actor.Actor import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} +/** + * An `Actor` that handles messages being dispatched to a specific `IFFLock`. + * @param term the `RepairRearmSilo` object being governed + * @see `CommonMessages` + */ class RepairRearmControl(term : RepairRearmSilo) extends Actor with FactionAffinityBehavior.Check with ProximityUnit.Use { def FactionObject : FactionAffinity = term diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala index 45a01302..b01cfbae 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala @@ -2,6 +2,9 @@ package net.psforever.objects.serverobject.terminals /** + * A structure-owned server object for preserving vehicle loadouts, + * obtaining vehicle weapon ammunition, + * and, with proper perks, automatically repairing damage doen to allied vehicles. * A server object that is a "terminal" that can be accessed for amenities and services, * triggered when a certain distance from the unit itself (proximity-based).
*
diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala index baf37bc4..2fd82802 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala @@ -7,6 +7,10 @@ import net.psforever.objects.loadouts.VehicleLoadout import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition.BuildSimplifiedPattern import net.psforever.packet.game.ItemTransactionMessage +/** + * The `Definition` for any `Terminal` that is of a type "repair_silo." + * Has both proximity-based operation and direct access purchasing power. + */ class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinition(objectId) { Name = "repair_silo" @@ -18,8 +22,8 @@ class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinit if(msg.item_page == 4) { //Favorites tab player.LoadLoadout(msg.unk1 + 10) match { case Some(loadout : VehicleLoadout) => - val weapons = loadout.VisibleSlots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) - val inventory = loadout.Inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + val weapons = loadout.visible_slots.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + val inventory = loadout.inventory.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) Terminal.VehicleLoadout(loadout.vehicle_definition, weapons, inventory) case _ => Terminal.NoDeal() diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala index bff5fe26..8785ae7a 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala @@ -487,12 +487,12 @@ abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition vehicles.get(msg.item_name) match { case Some(vehicle) => val (weapons, inventory) = trunk.get(msg.item_name) match { - case Some(loadout) => + case Some(loadout : VehicleLoadout) => ( - loadout.VisibleSlots.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }), - loadout.Inventory.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }) + loadout.visible_slots.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }), + loadout.inventory.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }) ) - case None => + case _ => (List.empty, List.empty) } Terminal.BuyVehicle(vehicle(), weapons, inventory) diff --git a/common/src/test/scala/objects/AvatarTest.scala b/common/src/test/scala/objects/AvatarTest.scala index 6a45225d..6c331798 100644 --- a/common/src/test/scala/objects/AvatarTest.scala +++ b/common/src/test/scala/objects/AvatarTest.scala @@ -293,12 +293,12 @@ class AvatarTest extends Specification { avatar.LoadLoadout(0) match { case Some(items : InfantryLoadout) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 + items.label mustEqual "test" + items.exosuit mustEqual obj.ExoSuit + items.subtype mustEqual 0 - items.VisibleSlots.length mustEqual 3 - val holsters = items.VisibleSlots.sortBy(_.index) + items.visible_slots.length mustEqual 3 + val holsters = items.visible_slots.sortBy(_.index) holsters.head.index mustEqual 0 holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual beamer holsters.head.item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 1 //we changed this @@ -308,8 +308,8 @@ class AvatarTest extends Specification { holsters(2).index mustEqual 4 holsters(2).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual forceblade - items.Inventory.length mustEqual 6 - val inventory = items.Inventory.sortBy(_.index) + items.inventory.length mustEqual 6 + val inventory = items.inventory.sortBy(_.index) inventory.head.index mustEqual 6 inventory.head.item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm inventory(1).index mustEqual 9 @@ -349,11 +349,11 @@ class AvatarTest extends Specification { avatar.LoadLoadout(0) match { case Some(items : InfantryLoadout) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 - items.VisibleSlots.length mustEqual 3 - items.Inventory.length mustEqual 0 //empty + items.label mustEqual "test" + items.exosuit mustEqual obj.ExoSuit + items.subtype mustEqual 0 + items.visible_slots.length mustEqual 3 + items.inventory.length mustEqual 0 //empty case _ => ko } @@ -368,11 +368,11 @@ class AvatarTest extends Specification { avatar.LoadLoadout(0) match { case Some(items : InfantryLoadout) => - items.Label mustEqual "test" - items.ExoSuit mustEqual obj.ExoSuit - items.Subtype mustEqual 0 - items.VisibleSlots.length mustEqual 0 //empty - items.Inventory.length mustEqual 6 + items.label mustEqual "test" + items.exosuit mustEqual obj.ExoSuit + items.subtype mustEqual 0 + items.visible_slots.length mustEqual 0 //empty + items.inventory.length mustEqual 6 case _ => ko } diff --git a/common/src/test/scala/objects/LoadoutTest.scala b/common/src/test/scala/objects/LoadoutTest.scala index 208f5f6d..1f32386e 100644 --- a/common/src/test/scala/objects/LoadoutTest.scala +++ b/common/src/test/scala/objects/LoadoutTest.scala @@ -39,12 +39,12 @@ class LoadoutTest extends Specification { val player = CreatePlayer() val obj = Loadout.Create(player, "test").asInstanceOf[InfantryLoadout] - obj.Label mustEqual "test" - obj.ExoSuit mustEqual obj.ExoSuit - obj.Subtype mustEqual 0 + obj.label mustEqual "test" + obj.exosuit mustEqual ExoSuitType.Standard + obj.subtype mustEqual 0 - obj.VisibleSlots.length mustEqual 3 - val holsters = obj.VisibleSlots.sortBy(_.index) + obj.visible_slots.length mustEqual 3 + val holsters = obj.visible_slots.sortBy(_.index) holsters.head.index mustEqual 0 holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual beamer holsters(1).index mustEqual 2 @@ -52,8 +52,8 @@ class LoadoutTest extends Specification { holsters(2).index mustEqual 4 holsters(2).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual forceblade - obj.Inventory.length mustEqual 5 - val inventory = obj.Inventory.sortBy(_.index) + obj.inventory.length mustEqual 5 + val inventory = obj.inventory.sortBy(_.index) inventory.head.index mustEqual 6 inventory.head.item.asInstanceOf[Loadout.ShorthandConstructionItem].definition mustEqual ace inventory(1).index mustEqual 9 @@ -66,6 +66,30 @@ class LoadoutTest extends Specification { inventory(4).item.asInstanceOf[Loadout.ShorthandSimpleItem].definition mustEqual remote_electronics_kit } + "create a loadout that contains a vehicle's inventory" in { + val vehicle = Vehicle(mediumtransport) + vehicle.Inventory += 30 -> AmmoBox(bullet_9mm) + vehicle.Inventory += 33 -> AmmoBox(bullet_9mm_AP) + val obj = Loadout.Create(vehicle, "test").asInstanceOf[VehicleLoadout] + + obj.label mustEqual "test" + obj.vehicle_definition mustEqual mediumtransport + + obj.visible_slots.length mustEqual 2 + val holsters = obj.visible_slots.sortBy(_.index) + holsters.head.index mustEqual 5 + holsters.head.item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual mediumtransport_weapon_systemA + holsters(1).index mustEqual 6 + holsters(1).item.asInstanceOf[Loadout.ShorthandTool].definition mustEqual mediumtransport_weapon_systemB + + obj.inventory.length mustEqual 2 + val inventory = obj.inventory.sortBy(_.index) + inventory.head.index mustEqual 30 + inventory.head.item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm + inventory(1).index mustEqual 33 + inventory(1).item.asInstanceOf[Loadout.ShorthandAmmoBox].definition mustEqual bullet_9mm_AP + } + "distinguish MAX subtype information" in { val player = CreatePlayer() val slot = player.Slot(0) @@ -83,9 +107,9 @@ class LoadoutTest extends Specification { slot.Equipment = Tool(trhev_burster) val ldout4 = Loadout.Create(player, "burster").asInstanceOf[InfantryLoadout] - ldout1.Subtype mustEqual 0 - ldout2.Subtype mustEqual 1 - ldout3.Subtype mustEqual 2 - ldout4.Subtype mustEqual 3 + ldout1.subtype mustEqual 0 + ldout2.subtype mustEqual 1 + ldout3.subtype mustEqual 2 + ldout4.subtype mustEqual 3 } } diff --git a/common/src/test/scala/objects/ServerObjectBuilderTest.scala b/common/src/test/scala/objects/ServerObjectBuilderTest.scala index 49678d5f..1f5b6dcf 100644 --- a/common/src/test/scala/objects/ServerObjectBuilderTest.scala +++ b/common/src/test/scala/objects/ServerObjectBuilderTest.scala @@ -208,6 +208,25 @@ class SpawnTubeObjectBuilderTest extends ActorTest { } } +class RepairRearmSiloObjectBuilderTest extends ActorTest { + import net.psforever.objects.GlobalDefinitions.repair_silo + import net.psforever.objects.serverobject.terminals.RepairRearmSilo + "LockerObjectBuilder" should { + "build" in { + val hub = ServerObjectBuilderTest.NumberPoolHub + val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, + RepairRearmSilo.Constructor(repair_silo)), hub), "silo") + actor ! "!" + + val reply = receiveOne(Duration.create(1000, "ms")) + assert(reply.isInstanceOf[RepairRearmSilo]) + assert(reply.asInstanceOf[RepairRearmSilo].HasGUID) + assert(reply.asInstanceOf[RepairRearmSilo].GUID == PlanetSideGUID(1)) + assert(reply == hub(1).get) + } + } +} + object ServerObjectBuilderTest { import net.psforever.objects.guid.source.LimitedNumberSource def NumberPoolHub : NumberPoolHub = { diff --git a/common/src/test/scala/objects/VehicleTest.scala b/common/src/test/scala/objects/VehicleTest.scala index 0124917d..e0495c4a 100644 --- a/common/src/test/scala/objects/VehicleTest.scala +++ b/common/src/test/scala/objects/VehicleTest.scala @@ -277,6 +277,38 @@ class VehicleTest extends Specification { filteredMap(0).UtilType mustEqual UtilityType.order_terminalb filteredMap(2).UtilType mustEqual UtilityType.order_terminalb } + + "access its mounted weapons by Slot" in { + val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + harasser_vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(10) + + harasser_vehicle.Slot(2).Equipment.get.GUID mustEqual PlanetSideGUID(10) + } + + "access its trunk by Slot" in { + val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + val ammobox = AmmoBox(GlobalDefinitions.armor_canister) + ammobox.GUID = PlanetSideGUID(10) + harasser_vehicle.Inventory += 30 -> ammobox + + harasser_vehicle.Slot(30).Equipment.get.GUID mustEqual PlanetSideGUID(10) + } + + "find its mounted weapons by GUID" in { + val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + harasser_vehicle.Weapons(2).Equipment.get.GUID = PlanetSideGUID(10) + + harasser_vehicle.Find(PlanetSideGUID(10)) mustEqual Some(2) + } + + "find items in its trunk by GUID" in { + val harasser_vehicle = Vehicle(GlobalDefinitions.two_man_assault_buggy) + val ammobox = AmmoBox(GlobalDefinitions.armor_canister) + ammobox.GUID = PlanetSideGUID(10) + harasser_vehicle.Inventory += 30 -> ammobox + + harasser_vehicle.Find(PlanetSideGUID(10)) mustEqual Some(30) + } } } diff --git a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala index 024c8794..76e30ba1 100644 --- a/common/src/test/scala/objects/terminal/OrderTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/OrderTerminalTest.scala @@ -72,12 +72,54 @@ class OrderTerminalTest extends Specification { terminal.Request(player, msg) mustEqual Terminal.NoDeal() } - //TODO loudout tests - "player can not buy equipment from the wrong page ('9mmbullet_AP', page 1)" in { val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "9mmbullet_AP", 0, PlanetSideGUID(0)) terminal.Request(player, msg) mustEqual Terminal.NoDeal() } + + "player can retrieve an infantry loadout" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + player2.ExoSuit = ExoSuitType.Agile + player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer) + player2.Slot(6).Equipment = Tool(GlobalDefinitions.beamer) + avatar.SaveLoadout(player2, "test", 0) + + val msg = terminal.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 0, PlanetSideGUID(0))) + msg.isInstanceOf[Terminal.InfantryLoadout] mustEqual true + val loadout = msg.asInstanceOf[Terminal.InfantryLoadout] + loadout.exosuit mustEqual ExoSuitType.Agile + loadout.subtype mustEqual 0 + loadout.holsters.size mustEqual 1 + loadout.holsters.head.obj.Definition mustEqual GlobalDefinitions.beamer + loadout.holsters.head.start mustEqual 0 + loadout.inventory.head.obj.Definition mustEqual GlobalDefinitions.beamer + loadout.inventory.head.start mustEqual 6 + } + + "player can not retrieve an infantry loadout from the wrong page" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + player2.ExoSuit = ExoSuitType.Agile + player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer) + player2.Slot(6).Equipment = Tool(GlobalDefinitions.beamer) + avatar.SaveLoadout(player2, "test", 0) + + val msg = terminal.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 3, "", 0, PlanetSideGUID(0))) //page 3 + msg.isInstanceOf[Terminal.NoDeal] mustEqual true + } + + "player can not retrieve an infantry loadout from the wrong line" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + player2.ExoSuit = ExoSuitType.Agile + player2.Slot(0).Equipment = Tool(GlobalDefinitions.beamer) + player2.Slot(6).Equipment = Tool(GlobalDefinitions.beamer) + avatar.SaveLoadout(player2, "test", 0) + + val msg = terminal.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 1, PlanetSideGUID(0))) + msg.isInstanceOf[Terminal.NoDeal] mustEqual true + } } } diff --git a/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala b/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala index f817be6a..a28235fe 100644 --- a/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/ProximityTerminalControlTest.scala @@ -25,8 +25,7 @@ class ProximityTerminalControl2Test extends ActorTest() { val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.medical_terminal, PlanetSideEmpire.TR) terminal.Actor !"hello" - val reply = receiveOne(Duration.create(500, "ms")) - assert(reply.isInstanceOf[Terminal.NoDeal]) + expectNoMsg(Duration.create(500, "ms")) } } diff --git a/common/src/test/scala/objects/terminal/ProximityTest.scala b/common/src/test/scala/objects/terminal/ProximityTest.scala new file mode 100644 index 00000000..7b799d1d --- /dev/null +++ b/common/src/test/scala/objects/terminal/ProximityTest.scala @@ -0,0 +1,170 @@ +// Copyright (c) 2017 PSForever +package objects.terminal + +import akka.actor.Props +import net.psforever.objects.serverobject.CommonMessages +import net.psforever.objects.serverobject.terminals.Terminal.TerminalMessage +import net.psforever.objects.serverobject.terminals.{ProximityTerminal, ProximityTerminalControl, ProximityUnit, Terminal} +import net.psforever.objects.{Avatar, GlobalDefinitions, Player} +import net.psforever.packet.game.PlanetSideGUID +import net.psforever.types.{CharacterGender, PlanetSideEmpire} +import objects.ActorTest +import org.specs2.mutable.Specification + +import scala.concurrent.duration._ + +class ProximityTest extends Specification { + "ProximityUnit" should { + "construct (with a Terminal object)" in { + val obj = new ProximityTest.SampleTerminal() + obj.NumberUsers mustEqual 0 + } + + "keep track of users (add)" in { + val obj = new ProximityTest.SampleTerminal() + obj.NumberUsers mustEqual 0 + obj.AddUser(PlanetSideGUID(10)) mustEqual obj.NumberUsers + obj.NumberUsers mustEqual 1 + obj.AddUser(PlanetSideGUID(20)) mustEqual obj.NumberUsers + obj.NumberUsers mustEqual 2 + } + + "keep track of users (remove)" in { + val obj = new ProximityTest.SampleTerminal() + obj.AddUser(PlanetSideGUID(10)) + obj.AddUser(PlanetSideGUID(20)) + obj.NumberUsers mustEqual 2 + obj.RemoveUser(PlanetSideGUID(10)) mustEqual obj.NumberUsers + obj.NumberUsers mustEqual 1 + obj.RemoveUser(PlanetSideGUID(20)) mustEqual obj.NumberUsers + obj.NumberUsers mustEqual 0 + } + + "can not add a user twice" in { + val obj = new ProximityTest.SampleTerminal() + obj.AddUser(PlanetSideGUID(10)) + obj.NumberUsers mustEqual 1 + obj.AddUser(PlanetSideGUID(10)) + obj.NumberUsers mustEqual 1 + } + + "can not remove a user that was not added" in { + val obj = new ProximityTest.SampleTerminal() + obj.AddUser(PlanetSideGUID(10)) + obj.NumberUsers mustEqual 1 + obj.RemoveUser(PlanetSideGUID(20)) + obj.NumberUsers mustEqual 1 + } + } + + "ProximityTerminal" should { + "construct" in { + ProximityTerminal(GlobalDefinitions.medical_terminal) + ok + } + } +} + +class ProximityTerminalControl1bTest extends ActorTest { + "ProximityTerminalControl" should { + "send out a start message" in { + val obj = ProximityTerminal(GlobalDefinitions.medical_terminal) + obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl") + val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player.GUID = PlanetSideGUID(10) + + assert(obj.NumberUsers == 0) + obj.Actor ! CommonMessages.Use(player) + val msg = receiveOne(200 milliseconds) + assert(obj.NumberUsers == 1) + assert(msg.isInstanceOf[TerminalMessage]) + val msgout = msg.asInstanceOf[TerminalMessage] + assert(msgout.player == player) + assert(msgout.msg == null) + assert(msgout.response.isInstanceOf[Terminal.StartProximityEffect]) + } + } +} + +class ProximityTerminalControl2bTest extends ActorTest { + "ProximityTerminalControl" should { + "will not send out one start message unless first user" in { + val obj = ProximityTerminal(GlobalDefinitions.medical_terminal) + obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl") + val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player1.GUID = PlanetSideGUID(10) + val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player2.GUID = PlanetSideGUID(11) + assert(obj.NumberUsers == 0) + + obj.Actor ! CommonMessages.Use(player1) + val msg = receiveOne(200 milliseconds) + assert(obj.NumberUsers == 1) + assert(msg.isInstanceOf[TerminalMessage]) + assert(msg.asInstanceOf[TerminalMessage].response.isInstanceOf[Terminal.StartProximityEffect]) + obj.Actor ! CommonMessages.Use(player2) + expectNoMsg(500 milliseconds) + assert(obj.NumberUsers == 2) + } + } +} + +class ProximityTerminalControl3bTest extends ActorTest { + "ProximityTerminalControl" should { + "send out a stop message" in { + val obj = ProximityTerminal(GlobalDefinitions.medical_terminal) + obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl") + val player = Player(Avatar("TestCharacter", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player.GUID = PlanetSideGUID(10) + + assert(obj.NumberUsers == 0) + obj.Actor ! CommonMessages.Use(player) + receiveOne(200 milliseconds) + assert(obj.NumberUsers == 1) + obj.Actor ! CommonMessages.Unuse(player) + val msg = receiveOne(200 milliseconds) + assert(obj.NumberUsers == 0) + assert(msg.isInstanceOf[TerminalMessage]) + val msgout = msg.asInstanceOf[TerminalMessage] + assert(msgout.player == player) + assert(msgout.msg == null) + assert(msgout.response.isInstanceOf[Terminal.StopProximityEffect]) + } + } +} + +class ProximityTerminalControl4bTest extends ActorTest { + "ProximityTerminalControl" should { + "will not send out one stop message until last user" in { + val obj = ProximityTerminal(GlobalDefinitions.medical_terminal) + obj.Actor = system.actorOf(Props(classOf[ProximityTerminalControl], obj), "prox-ctrl") + val player1 = Player(Avatar("TestCharacter1", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player1.GUID = PlanetSideGUID(10) + val player2 = Player(Avatar("TestCharacter2", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + player2.GUID = PlanetSideGUID(11) + assert(obj.NumberUsers == 0) + + obj.Actor ! CommonMessages.Use(player1) + receiveOne(200 milliseconds) //StartProximityEffect + assert(obj.NumberUsers == 1) + obj.Actor ! CommonMessages.Use(player2) + expectNoMsg(500 milliseconds) + assert(obj.NumberUsers == 2) + obj.Actor ! CommonMessages.Unuse(player1) + expectNoMsg(500 milliseconds) + assert(obj.NumberUsers == 1) + obj.Actor ! CommonMessages.Unuse(player2) + val msg = receiveOne(200 milliseconds) + assert(obj.NumberUsers == 0) + assert(msg.isInstanceOf[TerminalMessage]) + val msgout = msg.asInstanceOf[TerminalMessage] + assert(msgout.player == player2) + assert(msgout.msg == null) + assert(msgout.response.isInstanceOf[Terminal.StopProximityEffect]) + } + } +} + +object ProximityTest { + class SampleTerminal extends Terminal(GlobalDefinitions.dropship_vehicle_terminal) with ProximityUnit +} diff --git a/common/src/test/scala/objects/terminal/RepairRearmSiloTest.scala b/common/src/test/scala/objects/terminal/RepairRearmSiloTest.scala new file mode 100644 index 00000000..eeb6fa33 --- /dev/null +++ b/common/src/test/scala/objects/terminal/RepairRearmSiloTest.scala @@ -0,0 +1,91 @@ +// Copyright (c) 2017 PSForever +package objects.terminal + +import akka.actor.ActorRef +import net.psforever.objects.serverobject.structures.{Building, StructureType} +import net.psforever.objects._ +import net.psforever.objects.serverobject.terminals.{RepairRearmSilo, Terminal} +import net.psforever.objects.zones.Zone +import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} +import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType} +import org.specs2.mutable.Specification + +class RepairRearmSiloTest extends Specification { + "RepairRearmSilo" should { + val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) + val silo = RepairRearmSilo(GlobalDefinitions.repair_silo) + silo.Owner = new Building(0, Zone.Nowhere, StructureType.Building) + silo.Owner.Faction = PlanetSideEmpire.TR + + "define" in { + GlobalDefinitions.repair_silo.ObjectId mustEqual 729 + } + + "construct" in { + val obj = RepairRearmSilo(GlobalDefinitions.repair_silo) + obj.Actor mustEqual ActorRef.noSender + } + + "player can buy a box of ammunition ('bullet_35mm')" in { + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 3, "35mmbullet", 0, PlanetSideGUID(0)) + val reply = silo.Request(player, msg) + reply.isInstanceOf[Terminal.BuyEquipment] mustEqual true + val reply2 = reply.asInstanceOf[Terminal.BuyEquipment] + reply2.item.isInstanceOf[AmmoBox] mustEqual true + reply2.item.asInstanceOf[AmmoBox].Definition mustEqual GlobalDefinitions.bullet_35mm + reply2.item.asInstanceOf[AmmoBox].Capacity mustEqual 100 + } + + "player can not buy fake equipment ('sabot')" in { + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 3, "sabot", 0, PlanetSideGUID(0)) + + silo.Request(player, msg) mustEqual Terminal.NoDeal() + } + + "player can not buy equipment from the wrong page ('35mmbullet', page 1)" in { + val msg = ItemTransactionMessage(PlanetSideGUID(1), TransactionType.Buy, 1, "35mmbullet", 0, PlanetSideGUID(0)) + + silo.Request(player, msg) mustEqual Terminal.NoDeal() + } + + "player can retrieve a vehicle loadout" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm) + avatar.SaveLoadout(vehicle, "test", 10) + + val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 0, PlanetSideGUID(0))) + msg.isInstanceOf[Terminal.VehicleLoadout] mustEqual true + val loadout = msg.asInstanceOf[Terminal.VehicleLoadout] + loadout.vehicle_definition mustEqual GlobalDefinitions.fury + loadout.weapons.size mustEqual 1 + loadout.weapons.head.obj.Definition mustEqual GlobalDefinitions.fury_weapon_systema + loadout.weapons.head.start mustEqual 1 + loadout.inventory.head.obj.Definition mustEqual GlobalDefinitions.bullet_9mm + loadout.inventory.head.start mustEqual 30 + } + + "player can not retrieve a vehicle loadout from the wrong line" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm) + avatar.SaveLoadout(vehicle, "test", 10) + + val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 3, "", 0, PlanetSideGUID(0))) //page 3 + msg.isInstanceOf[Terminal.NoDeal] mustEqual true + } + + "player can not retrieve a vehicle loadout from the wrong line" in { + val avatar = Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0) + val player2 = Player(avatar) + val vehicle = Vehicle(GlobalDefinitions.fury) + vehicle.Slot(30).Equipment = AmmoBox(GlobalDefinitions.bullet_9mm) + avatar.SaveLoadout(vehicle, "test", 10) + + val msg = silo.Request(player2, ItemTransactionMessage(PlanetSideGUID(10), TransactionType.Loadout, 4, "", 1, PlanetSideGUID(0))) //line 11 + msg.isInstanceOf[Terminal.NoDeal] mustEqual true + } + } +} diff --git a/common/src/test/scala/objects/terminal/TerminalControlTest.scala b/common/src/test/scala/objects/terminal/TerminalControlTest.scala index 428d8d87..e8432d3d 100644 --- a/common/src/test/scala/objects/terminal/TerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/TerminalControlTest.scala @@ -26,8 +26,7 @@ class TerminalControl2Test extends ActorTest() { val (_, terminal) = TerminalControlTest.SetUpAgents(GlobalDefinitions.cert_terminal, PlanetSideEmpire.TR) terminal.Actor !"hello" - val reply = receiveOne(Duration.create(500, "ms")) - assert(reply.isInstanceOf[Terminal.NoDeal]) + expectNoMsg(Duration.create(500, "ms")) } } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 9b05f120..ca2fd7ae 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -468,6 +468,11 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } + case VehicleResponse.InventoryState2(obj_guid, parent_guid, value) => + if(tplayer_guid != guid) { + sendResponse(InventoryStateMessage(obj_guid, 0, parent_guid, value)) + } + case VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid) => sendResponse(DismountVehicleMsg(guid, unk1, unk2)) if(tplayer_guid == guid) { @@ -819,7 +824,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Terminal.InfantryLoadout(exosuit, subtype, holsters, inventory) => //TODO optimizations against replacing Equipment with the exact same Equipment and potentially for recycling existing Equipment log.info(s"$tplayer wants to change equipment loadout to their option #${msg.unk1 + 1}") - sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, true)) + sendResponse(ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Loadout, true)) val dropPred = DropPredicate(tplayer) val (dropHolsters, beforeHolsters) = clearHolsters(tplayer.Holsters().iterator).partition(dropPred) val (dropInventory, beforeInventory) = tplayer.Inventory.Clear().partition(dropPred) @@ -838,7 +843,7 @@ class WorldSessionActor extends Actor with MDCContextAware { taskResolver ! GUIDTask.UnregisterEquipment(elem.obj)(continent.GUID) }) //report change - sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, 0)) + sendResponse(ArmorChangedMessage(tplayer.GUID, exosuit, subtype)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.ArmorChanged(tplayer.GUID, exosuit, subtype)) sendResponse(PlanetsideAttributeMessage(tplayer.GUID, 4, tplayer.Armor)) avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.PlanetsideAttribute(tplayer.GUID, 4, tplayer.Armor)) @@ -882,44 +887,37 @@ class WorldSessionActor extends Actor with MDCContextAware { val objDef = obj.Definition avatarService ! AvatarServiceMessage(tplayer.Continent, AvatarAction.EquipmentOnGround(tplayer.GUID, pos, orient, objDef.ObjectId, obj.GUID, objDef.Packet.ConstructorData(obj).get)) }) - sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, true)) case Terminal.VehicleLoadout(definition, weapons, inventory) => log.info(s"$tplayer wants to change their vehicle equipment loadout to their option #${msg.unk1 + 1}") - log.info(s"Vehicle: $definition") - log.info(s"Weapons (${weapons.size}): $weapons") - log.info(s"Inventory (${inventory.size}): $inventory") - LocalVehicle match { + FindLocalVehicle match { case Some(vehicle) => sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, true)) val (_, afterInventory) = inventory.partition( DropPredicate(tplayer) ) //dropped items are lost - //common action - remove old inventory + //remove old inventory val deleteEquipment : (Int,Equipment)=>Unit = DeleteEquipmentFromVehicle(vehicle) - vehicle.Inventory.Clear().foreach({ case InventoryItem(obj, index) => - deleteEquipment(index, obj) - taskResolver ! GUIDTask.UnregisterEquipment(obj)(continent.GUID) - }) + vehicle.Inventory.Clear().foreach({ case InventoryItem(obj, index) => deleteEquipment(index, obj) }) val stowEquipment : (Int,Equipment)=>TaskResolver.GiveTask = StowNewEquipmentInVehicle(vehicle) (if(vehicle.Definition == definition) { - //vehicles are the same type; transfer over weapons - vehicle.Weapons - .filter({ case (_, slot) => slot.Equipment.nonEmpty }) - .foreach({ case (_, slot) => - val equipment = slot.Equipment.get - slot.Equipment = None - val equipment_guid = equipment.GUID - sendResponse(ObjectDeleteMessage(equipment_guid, 0)) - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, equipment_guid)) - taskResolver ! GUIDTask.UnregisterEquipment(equipment)(continent.GUID) - }) + //vehicles are the same type; transfer over weapon ammo + //TODO ammo switching? no vehicle weapon does that currently but ... + //TODO want to completely swap weapons, but holster icon vanishes temporarily after swap + //TODO BFR arms must be swapped properly + val channel = s"${vehicle.Actor}" weapons.foreach({ case InventoryItem(obj, index) => - //create weapons and share with everyone - taskResolver ! PutNewWeaponInVehicleSlot(vehicle, obj.asInstanceOf[Tool], index) + val savedWeapon = obj.asInstanceOf[Tool] + val existingWeapon = vehicle.Weapons(index).Equipment.get.asInstanceOf[Tool] + (0 until existingWeapon.MaxAmmoSlot).foreach({ index => + val existingBox = existingWeapon.AmmoSlots(index).Box + existingBox.Capacity = savedWeapon.AmmoSlots(index).Box.Capacity + //use VehicleAction.InventoryState2; VehicleAction.InventoryState temporarily glitches ammo count in ui + vehicleService ! VehicleServiceMessage(channel, VehicleAction.InventoryState2(PlanetSideGUID(0), existingBox.GUID, existingWeapon.GUID, existingBox.Capacity)) + }) }) afterInventory } else { - //do not transfer over weapons + //do not transfer over weapon ammo if(vehicle.Definition.TrunkSize == definition.TrunkSize && vehicle.Definition.TrunkOffset == definition.TrunkOffset) { afterInventory } @@ -934,6 +932,7 @@ class WorldSessionActor extends Actor with MDCContextAware { }) case None => log.error(s"can not apply the loadout - can not find a vehicle") + sendResponse(ItemTransactionResultMessage (msg.terminal_guid, TransactionType.Loadout, false)) } case Terminal.LearnCertification(cert, cost) => @@ -2123,7 +2122,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case None => None }) - .orElse(LocalVehicle match { + .orElse(FindLocalVehicle match { case Some(parent) => findFunc(parent) case None => @@ -2735,46 +2734,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - def PutNewWeaponInVehicleSlot(target : Vehicle, obj : Tool, index : Int) : TaskResolver.GiveTask = { - TaskResolver.GiveTask( - new Task() { - private val localTarget = target - private val localIndex = index - private val localObject = obj - private val localAnnounce = self - private val localAvatarService = avatarService - private val localVehicleService = vehicleService - - override def isComplete : Task.Resolution.Value = { - if(localTarget.Slot(localIndex).Equipment.contains(localObject)) { - Task.Resolution.Success - } - else { - Task.Resolution.Incomplete - } - } - - def Execute(resolver : ActorRef) : Unit = { - localTarget.Slot(localIndex).Equipment = localObject - resolver ! scala.util.Success(this) - } - - override def onSuccess() : Unit = { - val definition = localObject.Definition - if(localTarget.VisibleSlots.contains(localIndex)) { - localAvatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(localTarget.GUID, localTarget.GUID, localIndex, localObject)) - } - val channel = s"${localTarget.Actor}" - (0 until localObject.MaxAmmoSlot).foreach({ index => - val box = localObject.AmmoSlots(index).Box - val boxDef = box.Definition - val boxdata = boxDef.Packet.DetailedConstructorData(box).get - localVehicleService ! VehicleServiceMessage(channel, VehicleAction.InventoryState(PlanetSideGUID(0), box, localObject.GUID, index, boxdata)) - }) - } - }, List(GUIDTask.RegisterTool(obj)(continent.GUID))) - } - /** * Construct tasking that coordinates the following:
* 1) Remove a new piece of `Equipment` from where it is currently stored.
@@ -3418,6 +3377,25 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + /** + * Get the current `Vehicle` object that the player is riding/driving. + * The vehicle must be found solely through use of `player.VehicleSeated`. + * @return the vehicle + */ + def FindLocalVehicle : Option[Vehicle] = { + player.VehicleSeated match { + case Some(vehicle_guid) => + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + Some(obj) + case _ => + None + } + case None => + None + } + } + /** * Given an object that contains an item (`Equipment`) in its `Inventory` at a certain location, * remove it permanently. @@ -3428,7 +3406,8 @@ class WorldSessionActor extends Actor with MDCContextAware { */ private def DeleteEquipment(obj : PlanetSideGameObject with Container)(start : Int, item : Equipment) : Unit = { val item_guid = item.GUID - obj.Inventory -= start + obj.Slot(start).Equipment = None + //obj.Inventory -= start taskResolver ! GUIDTask.UnregisterEquipment(item)(continent.GUID) sendResponse(ObjectDeleteMessage(item_guid, 0)) } @@ -3762,20 +3741,6 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - def LocalVehicle : Option[Vehicle] = { - player.VehicleSeated match { - case Some(vehicle_guid) => - continent.GUID(vehicle_guid) match { - case Some(obj : Vehicle) => - Some(obj) - case _ => - None - } - case None => - None - } - } - /** * Perform specific operations depending on the target of deployment. * @param obj the object that has deployed diff --git a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala index 66afa834..06d3c768 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala @@ -16,6 +16,7 @@ object VehicleAction { final case class DeployRequest(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, state : DriveState.Value, unk1 : Int, unk2 : Boolean, pos : Vector3) extends Action final case class DismountVehicle(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean) extends Action final case class InventoryState(player_guid : PlanetSideGUID, obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Action + final case class InventoryState2(player_guid : PlanetSideGUID, obj_guid : PlanetSideGUID, parent_guid : PlanetSideGUID, value : Int) extends Action final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Action final case class LoadVehicle(player_guid : PlanetSideGUID, vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Action final case class MountVehicle(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, seat : Int) extends Action diff --git a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala index 5c2fb5b2..74280a63 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala @@ -17,6 +17,7 @@ object VehicleResponse { final case class DetachFromRails(vehicle_guid : PlanetSideGUID, rails_guid : PlanetSideGUID, rails_pos : Vector3, rails_rot : Float) extends Response final case class DismountVehicle(unk1 : Int, unk2 : Boolean) extends Response final case class InventoryState(obj : PlanetSideGameObject, parent_guid : PlanetSideGUID, start : Int, con_data : ConstructorData) extends Response + final case class InventoryState2(obj_guid : PlanetSideGUID, parent_guid : PlanetSideGUID, value : Int) extends Response final case class KickPassenger(unk1 : Int, unk2 : Boolean, vehicle_guid : PlanetSideGUID) extends Response final case class LoadVehicle(vehicle : Vehicle, vtype : Int, vguid : PlanetSideGUID, vdata : ConstructorData) extends Response final case class MountVehicle(object_guid : PlanetSideGUID, seat : Int) extends Response diff --git a/pslogin/src/main/scala/services/vehicle/VehicleService.scala b/pslogin/src/main/scala/services/vehicle/VehicleService.scala index e820db05..2061fdc0 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleService.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleService.scala @@ -60,6 +60,10 @@ class VehicleService extends Actor { VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.InventoryState(obj, parent_guid, start, con_data)) ) + case VehicleAction.InventoryState2(player_guid, obj_guid, parent_guid, value) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.InventoryState2(obj_guid, parent_guid, value)) + ) case VehicleAction.KickPassenger(player_guid, unk1, unk2, vehicle_guid) => VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid)) diff --git a/pslogin/src/test/scala/VehicleServiceTest.scala b/pslogin/src/test/scala/VehicleServiceTest.scala index d8cd0d4f..d1467ab8 100644 --- a/pslogin/src/test/scala/VehicleServiceTest.scala +++ b/pslogin/src/test/scala/VehicleServiceTest.scala @@ -136,6 +136,22 @@ class InventoryStateTest extends ActorTest { } } +class InventoryState2Test extends ActorTest { + ServiceManager.boot(system) + val tool = Tool(GlobalDefinitions.beamer) + tool.AmmoSlots.head.Box.GUID = PlanetSideGUID(13) + val cdata = tool.Definition.Packet.ConstructorData(tool).get + + "VehicleService" should { + "pass InventoryState2" in { + val service = system.actorOf(Props[VehicleService], "v-service") + service ! Service.Join("test") + service ! VehicleServiceMessage("test", VehicleAction.InventoryState2(PlanetSideGUID(10), PlanetSideGUID(11), PlanetSideGUID(12), 13)) + expectMsg(VehicleServiceResponse("/test/Vehicle", PlanetSideGUID(10), VehicleResponse.InventoryState2(PlanetSideGUID(11), PlanetSideGUID(12), 13))) + } + } +} + class KickPassengerTest extends ActorTest { ServiceManager.boot(system) From e9aac5514a36856047fefc8a62bd93121a537fdf Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 16 May 2018 19:24:50 -0400 Subject: [PATCH 07/13] more tests for the proximity amenities; added the other silo in Anguta, Ceryshen and the terminal-facing silo in home3; initial work on FacilityBenefitShieldCharge packet and tests --- .../objects/loadouts/InfantryLoadout.scala | 71 +++++++++++++++++++ .../psforever/objects/loadouts/Loadout.scala | 39 ++-------- .../terminals/MedicalTerminalDefinition.scala | 14 +--- .../terminals/ProximityDefinition.scala | 15 ++++ .../terminals/ProximityTerminal.scala | 6 +- .../terminals/RepairRearmControl.scala | 27 ------- .../terminals/RepairRearmSilo.scala | 41 ----------- .../terminals/RepairRearmSiloDefinition.scala | 2 +- .../psforever/packet/GamePacketOpcode.scala | 6 +- ...ityBenefitShieldChargeRequestMessage.scala | 24 +++++++ .../packet/game/FavoritesMessage.scala | 24 ++++++- ...enefitShieldChargeRequestMessageTest.scala | 28 ++++++++ .../scala/game/FavoritesMessageTest.scala | 2 +- .../src/test/scala/objects/LoadoutTest.scala | 38 +++++++++- .../objects/ServerObjectBuilderTest.scala | 19 ----- .../terminal/RepairRearmSiloTest.scala | 6 +- pslogin/src/main/scala/Maps.scala | 14 +++- .../src/main/scala/WorldSessionActor.scala | 40 ++++++----- 18 files changed, 252 insertions(+), 164 deletions(-) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityDefinition.scala delete mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala delete mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/FacilityBenefitShieldChargeRequestMessage.scala create mode 100644 common/src/test/scala/game/FacilityBenefitShieldChargeRequestMessageTest.scala diff --git a/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala index e7470546..5277f4ba 100644 --- a/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala +++ b/common/src/main/scala/net/psforever/objects/loadouts/InfantryLoadout.scala @@ -29,3 +29,74 @@ final case class InfantryLoadout(label : String, inventory : List[Loadout.SimplifiedEntry], exosuit : ExoSuitType.Value, subtype : Int) extends Loadout(label, visible_slots, inventory) + +object InfantryLoadout { + import net.psforever.objects.Player + import net.psforever.objects.GlobalDefinitions + import net.psforever.objects.equipment.Equipment + + /** + * The sub-type of the player's uniform. + * Applicable to mechanized assault units, mainly. + * The subtype is reported as a number but indicates the specialization - anti-infantry, ani-vehicular, anti-air - of the suit + * as indicated by the arm weapon(s). + * @param player the player + * @return the numeric subtype + */ + def DetermineSubtype(player : Player) : Int = { + DetermineSubtypeA(player.ExoSuit, player.Slot(0).Equipment) + } + + /** + * The sub-type of the player's uniform. + * Applicable to mechanized assault units, mainly. + * The subtype is reported as a number but indicates the specialization - anti-infantry, ani-vehicular, anti-air - of the suit + * as indicated by the arm weapon(s). + * @param suit the player's uniform; + * the target is for MAX armors + * @param weapon any weapon the player may have it his "first pistol slot;" + * to a MAX, that is its "primary weapon slot" + * @return the numeric subtype + */ + def DetermineSubtypeA(suit : ExoSuitType.Value, weapon : Option[Equipment]) : Int = { + if(suit == ExoSuitType.MAX) { + weapon match { + case Some(item) => + item.Definition match { + case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.nchev_scattercannon | GlobalDefinitions.vshev_quasar => + 1 + case GlobalDefinitions.trhev_pounder | GlobalDefinitions.nchev_falcon | GlobalDefinitions.vshev_comet => + 2 + case GlobalDefinitions.trhev_burster | GlobalDefinitions.nchev_sparrow | GlobalDefinitions.vshev_starfire => + 3 + case _ => + 0 + } + case None => + 0 + } + } + else { + 0 + } + } + + /** + * The sub-type of the player's uniform, as used in `FavoritesMessage`.
+ *
+ * The values for `Standard`, `Infiltration`, and the generic `MAX` are not perfectly known. + * The latter-most exo-suit option is presumed. + * @param suit the player's uniform + * @param subtype the mechanized assault exo-suit subtype as determined by their arm weapons + * @return the numeric subtype + */ + def DetermineSubtypeB(suit : ExoSuitType.Value, subtype : Int) : Int = { + suit match { + case ExoSuitType.Standard => 0 + case ExoSuitType.Agile => 1 + case ExoSuitType.Reinforced => 2 + case ExoSuitType.MAX => 3 + subtype //4, 5, 6 + case ExoSuitType.Infiltration => 7 + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala index 7035c89d..5ff4f92d 100644 --- a/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala +++ b/common/src/main/scala/net/psforever/objects/loadouts/Loadout.scala @@ -5,7 +5,6 @@ import net.psforever.objects._ import net.psforever.objects.definition._ import net.psforever.objects.equipment.Equipment import net.psforever.objects.inventory.InventoryItem -import net.psforever.types.ExoSuitType import scala.annotation.tailrec @@ -120,42 +119,16 @@ object Loadout { * @return the numeric subtype */ def DetermineSubtype(player : Player) : Int = { - DetermineSubtype(player.ExoSuit, player.Slot(0).Equipment) + InfantryLoadout.DetermineSubtype(player) } /** - * The sub-type of the player's uniform. - * Applicable to mechanized assault units, mainly. - * The subtype is reported as a number but indicates the specialization - anti-infantry, ani-vehicular, anti-air - of the suit - * as indicated by the arm weapon(s). - * @param suit the player's uniform; - * the target is for MAX armors - * @param weapon any weapon the player may have it his "first pistol slot;" - * to a MAX, that is its "primary weapon slot" - * @return the numeric subtype + * The sub-type of the vehicle. + * Vehicle's have no subtype. + * @param vehicle the vehicle + * @return the numeric subtype, always 0 */ - def DetermineSubtype(suit : ExoSuitType.Value, weapon : Option[Equipment]) : Int = { - if(suit == ExoSuitType.MAX) { - weapon match { - case Some(item) => - item.Definition match { - case GlobalDefinitions.trhev_dualcycler | GlobalDefinitions.nchev_scattercannon | GlobalDefinitions.vshev_quasar => - 1 - case GlobalDefinitions.trhev_pounder | GlobalDefinitions.nchev_falcon | GlobalDefinitions.vshev_comet => - 2 - case GlobalDefinitions.trhev_burster | GlobalDefinitions.nchev_sparrow | GlobalDefinitions.vshev_starfire => - 3 - case _ => - 0 - } - case None => - 0 - } - } - else { - 0 - } - } + def DetermineSubtype(vehicle : Vehicle) : Int = 0 /** * Overloaded entry point for constructing simplified blueprints from holster slot equipment. diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/MedicalTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MedicalTerminalDefinition.scala index fbd7f012..66581aac 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/MedicalTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/MedicalTerminalDefinition.scala @@ -1,18 +1,12 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.terminals -import net.psforever.objects.Player -import net.psforever.packet.game.ItemTransactionMessage - /** * The definition for any `Terminal` that is of a type "medical_terminal". - * This includes the limited proximity-based functionality of the formal medical terminals - * and the actual proximity-based functionality of the cavern crystals.
- *
- * Do not confuse the "medical_terminal" category and the actual `medical_terminal` object (529). - * Objects created by this definition being linked by their use of `ProximityTerminalUseMessage` is more accurate. + * This includes the functionality of the formal medical terminals and some of the cavern crystals. + * Do not confuse the game's internal "medical_terminal" object category and the actual `medical_terminal` object (529). */ -class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objectId) { +class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objectId) with ProximityDefinition { Name = if(objectId == 38) { "adv_med_terminal" } @@ -31,6 +25,4 @@ class MedicalTerminalDefinition(objectId : Int) extends TerminalDefinition(objec else { throw new IllegalArgumentException("medical terminal must be either object id 38, 225, 226, 529, or 689") } - - def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityDefinition.scala new file mode 100644 index 00000000..f1ccfcc4 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityDefinition.scala @@ -0,0 +1,15 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +import net.psforever.objects.Player +import net.psforever.packet.game.ItemTransactionMessage + +/** + * The definition for any `Terminal` that possesses a proximity-based effect. + * This includes the limited proximity-based functionality of the formal medical terminals + * and the actual proximity-based functionality of the cavern crystals. + * Objects created by this definition being linked by their use of `ProximityTerminalUseMessage`. + */ +trait ProximityDefinition { + def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala index 4eafb142..8b9b7e67 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ProximityTerminal.scala @@ -9,14 +9,14 @@ package net.psforever.objects.serverobject.terminals * For example, the cavern crystals are considered owner-neutral elements that are not attached to a `Building` object. * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ -class ProximityTerminal(tdef : MedicalTerminalDefinition) extends Terminal(tdef) with ProximityUnit +class ProximityTerminal(tdef : TerminalDefinition with ProximityDefinition) extends Terminal(tdef) with ProximityUnit object ProximityTerminal { /** * Overloaded constructor. * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields */ - def apply(tdef : MedicalTerminalDefinition) : ProximityTerminal = { + def apply(tdef : TerminalDefinition with ProximityDefinition) : ProximityTerminal = { new ProximityTerminal(tdef) } @@ -29,7 +29,7 @@ object ProximityTerminal { * @param context a context to allow the object to properly set up `ActorSystem` functionality * @return the `Terminal` object */ - def Constructor(tdef : MedicalTerminalDefinition)(id : Int, context : ActorContext) : Terminal = { + def Constructor(tdef : TerminalDefinition with ProximityDefinition)(id : Int, context : ActorContext) : Terminal = { import akka.actor.Props val obj = ProximityTerminal(tdef) obj.Actor = context.actorOf(Props(classOf[ProximityTerminalControl], obj), s"${tdef.Name}_$id") diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala deleted file mode 100644 index 2451fbb3..00000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmControl.scala +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.serverobject.terminals - -import akka.actor.Actor -import net.psforever.objects.serverobject.affinity.{FactionAffinity, FactionAffinityBehavior} - -/** - * An `Actor` that handles messages being dispatched to a specific `IFFLock`. - * @param term the `RepairRearmSilo` object being governed - * @see `CommonMessages` - */ -class RepairRearmControl(term : RepairRearmSilo) extends Actor with FactionAffinityBehavior.Check with ProximityUnit.Use { - def FactionObject : FactionAffinity = term - - def TerminalObject : Terminal with ProximityUnit = term - - def receive : Receive = checkBehavior - .orElse(proximityBehavior) - .orElse { - case Terminal.Request(player, msg) => - sender ! Terminal.TerminalMessage(player, msg, term.Request(player, msg)) - - case _ => ; - } - - override def toString : String = term.Definition.Name -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala deleted file mode 100644 index b01cfbae..00000000 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSilo.scala +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2017 PSForever -package net.psforever.objects.serverobject.terminals - -/** - * A structure-owned server object for preserving vehicle loadouts, - * obtaining vehicle weapon ammunition, - * and, with proper perks, automatically repairing damage doen to allied vehicles. - * A server object that is a "terminal" that can be accessed for amenities and services, - * triggered when a certain distance from the unit itself (proximity-based).
- *
- * Unlike conventional terminals, this structure is not necessarily structure-owned. - * For example, the cavern crystals are considered owner-neutral elements that are not attached to a `Building` object. - * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields - */ -class RepairRearmSilo(tdef : RepairRearmSiloDefinition) extends Terminal(tdef) with ProximityUnit - -object RepairRearmSilo { - /** - * Overloaded constructor. - * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields - */ - def apply(tdef : RepairRearmSiloDefinition) : RepairRearmSilo = { - new RepairRearmSilo(tdef) - } - - import akka.actor.ActorContext - - /** - * Instantiate an configure a `Terminal` object - * @param tdef the `ObjectDefinition` that constructs this object and maintains some of its immutable fields - * @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 - * @return the `Terminal` object - */ - def Constructor(tdef : RepairRearmSiloDefinition)(id : Int, context : ActorContext) : RepairRearmSilo = { - import akka.actor.Props - val obj = RepairRearmSilo(tdef) - obj.Actor = context.actorOf(Props(classOf[RepairRearmControl], obj), s"${tdef.Name}_$id") - obj - } -} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala index 2fd82802..9cfc6fa7 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala @@ -11,7 +11,7 @@ import net.psforever.packet.game.ItemTransactionMessage * The `Definition` for any `Terminal` that is of a type "repair_silo." * Has both proximity-based operation and direct access purchasing power. */ -class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinition(objectId) { +class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinition(objectId) with ProximityDefinition { Name = "repair_silo" private val buyFunc : (Player, ItemTransactionMessage)=>Terminal.Exchange = EquipmentTerminalDefinition.Buy(Map.empty, Map.empty, Map.empty) diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index e7ebd353..9d586476 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -312,7 +312,7 @@ object GamePacketOpcode extends Enumeration { = Value private def noDecoder(opcode : GamePacketOpcode.Type) = (a : BitVector) => - Attempt.failure(Err(s"Could not find a marshaller for game packet ${opcode}")) + Attempt.failure(Err(s"Could not find a marshaller for game packet $opcode")) /// Mapping of packet IDs to decoders. Notice that we are using the @switch annotation which ensures that the Scala /// compiler will be able to optimize this as a lookup table (switch statement). Microbenchmarks show a nearly 400x @@ -549,7 +549,7 @@ object GamePacketOpcode extends Enumeration { // OPCODES 0xc0-cf case 0xc0 => noDecoder(CaptureFlagUpdateMessage) case 0xc1 => noDecoder(VanuModuleUpdateMessage) - case 0xc2 => noDecoder(FacilityBenefitShieldChargeRequestMessage) + case 0xc2 => game.FacilityBenefitShieldChargeRequestMessage.decode case 0xc3 => game.ProximityTerminalUseMessage.decode case 0xc4 => game.QuantityDeltaUpdateMessage.decode case 0xc5 => noDecoder(ChainLashMessage) @@ -608,7 +608,7 @@ object GamePacketOpcode extends Enumeration { case 0xf1 => game.MailMessage.decode case 0xf2 => noDecoder(GameVarUpdate) case 0xf3 => noDecoder(ClientCheatedMessage) - case default => noDecoder(opcode) + case _ => noDecoder(opcode) } implicit val codec: Codec[this.Value] = PacketHelpers.createEnumerationCodec(this, uint8L) diff --git a/common/src/main/scala/net/psforever/packet/game/FacilityBenefitShieldChargeRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/FacilityBenefitShieldChargeRequestMessage.scala new file mode 100644 index 00000000..15389532 --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/FacilityBenefitShieldChargeRequestMessage.scala @@ -0,0 +1,24 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ + +/** + * Dispatched by the client when driving a vehicle in the sphere of influence of an allied base + * that is a dropship center or that possesses the lattice-connected benefit of a dropship center. + * The vehicle that is being driven will not have perfect fully-charged shields at the time. + * @param vehicle_guid the vehicle whose shield is being charged + */ +final case class FacilityBenefitShieldChargeRequestMessage(vehicle_guid : PlanetSideGUID) + extends PlanetSideGamePacket { + type Packet = FacilityBenefitShieldChargeRequestMessage + def opcode = GamePacketOpcode.FacilityBenefitShieldChargeRequestMessage + def encode = FacilityBenefitShieldChargeRequestMessage.encode(this) +} + +object FacilityBenefitShieldChargeRequestMessage extends Marshallable[FacilityBenefitShieldChargeRequestMessage] { + implicit val codec : Codec[FacilityBenefitShieldChargeRequestMessage] = + ("vehicle_guid" | PlanetSideGUID.codec).as[FacilityBenefitShieldChargeRequestMessage] +} diff --git a/common/src/main/scala/net/psforever/packet/game/FavoritesMessage.scala b/common/src/main/scala/net/psforever/packet/game/FavoritesMessage.scala index 314986c4..3e28c027 100644 --- a/common/src/main/scala/net/psforever/packet/game/FavoritesMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/FavoritesMessage.scala @@ -51,11 +51,31 @@ final case class FavoritesMessage(list : LoadoutType.Value, } object FavoritesMessage extends Marshallable[FavoritesMessage] { + /** + * Overloaded constructor, for infantry loadouts specifically. + * @param list the destination list + * @param player_guid the player + * @param line the zero-indexed line number of this entry in its list + * @param label the identifier for this entry + * @param armor the type of exo-suit, if an Infantry loadout + * @return a `FavoritesMessage` object + */ + def apply(list : LoadoutType.Value, player_guid : PlanetSideGUID, line : Int, label : String, armor : Int) : FavoritesMessage = { + FavoritesMessage(list, player_guid, line, label, Some(armor)) + } + + /** + * Overloaded constructor, for vehicle loadouts specifically. + * @param list the destination list + * @param player_guid the player + * @param line the zero-indexed line number of this entry in its list + * @param label the identifier for this entry + * @return a `FavoritesMessage` object + */ def apply(list : LoadoutType.Value, player_guid : PlanetSideGUID, line : Int, label : String) : FavoritesMessage = { FavoritesMessage(list, player_guid, line, label, None) } - - implicit val codec : Codec[FavoritesMessage] = ( +implicit val codec : Codec[FavoritesMessage] = ( ("list" | LoadoutType.codec) >>:~ { value => ("player_guid" | PlanetSideGUID.codec) :: ("line" | uint4L) :: diff --git a/common/src/test/scala/game/FacilityBenefitShieldChargeRequestMessageTest.scala b/common/src/test/scala/game/FacilityBenefitShieldChargeRequestMessageTest.scala new file mode 100644 index 00000000..71b885c0 --- /dev/null +++ b/common/src/test/scala/game/FacilityBenefitShieldChargeRequestMessageTest.scala @@ -0,0 +1,28 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class FacilityBenefitShieldChargeRequestMessageTest extends Specification { + val string = hex"C2 4C00" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case FacilityBenefitShieldChargeRequestMessage(guid) => + guid mustEqual PlanetSideGUID(76) + case _ => + ko + } + } + + "encode" in { + val msg = FacilityBenefitShieldChargeRequestMessage(PlanetSideGUID(76)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + + pkt mustEqual string + } +} + diff --git a/common/src/test/scala/game/FavoritesMessageTest.scala b/common/src/test/scala/game/FavoritesMessageTest.scala index 74e25392..660d088d 100644 --- a/common/src/test/scala/game/FavoritesMessageTest.scala +++ b/common/src/test/scala/game/FavoritesMessageTest.scala @@ -26,7 +26,7 @@ class FavoritesMessageTest extends Specification { } "encode (for infantry)" in { - val msg = FavoritesMessage(LoadoutType.Infantry, PlanetSideGUID(3760), 0, "Agile (basic)", Option(1)) + val msg = FavoritesMessage(LoadoutType.Infantry, PlanetSideGUID(3760), 0, "Agile (basic)", 1) val pkt = PacketCoding.EncodePacket(msg).require.toByteVector pkt mustEqual stringInfantry diff --git a/common/src/test/scala/objects/LoadoutTest.scala b/common/src/test/scala/objects/LoadoutTest.scala index 1f32386e..67d0f310 100644 --- a/common/src/test/scala/objects/LoadoutTest.scala +++ b/common/src/test/scala/objects/LoadoutTest.scala @@ -110,6 +110,42 @@ class LoadoutTest extends Specification { ldout1.subtype mustEqual 0 ldout2.subtype mustEqual 1 ldout3.subtype mustEqual 2 - ldout4.subtype mustEqual 3 + ldout4.subtype mustEqual InfantryLoadout.DetermineSubtype(player) //example + } + + "players have additional uniform subtype" in { + val player = CreatePlayer() + val slot = player.Slot(0) + slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max) + + player.ExoSuit = ExoSuitType.Standard + val ldout0 = Loadout.Create(player, "standard").asInstanceOf[InfantryLoadout] + player.ExoSuit = ExoSuitType.Agile + val ldout1 = Loadout.Create(player, "agile").asInstanceOf[InfantryLoadout] + player.ExoSuit = ExoSuitType.Reinforced + val ldout2 = Loadout.Create(player, "rein").asInstanceOf[InfantryLoadout] + player.ExoSuit = ExoSuitType.Infiltration + val ldout7 = Loadout.Create(player, "inf").asInstanceOf[InfantryLoadout] + + Player.SuitSetup(player, ExoSuitType.MAX) + val ldout3 = Loadout.Create(player, "weaponless").asInstanceOf[InfantryLoadout] + slot.Equipment = None + slot.Equipment = Tool(trhev_dualcycler) + val ldout4 = Loadout.Create(player, "cycler").asInstanceOf[InfantryLoadout] + slot.Equipment = None + slot.Equipment = Tool(trhev_pounder) + val ldout5 = Loadout.Create(player, "pounder").asInstanceOf[InfantryLoadout] + slot.Equipment = None + slot.Equipment = Tool(trhev_burster) + val ldout6 = Loadout.Create(player, "burster").asInstanceOf[InfantryLoadout] + + InfantryLoadout.DetermineSubtypeB(ldout0.exosuit, ldout0.subtype) mustEqual 0 + InfantryLoadout.DetermineSubtypeB(ldout1.exosuit, ldout1.subtype) mustEqual 1 + InfantryLoadout.DetermineSubtypeB(ldout2.exosuit, ldout2.subtype) mustEqual 2 + InfantryLoadout.DetermineSubtypeB(ldout3.exosuit, ldout3.subtype) mustEqual 3 + InfantryLoadout.DetermineSubtypeB(ldout4.exosuit, ldout4.subtype) mustEqual 4 + InfantryLoadout.DetermineSubtypeB(ldout5.exosuit, ldout5.subtype) mustEqual 5 + InfantryLoadout.DetermineSubtypeB(ldout6.exosuit, ldout6.subtype) mustEqual 6 + InfantryLoadout.DetermineSubtypeB(ldout7.exosuit, ldout7.subtype) mustEqual 7 } } diff --git a/common/src/test/scala/objects/ServerObjectBuilderTest.scala b/common/src/test/scala/objects/ServerObjectBuilderTest.scala index 1f5b6dcf..49678d5f 100644 --- a/common/src/test/scala/objects/ServerObjectBuilderTest.scala +++ b/common/src/test/scala/objects/ServerObjectBuilderTest.scala @@ -208,25 +208,6 @@ class SpawnTubeObjectBuilderTest extends ActorTest { } } -class RepairRearmSiloObjectBuilderTest extends ActorTest { - import net.psforever.objects.GlobalDefinitions.repair_silo - import net.psforever.objects.serverobject.terminals.RepairRearmSilo - "LockerObjectBuilder" should { - "build" in { - val hub = ServerObjectBuilderTest.NumberPoolHub - val actor = system.actorOf(Props(classOf[ServerObjectBuilderTest.BuilderTestActor], ServerObjectBuilder(1, - RepairRearmSilo.Constructor(repair_silo)), hub), "silo") - actor ! "!" - - val reply = receiveOne(Duration.create(1000, "ms")) - assert(reply.isInstanceOf[RepairRearmSilo]) - assert(reply.asInstanceOf[RepairRearmSilo].HasGUID) - assert(reply.asInstanceOf[RepairRearmSilo].GUID == PlanetSideGUID(1)) - assert(reply == hub(1).get) - } - } -} - object ServerObjectBuilderTest { import net.psforever.objects.guid.source.LimitedNumberSource def NumberPoolHub : NumberPoolHub = { diff --git a/common/src/test/scala/objects/terminal/RepairRearmSiloTest.scala b/common/src/test/scala/objects/terminal/RepairRearmSiloTest.scala index eeb6fa33..516e834c 100644 --- a/common/src/test/scala/objects/terminal/RepairRearmSiloTest.scala +++ b/common/src/test/scala/objects/terminal/RepairRearmSiloTest.scala @@ -4,7 +4,7 @@ package objects.terminal import akka.actor.ActorRef import net.psforever.objects.serverobject.structures.{Building, StructureType} import net.psforever.objects._ -import net.psforever.objects.serverobject.terminals.{RepairRearmSilo, Terminal} +import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.objects.zones.Zone import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType} @@ -13,7 +13,7 @@ import org.specs2.mutable.Specification class RepairRearmSiloTest extends Specification { "RepairRearmSilo" should { val player = Player(Avatar("test", PlanetSideEmpire.TR, CharacterGender.Male, 0, 0)) - val silo = RepairRearmSilo(GlobalDefinitions.repair_silo) + val silo = Terminal(GlobalDefinitions.repair_silo) silo.Owner = new Building(0, Zone.Nowhere, StructureType.Building) silo.Owner.Faction = PlanetSideEmpire.TR @@ -22,7 +22,7 @@ class RepairRearmSiloTest extends Specification { } "construct" in { - val obj = RepairRearmSilo(GlobalDefinitions.repair_silo) + val obj = Terminal(GlobalDefinitions.repair_silo) obj.Actor mustEqual ActorRef.noSender } diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index 7d0494dd..c1b62dfa 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -7,7 +7,7 @@ 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.structures.{Building, FoundationBuilder, StructureType, WarpGate} -import net.psforever.objects.serverobject.terminals.{ProximityTerminal, RepairRearmSilo, Terminal} +import net.psforever.objects.serverobject.terminals.{ProximityTerminal, Terminal} import net.psforever.objects.serverobject.tube.SpawnTube import net.psforever.types.Vector3 @@ -113,8 +113,10 @@ object Maps { LocalObject(2145, SpawnTube.Constructor(Vector3(3980.4062f, 4252.7656f, 257.5625f), Vector3(0, 0, 90))) LocalObject(2146, SpawnTube.Constructor(Vector3(3980.4062f, 4259.992f, 257.5625f), Vector3(0, 0, 90))) LocalObject(2147, SpawnTube.Constructor(Vector3(3980.4062f, 4267.3047f, 257.5625f), Vector3(0, 0, 90))) - LocalObject(2050, RepairRearmSilo.Constructor(repair_silo)) - LocalObject(2062, RepairRearmSilo.Constructor(repair_silo)) + LocalObject(2049, ProximityTerminal.Constructor(repair_silo)) //repair terminal A + LocalObject(2050, Terminal.Constructor(repair_silo)) //rearm terminal A + LocalObject(2061, ProximityTerminal.Constructor(repair_silo)) //repair terminal B + LocalObject(2062, Terminal.Constructor(repair_silo)) //rearm terminal B LocalObject(2239, Terminal.Constructor(spawn_terminal)) LocalObject(2244, Terminal.Constructor(spawn_terminal)) LocalObject(2245, Terminal.Constructor(spawn_terminal)) @@ -216,7 +218,9 @@ object Maps { ObjectToBuilding(1576, 2) ObjectToBuilding(1577, 2) ObjectToBuilding(1578, 2) + ObjectToBuilding(2049, 2) ObjectToBuilding(2050, 2) + ObjectToBuilding(2061, 2) ObjectToBuilding(2062, 2) ObjectToBuilding(2145, 2) ObjectToBuilding(2146, 2) @@ -432,6 +436,8 @@ object Maps { def Building2() : Unit = { //HART building C LocalBuilding(2, FoundationBuilder(Building.Structure(StructureType.Building))) + LocalObject(12, ProximityTerminal.Constructor(repair_silo)) //repair terminal A + LocalObject(13, Terminal.Constructor(repair_silo)) //rearm terminal A //ItemTransaction: ItemTransactionMessage(PlanetSideGUID(2050),Buy,3,25mmbullet,0,PlanetSideGUID(0)) LocalObject(186, Terminal.Constructor(cert_terminal)) LocalObject(187, Terminal.Constructor(cert_terminal)) LocalObject(188, Terminal.Constructor(cert_terminal)) @@ -479,6 +485,8 @@ object Maps { LocalObject(1087, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct LocalObject(1088, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct LocalObject(1089, Terminal.Constructor(implant_terminal_interface)) //TODO guid not correct + ObjectToBuilding(12, 2) + ObjectToBuilding(13, 2) ObjectToBuilding(186, 2) ObjectToBuilding(187, 2) ObjectToBuilding(188, 2) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index ca2fd7ae..2461e431 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1104,7 +1104,7 @@ class WorldSessionActor extends Actor with MDCContextAware { vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) } sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on? - //sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, vehicle.Definition.MaxHealth))) + sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, 10))//vehicle.Definition.MaxHealth)) sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) //??? sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) //??? ReloadVehicleAccessPermissions(vehicle) @@ -2327,21 +2327,20 @@ class WorldSessionActor extends Actor with MDCContextAware { } } - case Some(obj : RepairRearmSilo) => - player.VehicleSeated match { - case Some(vehicle_guid) => - val vehicle = continent.GUID(vehicle_guid).get.asInstanceOf[Vehicle] - sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) - sendResponse(UseItemMessage(avatar_guid, unk1, vehicle_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, vehicle.Definition.ObjectId)) - case None => - log.error("UseItem: expected seated vehicle, but found none") - } - case Some(obj : Terminal) => if(obj.Definition.isInstanceOf[MatrixTerminalDefinition]) { //TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks) sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, obj.Position)) } + else if(obj.Definition == GlobalDefinitions.repair_silo) { + FindLocalVehicle match { + case Some(vehicle) => + sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + sendResponse(UseItemMessage(avatar_guid, unk1, vehicle.GUID, unk2, unk3, unk4, unk5, unk6, unk7, unk8, vehicle.Definition.ObjectId)) + case None => + log.error("UseItem: expected seated vehicle, but found none") + } + } else { sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) } @@ -2368,7 +2367,7 @@ class WorldSessionActor extends Actor with MDCContextAware { } case msg @ ProximityTerminalUseMessage(player_guid, object_guid, _) => - log.info(s"ProximityTerminal: $msg") + log.info(s"ProximityTerminalUse: $msg") continent.GUID(object_guid) match { case Some(obj : Terminal with ProximityUnit) => if(usingProximityTerminal.contains(object_guid)) { @@ -2378,7 +2377,7 @@ class WorldSessionActor extends Actor with MDCContextAware { StartUsingProximityUnit(obj) } case Some(obj) => ; - log.warn(s"ProximityTerminalUse: object is not a proximity terminal - $obj") + log.warn(s"ProximityTerminalUse: object does not have proximity effects - $obj") case None => log.warn(s"ProximityTerminalUse: no object with guid $object_guid found") } @@ -2438,14 +2437,16 @@ class WorldSessionActor extends Actor with MDCContextAware { else { None }) match { - case Some(owner : Player) => + case Some(owner : Player) => //InfantryLoadout avatar.SaveLoadout(owner, name, lineno) - case Some(owner : Vehicle) => + import InfantryLoadout._ + sendResponse(FavoritesMessage(list, player_guid, line, name, DetermineSubtypeB(player.ExoSuit, DetermineSubtype(player)))) + case Some(owner : Vehicle) => //VehicleLoadout avatar.SaveLoadout(owner, name, lineno) + sendResponse(FavoritesMessage(list, player_guid, line, name)) case Some(_) | None => log.error("FavoritesRequest: unexpected owner for favorites") } - sendResponse(FavoritesMessage(list, player_guid, line, name)) case FavoritesAction.Delete => avatar.DeleteLoadout(lineno) @@ -2637,6 +2638,9 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PlanetsideAttributeMessage(object_guid, attribute_type, attribute_value)) } + case msg @ FacilityBenefitShieldChargeRequestMessage(guid) => + //log.info(s"ShieldChargeRequest: $msg") + case msg @ BattleplanMessage(char_id, player_name, zonr_id, diagrams) => log.info("Battleplan: "+msg) @@ -4193,6 +4197,10 @@ class WorldSessionActor extends Actor with MDCContextAware { SetDelayedProximityUnitReset(terminal) ProximityHealCrystal(terminal) + case GlobalDefinitions.repair_silo => + SetDelayedProximityUnitReset(terminal) + //TODO insert vehicle repair here; see ProximityMedicalTerminal for example + case _ => ; } } From e368c8b5be1a43762fa57eddb9f61cc9bf28ace7 Mon Sep 17 00:00:00 2001 From: FateJH Date: Wed, 16 May 2018 20:35:33 -0400 Subject: [PATCH 08/13] added silos and air pads on z6 and on home3; undid temp lattice benefits and temp vehicle damage --- .../psforever/objects/GlobalDefinitions.scala | 2 ++ .../terminals/RepairRearmSiloDefinition.scala | 10 +++++++- pslogin/src/main/scala/Maps.scala | 24 +++++++++++++++++++ .../src/main/scala/WorldSessionActor.scala | 7 +++--- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index d39790ef..91f6c77e 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -524,6 +524,8 @@ object GlobalDefinitions { val medical_terminal = new MedicalTerminalDefinition(529) + val pad_landing = new RepairRearmSiloDefinition(719) + val repair_silo = new RepairRearmSiloDefinition(729) val spawn_pad = new VehicleSpawnPadDefinition diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala index 9cfc6fa7..f7463bf0 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/RepairRearmSiloDefinition.scala @@ -12,7 +12,15 @@ import net.psforever.packet.game.ItemTransactionMessage * Has both proximity-based operation and direct access purchasing power. */ class RepairRearmSiloDefinition(objectId : Int) extends EquipmentTerminalDefinition(objectId) with ProximityDefinition { - Name = "repair_silo" + Name = if(objectId == 719) { + "pad_landing" + } + else if(objectId == 729) { + "repair_silo" + } + else { + throw new IllegalArgumentException("repair re-arm terminal must be either object id 719 or 729") + } private val buyFunc : (Player, ItemTransactionMessage)=>Terminal.Exchange = EquipmentTerminalDefinition.Buy(Map.empty, Map.empty, Map.empty) diff --git a/pslogin/src/main/scala/Maps.scala b/pslogin/src/main/scala/Maps.scala index c1b62dfa..4c750b62 100644 --- a/pslogin/src/main/scala/Maps.scala +++ b/pslogin/src/main/scala/Maps.scala @@ -110,6 +110,14 @@ object Maps { LocalObject(1576, Terminal.Constructor(order_terminal)) LocalObject(1577, Terminal.Constructor(order_terminal)) LocalObject(1578, Terminal.Constructor(order_terminal)) + LocalObject(1744, ProximityTerminal.Constructor(pad_landing)) //air pad A + LocalObject(1745, Terminal.Constructor(pad_landing)) //air pad A + LocalObject(1747, ProximityTerminal.Constructor(pad_landing)) //air pad B + LocalObject(1748, Terminal.Constructor(pad_landing)) //air pad B + LocalObject(1756, ProximityTerminal.Constructor(pad_landing)) //air pad C + LocalObject(1757, Terminal.Constructor(pad_landing)) //air pad C + LocalObject(1765, ProximityTerminal.Constructor(pad_landing)) //air pad D + LocalObject(1766, Terminal.Constructor(pad_landing)) //air pad D LocalObject(2145, SpawnTube.Constructor(Vector3(3980.4062f, 4252.7656f, 257.5625f), Vector3(0, 0, 90))) LocalObject(2146, SpawnTube.Constructor(Vector3(3980.4062f, 4259.992f, 257.5625f), Vector3(0, 0, 90))) LocalObject(2147, SpawnTube.Constructor(Vector3(3980.4062f, 4267.3047f, 257.5625f), Vector3(0, 0, 90))) @@ -218,6 +226,14 @@ object Maps { ObjectToBuilding(1576, 2) ObjectToBuilding(1577, 2) ObjectToBuilding(1578, 2) + ObjectToBuilding(1744, 2) + ObjectToBuilding(1745, 2) + ObjectToBuilding(1747, 2) + ObjectToBuilding(1748, 2) + ObjectToBuilding(1756, 2) + ObjectToBuilding(1757, 2) + ObjectToBuilding(1765, 2) + ObjectToBuilding(1766, 2) ObjectToBuilding(2049, 2) ObjectToBuilding(2050, 2) ObjectToBuilding(2061, 2) @@ -359,6 +375,10 @@ object Maps { LocalObject(1591, Terminal.Constructor(order_terminal)) LocalObject(1592, Terminal.Constructor(order_terminal)) LocalObject(1593, Terminal.Constructor(order_terminal)) + LocalObject(1846, ProximityTerminal.Constructor(pad_landing)) //air pad S + LocalObject(1847, Terminal.Constructor(pad_landing)) //air pad S + LocalObject(1849, ProximityTerminal.Constructor(pad_landing)) //air pad N + LocalObject(1850, Terminal.Constructor(pad_landing)) //air pad N LocalObject(2156, SpawnTube.Constructor(respawn_tube_tower, Vector3(4364.633f, 3994.125f, 228.1875f), Vector3(0, 0, 90))) LocalObject(2157, SpawnTube.Constructor(respawn_tube_tower, Vector3(4364.633f, 3977.7266f, 228.1875f), Vector3(0, 0, 90))) LocalObject(2333, Door.Constructor) //spawn tube door @@ -382,6 +402,10 @@ object Maps { ObjectToBuilding(1591, 49) ObjectToBuilding(1592, 49) ObjectToBuilding(1593, 49) + ObjectToBuilding(1846, 49) + ObjectToBuilding(1847, 49) + ObjectToBuilding(1849, 49) + ObjectToBuilding(1850, 49) ObjectToBuilding(2156, 49) ObjectToBuilding(2157, 49) ObjectToBuilding(2333, 49) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 2461e431..3ef631db 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1104,7 +1104,7 @@ class WorldSessionActor extends Actor with MDCContextAware { vehicleService ! VehicleServiceMessage.UnscheduleDeconstruction(vehicle_guid) } sendResponse(PlanetsideAttributeMessage(vehicle_guid, 22, 0L)) //mount points on? - sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, 10))//vehicle.Definition.MaxHealth)) + //sendResponse(PlanetsideAttributeMessage(vehicle_guid, 0, 10))//vehicle.Definition.MaxHealth)) sendResponse(PlanetsideAttributeMessage(vehicle_guid, 68, 0L)) //??? sendResponse(PlanetsideAttributeMessage(vehicle_guid, 113, 0L)) //??? ReloadVehicleAccessPermissions(vehicle) @@ -2332,7 +2332,7 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO matrix spawn point; for now, just blindly bind to show work (and hope nothing breaks) sendResponse(BindPlayerMessage(1, "@ams", true, true, 0, 0, 0, obj.Position)) } - else if(obj.Definition == GlobalDefinitions.repair_silo) { + else if(obj.Definition.isInstanceOf[RepairRearmSiloDefinition]) { FindLocalVehicle match { case Some(vehicle) => sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) @@ -2363,7 +2363,8 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(ObjectDeleteMessage(PlanetSideGUID(unk1), 2)) } - case None => ; + case None => + log.error(s"UseItem: can not find object $object_guid") } case msg @ ProximityTerminalUseMessage(player_guid, object_guid, _) => From f6bb87941ee0c7c10f83521da9e068f0a3f1d3b7 Mon Sep 17 00:00:00 2001 From: Mazo Date: Sun, 20 May 2018 18:32:16 +0100 Subject: [PATCH 09/13] Remove passwords being logged in plaintext on logins --- pslogin/src/main/scala/LoginSessionActor.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pslogin/src/main/scala/LoginSessionActor.scala b/pslogin/src/main/scala/LoginSessionActor.scala index 6260563c..55e234d6 100644 --- a/pslogin/src/main/scala/LoginSessionActor.scala +++ b/pslogin/src/main/scala/LoginSessionActor.scala @@ -128,7 +128,7 @@ class LoginSessionActor extends Actor with MDCContextAware { if(token.isDefined) log.info(s"New login UN:$username Token:${token.get}. $clientVersion") else - log.info(s"New login UN:$username PW:$password. $clientVersion") + log.info(s"New login UN:$username. $clientVersion") // This is temporary until a schema has been developed //val loginSucceeded = accountLookup(username, password.getOrElse(token.get)) From 8971addc1e722405ff79fcd4c2851b88a70667da Mon Sep 17 00:00:00 2001 From: Mazo Date: Sun, 20 May 2018 18:37:58 +0100 Subject: [PATCH 10/13] Update readme with clearer instructions for PSCrypto and potential workarounds for the library not being detected properly --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index bcceb53c..67956db7 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,11 @@ In order to use scala, you need the compiler `scalac`. This is equivalent to Jav Install this on to your system and the compiler and Scala REPL will be added to your PATH. ### Downloading PSCrypto -The server requires PSCrypto in order to run. [Download the latest release](https://github.com/psforever/PSCrypto/releases/download/v1.1/pscrypto-lib-1.1.zip) and extract the ZIP in to the top level of your source directory. SBT, IDEA, and Java will automatically find the required libraries when running the server. +The server requires PSCrypto in order to run. [Download the latest release](https://github.com/psforever/PSCrypto/releases/download/v1.1/pscrypto-lib-1.1.zip) and extract the the approprate dll for your operating system to the top level of your source directory (the root directory, not /pslogin/src/main/scala). SBT, IDEA, and Java will automatically find the required libraries when running the server. If you are not comfortable with compiled binaries, you can [build the libraries yourself](https://github.com/psforever/PSCrypto). +If you have any issues with PSCrypto being detected when trying to run the server try adding `-Djava.library.path=` (no path necessary) to your preferred IDE's build configuration, for example with IDEA: Run -> Edit Configuration -> VM Options + ### Using an IDE Scala code can be fairly complex and a good IDE helps you understand the code and what methods are available for certain types. IntelliJ IDEA has some of the most mature support for Scala of any IDE today. It has advanced type introspection and excellent code completion. It's recommended for those who are new to Scala in order to get familiar with the syntax. From 26d42e9f37e174e337544e466727175307d627c7 Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 21 May 2018 07:29:09 -0400 Subject: [PATCH 11/13] fixing incorrect documentation --- .../packet/game/FacilityBenefitShieldChargeRequestMessage.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/src/main/scala/net/psforever/packet/game/FacilityBenefitShieldChargeRequestMessage.scala b/common/src/main/scala/net/psforever/packet/game/FacilityBenefitShieldChargeRequestMessage.scala index 15389532..6b1d6110 100644 --- a/common/src/main/scala/net/psforever/packet/game/FacilityBenefitShieldChargeRequestMessage.scala +++ b/common/src/main/scala/net/psforever/packet/game/FacilityBenefitShieldChargeRequestMessage.scala @@ -7,7 +7,7 @@ import scodec.codecs._ /** * Dispatched by the client when driving a vehicle in the sphere of influence of an allied base - * that is a dropship center or that possesses the lattice-connected benefit of a dropship center. + * that is an amp station facility or that possesses the lattice-connected benefit of an amp station. * The vehicle that is being driven will not have perfect fully-charged shields at the time. * @param vehicle_guid the vehicle whose shield is being charged */ From 0b15837436a91c98ff370c7b9dd75637a4312f3b Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 17 May 2018 21:08:09 -0400 Subject: [PATCH 12/13] Nick wanted CSRZone and CSRWarp working, so that's what I did --- .../src/main/scala/WorldSessionActor.scala | 28 + pslogin/src/main/scala/Zones.scala | 8 +- pslogin/src/main/scala/csr/CSRWarp.scala | 129 ++++ pslogin/src/main/scala/csr/CSRZone.scala | 114 ++++ pslogin/src/main/scala/csr/CSRZoneImpl.scala | 578 ++++++++++++++++++ pslogin/src/main/scala/csr/Traveler.scala | 37 ++ 6 files changed, 890 insertions(+), 4 deletions(-) create mode 100644 pslogin/src/main/scala/csr/CSRWarp.scala create mode 100644 pslogin/src/main/scala/csr/CSRZone.scala create mode 100644 pslogin/src/main/scala/csr/CSRZoneImpl.scala create mode 100644 pslogin/src/main/scala/csr/Traveler.scala diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index f5b22034..b66a2f38 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -9,6 +9,7 @@ import scodec.Attempt.{Failure, Successful} import scodec.bits._ import org.log4s.MDC import MDCContextAware.Implicits._ +import csr.{CSRWarp, CSRZone, Traveler} import net.psforever.objects.GlobalDefinitions._ import services.ServiceManager.Lookup import net.psforever.objects._ @@ -71,6 +72,7 @@ class WorldSessionActor extends Actor with MDCContextAware { var usingProximityTerminal : Set[PlanetSideGUID] = Set.empty var delayedProximityTerminalResets : Map[PlanetSideGUID, Cancellable] = Map.empty var controlled : Option[Int] = None //keep track of avatar's ServerVehicleOverride state + var traveler : Traveler = null var clientKeepAlive : Cancellable = DefaultCancellable.obj var progressBarUpdate : Cancellable = DefaultCancellable.obj @@ -1281,6 +1283,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case InterstellarCluster.ClientInitializationComplete() => StopBundlingPackets() LivePlayerList.Add(sessionId, avatar) + traveler = new Traveler(self, continent.Id) //PropertyOverrideMessage sendResponse(PlanetsideAttributeMessage(PlanetSideGUID(0), 112, 1)) sendResponse(ReplicationStreamMessage(5, Some(6), Vector(SquadListing()))) //clear squad list @@ -1529,6 +1532,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case msg @ BeginZoningMessage() => log.info("Reticulating splines ...") + traveler.zone = continent.Id StartBundlingPackets() configZone(continent) sendResponse(TimeOfDayMessage(1191182336)) @@ -1760,6 +1764,30 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + CSRZone.read(traveler, msg) match { + case (true, zone, pos) => + if(player.isAlive) { + player.Die //die to suspend position client-driven change updates + player.Position = pos + traveler.zone = zone + continent.Population ! Zone.Population.Release(avatar) + continent.Population ! Zone.Population.Leave(avatar) + taskResolver ! TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(player)(continent.GUID), zone) + } + + case (false, _, _) => ; + } + + CSRWarp.read(traveler, msg) match { + case (true, pos) => + if(player.isAlive) { + sendResponse(PlayerStateShiftMessage(ShiftState(0, pos, player.Orientation.z, None))) + player.Position = pos + } + + case (false, _) => ; + } + // TODO: Prevents log spam, but should be handled correctly if (messagetype != ChatMessageType.CMT_TOGGLE_GM) { log.info("Chat: " + msg) diff --git a/pslogin/src/main/scala/Zones.scala b/pslogin/src/main/scala/Zones.scala index 55d18bb9..0e0bef78 100644 --- a/pslogin/src/main/scala/Zones.scala +++ b/pslogin/src/main/scala/Zones.scala @@ -88,13 +88,13 @@ object Zones { val c6 = new Zone("c6", Maps.ugd06, 28) - val i1 = new Zone("i1", Maps.map96, 29) + val i1 = new Zone("i1", Maps.map99, 29) - val i2 = new Zone("i2", Maps.map97, 30) + val i2 = new Zone("i2", Maps.map98, 30) - val i3 = new Zone("i3", Maps.map98, 31) + val i3 = new Zone("i3", Maps.map97, 31) - val i4 = new Zone("i4", Maps.map99, 32) + val i4 = new Zone("i4", Maps.map96, 32) /** * Get the zone identifier name for the sanctuary continent of a given empire. diff --git a/pslogin/src/main/scala/csr/CSRWarp.scala b/pslogin/src/main/scala/csr/CSRWarp.scala new file mode 100644 index 00000000..eb3048f8 --- /dev/null +++ b/pslogin/src/main/scala/csr/CSRWarp.scala @@ -0,0 +1,129 @@ +package csr + +import net.psforever.packet.PacketCoding +import net.psforever.packet.game.ChatMsg +import net.psforever.types.{ChatMessageType, Vector3} + +import scala.collection.mutable.ArrayBuffer +import scala.util.Try + +/* +The following is STILL for development and fun. +*/ +/** + * An implementation of the CSR command `/warp`, highly modified to serve the purposes of the testing phases of the server. + * See `help()` for details. + */ +object CSRWarp { + /** + * Accept and confirm that a message sent to a player is a valid `/warp` invocation. + * If so, parse the message and send the player to whichever destination in this zone was requested. + * @param traveler the player + * @param msg the message the player received + * @return true, if the player is being transported to another place; false, otherwise + */ + def read(traveler : Traveler, msg : ChatMsg) : (Boolean, Vector3) = { + if(!isProperRequest(msg)) + return (false, Vector3.Zero) //we do not handle this message + + val buffer = decomposeMessage(msg.contents) + if(buffer.length == 0 || buffer(0).equals("") || buffer(0).equals("-help")) { + CSRWarp.help(traveler) //print usage information to chat + return (false, Vector3.Zero) + } + var destId : String = "" + var coords : ArrayBuffer[Int] = ArrayBuffer.empty[Int] + var list : Boolean = false + var failedCoordInput = false + for(o <- buffer) { + val toInt = Try(o.toInt) + if(toInt.isSuccess) { + coords += toInt.get + } + else if(coords.nonEmpty && coords.size < 3) + failedCoordInput = true + if(o.equals("-list")) + list = true + else if(destId.equals("")) + destId = o + } + if(failedCoordInput || (coords.nonEmpty && coords.size < 3)) { + CSRWarp.error(traveler, "Needs three integer components ( )") + return (false, Vector3.Zero) + } + else { + coords.slice(0, 3).foreach(x => { + if(x < 0 || x > 8191) { + CSRWarp.error(traveler, "Out of range - 0 < n < 8191, but n = " + x) + return (false, Vector3.Zero) + } + }) + } + val zone = CSRZoneImpl.get(traveler.zone).get //the traveler is already in the appropriate zone + if(list && coords.isEmpty && destId.equals("")) { + CSRWarp.reply(traveler, CSRZoneImpl.listLocations(zone) + "; " + CSRZoneImpl.listWarpgates(zone)) + return (false, Vector3.Zero) + } + val dest : Option[Vector3] = if(coords.nonEmpty) Some(Vector3(coords(0), coords(1), coords(2))) + else CSRZoneImpl.getWarpLocation(zone, destId) //coords before destId + if(dest.isEmpty) { + CSRWarp.error(traveler, "Invalid location") + return (false, Vector3.Zero) + } + (true, dest.get) + } + + /** + * Check that the incoming message is an appropriate type for this command. + * @param msg the message + * @return true, if we will handle it; false, otherwise + */ + def isProperRequest(msg : ChatMsg) : Boolean = { + msg.messageType == ChatMessageType.CMT_WARP + } + + /** + * Break the message in the packet down for parsing. + * @param msg the contents portion of the message, a space-separated `String` + * @return the contents portion of the message, transformed into an `Array` + */ + private def decomposeMessage(msg : String) : Array[String] = { + msg.trim.toLowerCase.split("\\s+") + } + + /** + * Send a message back to the `Traveler` that will be printed into his chat window. + * @param traveler the player + * @param msg the message to be sent + */ + private def reply(traveler : Traveler, msg : String) : Unit = { + traveler ! PacketCoding.CreateGamePacket(0, ChatMsg(ChatMessageType.CMT_OPEN, true, "", msg, None)) + } + + /** + * Print usage information to the `Traveler`'s chat window.
+ *
+ * The "official" use information for help dictates the command should follow this format: + * `/warp <x><y><z> | to <character> | near <object> | above <object> | waypoint`. + * In our case, creating fixed coordinate points of interest is not terribly dissimilar from the "near" and "to" aspect. + * We can not currently implement most of the options for now, however.
+ *
+ * The destination prioritizes evaluation of the coordinates before the location string. + * When the user provides coordinates, he must provide all three components of the coordinate at once, else none will be accepted. + * If the coordinates are invalid, the location string will still be checked. + * "-list" is accepted while no serious attempt is made to indicate a destination (no location string or not enough coordinates). + * @param traveler the player + */ + private def help(traveler : Traveler) : Unit = { + CSRWarp.reply(traveler, "usage: /warp | | | [-list]") + } + + /** + * Print error information to the `Traveler`'s chat window.
+ * The most common reason for error is the lack of information, or wrong information. + * @param traveler the player + */ + private def error(traveler : Traveler, msg : String) : Unit = { + CSRWarp.reply(traveler, "Error! " + msg) + } +} diff --git a/pslogin/src/main/scala/csr/CSRZone.scala b/pslogin/src/main/scala/csr/CSRZone.scala new file mode 100644 index 00000000..a2f5329c --- /dev/null +++ b/pslogin/src/main/scala/csr/CSRZone.scala @@ -0,0 +1,114 @@ +package csr + +import net.psforever.packet.PacketCoding +import net.psforever.packet.game.ChatMsg +import net.psforever.types.{ChatMessageType, Vector3} + +/* +The following is STILL for development and fun. +*/ +/** + * An implementation of the CSR command `/zone`, slightly modified to serve the purposes of the testing phases of the server. + */ +object CSRZone { + /** + * Accept and confirm that a message sent to a player is a valid `/zone` invocation. + * If so, parse the message and send the player to whichever zone was requested. + * @param traveler the player + * @param msg the message the player received + * @return true, if the player is being transported to another zone; false, otherwise + */ + def read(traveler : Traveler, msg : ChatMsg) : (Boolean, String , Vector3 ) = { + if(!isProperRequest(msg)) + return (false,"", Vector3.Zero) //we do not handle this message + + val buffer = decomposeMessage(msg.contents) + if(buffer.length == 0 || buffer(0).equals("-help")) { + CSRZone.help(traveler) //print usage information to chat + return (false,"", Vector3.Zero) + } + + var zoneId = "" + var gateId = "" //the user can define which warpgate they may visit (actual keyword protocol missing) + var list = false //if the user wants a printed list of destination locations + for(o <- buffer) { + if(o.equals("-list")) { + if(zoneId.equals("") || gateId.equals("")) { + list = true + } + } + else if(zoneId.equals("")) + zoneId = o + else if(gateId.equals("")) + gateId = o + } + + val zoneOpt = CSRZoneImpl.get(zoneId) + if(zoneOpt.isEmpty) { + if(list) + CSRZone.reply(traveler, CSRZoneImpl.list) + else + CSRZone.error(traveler, "Give a valid zonename (use '/zone -list')") + return (false,"", Vector3.Zero) + } + val zone = zoneOpt.get + var destination : Vector3 = CSRZoneImpl.selectRandom(zone) //the destination in the new zone starts as random + + if(!gateId.equals("")) { //if we've defined a warpgate, and can find that warpgate, we re-assign the destination + val gateOpt = CSRZoneImpl.getWarpgate(zone, gateId) + if(gateOpt.isDefined) + destination = gateOpt.get + else + CSRZone.error(traveler, "Gate id not defined (use '/zone -list')") + } + else if(list) { + CSRZone.reply(traveler, CSRZoneImpl.listWarpgates(zone)) + return (false,"",Vector3.Zero) + } + (true, zone.zonename, destination) + } + + /** + * Check that the incoming message is an appropriate type for this command. + * @param msg the message + * @return true, if we will handle it; false, otherwise + */ + def isProperRequest(msg : ChatMsg) : Boolean ={ + msg.messageType == ChatMessageType.CMT_ZONE + } + + /** + * Break the message in the packet down for parsing. + * @param msg the contents portion of the message, a space-separated `String` + * @return the contents portion of the message, transformed into an `Array` + */ + private def decomposeMessage(msg : String) : Array[String] = { + msg.trim.toLowerCase.split("\\s+") + } + + /** + * Send a message back to the `Traveler` that will be printed into his chat window. + * @param traveler the player + * @param msg the message to be sent + */ + private def reply(traveler : Traveler, msg : String) : Unit = { + traveler ! PacketCoding.CreateGamePacket(0, ChatMsg(ChatMessageType.CMT_OPEN,true,"", msg, None)) + } + + /** + * Print usage information to the `Traveler`'s chat window. + * @param traveler the player + */ + private def help(traveler : Traveler) : Unit = { + CSRZone.reply(traveler, "usage: /zone [gatename] | [-list]") + } + + /** + * Print error information to the `Traveler`'s chat window.
+ * The most common reason for error is the lack of information, or wrong information. + * @param traveler the player + */ + private def error(traveler : Traveler, msg : String) : Unit = { + CSRZone.reply(traveler, "Error! "+msg) + } +} diff --git a/pslogin/src/main/scala/csr/CSRZoneImpl.scala b/pslogin/src/main/scala/csr/CSRZoneImpl.scala new file mode 100644 index 00000000..ff5a2a70 --- /dev/null +++ b/pslogin/src/main/scala/csr/CSRZoneImpl.scala @@ -0,0 +1,578 @@ +package csr + +// Copyright (c) 2017 PSForever +import net.psforever.types.Vector3 + +import scala.collection.mutable +import scala.util.Random + +/* +The following is STILL for development and fun. +*/ +/** + * A crude representation of the information needed to describe a continent (hitherto, a "zone"). + * The information is mainly catered to the simulation of the CSR commands `/zone` and `/warp`. + * (The exception is `alias` which is maintained for cosmetic purposes and clarification.) + * @param alias the common name of the zone + * @param map the map name of the zone (this map is loaded) + * @param zonename the zone's internal name + */ +class CSRZoneImpl(val alias : String, val map : String, val zonename : String) { + /** + * A listing of warpgates, geowarps, and island warpgates in this zone. + * The coordinates specified will only ever drop the user on a specific point within the protective bubble of the warpgate. + * This breaks from the expected zoning functionality where the user is placed in a random spot under the bubble. + * There is no prior usage details for the searchability format of this field's key values. + */ + private val gates : mutable.HashMap[String, Vector3] = mutable.HashMap() + /** + * A listing of special locations in this zone, i.e., major faciities, and some landmarks of interest. + * There is no prior usage details for the searchability format of this field's key values. + */ + private val locations : mutable.HashMap[String, Vector3] = mutable.HashMap() +} + +object CSRZoneImpl { + /** + * A listing of all zones that can be visited by their internal name. + * The keys in this map should be directly usable by the `/zone` command. + */ + private val zones = Map[String, CSRZoneImpl]( + "z1" -> CSRZoneImpl("Solsar", "map01", "z1"), + "z2" -> CSRZoneImpl("Hossin", "map02", "z2"), + "z3" -> CSRZoneImpl("Cyssor", "map03", "z3"), + "z4" -> CSRZoneImpl("Ishundar", "map04", "z4"), + "z5" -> CSRZoneImpl("Forseral", "map05", "z5"), + "z6" -> CSRZoneImpl("Ceryshen", "map06", "z6"), + "z7" -> CSRZoneImpl("Esamir", "map07", "z7"), + "z8" -> CSRZoneImpl("Oshur", "map08", "z8"), + "z9" -> CSRZoneImpl("Searhus", "map09", "z9"), + "z10" -> CSRZoneImpl("Amerish", "map10", "z10"), + "home1" -> CSRZoneImpl("NC Sanctuary", "map11", "home1"), + "home2" -> CSRZoneImpl("TR Sanctuary", "map12", "home2"), + "home3" -> CSRZoneImpl("VS Sanctuary", "map13", "home3"), + "tzshtr" -> CSRZoneImpl("VR Shooting Range TR", "map14", "tzshtr"), + "tzdrtr" -> CSRZoneImpl("VR Driving Range TR", "map15", "tzdrtr"), + "tzcotr" -> CSRZoneImpl("VR Combat csr.CSRZoneImpl TR", "map16", "tzcotr"), + "tzshvs" -> CSRZoneImpl("VR Shooting Range VS", "map14", "tzshvs"), + "tzdrvs" -> CSRZoneImpl("VR Driving Range VS", "map15", "tzdrvs"), + "tzcovs" -> CSRZoneImpl("VR Combat csr.CSRZoneImpl VS", "map16", "tzcovs"), + "tzshnc" -> CSRZoneImpl("VR Shooting Range NC", "map14", "tzshnc"), + "tzdrnc" -> CSRZoneImpl("VR Driving Range NC", "map15", "tzdrnc"), + "tzconc" -> CSRZoneImpl("VR Combat csr.CSRZoneImpl NC", "map16", "tzconc"), + "c1" -> CSRZoneImpl("Supai", "ugd01", "c1"), + "c2" -> CSRZoneImpl("Hunhau", "ugd02", "c2"), + "c3" -> CSRZoneImpl("Adlivun", "ugd03", "c3"), + "c4" -> CSRZoneImpl("Byblos", "ugd04", "c4"), + "c5" -> CSRZoneImpl("Annwn", "ugd05", "c5"), + "c6" -> CSRZoneImpl("Drugaskan", "ugd06", "c6"), + "i4" -> CSRZoneImpl("Nexus", "map96", "i4"), + "i3" -> CSRZoneImpl("Desolation", "map97", "i3"), + "i2" -> CSRZoneImpl("Ascension", "map98", "i2"), + "i1" -> CSRZoneImpl("Extinction", "map99", "i1"), + "homebo" -> CSRZoneImpl("Black_ops_hq", "Black_ops_hq", "homebo"), + "station1" -> CSRZoneImpl("TR Station", "Station1", "station1"), + "station2" -> CSRZoneImpl("NC Station", "Station2", "station2"), + "station3" -> CSRZoneImpl("VS Station", "Station3", "station3") + ) + /** + * A listing of all zones that can be visited by their common name. + * The keys in this map should be directly usable by the `/zone` command. + * Though the behavior is undocumented, access to this alias list is for the benefit of the user. + */ + private val alias = Map[String, String]( + "solsar" -> "z1", + "hossin" -> "z2", + "cyssor" -> "z3", + "ishundar" -> "z4", + "forseral" -> "z5", + "ceryshen" -> "z6", + "esamir" -> "z7", + "oshur" -> "z8", + "searhus" -> "z9", + "amerish" -> "z10", + "nc-sanctuary" -> "home1", + "tr-sanctuary" -> "home2", + "vs-sanctuary" -> "home3", + "tr-shooting" -> "tzshtr", + "tr-driving" -> "tzdrtr", + "tr-combat" -> "tzcotr", + "vs-shooting" -> "tzshvs", + "vs-driving" -> "tzdrvs", + "vs-combat" -> "tzcovs", + "nc-shooting" -> "tzshnc", + "nc-driving" -> "tzdrnc", + "nc-combat" -> "tzconc", + "supai" -> "c1", + "hunhau" -> "c2", + "adlivun" -> "c3", + "byblos" -> "c4", + "annwn" -> "c5", + "drugaskan" -> "c6", + "nexus" -> "i4", + "desolation" -> "i3", + "ascension" -> "i2", + "extinction" -> "i1", + "Black_ops_hq" -> "homebo", + "TR-Station" -> "station1", + "NC-Station" -> "station2", + "VS-Station" -> "station3" + ) + /** + * A value used for selecting where to appear in a zone from the list of locations when the user has no indicated one. + */ + private val rand = Random + setup() + + /** + * An abbreviated constructor for creating `CSRZone`s without invocation of `new`. + * @param alias the common name of the zone + * @param map the map name of the zone (this map is loaded) + * @param zonename the zone's internal name + */ + def apply(alias : String, map : String, zonename : String) : CSRZoneImpl = new CSRZoneImpl(alias, map, zonename) + + /** + * Get a valid `CSRZone`'s information. + * @param zoneId a name that describes the zone and should be searchable + * @return the `CSRZone`, or `None` + */ + def get(zoneId : String) : Option[CSRZoneImpl] = { + var zId = zoneId.toLowerCase + if(alias.get(zId).isDefined) + zId = alias(zId) + zones.get(zId) + } + + /** + * Get a location within the `CSRZone`. + * The location should be a facility or a warpgate or interesting. + * @param zone the `CSRZone` + * @param locId a name that describes a known location in the provided `CSRZone` and is searchable + * @return the coordinates of that location, or None + */ + def getWarpLocation(zone : CSRZoneImpl, locId : String) : Option[Vector3] = { + val low_locId = locId.toLowerCase + var location = zone.locations.get(low_locId) + if(location.isEmpty) + location = zone.gates.get(low_locId) + location + } + + /** + * Get the position of a warpgate within the zone. + * @param zone the `CSRZone` + * @param gateId a name that describes a known warpgate in the provided `CSRZone` and is searchable + * @return the coordinates of that warpgate, or None + */ + def getWarpgate(zone : CSRZoneImpl, gateId : String) : Option[Vector3] = { + zone.gates.get(gateId.toLowerCase) + } + + /** + * Get the names for all of the `CSRZones` that can be visited. + * @return all of the zonenames + */ + def list : String = { + "zonenames: z1 - z10, home1 - home3, tzshnc, tzdrnc, tzconc, tzshtr, tzdrtr, tzcotr, tzshvs, tzdrvs, tzcovs, c1 - c6, i1 - i4; zones are also aliased to their continent name" + } + + /** + * Get the name for all of the locations that can be visited in this `CSRZone`, excluding warpgates. + * @param zone the `CSRZone` + * @return all of the location keys + */ + def listLocations(zone : CSRZoneImpl) : String = { + var out : String = "warps: " + if(zone.locations.nonEmpty) { + out += zone.locations.keys.toArray.sorted.mkString(", ") + } + else + out = "none" + out + } + + /** + * Get the name for all of the warpgates that can be visited in this `CSRZone`. + * @param zone the `CSRZone` + * @return all of the warpgate keys + */ + def listWarpgates(zone : CSRZoneImpl) : String = { + var out : String = "gatenames: " + if(zone.gates.isEmpty) + out += "none" + else + out += zone.gates.keys.toArray.sorted.mkString(", ") + out + } + + /** + * Select, of all the `CSRZone` locations and warpgates, a pseudorandom destination to spawn the player in the zone if none has been specified. + * @param zone the `CSRZone` + * @return the coordinates of the spawn point + */ + def selectRandom(zone : CSRZoneImpl) : Vector3 = { + var outlets = zone.locations //random location? + if(outlets.nonEmpty) { + return outlets.values.toArray.apply(rand.nextInt(outlets.size)) + } + outlets = zone.gates //random warpgate? + if(outlets.nonEmpty) { + return outlets.values.toArray.apply(rand.nextInt(outlets.size)) + } + Vector3.Zero //fallback coordinates (that will always be valid) + } + + /** + * Load all zones with selected places of interest and the coordinates to place the player nearby that given place of interest. + * All of these keys should be searchable under the `/warp` command. + * Only the warpgate keys are searchable by the `/zone` command. + */ + def setup() : Unit = { + zones("z1").gates += ( + "gate1" -> Vector3(4150, 7341, 82), + "gate2" -> Vector3(5698, 3404, 129), + "gate3" -> Vector3(2650, 5363, 176), + "gate4" -> Vector3(3022, 1225, 66), + "geowarp1" -> Vector3(3678, 2895, 108), + "geowarp2" -> Vector3(5672, 4750, 70) + ) + zones("z1").locations += ( + "amun" -> Vector3(4337, 2278, 68), + "aton" -> Vector3(3772, 5463, 54), + "bastet" -> Vector3(5412, 5588, 56), + "hapi" -> Vector3(4256, 4436, 59), + "horus" -> Vector3(3725, 2114, 73), + "mont" -> Vector3(3354, 4205, 83), + "seth" -> Vector3(4495, 6026, 58), + "sobek" -> Vector3(3094, 3027, 75), + "thoth" -> Vector3(4615, 3373, 53), + "lake" -> Vector3(4317, 4008, 37), + "monolith" -> Vector3(5551, 5047, 64) + ) + zones("z2").gates += ( + "gate1" -> Vector3(1881, 4873, 19), + "gate2" -> Vector3(4648, 4625, 28), + "gate3" -> Vector3(3296, 2045, 21), + "gate4" -> Vector3(5614, 1781, 32), + "geowarp1" -> Vector3(5199, 4869, 39), + "geowarp2" -> Vector3(3911, 2407, 15) + ) + zones("z2").locations += ( + "acan" -> Vector3(3534, 4015, 30), + "bitol" -> Vector3(4525, 2632, 30), + "chac" -> Vector3(4111, 5950, 39), + "ghanon" -> Vector3(2565, 3707, 41), + "hurakan" -> Vector3(1840, 2934, 38), + "ixtab" -> Vector3(3478, 3143, 40), + "kisin" -> Vector3(3356, 5374, 31), + "mulac" -> Vector3(5592, 2738, 37), + "naum" -> Vector3(5390, 3454, 28), + "voltan" -> Vector3(4529, 3414, 28), + "zotz" -> Vector3(6677, 2342, 129), + "monolith" -> Vector3(2938, 2485, 14) + ) + zones("z3").gates += ( + "gate1" -> Vector3(2616, 6567, 58), + "gate2" -> Vector3(6980, 5336, 57), + "gate3" -> Vector3(1199, 1332, 66), + "gate4" -> Vector3(5815, 1974, 63), + "geowarp1" -> Vector3(2403, 4278, 60), + "geowarp2" -> Vector3(4722, 2665, 78) + ) + zones("z3").locations += ( + "aja" -> Vector3(754, 5435, 48), + "chuku" -> Vector3(4208, 7021, 54), + "bomazi" -> Vector3(1198, 4492, 58), + "ekera" -> Vector3(5719, 6555, 51), + "faro" -> Vector3(5030, 5700, 57), + "gunuku" -> Vector3(4994, 4286, 54), + "honsi" -> Vector3(4042, 4588, 89), + "itan" -> Vector3(5175, 3393, 48), + "kaang" -> Vector3(5813, 3862, 62), + "leza" -> Vector3(2691, 1561, 64), + "mukuru" -> Vector3(661, 2380, 54), + "nzame" -> Vector3(1670, 2706, 45), + "orisha" -> Vector3(7060, 1327, 59), + "pamba" -> Vector3(7403, 3123, 63), + "shango" -> Vector3(6846, 2319, 63), + "tore" -> Vector3(3017, 2272, 58), + "wele" -> Vector3(436, 7040, 60), + "monolith" -> Vector3(4515, 4105, 38), + "peak" -> Vector3(3215, 5063, 579) + ) + zones("z4").gates += ( + "gate1" -> Vector3(4702, 6768, 30), + "gate2" -> Vector3(5515, 3368, 69), + "gate3" -> Vector3(1564, 3356, 46), + "gate4" -> Vector3(3889, 1118, 56), + "geowarp1" -> Vector3(4202, 4325, 68), + "geowarp2" -> Vector3(2384, 1925, 37) + ) + zones("z4").locations += ( + "akkan" -> Vector3(2746, 4260, 39), + "baal" -> Vector3(825, 5470, 72), + "dagon" -> Vector3(1739, 5681, 40), + "enkidu" -> Vector3(3217, 3574, 37), + "girru" -> Vector3(4475, 5853, 78), + "hanish" -> Vector3(3794, 5540, 89), + "irkall" -> Vector3(4742, 5270, 66), + "kusag" -> Vector3(6532, 4692, 46), + "lahar" -> Vector3(6965, 5306, 38), + "marduk" -> Vector3(3059, 2144, 70), + "neti" -> Vector3(3966, 2417, 80), + "zaqar" -> Vector3(4796, 2177, 75), + "monolith" -> Vector3(5165, 4083, 35), + "stonehenge" -> Vector3(4992, 3776, 56) + ) + zones("z5").gates += ( + "gate1" -> Vector3(3432, 6498, 73), + "gate2" -> Vector3(7196, 3917, 47), + "gate3" -> Vector3(1533, 3540, 56), + "gate4" -> Vector3(3197, 1390, 45), + "geowarp1" -> Vector3(4899, 5633, 38), + "geowarp2" -> Vector3(5326, 2558, 54) + ) + zones("z5").locations += ( + "anu" -> Vector3(3479, 2556, 56), + "bel" -> Vector3(3665, 4626, 58), + "caer" -> Vector3(4570, 2601, 56), + "dagd" -> Vector3(5825, 4449, 55), + "eadon" -> Vector3(2725, 2853, 53), + "gwydion" -> Vector3(5566, 3739, 61), + "lugh" -> Vector3(6083, 5069, 72), + "neit" -> Vector3(4345, 4319, 76), + "ogma" -> Vector3(3588, 3227, 114), + "pwyll" -> Vector3(4683, 4764, 104), + "monolith" -> Vector3(3251, 3245, 160), + "islands1" -> Vector3(6680, 6217, 125), + "islands2" -> Vector3(1059, 6213, 120) + ) + zones("z6").gates += ( + "gate1" -> Vector3(5040, 4327, 46), + "gate2" -> Vector3(2187, 5338, 30), + "gate3" -> Vector3(4960, 1922, 15), + "gate4" -> Vector3(2464, 3088, 189), + "geowarp1" -> Vector3(3221, 5328, 242), + "geowarp2" -> Vector3(2237, 1783, 238) + ) + zones("z6").locations += ( + "akna" -> Vector3(4509, 3732, 219), + "anguta" -> Vector3(3999, 4170, 266), + "igaluk" -> Vector3(3241, 5658, 235), + "keelut" -> Vector3(3630, 1904, 265), + "nerrivik" -> Vector3(3522, 3703, 322), + "pinga" -> Vector3(5938, 3545, 96), + "sedna" -> Vector3(3932, 5160, 232), + "tarqaq" -> Vector3(2980, 2155, 237), + "tootega" -> Vector3(5171, 3251, 217), + "monolith" -> Vector3(4011, 4851, 32), + "bridge" -> Vector3(3729, 4859, 234) + ) + zones("z7").gates += ( + "gate1" -> Vector3(1516, 6448, 61), + "gate2" -> Vector3(5249, 3819, 69), + "gate3" -> Vector3(2763, 2961, 86), + "gate4" -> Vector3(6224, 1152, 78), + "geowarp1" -> Vector3(6345, 4802, 90), + "geowarp2" -> Vector3(3800, 2197, 64) + ) + zones("z7").locations += ( + "andvari" -> Vector3(3233, 7207, 78), + "dagur" -> Vector3(4026, 6191, 60), + "eisa" -> Vector3(3456, 4513, 75), + "freyr" -> Vector3(2853, 3840, 56), + "gjallar" -> Vector3(1056, 2656, 74), + "helheim" -> Vector3(5542, 2532, 53), + "jarl" -> Vector3(1960, 5462, 68), + "kvasir" -> Vector3(4096, 1571, 69), + "mani" -> Vector3(5057, 4989, 58), + "nott" -> Vector3(6783, 4329, 46), + "ran" -> Vector3(2378, 1919, 85), + "vidar" -> Vector3(3772, 3024, 67), + "ymir" -> Vector3(1911, 4008, 69), + "monolith" -> Vector3(6390, 1622, 63) + ) + zones("z8").gates += ( + "gate1" -> Vector3(5437, 5272, 32), + "gate2" -> Vector3(3251, 5650, 60), + "gate3" -> Vector3(5112, 2616, 40), + "gate4" -> Vector3(2666, 1665, 45), + "geowarp1" -> Vector3(3979, 5370, 47), + "geowarp2" -> Vector3(6018, 3136, 35) + ) + zones("z8").locations += ( + "atar" -> Vector3(3609, 2730, 47), + "dahaka" -> Vector3(4633, 5379, 54), + "hvar" -> Vector3(3857, 4764, 49), + "izha" -> Vector3(5396, 3852, 51), + "jamshid" -> Vector3(2371, 3378, 52), + "mithra" -> Vector3(2480, 4456, 44), + "rashnu" -> Vector3(3098, 3961, 59), + "yazata" -> Vector3(4620, 3983, 62), + "zal" -> Vector3(3966, 2164, 61), + "arch1" -> Vector3(4152, 3285, 31), + "arch2" -> Vector3(4688, 5272, 68), + "pride" -> Vector3(2913, 4412, 63) + ) + zones("z9").gates += ( + "gate1" -> Vector3(1505, 6981, 65), + "gate2" -> Vector3(6835, 3517, 56), + "gate3" -> Vector3(1393, 1376, 53), + "geowarp1" -> Vector3(7081, 5552, 46), + "geowarp2" -> Vector3(3776, 1092, 49) + ) + zones("z9").locations += ( + "akua" -> Vector3(5258, 4041, 346), + "drakulu" -> Vector3(3806, 2647, 151), + "hiro" -> Vector3(4618, 5761, 190), + "iva" -> Vector3(6387, 5199, 55), + "karihi" -> Vector3(3879, 5574, 236), + "laka" -> Vector3(4720, 6718, 49), + "matagi" -> Vector3(5308, 5093, 239), + "ngaru" -> Vector3(4103, 4077, 205), + "oro" -> Vector3(4849, 4456, 208), + "pele" -> Vector3(4549, 3712, 208), + "rehua" -> Vector3(3843, 2195, 60), + "sina" -> Vector3(5919, 2177, 91), + "tara" -> Vector3(1082, 4225, 60), + "wakea" -> Vector3(1785, 5241, 63), + "monolith" -> Vector3(3246, 6507, 105) + ) + zones("z10").gates += ( + "gate1" -> Vector3(6140, 6599, 71), + "gate2" -> Vector3(4814, 4608, 59), + "gate3" -> Vector3(3152, 3480, 54), + "gate4" -> Vector3(1605, 1446, 40), + "geowarp1" -> Vector3(3612, 6918, 38), + "geowarp2" -> Vector3(3668, 3327, 55) + ) + zones("z10").locations += ( + "azeban" -> Vector3(6316, 5160, 62), + "cetan" -> Vector3(3587, 2522, 48), + "heyoka" -> Vector3(4395, 2327, 47), + "ikanam" -> Vector3(2740, 2412, 57), + "kyoi" -> Vector3(5491, 2284, 62), + "mekala" -> Vector3(6087, 2925, 59), + "onatha" -> Vector3(3397, 5799, 48), + "qumu" -> Vector3(3990, 5152, 46), + "sungrey" -> Vector3(4609, 5624, 72), + "tumas" -> Vector3(4687, 6392, 69), + "verica" -> Vector3(4973, 3459, 47), + "xelas" -> Vector3(6609, 4479, 56), + "monolith" -> Vector3(5651, 6024, 38) + ) + zones("home1").gates += ( + "gate1" -> Vector3(4158, 6344, 44), + "gate2" -> Vector3(2214, 5797, 48), + "gate3" -> Vector3(5032, 3241, 53) + ) + zones("home1").locations += "hart_c" -> Vector3(2352, 5523, 66) + zones("home2").gates += ( + "gate1" -> Vector3(5283, 4317, 44), + "gate2" -> Vector3(3139, 4809, 40), + "gate3" -> Vector3(3659, 2894, 26) + ) + zones("home2").locations += "hart_c" -> Vector3(3125, 2864, 35) + zones("home3").gates += ( + "gate1" -> Vector3(5657, 4681, 98), + "gate2" -> Vector3(2639, 5366, 57), + "gate3" -> Vector3(4079, 2467, 155) + ) + zones("home3").locations += "hart_c" -> Vector3(3675, 2727, 91) + zones("tzshtr").locations += "roof" -> Vector3(499, 1568, 25) + zones("tzcotr").locations += "spawn" -> Vector3(960, 1002, 32) + zones("tzdrtr").locations += ( + "start" -> Vector3(2457, 1864, 23), + "air_pad" -> Vector3(1700, 1900, 32) + ) + zones("tzshvs").locations += "roof" -> Vector3(499, 1568, 25) + zones("tzcovs").locations += "spawn" -> Vector3(960, 1002, 32) + zones("tzdrvs").locations += ( + "start" -> Vector3(2457, 1864, 23), + "air_pad" -> Vector3(1700, 1900, 32) + ) + zones("tzshnc").locations += "roof" -> Vector3(499, 1568, 25) + zones("tzconc").locations += "spawn" -> Vector3(960, 1002, 32) + zones("tzdrnc").locations += ( + "start" -> Vector3(2457, 1864, 23), + "air_pad" -> Vector3(1700, 1900, 32) + ) + zones("c1").gates += ( + "geowarp1" -> Vector3(998, 2038, 103), + "geowarp2" -> Vector3(231, 1026, 82), + "geowarp3" -> Vector3(2071, 1405, 102), + "geowarp4" -> Vector3(1051, 370, 103) + ) + zones("c2").gates += ( + "geowarp1" -> Vector3(999, 2386, 243), + "geowarp2" -> Vector3(283, 1249, 172), + "geowarp3" -> Vector3(1887, 1307, 192), + "geowarp4" -> Vector3(1039, 155, 143) + ) + zones("c3").gates += ( + "geowarp1" -> Vector3(1095, 1725, 25), + "geowarp2" -> Vector3(226, 832, 42), + "geowarp3" -> Vector3(1832, 1026, 43), + "geowarp4" -> Vector3(981, 320, 46) + ) + zones("c4").gates += ( + "geowarp1" -> Vector3(902, 1811, 93), + "geowarp2" -> Vector3(185, 922, 113), + "geowarp3" -> Vector3(1696, 1188, 92), + "geowarp4" -> Vector3(887, 227, 115) + ) + zones("c5").gates += ( + "geowarp1" -> Vector3(1195, 1752, 244), + "geowarp2" -> Vector3(290, 1104, 235), + "geowarp3" -> Vector3(1803, 899, 243), + "geowarp4" -> Vector3(1042, 225, 246) + ) + zones("c6").gates += ( + "geowarp1" -> Vector3(1067, 2044, 95), + "geowarp2" -> Vector3(290, 693, 73), + "geowarp3" -> Vector3(1922, 928, 33), + "geowarp4" -> Vector3(1174, 249, 114) + ) + zones("i3").gates += ( + "gate1" -> Vector3(1219, 2580, 30), + "gate2" -> Vector3(2889, 2919, 33), + "gate3" -> Vector3(2886, 1235, 32) + ) + zones("i3").locations += ( + "dahaka" -> Vector3(1421, 2216, 30), + "jamshid" -> Vector3(2500, 2543, 30), + "izha" -> Vector3(2569, 1544, 30), + "oasis" -> Vector3(2084, 1935, 40) + ) + zones("i2").gates += ( + "gate1" -> Vector3(1243, 1393, 12), + "gate2" -> Vector3(2510, 2544, 12), + "gate3" -> Vector3(2634, 1477, 12) + ) + zones("i2").locations += ( + "rashnu" -> Vector3(1709, 1802, 91), + "sraosha" -> Vector3(2729, 2349, 91), + "zal" -> Vector3(1888, 2728, 91), + "center" -> Vector3(2082, 2192, 160), + "vpad" -> Vector3(1770, 2686, 92) + ) + zones("i1").gates += ( + "gate1" -> Vector3(1225, 2036, 67), + "gate2" -> Vector3(2548, 2801, 65), + "gate3" -> Vector3(2481, 1194, 89) + ) + zones("i1").locations += ( + "hvar" -> Vector3(1559, 1268, 88), + "mithra" -> Vector3(2855, 2850, 89), + "yazata" -> Vector3(1254, 2583, 88), + "south_of_volcano" -> Vector3(2068, 1686, 88) + ) + zones("i4").gates += ( + "gate1" -> Vector3(2359, 2717, 36), + "gate2" -> Vector3(2732, 1355, 36), + "geowarp" -> Vector3(1424, 1640, 45) + ) + zones("i4").locations += "atar" -> Vector3(1915, 1936, 43) + } +} diff --git a/pslogin/src/main/scala/csr/Traveler.scala b/pslogin/src/main/scala/csr/Traveler.scala new file mode 100644 index 00000000..a60d4188 --- /dev/null +++ b/pslogin/src/main/scala/csr/Traveler.scala @@ -0,0 +1,37 @@ +package csr + +// Copyright (c) 2017 PSForever +import akka.actor.ActorRef +import net.psforever.packet.PlanetSidePacketContainer + +/* +The following is STILL for development and fun. +*/ +/** + * The traveler is synonymous with the player. + * The primary purpose of the object is to keep track of but not expose the player's session so that packets may be relayed back to him. + * csr.Traveler also keeps track of which zone the player currently occupies. + * @param session the player's session + */ +class Traveler(private val session : ActorRef, var zone : String) { + /** + * `sendToSelf` is a call that permits the session to gain access to its internal `rightRef` so that it can dispatch a packet. + * @param msg the byte-code translation of a packet + */ + def sendToSelf(msg : PlanetSidePacketContainer) : Unit = { + // this.session.sendResponse(msg) + } + + def !(msg : Any) : Unit = { + session ! msg + } +} + +object Traveler { + /** + * An abbreviated constructor for creating `csr.Traveler`s without invocation of `new`. + * @param session the player's session + * @return a traveler object for this player + */ + def apply(session : ActorRef, zoneId : String) : Traveler = new Traveler(session, zoneId) +} From 70b5d79c6dcb3fcbe785e3ef7c3b43b7be1b5bdc Mon Sep 17 00:00:00 2001 From: FateJH Date: Thu, 17 May 2018 22:00:19 -0400 Subject: [PATCH 13/13] better safe than sorry --- pslogin/src/main/scala/WorldSessionActor.scala | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index b66a2f38..c3c73fdb 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -1767,11 +1767,13 @@ class WorldSessionActor extends Actor with MDCContextAware { CSRZone.read(traveler, msg) match { case (true, zone, pos) => if(player.isAlive) { - player.Die //die to suspend position client-driven change updates + player.Die //die to suspend client-driven position change updates + PlayerActionsToCancel() player.Position = pos traveler.zone = zone continent.Population ! Zone.Population.Release(avatar) continent.Population ! Zone.Population.Leave(avatar) + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player.GUID, player.GUID)) taskResolver ! TaskBeforeZoneChange(GUIDTask.UnregisterAvatar(player)(continent.GUID), zone) } @@ -1781,6 +1783,7 @@ class WorldSessionActor extends Actor with MDCContextAware { CSRWarp.read(traveler, msg) match { case (true, pos) => if(player.isAlive) { + PlayerActionsToCancel() sendResponse(PlayerStateShiftMessage(ShiftState(0, pos, player.Orientation.z, None))) player.Position = pos }