From 2a4fe4865e7090741d8cf33a30dbbfa49e0877b3 Mon Sep 17 00:00:00 2001 From: FateJH Date: Tue, 15 May 2018 08:05:33 -0400 Subject: [PATCH] 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