From 8fbbd31967ddf70b6768b2942aada77630f6783a Mon Sep 17 00:00:00 2001 From: FateJH Date: Mon, 11 Dec 2017 18:17:05 -0500 Subject: [PATCH] modified how subscriptions can subscribe and unsubscribe from specific channels (works now); vehicles now come with default loadouts at time of spawn; MoveItem is now a much more generic process that handles all Container objects; alternate fire modes now have correct ammunition values, but still do not change ammunition type properly; terminals are now distinctively split a bit more; LootItemMessage packet --- .codecov.yml | 2 +- .../net/psforever/objects/EquipmentSlot.scala | 1 - .../psforever/objects/ExoSuitDefinition.scala | 2 +- .../psforever/objects/GlobalDefinitions.scala | 34 +- .../scala/net/psforever/objects/Loadout.scala | 110 ++-- .../objects/OffhandEquipmentSlot.scala | 9 +- .../scala/net/psforever/objects/Player.scala | 41 +- .../scala/net/psforever/objects/Vehicle.scala | 43 +- .../objects/{ => avatar}/Avatars.scala | 2 +- .../objects/definition/AvatarDefinition.scala | 2 +- .../converter/AvatarConverter.scala | 12 +- .../converter/VehicleConverter.scala | 26 +- .../objects/inventory/Container.scala | 170 ++++++ ...eControl.scala => MountableBehavior.scala} | 18 +- .../ImplantTerminalMechControl.scala | 9 +- .../AirVehicleTerminalDefinition.scala | 15 +- .../terminals/BFRTerminalDefinition.scala | 16 +- .../DropshipVehicleTerminalDefinition.scala | 16 +- .../EquipmentTerminalDefinition.scala | 320 ++++++++++++ .../GroundVehicleTerminalDefinition.scala | 15 +- .../ImplantTerminalInterfaceDefinition.scala | 5 +- .../terminals/OrderTerminalDefinition.scala | 48 +- .../serverobject/terminals/Terminal.scala | 5 +- .../terminals/TerminalDefinition.scala | 359 +------------ .../VehicleTerminalCombinedDefinition.scala | 16 +- .../terminals/VehicleTerminalDefinition.scala | 487 ++++++++++++++++++ .../objects/vehicles/VehicleControl.scala | 9 +- .../psforever/packet/GamePacketOpcode.scala | 2 +- .../packet/game/LootItemMessage.scala | 27 + .../test/scala/game/LootItemMessageTest.scala | 27 + .../test/scala/objects/ContainerTest.scala | 71 +++ .../scala/objects/EquipmentSlotTest.scala | 148 ++++++ .../src/test/scala/objects/LoadoutTest.scala | 196 +++++++ .../test/scala/objects/MountableTest.scala | 10 +- .../terminal/AirVehicleTerminalTest.scala | 11 +- .../DropshipVehicleTerminalTest.scala | 15 +- .../terminal/GroundVehicleTerminalTest.scala | 9 +- .../terminal/TerminalControlTest.scala | 9 +- .../VehicleTerminalCombinedTest.scala | 18 +- .../src/main/scala/WorldSessionActor.scala | 345 +++++++++---- pslogin/src/main/scala/services/Service.scala | 2 +- .../scala/services/avatar/AvatarService.scala | 12 +- .../scala/services/local/LocalService.scala | 10 +- .../services/vehicle/VehicleAction.scala | 5 +- .../services/vehicle/VehicleResponse.scala | 4 +- .../services/vehicle/VehicleService.scala | 25 +- .../vehicle/support/DeconstructionActor.scala | 2 +- 47 files changed, 2053 insertions(+), 687 deletions(-) rename common/src/main/scala/net/psforever/objects/{ => avatar}/Avatars.scala (94%) create mode 100644 common/src/main/scala/net/psforever/objects/inventory/Container.scala rename common/src/main/scala/net/psforever/objects/mount/{MountableControl.scala => MountableBehavior.scala} (65%) create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala create mode 100644 common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala create mode 100644 common/src/main/scala/net/psforever/packet/game/LootItemMessage.scala create mode 100644 common/src/test/scala/game/LootItemMessageTest.scala create mode 100644 common/src/test/scala/objects/ContainerTest.scala create mode 100644 common/src/test/scala/objects/EquipmentSlotTest.scala create mode 100644 common/src/test/scala/objects/LoadoutTest.scala diff --git a/.codecov.yml b/.codecov.yml index 67d23fb7a..698d16cc3 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -2,6 +2,7 @@ comment: off ignore: + - "common/src/main/scala/net/psforever/objects/avatar/Avatars.scala" - "common/src/main/scala/net/psforever/objects/equipment/Ammo.scala" - "common/src/main/scala/net/psforever/objects/equipment/CItem.scala" - "common/src/main/scala/net/psforever/objects/equipment/EquipmentSize.scala" @@ -11,7 +12,6 @@ ignore: - "common/src/main/scala/net/psforever/objects/vehicles/AccessPermissionGroup.scala" - "common/src/main/scala/net/psforever/objects/vehicles/SeatArmoRestriction.scala" - "common/src/main/scala/net/psforever/objects/vehicles/VehicleLockState.scala" - - "common/src/main/scala/net/psforever/objects/Avatars.scala" - "common/src/main/scala/net/psforever/packet/crypto" - "common/src/main/scala/net/psforever/packet/game/objectcreate/DrawnSlot.scala" - "common/src/main/scala/net/psforever/packet/game/objectcreate/DriveState.scala" diff --git a/common/src/main/scala/net/psforever/objects/EquipmentSlot.scala b/common/src/main/scala/net/psforever/objects/EquipmentSlot.scala index 816c1ec16..dd2d4ab6b 100644 --- a/common/src/main/scala/net/psforever/objects/EquipmentSlot.scala +++ b/common/src/main/scala/net/psforever/objects/EquipmentSlot.scala @@ -10,7 +10,6 @@ import net.psforever.objects.equipment.{Equipment, EquipmentSize} class EquipmentSlot { private var size : EquipmentSize.Value = EquipmentSize.Blocked private var tool : Option[Equipment] = None - //TODO eventually move this object from storing the item directly to just storing its GUID? def Size : EquipmentSize.Value = size diff --git a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala index 4eea81599..4d90ef8fb 100644 --- a/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/ExoSuitDefinition.scala @@ -104,7 +104,7 @@ object ExoSuitDefinition { MAX.MaxArmor = 650 MAX.InventoryScale = InventoryTile.Tile1612 MAX.InventoryOffset = 6 - MAX.Holster(0, EquipmentSize.Max) + MAX.Holster(2, EquipmentSize.Max) MAX.Holster(4, EquipmentSize.Melee) def apply(suitType : ExoSuitType.Value) : ExoSuitDefinition = { diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 6b9e2d49f..cbb0655ed 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -910,7 +910,7 @@ object GlobalDefinitions { bullet_20mm.Capacity = 200 bullet_20mm.Tile = InventoryTile.Tile44 - bullet_12mm.Capacity = 200 + bullet_12mm.Capacity = 300 bullet_12mm.Tile = InventoryTile.Tile44 wasp_rocket_ammo.Capacity = 6 @@ -1397,6 +1397,24 @@ object GlobalDefinitions { flamethrower.FireModes(1).Magazine = 100 flamethrower.FireModes(1).Chamber = 50 flamethrower.Tile = InventoryTile.Tile63 +//TODO + trhev_dualcycler.Size = EquipmentSize.Max + trhev_dualcycler.AmmoTypes += bullet_9mm + trhev_dualcycler.FireModes += new FireModeDefinition + trhev_dualcycler.FireModes.head.AmmoTypeIndices += 0 + trhev_dualcycler.FireModes.head.AmmoSlotIndex = 0 +//TODO + trhev_pounder.Size = EquipmentSize.Max + trhev_pounder.AmmoTypes += bullet_9mm + trhev_pounder.FireModes += new FireModeDefinition + trhev_pounder.FireModes.head.AmmoTypeIndices += 0 + trhev_pounder.FireModes.head.AmmoSlotIndex = 0 +//TODO + trhev_burster.Size = EquipmentSize.Max + trhev_burster.AmmoTypes += bullet_9mm + trhev_burster.FireModes += new FireModeDefinition + trhev_burster.FireModes.head.AmmoTypeIndices += 0 + trhev_burster.FireModes.head.AmmoSlotIndex = 0 medicalapplicator.Size = EquipmentSize.Pistol medicalapplicator.AmmoTypes += health_canister @@ -1511,7 +1529,7 @@ object GlobalDefinitions { skyguard_weapon_system.FireModes += new FireModeDefinition skyguard_weapon_system.FireModes(1).AmmoTypeIndices += 1 skyguard_weapon_system.FireModes(1).AmmoSlotIndex = 1 - skyguard_weapon_system.FireModes(1).Magazine = 1 //TODO check + skyguard_weapon_system.FireModes(1).Magazine = 250 grenade_launcher_marauder.Size = EquipmentSize.VehicleWeapon grenade_launcher_marauder.AmmoTypes += heavy_grenade_mortar @@ -1692,7 +1710,7 @@ object GlobalDefinitions { lightning_weapon_system.FireModes += new FireModeDefinition lightning_weapon_system.FireModes(1).AmmoTypeIndices += 1 lightning_weapon_system.FireModes(1).AmmoSlotIndex = 1 - lightning_weapon_system.FireModes(1).Magazine = 1 //TODO check + lightning_weapon_system.FireModes(1).Magazine = 150 prowler_weapon_systemA.Size = EquipmentSize.VehicleWeapon prowler_weapon_systemA.AmmoTypes += bullet_105mm @@ -1718,7 +1736,7 @@ object GlobalDefinitions { vanguard_weapon_system.FireModes += new FireModeDefinition vanguard_weapon_system.FireModes(1).AmmoTypeIndices += 1 vanguard_weapon_system.FireModes(1).AmmoSlotIndex = 1 - vanguard_weapon_system.FireModes(1).Magazine = 1 //TODO check + vanguard_weapon_system.FireModes(1).Magazine = 200 particle_beam_magrider.Size = EquipmentSize.VehicleWeapon particle_beam_magrider.AmmoTypes += pulse_battery @@ -1754,11 +1772,11 @@ object GlobalDefinitions { lightgunship_weapon_system.FireModes += new FireModeDefinition lightgunship_weapon_system.FireModes.head.AmmoTypeIndices += 0 lightgunship_weapon_system.FireModes.head.AmmoSlotIndex = 0 - lightgunship_weapon_system.FireModes.head.Magazine = 150 + lightgunship_weapon_system.FireModes.head.Magazine = 245 lightgunship_weapon_system.FireModes += new FireModeDefinition lightgunship_weapon_system.FireModes(1).AmmoTypeIndices += 1 lightgunship_weapon_system.FireModes(1).AmmoSlotIndex = 1 - lightgunship_weapon_system.FireModes(1).Magazine = 1 //TODO check + lightgunship_weapon_system.FireModes(1).Magazine = 16 wasp_weapon_system.Size = EquipmentSize.VehicleWeapon wasp_weapon_system.AmmoTypes += wasp_gun_ammo @@ -1770,7 +1788,7 @@ object GlobalDefinitions { wasp_weapon_system.FireModes += new FireModeDefinition wasp_weapon_system.FireModes(1).AmmoTypeIndices += 1 wasp_weapon_system.FireModes(1).AmmoSlotIndex = 1 - wasp_weapon_system.FireModes(1).Magazine = 1 //TODO check + wasp_weapon_system.FireModes(1).Magazine = 2 liberator_weapon_system.Size = EquipmentSize.VehicleWeapon liberator_weapon_system.AmmoTypes += bullet_35mm @@ -1893,7 +1911,7 @@ object GlobalDefinitions { two_man_assault_buggy.Weapons += 2 -> chaingun_p two_man_assault_buggy.MountPoints += 1 -> 0 two_man_assault_buggy.MountPoints += 2 -> 1 - two_man_assault_buggy.TrunkSize = InventoryTile.Tile1111 + two_man_assault_buggy.TrunkSize = InventoryTile.Tile1511 two_man_assault_buggy.TrunkOffset = 30 skyguard.Seats += 0 -> new SeatDefinition() diff --git a/common/src/main/scala/net/psforever/objects/Loadout.scala b/common/src/main/scala/net/psforever/objects/Loadout.scala index 355a7d171..340cb466e 100644 --- a/common/src/main/scala/net/psforever/objects/Loadout.scala +++ b/common/src/main/scala/net/psforever/objects/Loadout.scala @@ -8,6 +8,14 @@ import net.psforever.types.ExoSuitType import scala.annotation.tailrec +//trait Loadout { +// def Label : String +// def VisibleSlots : List[Loadout.SimplifiedEntry] +// def Inventory : List[Loadout.SimplifiedEntry] +// def ExoSuit : ExoSuitType.Value +// def Subtype : Int +//} + /** * From a `Player` their current exo-suit and their `Equipment`, retain a set of instructions to reconstruct this arrangement.
*
@@ -31,36 +39,17 @@ import scala.annotation.tailrec * The fifth tab on an `order_terminal` window is for "Favorite" blueprints for `Loadout` entries. * The ten-long list is initialized with `FavoritesMessage` packets. * Specific entries are loaded or removed using `FavoritesRequest` packets. - * @param player the player * @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 */ -class Loadout(player : Player, private val label : String) { - /** the exo-suit */ - private val exosuit : ExoSuitType.Value = player.ExoSuit - /** the MAX specialization, to differentiate the three types of MAXes who all use the same exo-suit name */ - private val subtype = - if(exosuit == ExoSuitType.MAX) { - player.Holsters().head.Equipment.get.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 - } - } - else { - 0 - } - /** simplified representation of the holster `Equipment` */ - private val holsters : List[Loadout.SimplifiedEntry] = - Loadout.packageSimplifications(player.Holsters()) - /** simplified representation of the inventory `Equipment` */ - private val inventory : List[Loadout.SimplifiedEntry] = - Loadout.packageSimplifications(player.Inventory.Items.values.toList) - +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) { /** * The label by which this `Loadout` is called. * @return the label @@ -89,7 +78,7 @@ class Loadout(player : Player, private val label : String) { * The `Equipment` in the `Player`'s holster slots when this `Loadout` is created. * @return a `List` of the holster item blueprints */ - def Holsters : List[Loadout.SimplifiedEntry] = holsters + def VisibleSlots : List[Loadout.SimplifiedEntry] = visible_slots /** * The `Equipment` in the `Player`'s inventory region when this `Loadout` is created. @@ -99,6 +88,28 @@ class Loadout(player : Player, 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( + label, + packageSimplifications(player.Holsters()), + packageSimplifications(player.Inventory.Items.values.toList), + player.ExoSuit, + determineSubtype(player) + ) + } + + def Create(vehicle : Vehicle, label : String) : Loadout = { + Loadout( + label, + packageSimplifications(vehicle.Weapons.map({ case ((index, weapon)) => InventoryItem(weapon.Equipment.get, index) }).toList), + packageSimplifications(vehicle.Trunk.Items.values.toList) + ) + } + /** * A basic `Trait` connecting all of the `Equipment` blueprints. */ @@ -124,13 +135,13 @@ object Loadout { * @param tdef the `ToolDefinition` that describes this future object * @param ammo the blueprints to construct the correct number of ammunition slots in the `Tool` */ - final case class ShorthandTool(tdef : ToolDefinition, ammo : List[ShorthandAmmotSlot]) extends Simplification + final case class ShorthandTool(tdef : ToolDefinition, ammo : List[ShorthandAmmoSlot]) extends Simplification /** * The simplified form of a `Tool` `FireMode` * @param ammoIndex the index that points to the type of ammunition this slot currently uses * @param ammo a `ShorthandAmmoBox` object to load into that slot */ - final case class ShorthandAmmotSlot(ammoIndex : Int, ammo : ShorthandAmmoBox) + final case class ShorthandAmmoSlot(ammoIndex : Int, ammo : ShorthandAmmoBox) /** * The simplified form of a `ConstructionItem`. * @param cdef the `ConstructionItemDefinition` that describes this future object @@ -147,6 +158,29 @@ object Loadout { */ final case class ShorthandKit(kdef : KitDefinition) extends Simplification + private def determineSubtype(player : Player) : Int = { + if(player.ExoSuit == ExoSuitType.MAX) { + player.Slot(2).Equipment 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 + } + } + /** * Overloaded entry point for constructing simplified blueprints from holster slot equipment. * @param equipment the holster slots @@ -165,6 +199,7 @@ object Loadout { equipment.map(entry => { SimplifiedEntry(buildSimplification(entry.obj), entry.start) }) } + /** * Traverse a `Player`'s holsters and transform occupied slots into simplified blueprints for the contents of that slot. * The holsters are fixed positions and can be unoccupied. @@ -193,29 +228,30 @@ object Loadout { } /** - * Ammunition slots are internal connection points where `AmmoBox` units and their characteristics represent a `Tool`'s magazine. - * Their simplification process has a layer of complexity that ensures that the content of the slot matches the type of content that should be in the slot. - * If it does not, it extracts information about the slot from the `EquipmentDefinition` and sets the blueprints to that. + * Ammunition slots are internal connection points where `AmmoBox` units represent the characteristics of a magazine. + * Their simplification process has a layer of complexity that ensures that the content of the slot + * matches the type of content that should be in the slot. + * If it does not, it extracts information about the slot from the `EquipmentDefinition` and sets the blueprints. * @param iter an `Iterator` * @param list an updating `List` of simplified ammo slot blueprints; * empty, by default * @return a `List` of simplified ammo slot blueprints * @see `Tool.FireModeSlot` */ - @tailrec private def recursiveFireModeSimplications(iter : Iterator[Tool.FireModeSlot], list : List[ShorthandAmmotSlot] = Nil) : List[ShorthandAmmotSlot] = { + @tailrec private def recursiveFireModeSimplications(iter : Iterator[Tool.FireModeSlot], list : List[ShorthandAmmoSlot] = Nil) : List[ShorthandAmmoSlot] = { if(!iter.hasNext) { list } else { val entry = iter.next val fmodeSimp = if(entry.Box.AmmoType == entry.AmmoType) { - ShorthandAmmotSlot( + ShorthandAmmoSlot( entry.AmmoTypeIndex, ShorthandAmmoBox(entry.Box.Definition, entry.Box.Capacity) ) } else { - ShorthandAmmotSlot( + ShorthandAmmoSlot( entry.AmmoTypeIndex, ShorthandAmmoBox(entry.Tool.AmmoTypes(entry.Definition.AmmoTypeIndices.head), 1) ) diff --git a/common/src/main/scala/net/psforever/objects/OffhandEquipmentSlot.scala b/common/src/main/scala/net/psforever/objects/OffhandEquipmentSlot.scala index 6ac7ebb0a..0d9a5151f 100644 --- a/common/src/main/scala/net/psforever/objects/OffhandEquipmentSlot.scala +++ b/common/src/main/scala/net/psforever/objects/OffhandEquipmentSlot.scala @@ -17,4 +17,11 @@ class OffhandEquipmentSlot(size : EquipmentSize.Value) extends EquipmentSlot { * @return the capacity for this slot */ override def Size_=(assignSize : EquipmentSize.Value) : EquipmentSize.Value = Size -} \ No newline at end of file +} + +object OffhandEquipmentSlot { + /** + * An `EquipmentSlot` that can not be manipulated because its size is `Blocked` permanently. + */ + final val BlockedSlot = new OffhandEquipmentSlot(EquipmentSize.Blocked) +} diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index b5ea8d885..369a6b661 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -3,19 +3,20 @@ package net.psforever.objects import net.psforever.objects.definition.{AvatarDefinition, ImplantDefinition} import net.psforever.objects.equipment.{Equipment, EquipmentSize} -import net.psforever.objects.inventory.{GridInventory, InventoryItem} +import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.packet.game.PlanetSideGUID import net.psforever.types._ import scala.annotation.tailrec import scala.collection.mutable +import scala.util.{Success, Try} class Player(private val name : String, private val faction : PlanetSideEmpire.Value, private val sex : CharacterGender.Value, private val head : Int, private val voice : Int - ) extends PlanetSideGameObject { + ) extends PlanetSideGameObject with Container { private var alive : Boolean = false private var backpack : Boolean = false private var health : Int = 0 @@ -65,8 +66,8 @@ class Player(private val name : String, private var vehicleSeated : Option[PlanetSideGUID] = None private var vehicleOwned : Option[PlanetSideGUID] = None - private var continent : String = "home2" //actually, the zoneId - private var playerDef : AvatarDefinition = Player.definition + private var continent : String = "home2" //the zone id + private val playerDef : AvatarDefinition = Player.definition //TODO could be a var //SouNourS things /** Last medkituse. */ @@ -80,7 +81,7 @@ class Player(private val name : String, var PlanetsideAttribute : Array[Long] = Array.ofDim(120) Player.SuitSetup(this, ExoSuit) - fifthSlot.Equipment = new LockerContainer() //the fifth slot is the player's "locker" + fifthSlot.Equipment = new LockerContainer //the fifth slot is the player's "locker" def Name : String = name @@ -161,7 +162,9 @@ class Player(private val name : String, def MaxArmor : Int = ExoSuitDefinition.Select(exosuit).MaxArmor - def Slot(slot : Int) : EquipmentSlot = { + def VisibleSlots : Set[Int] = if(exosuit == ExoSuitType.MAX) { Set(2) } else { Set(0,1,2,3,4) } + + override def Slot(slot : Int) : EquipmentSlot = { if(inventory.Offset <= slot && slot <= inventory.LastIndex) { inventory.Slot(slot) } @@ -177,7 +180,7 @@ class Player(private val name : String, freeHand } else { - new OffhandEquipmentSlot(EquipmentSize.Blocked) + OffhandEquipmentSlot.BlockedSlot } } @@ -238,7 +241,7 @@ class Player(private val name : String, } def SaveLoadout(label : String, line : Int) : Unit = { - loadouts(line) = Some(new Loadout(this, label)) + loadouts(line) = Some(Loadout.Create(this, label)) } def LoadLoadout(line : Int) : Option[Loadout] = loadouts(line) @@ -298,6 +301,28 @@ class Player(private val name : String, } } + override def Collisions(dest : Int, width : Int, height : Int) : Try[List[InventoryItem]] = { + if(-1 < dest && dest < 5) { + holsters(dest).Equipment match { + case Some(item) => + Success(List(InventoryItem(item, dest))) + case None => + Success(List()) + } + } + else if(dest == Player.FreeHandSlot) { + freeHand.Equipment match { + case Some(item) => + Success(List(InventoryItem(item, dest))) + case None => + Success(List()) + } + } + else { + super.Collisions(dest, width, height) + } + } + def DrawnSlot : Int = drawnSlot def DrawnSlot_=(slot : Int = Player.HandsDownSlot) : Int = { diff --git a/common/src/main/scala/net/psforever/objects/Vehicle.scala b/common/src/main/scala/net/psforever/objects/Vehicle.scala index fd3c0931b..98e0f4851 100644 --- a/common/src/main/scala/net/psforever/objects/Vehicle.scala +++ b/common/src/main/scala/net/psforever/objects/Vehicle.scala @@ -3,7 +3,7 @@ package net.psforever.objects import net.psforever.objects.definition.VehicleDefinition import net.psforever.objects.equipment.{Equipment, EquipmentSize} -import net.psforever.objects.inventory.{GridInventory, InventoryTile} +import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem, InventoryTile} import net.psforever.objects.mount.Mountable import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.vehicles.{AccessPermissionGroup, Seat, Utility, VehicleLockState} @@ -27,7 +27,7 @@ import scala.collection.mutable * stores and unloads pertinent information about the `Vehicle`'s configuration; * used in the initialization process (`loadVehicleDefinition`) */ -class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject with Mountable { +class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServerObject with Mountable with Container { private var faction : PlanetSideEmpire.Value = PlanetSideEmpire.TR private var owner : Option[PlanetSideGUID] = None private var health : Int = 1 @@ -337,6 +337,45 @@ class Vehicle(private val vehicleDef : VehicleDefinition) extends PlanetSideServ } } + def Inventory : GridInventory = trunk + + def Find(obj : Equipment) : Option[Int] = Find(obj.GUID) + + def Find(guid : PlanetSideGUID) : Option[Int] = { + findInInventory(Inventory.Items.values.iterator, guid) match { + case Some(index) => + Some(index) + case None => + None + } + } + + @tailrec private def findInInventory(iter : Iterator[InventoryItem], guid : PlanetSideGUID) : Option[Int] = { + if(!iter.hasNext) { + None + } + else { + val item = iter.next + if(item.obj.GUID == guid) { + Some(item.start) + } + else { + findInInventory(iter, guid) + } + } + } + + def VisibleSlots : Set[Int] = weapons.keySet + + override def Slot(slot : Int) : EquipmentSlot = { + if(Inventory.Offset <= slot && slot <= Inventory.LastIndex) { + Inventory.Slot(slot) + } + else { + OffhandEquipmentSlot.BlockedSlot + } + } + /** * A reference to the `Vehicle` `Trunk` space. * @return this `Vehicle` `Trunk` diff --git a/common/src/main/scala/net/psforever/objects/Avatars.scala b/common/src/main/scala/net/psforever/objects/avatar/Avatars.scala similarity index 94% rename from common/src/main/scala/net/psforever/objects/Avatars.scala rename to common/src/main/scala/net/psforever/objects/avatar/Avatars.scala index cb9d49d09..e266ed853 100644 --- a/common/src/main/scala/net/psforever/objects/Avatars.scala +++ b/common/src/main/scala/net/psforever/objects/avatar/Avatars.scala @@ -1,5 +1,5 @@ // Copyright (c) 2017 PSForever -package net.psforever.objects +package net.psforever.objects.avatar /** * An `Enumeration` of all the avatar types in the game, paired with their object id as the `Value`. diff --git a/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala b/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala index 0214b3087..4f265ccf1 100644 --- a/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/definition/AvatarDefinition.scala @@ -1,8 +1,8 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.definition +import net.psforever.objects.avatar.Avatars import net.psforever.objects.definition.converter.AvatarConverter -import net.psforever.objects.Avatars /** * The definition for game objects that look like other people, and also for players. diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala index 8d0dd2944..99fa4d2cc 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/AvatarConverter.scala @@ -1,7 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.definition.converter -import net.psforever.objects.{EquipmentSlot, GlobalDefinitions, ImplantSlot, Player} +import net.psforever.objects.{EquipmentSlot, ImplantSlot, Player} import net.psforever.objects.equipment.Equipment import net.psforever.packet.game.objectcreate.{BasicCharacterData, CharacterAppearanceData, CharacterData, Cosmetics, DetailedCharacterData, DrawnSlot, ImplantEffects, ImplantEntry, InternalSlot, InventoryData, PlacementData, RibbonBars, UniformStyle} import net.psforever.types.{GrenadeState, ImplantType} @@ -164,14 +164,14 @@ class AvatarConverter extends ObjectCreateConverter[Player]() { else { val slot = iter.next if(slot.Active) { - slot.Installed match { - case Some(GlobalDefinitions.advanced_regen) => + slot.Implant match { + case ImplantType.AdvancedRegen => Some(ImplantEffects.RegenEffects) - case Some(GlobalDefinitions.darklight_vision) => + case ImplantType.DarklightVision => Some(ImplantEffects.DarklightEffects) - case Some(GlobalDefinitions.personal_shield) => + case ImplantType.PersonalShield => Some(ImplantEffects.PersonalShieldEffects) - case Some(GlobalDefinitions.surge) => + case ImplantType.Surge => Some(ImplantEffects.SurgeEffects) case _ => recursiveMakeImplantEffects(iter) diff --git a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala index d6e6398bb..14f8b7660 100644 --- a/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala +++ b/common/src/main/scala/net/psforever/objects/definition/converter/VehicleConverter.scala @@ -29,7 +29,7 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { false, obj.Cloaked, SpecificFormatData(obj), - Some(InventoryData((MakeMountings(obj) ++ MakeTrunk(obj)).sortBy(_.parentSlot))) + Some(InventoryData(MakeMountings(obj).sortBy(_.parentSlot))) )(SpecificFormatModifier) ) } @@ -47,18 +47,18 @@ class VehicleConverter extends ObjectCreateConverter[Vehicle]() { }).toList } - /** - * na - * @param obj the `Player` game object - * @return a list of all items that were in the inventory in decoded packet form - */ - private def MakeTrunk(obj : Vehicle) : List[InternalSlot] = { - obj.Trunk.Items.map({ - case(_, item) => - val equip : Equipment = item.obj - InventoryItemData(equip.Definition.ObjectId, equip.GUID, item.start, equip.Definition.Packet.ConstructorData(equip).get) - }).toList - } +// /** +// * na +// * @param obj the `Player` game object +// * @return a list of all items that were in the inventory in decoded packet form +// */ +// private def MakeTrunk(obj : Vehicle) : List[InternalSlot] = { +// obj.Trunk.Items.map({ +// case(_, item) => +// val equip : Equipment = item.obj +// InventoryItemData(equip.Definition.ObjectId, equip.GUID, item.start, equip.Definition.Packet.ConstructorData(equip).get) +// }).toList +// } // @tailrec private def recursiveMakeSeats(iter : Iterator[(Int, Seat)], list : List[InventoryItemData.InventoryItem] = Nil) : List[InventoryItemData.InventoryItem] = { // if(!iter.hasNext) { diff --git a/common/src/main/scala/net/psforever/objects/inventory/Container.scala b/common/src/main/scala/net/psforever/objects/inventory/Container.scala new file mode 100644 index 000000000..4520e35cb --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/inventory/Container.scala @@ -0,0 +1,170 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.inventory + +import net.psforever.objects.{EquipmentSlot, OffhandEquipmentSlot} +import net.psforever.packet.game.PlanetSideGUID + +import scala.util.Try + +/** + * This object is capable of storing ("stowing") `Equipment` within itself.
+ *
+ * The following objects are considered item containers: + * players (their own inventory), + * players (their corpse's loot), + * vehicles (their trunk), and + * lockers (contents of the player's fifth slot). + */ +trait Container { + /** + * A(n imperfect) reference to a generalized pool of the contained objects. + * Having access to all of the available positions is not required. + * The entries in this reference should definitely include all unseen positions. + * @see `VisibleSlots` + */ + def Inventory : GridInventory + + /** + * Given globally unique identifier, if the object using it is stowed, attempt to locate its slot. + * All positions, `VisibleSlot` and `Inventory`, and wherever else, should be searchable. + * @param guid the GUID of the `Equipment` + * @return the index of the `EquipmentSlot`, or `None` + */ + def Find(guid : PlanetSideGUID) : Option[Int] + + /** + * A(n imperfect) reference to a generalized pool of the contained objects.
+ *
+ * Having access to all of the available positions is not required. + * Only the positions that can be actively viewed by other clients are listed. + * @see `Inventory` + * @return all of the affected slot indices + */ + def VisibleSlots : Set[Int] + + /** + * Access to all stowable positions on this object by index.
+ *
+ * All positions, `VisibleSlot` and `Inventory`, and wherever else, should be reachable. + * Regardless of the internal storage medium, the format of return is expected to be the same structure of object + * as the most basic storage component for `Equipment`, namely, `EquipmentSlot` objects. + * By default, it is expected to return an `EquipmentSlot` that can not be manipulated because it is `Blocked`. + * @see `OffhandEquipmentSlot` + * @param slotNum an index + * @return the searchable position identified by that index + */ + def Slot(slotNum : Int) : EquipmentSlot = OffhandEquipmentSlot.BlockedSlot + + /** + * Given a region of "searchable unit positions" considered as stowable, + * determine if any previously stowed items are contained within that region.
+ *
+ * Default usage, and recommended the continued inclusion of that use, + * is defined in terms of `Equipment` being stowed in a `GridInventory`. + * Where the `Equipment` object is defined by the dimensions `width` and `height`, + * starting a search at `index` will search all positions within a grid-like range of numbers. + * Under certain searching conditions, this range may be meaningless, + * such as is the case when searching individual positions that are normal `EquipmentSlot` objects. + * Regardless, the value collected indicates the potential of multiple objects being discovered and + * maintains a reference to the object itself and the slot position where the object is located. + * (As any object can be discovered within the range, that is important.) + * @see `GridInventory.CheckCollisionsVar` + * @param index the position to start searching + * @param width the width of the searchable space + * @param height the height of the serachable space + * @return a list of objects that have been encountered within the searchable space + */ + def Collisions(index : Int, width : Int, height : Int) : Try[List[InventoryItem]] = + Inventory.CheckCollisionsVar(index, width, height) +} + +//object Container { +// type ValidContainer = PlanetSideServerObject with Container +// +// final case class GetMoveItem(where_src : Int, other : ValidContainer, where_other : Int, other_item : Option[Equipment]) +// +// final case class GiveMoveItem(cont1 : ValidContainer, where_cont1 : Int, item : Option[Equipment], cont2 : ValidContainer, where_cont2 : Int, other_item : Option[Equipment]) +// +// +// final case class TakeMoveItem(source_index : Int, destination : ValidContainer, destination_index : Int) +// +// final case class TakeMoveItem2(item_guid : PlanetSideGUID, destination : ValidContainer, destination_index : Int) +// +// final case class GivingMoveItem(item : Equipment, source : ValidContainer, source_index : Int, destination : ValidContainer, destination_index : Int) +// +// final case class PutMoveItem(item : Equipment, target_index : Int, source : ValidContainer, source_index : Int) +// +// final case class DropMoveItem(item : Equipment, source : ValidContainer, source_index : Int) +// +// final case class ItemMoved(item : Equipment, location : ValidContainer, location_index : Int) +// +// final case class NoMoveItem(source : ValidContainer, source_index : Int) +//} +// +//trait ContainerBehavior { +// this : Actor => +// +// def ContainableObject : Container.ValidContainer +// +// val containerBehavior : Receive = { +// case Container.GetMoveItem(where_src, destination, where_dest, other_item) => +// val slot : EquipmentSlot = ContainableObject.Slot(where_src) +// val equipment = slot.Equipment +// slot.Equipment = None +// sender ! Container.GiveMoveItem(ContainableObject, where_src, equipment, destination, where_dest, other_item) +// +// case Container.TakeMoveItem(source_index, destination, destination_index) => +// val slot : EquipmentSlot = ContainableObject.Slot(source_index) +// val equipment : Option[Equipment] = slot.Equipment +// slot.Equipment = None +// sender ! (equipment match { +// case Some(item) => +// Container.GivingMoveItem(item, ContainableObject, source_index, destination, destination_index) +// case None => +// Container.NoMoveItem(ContainableObject, source_index) +// }) +// +// case Container.TakeMoveItem2(item_guid, destination, destination_index) => +// ContainableObject.Find(item_guid) match { +// case Some(source_index) => +// val slot : EquipmentSlot = ContainableObject.Slot(source_index) +// val equipment : Option[Equipment] = slot.Equipment +// slot.Equipment = None +// sender ! (equipment match { +// case Some(item) => +// Container.GivingMoveItem(item, ContainableObject, source_index, destination, destination_index) +// case None => ; +// }) +// +// case None => +// sender ! Container.NoMoveItem(ContainableObject, 65535) +// } +// +// case Container.PutMoveItem(item, target_index, source, source_index) => +// val slot : EquipmentSlot = ContainableObject.Slot(target_index) +// val equipment : Option[Equipment] = slot.Equipment +// if( { +// val tile = item.Definition.Tile +// ContainableObject.Collisions(target_index, tile.Width, tile.Height) match { +// case Success(Nil) => //no item swap +// true +// case Success(_ :: Nil) => //one item to swap +// true +// case Success(_) | scala.util.Failure(_) => +// false //abort when too many items at destination or other failure case +// } +// }) { +// slot.Equipment = None +// slot.Equipment = item +// equipment match { +// case Some(swapItem) => +// sender ! Container.GivingMoveItem(swapItem, ContainableObject, target_index, source, source_index) +// case None => ; +// } +// sender ! Container.ItemMoved(item, ContainableObject, target_index) +// } +// else { +// sender ! Container.DropMoveItem(item, source, source_index) +// } +// } +//} diff --git a/common/src/main/scala/net/psforever/objects/mount/MountableControl.scala b/common/src/main/scala/net/psforever/objects/mount/MountableBehavior.scala similarity index 65% rename from common/src/main/scala/net/psforever/objects/mount/MountableControl.scala rename to common/src/main/scala/net/psforever/objects/mount/MountableBehavior.scala index 2084ad2c2..6ccd0dfbd 100644 --- a/common/src/main/scala/net/psforever/objects/mount/MountableControl.scala +++ b/common/src/main/scala/net/psforever/objects/mount/MountableBehavior.scala @@ -5,23 +5,27 @@ import akka.actor.Actor /** * The logic governing `Mountable` objects that use the `TryMount` message. + * This is a mix-in trait for combining the `Receive` logic. * @see `Seat` * @see `Mountable` - * @param obj the `Mountable` object governed beholden to this logic */ -abstract class MountableControl(obj : Mountable) extends Actor { - def receive : Receive = { +trait MountableBehavior { + this : Actor => + + def MountableObject : Mountable + + val mountableBehavior : Receive = { case Mountable.TryMount(user, seat_num) => - obj.Seat(seat_num) match { + MountableObject.Seat(seat_num) match { case Some(seat) => if((seat.Occupant = user).contains(user)) { - sender ! Mountable.MountMessages(user, Mountable.CanMount(obj, seat_num)) + sender ! Mountable.MountMessages(user, Mountable.CanMount(MountableObject, seat_num)) } else { - sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num)) + sender ! Mountable.MountMessages(user, Mountable.CanNotMount(MountableObject, seat_num)) } case None => - sender ! Mountable.MountMessages(user, Mountable.CanNotMount(obj, seat_num)) + sender ! Mountable.MountMessages(user, Mountable.CanNotMount(MountableObject, seat_num)) } } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala index f418a27e7..e47a675bb 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/implantmech/ImplantTerminalMechControl.scala @@ -1,14 +1,17 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.implantmech -import net.psforever.objects.mount.MountableControl +import akka.actor.Actor +import net.psforever.objects.mount.MountableBehavior /** * An `Actor` that handles messages being dispatched to a specific `ImplantTerminalMech`. * @param mech the "mech" object being governed */ -class ImplantTerminalMechControl(mech : ImplantTerminalMech) extends MountableControl(mech) { - override def receive : Receive = super[MountableControl].receive.orElse { +class ImplantTerminalMechControl(mech : ImplantTerminalMech) extends Actor with MountableBehavior { + override def MountableObject = mech + + def receive : Receive = mountableBehavior.orElse { case _ => ; } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/AirVehicleTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/AirVehicleTerminalDefinition.scala index fd62de60c..f10f646d3 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/AirVehicleTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/AirVehicleTerminalDefinition.scala @@ -1,18 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.terminals -import net.psforever.objects.Player -import net.psforever.packet.game.ItemTransactionMessage - -class AirVehicleTerminalDefinition extends TerminalDefinition(43) { +class AirVehicleTerminalDefinition extends VehicleTerminalDefinition(43) { + vehicles = flight1Vehicles Name = "air_vehicle_terminal" - - def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { - flight1Vehicles.get(msg.item_name) match { - case Some(vehicle) => - Terminal.BuyVehicle(vehicle(), Nil) - case None => - Terminal.NoDeal() - } - } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/BFRTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/BFRTerminalDefinition.scala index 263067491..61b651b21 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/BFRTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/BFRTerminalDefinition.scala @@ -1,19 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.terminals -import net.psforever.objects.Player -import net.psforever.packet.game.ItemTransactionMessage - -class BFRTerminalDefinition extends TerminalDefinition(143) { +class BFRTerminalDefinition extends VehicleTerminalDefinition(143) { + vehicles = bfrVehicles Name = "bfr_terminal" - - def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { - bfrVehicles.get(msg.item_name) match { - case Some(vehicle) => - //Terminal.BuyVehicle(vehicle, Nil) - Terminal.NoDeal() - case None => - Terminal.NoDeal() - } - } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/DropshipVehicleTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/DropshipVehicleTerminalDefinition.scala index 7176f9785..02b560278 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/DropshipVehicleTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/DropshipVehicleTerminalDefinition.scala @@ -1,19 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.terminals -import net.psforever.objects.Player -import net.psforever.packet.game.ItemTransactionMessage - -class DropshipVehicleTerminalDefinition extends TerminalDefinition(263) { - private val flightVehicles = flight1Vehicles ++ flight2Vehicles +class DropshipVehicleTerminalDefinition extends VehicleTerminalDefinition(263) { + vehicles = flight1Vehicles ++ flight2Vehicles Name = "dropship_vehicle_terminal" - - def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { - flightVehicles.get(msg.item_name) match { - case Some(vehicle) => - Terminal.BuyVehicle(vehicle(), Nil) - case None => - Terminal.NoDeal() - } - } } 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 new file mode 100644 index 000000000..ce4084ade --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/EquipmentTerminalDefinition.scala @@ -0,0 +1,320 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +import net.psforever.objects._ +import net.psforever.objects.definition._ +import net.psforever.objects.equipment.Equipment +import net.psforever.types.ExoSuitType + +abstract class EquipmentTerminalDefinition(objId : Int) extends TerminalDefinition(objId) { + Name = "equipment_terminal" +} + +object EquipmentTerminalDefinition { + private[this] val log = org.log4s.getLogger("TerminalDefinition") + + /** + * A `Map` of information for changing exo-suits. + * key - an identification string sent by the client + * value - a `Tuple` containing exo-suit specifications + */ + val suits : Map[String, (ExoSuitType.Value, Int)] = Map( + "standard_issue_armor" -> (ExoSuitType.Standard, 0), + "lite_armor" -> (ExoSuitType.Agile, 0), + "med_armor" -> (ExoSuitType.Reinforced, 0) + //TODO max and infiltration suit + ) + + import net.psforever.objects.GlobalDefinitions._ + /** + * A `Map` of operations for producing the `AmmoBox` `Equipment` for infantry-held weaponry. + * key - an identification string sent by the client + * value - a curried function that builds the object + */ + val infantryAmmunition : Map[String, () => Equipment] = Map( + "9mmbullet" -> MakeAmmoBox(bullet_9mm), + "9mmbullet_AP" -> MakeAmmoBox(bullet_9mm_AP), + "shotgun_shell" -> MakeAmmoBox(shotgun_shell), + "shotgun_shell_AP" -> MakeAmmoBox(shotgun_shell_AP), + "energy_cell" -> MakeAmmoBox(energy_cell), + "anniversary_ammo" -> MakeAmmoBox(anniversary_ammo), //10mm multi-phase + "rocket" -> MakeAmmoBox(rocket), + "frag_cartridge" -> MakeAmmoBox(frag_cartridge), + "jammer_cartridge" -> MakeAmmoBox(jammer_cartridge), + "plasma_cartridge" -> MakeAmmoBox(plasma_cartridge), + "ancient_ammo_combo" -> MakeAmmoBox(ancient_ammo_combo), + "maelstrom_ammo" -> MakeAmmoBox(maelstrom_ammo), + "striker_missile_ammo" -> MakeAmmoBox(striker_missile_ammo), + "hunter_seeker_missile" -> MakeAmmoBox(hunter_seeker_missile), //phoenix missile + "lancer_cartridge" -> MakeAmmoBox(lancer_cartridge), + "bolt" -> MakeAmmoBox(bolt), + "oicw_ammo" -> MakeAmmoBox(oicw_ammo), //scorpion missile + "flamethrower_ammo" -> MakeAmmoBox(flamethrower_ammo) + ) + /** + * A `Map` of operations for producing the `AmmoBox` `Equipment` for infantry-held utilities. + * key - an identification string sent by the client + * value - a curried function that builds the object + */ + val supportAmmunition : Map[String, () => Equipment] = Map( + "health_canister" -> MakeAmmoBox(health_canister), + "armor_canister" -> MakeAmmoBox(armor_canister), + "upgrade_canister" -> MakeAmmoBox(upgrade_canister) + ) + /** + * A `Map` of operations for producing the `AmmoBox` `Equipment` for vehicle-mounted weaponry. + * key - an identification string sent by the client + * value - a curried function that builds the object + */ + val vehicleAmmunition : Map[String, () => Equipment] = Map( + "35mmbullet" -> MakeAmmoBox(bullet_35mm), + "hellfire_ammo" -> MakeAmmoBox(hellfire_ammo), + "liberator_bomb" -> MakeAmmoBox(liberator_bomb), + "25mmbullet" -> MakeAmmoBox(bullet_25mm), + "75mmbullet" -> MakeAmmoBox(bullet_75mm), + "heavy_grenade_mortar" -> MakeAmmoBox(heavy_grenade_mortar), + "reaver_rocket" -> MakeAmmoBox(reaver_rocket), + "20mmbullet" -> MakeAmmoBox(bullet_20mm), + "12mmbullet" -> MakeAmmoBox(bullet_12mm), + "wasp_rocket_ammo" -> MakeAmmoBox(wasp_rocket_ammo), + "wasp_gun_ammo" -> MakeAmmoBox(wasp_gun_ammo), + "aphelion_laser_ammo" -> MakeAmmoBox(aphelion_laser_ammo), + "aphelion_immolation_cannon_ammo" -> MakeAmmoBox(aphelion_immolation_cannon_ammo), + "aphelion_plasma_rocket_ammo" -> MakeAmmoBox(aphelion_plasma_rocket_ammo), + "aphelion_ppa_ammo" -> MakeAmmoBox(aphelion_ppa_ammo), + "aphelion_starfire_ammo" -> MakeAmmoBox(aphelion_starfire_ammo), + "skyguard_flak_cannon_ammo" -> MakeAmmoBox(skyguard_flak_cannon_ammo), + "flux_cannon_thresher_battery" -> MakeAmmoBox(flux_cannon_thresher_battery), + "fluxpod_ammo" -> MakeAmmoBox(fluxpod_ammo), + "pulse_battery" -> MakeAmmoBox(pulse_battery), + "heavy_rail_beam_battery" -> MakeAmmoBox(heavy_rail_beam_battery), + "15mmbullet" -> MakeAmmoBox(bullet_15mm), + "colossus_100mm_cannon_ammo" -> MakeAmmoBox(colossus_100mm_cannon_ammo), + "colossus_burster_ammo" -> MakeAmmoBox(colossus_burster_ammo), + "colossus_cluster_bomb_ammo" -> MakeAmmoBox(colossus_cluster_bomb_ammo), + "colossus_chaingun_ammo" -> MakeAmmoBox(colossus_chaingun_ammo), + "colossus_tank_cannon_ammo" -> MakeAmmoBox(colossus_tank_cannon_ammo), + "105mmbullet" -> MakeAmmoBox(bullet_105mm), + "gauss_cannon_ammo" -> MakeAmmoBox(gauss_cannon_ammo), + "peregrine_dual_machine_gun_ammo" -> MakeAmmoBox(peregrine_dual_machine_gun_ammo), + "peregrine_mechhammer_ammo" -> MakeAmmoBox(peregrine_mechhammer_ammo), + "peregrine_particle_cannon_ammo" -> MakeAmmoBox(peregrine_particle_cannon_ammo), + "peregrine_rocket_pod_ammo" -> MakeAmmoBox(peregrine_rocket_pod_ammo), + "peregrine_sparrow_ammo" -> MakeAmmoBox(peregrine_sparrow_ammo), + "150mmbullet" -> MakeAmmoBox(bullet_150mm) + ) + /** + * A `Map` of operations for producing the `Tool` `Equipment` for infantry weapons. + * key - an identification string sent by the client + * value - a curried function that builds the object + */ + val infantryWeapons : Map[String, () => Equipment] = Map( + "ilc9" -> MakeTool(ilc9), + "repeater" -> MakeTool(repeater), + "isp" -> MakeTool(isp), //amp + "beamer" -> MakeTool(beamer), + "suppressor" -> MakeTool(suppressor), + "anniversary_guna" -> MakeTool(anniversary_guna), //tr stinger + "anniversary_gun" -> MakeTool(anniversary_gun), //nc spear + "anniversary_gunb" -> MakeTool(anniversary_gunb), //vs eraser + "cycler" -> MakeTool(cycler), + "gauss" -> MakeTool(gauss), + "pulsar" -> MakeTool(pulsar), + "punisher" -> MakeTool(punisher), + "flechette" -> MakeTool(flechette), + "spiker" -> MakeTool(spiker), + "frag_grenade" -> MakeTool(frag_grenade), + "jammer_grenade" -> MakeTool(jammer_grenade), + "plasma_grenade" -> MakeTool(plasma_grenade), + "katana" -> MakeTool(katana), + "chainblade" -> MakeTool(chainblade), + "magcutter" -> MakeTool(magcutter), + "forceblade" -> MakeTool(forceblade), + "mini_chaingun" -> MakeTool(mini_chaingun), + "r_shotgun" -> MakeTool(r_shotgun), //jackhammer + "lasher" -> MakeTool(lasher), + "maelstrom" -> MakeTool(maelstrom), + "striker" -> MakeTool(striker), + "hunterseeker" -> MakeTool(hunterseeker), //phoenix + "lancer" -> MakeTool(lancer), + "phoenix" -> MakeTool(phoenix), //decimator + "rocklet" -> MakeTool(rocklet), + "thumper" -> MakeTool(thumper), + "radiator" -> MakeTool(radiator), + "heavy_sniper" -> MakeTool(heavy_sniper), //hsr + "bolt_driver" -> MakeTool(bolt_driver), + "oicw" -> MakeTool(oicw), //scorpion + "flamethrower" -> MakeTool(flamethrower) + ) + /** + * A `Map` of operations for producing the `Tool` `Equipment` for utilities. + * key - an identification string sent by the client + * value - a curried function that builds the object + */ + val supportWeapons : Map[String, () => Equipment] = Map( + "medkit" -> MakeKit(medkit), + "super_medkit" -> MakeKit(super_medkit), + "super_armorkit" -> MakeKit(super_armorkit), + "super_staminakit" -> MakeKit(super_staminakit), + "medicalapplicator" -> MakeTool(medicalapplicator), + "bank" -> MakeTool(bank, armor_canister), + "nano_dispenser" -> MakeTool(nano_dispenser), + //TODO "ace" -> MakeConstructionItem(ace), + //TODO "advanced_ace" -> MakeConstructionItem(advanced_ace), + "remote_electronics_kit" -> MakeSimpleItem(remote_electronics_kit), + "trek" -> MakeTool(trek), + "command_detonater" -> MakeSimpleItem(command_detonater), + "flail_targeting_laser" -> MakeSimpleItem(flail_targeting_laser) + ) + + /** + * Create a new `Tool` from provided `EquipmentDefinition` objects. + * @param tdef the `ToolDefinition` object + * @return a partial function that, when called, creates the piece of `Equipment` + */ + private def MakeTool(tdef : ToolDefinition)() : Tool = MakeTool(tdef, Nil) + + /** + * Create a new `Tool` from provided `EquipmentDefinition` objects. + * @param tdef the `ToolDefinition` object + * @param adef an `AmmoBoxDefinition` object + * @return a partial function that, when called, creates the piece of `Equipment` + */ + private def MakeTool(tdef : ToolDefinition, adef : AmmoBoxDefinition)() : Tool = MakeTool(tdef, List(adef)) + + /** + * Create a new `Tool` from provided `EquipmentDefinition` objects. + * Only use this function to create default `Tools` with the default parameters. + * For example, loadouts can retain `Tool` information that utilizes alternate, valid ammunition types; + * and, this method function will not construct a complete object if provided that information. + * @param tdef the `ToolDefinition` object + * @param adefs a `List` of `AmmoBoxDefinition` objects + * @return a curried function that, when called, creates the piece of `Equipment` + * @see `GlobalDefinitions` + * @see `OrderTerminalDefinition.BuildSimplifiedPattern` + */ + private def MakeTool(tdef : ToolDefinition, adefs : List[AmmoBoxDefinition])() : Tool = { + val obj = Tool(tdef) + adefs match { + case _ :: _ => + LoadAmmunitionIntoWeapon(obj, adefs) + case Nil => ; //as-is + } + obj + } + + /** + * Given a weapon, and custom ammunition profiles, attempt to load those boxes of ammunition into the weapon.
+ *
+ * This is a customization function that should normally go unused. + * All of the information necessary to generate a `Tool` from a `Terminal` request should be available on the `ToolDefinition` object. + * The ammunition information, regardless of "customization," must satisfy the type limits of the original definition. + * As thus, to introduce very strange ammunition to a give `Tool`, + * either the definition must be modified or a different definition must be used. + * The custom ammunition is organized into order of ammunition slots based on the `FireModeDefinition` objects. + * That is: + * the first custom element is processed by the first ammunition slot; + * the second custom element is processed by the second ammunition slot; and, so forth. + * @param weapon the `Tool` object + * @param adefs a sequential `List` of ammunition to be loaded into weapon + * @see `AmmoBoxDefinition` + * @see `FireModeDefinition` + */ + private def LoadAmmunitionIntoWeapon(weapon : Tool, adefs : List[AmmoBoxDefinition]) : Unit = { + val definition = weapon.Definition + (0 until math.min(weapon.MaxAmmoSlot, adefs.length)).foreach(index => { + val ammoSlot = weapon.AmmoSlots(index) + adefs.lift(index) match { + case Some(aType) => + ammoSlot.AllAmmoTypes.indexOf(aType.AmmoType) match { + case -1 => + log.warn(s"terminal plans do not match definition: can not feed ${aType.AmmoType} ammunition into Tool (${definition.ObjectId} @ ammo $index)") + case n => + ammoSlot.AmmoTypeIndex = n + ammoSlot.Box = MakeAmmoBox(aType, Some(definition.FireModes(index).Magazine)) //make new internal magazine, full + } + case None => ; + } + }) + } + + /** + * Create a new `AmmoBox` from provided `EquipmentDefinition` objects. + * @param adef the `AmmoBoxDefinition` object + * @param capacity optional number of rounds in this `AmmoBox`, deviating from the `EquipmentDefinition`; + * necessary for constructing the magazine (`AmmoSlot`) of `Tool`s + * @return a curried function that, when called, creates the piece of `Equipment` + * @see `GlobalDefinitions` + */ + private def MakeAmmoBox(adef : AmmoBoxDefinition, capacity : Option[Int] = None)() : AmmoBox = { + capacity match { + case Some(cap) => + AmmoBox(adef, cap) + case None => + AmmoBox(adef) + } + } + + /** + * Create a new `Kit` from provided `EquipmentDefinition` objects. + * @param kdef the `KitDefinition` object + * @return a curried function that, when called, creates the piece of `Equipment` + * @see `GlobalDefinitions` + */ + private def MakeKit(kdef : KitDefinition)() : Kit = Kit(kdef) + + /** + * Create a new `SimpleItem` from provided `EquipmentDefinition` objects. + * @param sdef the `SimpleItemDefinition` object + * @return a curried function that, when called, creates the piece of `Equipment` + * @see `GlobalDefinitions` + */ + private def MakeSimpleItem(sdef : SimpleItemDefinition)() : SimpleItem = SimpleItem(sdef) + + /** + * Create a new `ConstructionItem` from provided `EquipmentDefinition` objects. + * @param cdef the `ConstructionItemDefinition` object + * @return a curried function that, when called, creates the piece of `Equipment` + * @see `GlobalDefinitions` + */ + private def MakeConstructionItem(cdef : ConstructionItemDefinition)() : ConstructionItem = ConstructionItem(cdef) + + /** + * Accept a simplified blueprint for some piece of `Equipment` and create an actual piece of `Equipment` based on it. + * Used specifically for the reconstruction of `Equipment` via an `Loadout`. + * @param entry the simplified blueprint + * @return some `Equipment` object + * @see `TerminalDefinition.MakeTool`
+ * `TerminalDefinition.MakeAmmoBox`
+ * `TerminalDefinition.MakeSimpleItem`
+ * `TerminalDefinition.MakeConstructionItem`
+ * `TerminalDefinition.MakeKit` + */ + def BuildSimplifiedPattern(entry : Loadout.Simplification) : Equipment = { + import net.psforever.objects.Loadout._ + entry match { + case obj : ShorthandTool => + val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.adef }) + val tool = Tool(obj.tdef) + //makes Tools where an ammo slot may have one of its alternate ammo types + (0 until tool.MaxAmmoSlot).foreach(index => { + val slot = tool.AmmoSlots(index) + slot.AmmoTypeIndex += obj.ammo(index).ammoIndex + slot.Box = MakeAmmoBox(ammo(index), Some(obj.ammo(index).ammo.capacity)) + }) + tool + + case obj : ShorthandAmmoBox => + MakeAmmoBox(obj.adef, Some(obj.capacity)) + + case obj : ShorthandConstructionItem => + MakeConstructionItem(obj.cdef) + + case obj : ShorthandSimpleItem => + MakeSimpleItem(obj.sdef) + + case obj : ShorthandKit => + MakeKit(obj.kdef) + } + } +} diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/GroundVehicleTerminalDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/GroundVehicleTerminalDefinition.scala index 1087c575a..aa61fba1a 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/GroundVehicleTerminalDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/GroundVehicleTerminalDefinition.scala @@ -1,18 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.terminals -import net.psforever.objects.Player -import net.psforever.packet.game.ItemTransactionMessage - -class GroundVehicleTerminalDefinition extends TerminalDefinition(386) { +class GroundVehicleTerminalDefinition extends VehicleTerminalDefinition(386) { + vehicles = groundVehicles Name = "ground_vehicle_terminal" - - def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { - groundVehicles.get(msg.item_name) match { - case Some(vehicle) => - Terminal.BuyVehicle(vehicle(), Nil) - case None => - Terminal.NoDeal() - } - } } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ImplantTerminalInterfaceDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ImplantTerminalInterfaceDefinition.scala index 82298076b..1549d76f3 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/ImplantTerminalInterfaceDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/ImplantTerminalInterfaceDefinition.scala @@ -15,6 +15,9 @@ import net.psforever.packet.game.objectcreate.ObjectClass * attached as a child of the visible implant terminal component - the "implant_terminal_mech." */ class ImplantTerminalInterfaceDefinition extends TerminalDefinition(ObjectClass.implant_terminal_interface) { + Packet = new ImplantTerminalInterfaceConverter + Name = "implante_terminal_interface" + private val implants : Map[String, ImplantDefinition] = Map ( "advanced_regen" -> GlobalDefinitions.advanced_regen, "targeting" -> GlobalDefinitions.targeting, @@ -27,8 +30,6 @@ class ImplantTerminalInterfaceDefinition extends TerminalDefinition(ObjectClass. "silent_run" -> GlobalDefinitions.silent_run, "surge" -> GlobalDefinitions.surge ) - Packet = new ImplantTerminalInterfaceConverter - Name = "implante_terminal_interface" def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { implants.get(msg.item_name) match { 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 2735b1bb3..cdaee2e19 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 @@ -1,12 +1,11 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.terminals -import net.psforever.objects.Loadout.Simplification -import net.psforever.objects.{Player, Tool} -import net.psforever.objects.definition._ +import net.psforever.objects.Player import net.psforever.objects.equipment.Equipment import net.psforever.objects.inventory.InventoryItem import net.psforever.packet.game.ItemTransactionMessage +import net.psforever.objects.serverobject.terminals.EquipmentTerminalDefinition._ import scala.annotation.switch @@ -15,7 +14,7 @@ import scala.annotation.switch * `Buy` and `Sell` `Equipment` items and `AmmoBox` items. * Change `ExoSuitType` and retrieve `Loadout` entries. */ -class OrderTerminalDefinition extends TerminalDefinition(612) { +class OrderTerminalDefinition extends EquipmentTerminalDefinition(612) { Name = "order_terminal" /** @@ -91,7 +90,7 @@ class OrderTerminalDefinition extends TerminalDefinition(612) { if(msg.item_page == 4) { //Favorites tab player.LoadLoadout(msg.unk1) match { case Some(loadout) => - val holsters = loadout.Holsters.map(entry => { InventoryItem(BuildSimplifiedPattern(entry.item), entry.index) }) + 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 => @@ -102,43 +101,4 @@ class OrderTerminalDefinition extends TerminalDefinition(612) { Terminal.NoDeal() } } - - /** - * Accept a simplified blueprint for some piece of `Equipment` and create an actual piece of `Equipment` based on it. - * Used specifically for the reconstruction of `Equipment` via an `Loadout`. - * @param entry the simplified blueprint - * @return some `Equipment` object - * @see `TerminalDefinition.MakeTool`
- * `TerminalDefinition.MakeAmmoBox`
- * `TerminalDefinition.MakeSimpleItem`
- * `TerminalDefinition.MakeConstructionItem`
- * `TerminalDefinition.MakeKit` - */ - private def BuildSimplifiedPattern(entry : Simplification) : Equipment = { - import net.psforever.objects.Loadout._ - entry match { - case obj : ShorthandTool => - val ammo : List[AmmoBoxDefinition] = obj.ammo.map(fmode => { fmode.ammo.adef }) - val tool = Tool(obj.tdef) - //makes Tools where an ammo slot may have one of its alternate ammo types - (0 until tool.MaxAmmoSlot).foreach(index => { - val slot = tool.AmmoSlots(index) - slot.AmmoTypeIndex += obj.ammo(index).ammoIndex - slot.Box = MakeAmmoBox(ammo(index), Some(obj.ammo(index).ammo.capacity)) - }) - tool - - case obj : ShorthandAmmoBox => - MakeAmmoBox(obj.adef, Some(obj.capacity)) - - case obj : ShorthandConstructionItem => - MakeConstructionItem(obj.cdef) - - case obj : ShorthandSimpleItem => - MakeSimpleItem(obj.sdef) - - case obj : ShorthandKit => - MakeKit(obj.kdef) - } - } } 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 8a03fb597..e3cbf252c 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 @@ -168,9 +168,10 @@ object Terminal { /** * Provide a vehicle that was constructed for the player. * @param vehicle the vehicle - * @param loadout the vehicle's trunk contents + * @param weapons the vehicle's mounted armament + * @param inventory the vehicle's trunk contents */ - final case class BuyVehicle(vehicle : Vehicle, loadout: List[Any]) extends Exchange + final case class BuyVehicle(vehicle : Vehicle, weapons : List[InventoryItem], inventory : List[InventoryItem]) extends Exchange /** * Recover a former exo-suit and `Equipment` configuration that the `Player` possessed. 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 aa98b0f74..027678485 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 @@ -1,18 +1,14 @@ // Copyright (c) 2017 PSForever 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.Player import net.psforever.packet.game.ItemTransactionMessage -import net.psforever.types.ExoSuitType /** * The basic definition for any `Terminal`. * @param objectId the object's identifier number */ -abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objectId) { - private[this] val log = org.log4s.getLogger("TerminalDefinition") +abstract class TerminalDefinition(objectId : Int) extends net.psforever.objects.definition.ObjectDefinition(objectId) { Name = "terminal" /** @@ -23,359 +19,10 @@ abstract class TerminalDefinition(objectId : Int) extends ObjectDefinition(objec /** * The unimplemented functionality for this `Terminal`'s `TransactionType.Sell` activity. */ - def Sell(player: Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() + def Sell(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() /** * The unimplemented functionality for this `Terminal`'s `TransactionType.InfantryLoadout` activity. */ def Loadout(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = Terminal.NoDeal() - - /** - * A `Map` of information for changing exo-suits. - * key - an identification string sent by the client - * value - a `Tuple` containing exo-suit specifications - */ - protected val suits : Map[String, (ExoSuitType.Value, Int)] = Map( - "standard_issue_armor" -> (ExoSuitType.Standard, 0), - "lite_armor" -> (ExoSuitType.Agile, 0), - "med_armor" -> (ExoSuitType.Reinforced, 0) - //TODO max and infiltration suit - ) - - import net.psforever.objects.GlobalDefinitions._ - /** - * A `Map` of operations for producing the `AmmoBox` `Equipment` for infantry-held weaponry. - * key - an identification string sent by the client - * value - a curried function that builds the object - */ - protected val infantryAmmunition : Map[String, ()=>Equipment] = Map( - "9mmbullet" -> MakeAmmoBox(bullet_9mm), - "9mmbullet_AP" -> MakeAmmoBox(bullet_9mm_AP), - "shotgun_shell" -> MakeAmmoBox(shotgun_shell), - "shotgun_shell_AP" -> MakeAmmoBox(shotgun_shell_AP), - "energy_cell" -> MakeAmmoBox(energy_cell), - "anniversary_ammo" -> MakeAmmoBox(anniversary_ammo), //10mm multi-phase - "rocket" -> MakeAmmoBox(rocket), - "frag_cartridge" -> MakeAmmoBox(frag_cartridge), - "jammer_cartridge" -> MakeAmmoBox(jammer_cartridge), - "plasma_cartridge" -> MakeAmmoBox(plasma_cartridge), - "ancient_ammo_combo" -> MakeAmmoBox(ancient_ammo_combo), - "maelstrom_ammo" -> MakeAmmoBox(maelstrom_ammo), - "striker_missile_ammo" -> MakeAmmoBox(striker_missile_ammo), - "hunter_seeker_missile" -> MakeAmmoBox(hunter_seeker_missile), //phoenix missile - "lancer_cartridge" -> MakeAmmoBox(lancer_cartridge), - "bolt" -> MakeAmmoBox(bolt), - "oicw_ammo" -> MakeAmmoBox(oicw_ammo), //scorpion missile - "flamethrower_ammo" -> MakeAmmoBox(flamethrower_ammo) - ) - - /** - * A `Map` of operations for producing the `AmmoBox` `Equipment` for infantry-held utilities. - * key - an identification string sent by the client - * value - a curried function that builds the object - */ - protected val supportAmmunition : Map[String, ()=>Equipment] = Map( - "health_canister" -> MakeAmmoBox(health_canister), - "armor_canister" -> MakeAmmoBox(armor_canister), - "upgrade_canister" -> MakeAmmoBox(upgrade_canister) - ) - - /** - * A `Map` of operations for producing the `AmmoBox` `Equipment` for vehicle-mounted weaponry. - * key - an identification string sent by the client - * value - a curried function that builds the object - */ - protected val vehicleAmmunition : Map[String, ()=>Equipment] = Map( - "35mmbullet" -> MakeAmmoBox(bullet_35mm), - "hellfire_ammo" -> MakeAmmoBox(hellfire_ammo), - "liberator_bomb" -> MakeAmmoBox(liberator_bomb), - "25mmbullet" -> MakeAmmoBox(bullet_25mm), - "75mmbullet" -> MakeAmmoBox(bullet_75mm), - "heavy_grenade_mortar" -> MakeAmmoBox(heavy_grenade_mortar), - "reaver_rocket" -> MakeAmmoBox(reaver_rocket), - "20mmbullet" -> MakeAmmoBox(bullet_20mm), - "12mmbullet" -> MakeAmmoBox(bullet_12mm), - "wasp_rocket_ammo" -> MakeAmmoBox(wasp_rocket_ammo), - "wasp_gun_ammo" -> MakeAmmoBox(wasp_gun_ammo), - "aphelion_laser_ammo" -> MakeAmmoBox(aphelion_laser_ammo), - "aphelion_immolation_cannon_ammo" -> MakeAmmoBox(aphelion_immolation_cannon_ammo), - "aphelion_plasma_rocket_ammo" -> MakeAmmoBox(aphelion_plasma_rocket_ammo), - "aphelion_ppa_ammo" -> MakeAmmoBox(aphelion_ppa_ammo), - "aphelion_starfire_ammo" -> MakeAmmoBox(aphelion_starfire_ammo), - "skyguard_flak_cannon_ammo" -> MakeAmmoBox(skyguard_flak_cannon_ammo), - "flux_cannon_thresher_battery" -> MakeAmmoBox(flux_cannon_thresher_battery), - "fluxpod_ammo" -> MakeAmmoBox(fluxpod_ammo), - "pulse_battery" -> MakeAmmoBox(pulse_battery), - "heavy_rail_beam_battery" -> MakeAmmoBox(heavy_rail_beam_battery), - "15mmbullet" -> MakeAmmoBox(bullet_15mm), - "colossus_100mm_cannon_ammo" -> MakeAmmoBox(colossus_100mm_cannon_ammo), - "colossus_burster_ammo" -> MakeAmmoBox(colossus_burster_ammo), - "colossus_cluster_bomb_ammo" -> MakeAmmoBox(colossus_cluster_bomb_ammo), - "colossus_chaingun_ammo" -> MakeAmmoBox(colossus_chaingun_ammo), - "colossus_tank_cannon_ammo" -> MakeAmmoBox(colossus_tank_cannon_ammo), - "105mmbullet" -> MakeAmmoBox(bullet_105mm), - "gauss_cannon_ammo" -> MakeAmmoBox(gauss_cannon_ammo), - "peregrine_dual_machine_gun_ammo" -> MakeAmmoBox(peregrine_dual_machine_gun_ammo), - "peregrine_mechhammer_ammo" -> MakeAmmoBox(peregrine_mechhammer_ammo), - "peregrine_particle_cannon_ammo" -> MakeAmmoBox(peregrine_particle_cannon_ammo), - "peregrine_rocket_pod_ammo" -> MakeAmmoBox(peregrine_rocket_pod_ammo), - "peregrine_sparrow_ammo" -> MakeAmmoBox(peregrine_sparrow_ammo), - "150mmbullet" -> MakeAmmoBox(bullet_150mm) - ) - - /** - * A `Map` of operations for producing the `Tool` `Equipment` for infantry weapons. - * key - an identification string sent by the client - * value - a curried function that builds the object - */ - protected val infantryWeapons : Map[String, ()=>Equipment] = Map( - "ilc9" -> MakeTool(ilc9), - "repeater" -> MakeTool(repeater), - "isp" -> MakeTool(isp), //amp - "beamer" -> MakeTool(beamer), - "suppressor" -> MakeTool(suppressor), - "anniversary_guna" -> MakeTool(anniversary_guna), //tr stinger - "anniversary_gun" -> MakeTool(anniversary_gun), //nc spear - "anniversary_gunb" -> MakeTool(anniversary_gunb), //vs eraser - "cycler" -> MakeTool(cycler), - "gauss" -> MakeTool(gauss), - "pulsar" -> MakeTool(pulsar), - "punisher" -> MakeTool(punisher), - "flechette" -> MakeTool(flechette), - "spiker" -> MakeTool(spiker), - "frag_grenade" -> MakeTool(frag_grenade), - "jammer_grenade" -> MakeTool(jammer_grenade), - "plasma_grenade" -> MakeTool(plasma_grenade), - "katana" -> MakeTool(katana), - "chainblade" -> MakeTool(chainblade), - "magcutter" -> MakeTool(magcutter), - "forceblade" -> MakeTool(forceblade), - "mini_chaingun" -> MakeTool(mini_chaingun), - "r_shotgun" -> MakeTool(r_shotgun), //jackhammer - "lasher" -> MakeTool(lasher), - "maelstrom" -> MakeTool(maelstrom), - "striker" -> MakeTool(striker), - "hunterseeker" -> MakeTool(hunterseeker), //phoenix - "lancer" -> MakeTool(lancer), - "phoenix" -> MakeTool(phoenix), //decimator - "rocklet" -> MakeTool(rocklet), - "thumper" -> MakeTool(thumper), - "radiator" -> MakeTool(radiator), - "heavy_sniper" -> MakeTool(heavy_sniper), //hsr - "bolt_driver" -> MakeTool(bolt_driver), - "oicw" -> MakeTool(oicw), //scorpion - "flamethrower" -> MakeTool(flamethrower) - ) - - /** - * A `Map` of operations for producing the `Tool` `Equipment` for utilities. - * key - an identification string sent by the client - * value - a curried function that builds the object - */ - protected val supportWeapons : Map[String, ()=>Equipment] = Map( - "medkit" -> MakeKit(medkit), - "super_medkit" -> MakeKit(super_medkit), - "super_armorkit" -> MakeKit(super_armorkit), - "super_staminakit" -> MakeKit(super_staminakit), - "medicalapplicator" -> MakeTool(medicalapplicator), - "bank" -> MakeTool(bank, armor_canister), - "nano_dispenser" -> MakeTool(nano_dispenser), - //TODO "ace" -> MakeConstructionItem(ace), - //TODO "advanced_ace" -> MakeConstructionItem(advanced_ace), - "remote_electronics_kit" -> MakeSimpleItem(remote_electronics_kit), - "trek" -> MakeTool(trek), - "command_detonater" -> MakeSimpleItem(command_detonater), - "flail_targeting_laser" -> MakeSimpleItem(flail_targeting_laser) - ) - - /** - * A `Map` of operations for producing a ground-based `Vehicle`. - * key - an identification string sent by the client - * value - a curried function that builds the object - */ - protected val groundVehicles : Map[String, ()=>Vehicle] = Map( - "quadassault" -> MakeVehicle(quadassault), - "fury" -> MakeVehicle(fury), - "quadstealth" -> MakeVehicle(quadstealth), - "ant" -> MakeVehicle(ant), - "ams" -> MakeVehicle(ams), - "mediumtransport" -> MakeVehicle(mediumtransport), - "two_man_assault_buggy" -> MakeVehicle(two_man_assault_buggy), - "skyguard" -> MakeVehicle(skyguard), - "lightning" -> MakeVehicle(lightning), - "threemanheavybuggy" -> MakeVehicle(threemanheavybuggy), - "battlewagon" -> MakeVehicle(battlewagon), - "apc_tr" -> MakeVehicle(apc_tr), - "prowler" -> MakeVehicle(prowler), - "twomanheavybuggy" -> MakeVehicle(twomanheavybuggy), - "thunderer" -> MakeVehicle(thunderer), - "apc_nc" -> MakeVehicle(apc_nc), - "vanguard" -> MakeVehicle(vanguard), - "twomanhoverbuggy" -> MakeVehicle(twomanhoverbuggy), - "aurora" -> MakeVehicle(aurora), - "apc_vs" -> MakeVehicle(apc_vs), - "magrider" -> MakeVehicle(magrider), - "flail" -> MakeVehicle(flail), - "switchblade" -> MakeVehicle(switchblade), - "router" -> MakeVehicle(router) - ) - - /** - * A `Map` of operations for producing most flight-based `Vehicle`. - * key - an identification string sent by the client - * value - a curried function that builds the object - */ - protected val flight1Vehicles : Map[String, ()=>Vehicle] = Map( - "mosquito" -> MakeVehicle(mosquito), - "lightgunship" -> MakeVehicle(lightgunship), - "wasp" -> MakeVehicle(wasp), - "phantasm" -> MakeVehicle(phantasm), - "vulture" -> MakeVehicle(vulture), - "liberator" -> MakeVehicle(liberator) - ) - - /** - * A `Map` of operations for producing a flight-based `Vehicle` specific to the dropship terminal. - * key - an identification string sent by the client - * value - a curried function that builds the object - */ - protected val flight2Vehicles : Map[String, ()=>Vehicle] = Map( - "dropship" -> MakeVehicle(dropship), - "galaxy_gunship" -> MakeVehicle(galaxy_gunship), - "lodestar" -> MakeVehicle(lodestar) - ) - - /** - * A `Map` of operations for producing a ground-based `Vehicle` specific to the bfr terminal. - * key - an identification string sent by the client - * value - a curried function that builds the object - */ - protected val bfrVehicles : Map[String, ()=>Vehicle] = Map( -// "colossus_gunner" -> (()=>Unit), -// "colossus_flight" -> (()=>Unit), -// "peregrine_gunner" -> (()=>Unit), -// "peregrine_flight" -> (()=>Unit), -// "aphelion_gunner" -> (()=>Unit), -// "aphelion_flight" -> (()=>Unit) - ) - - /** - * Create a new `Tool` from provided `EquipmentDefinition` objects. - * @param tdef the `ToolDefinition` object - * @return a partial function that, when called, creates the piece of `Equipment` - */ - protected def MakeTool(tdef : ToolDefinition)() : Tool = MakeTool(tdef, Nil) - - /** - * Create a new `Tool` from provided `EquipmentDefinition` objects. - * @param tdef the `ToolDefinition` object - * @param adef an `AmmoBoxDefinition` object - * @return a partial function that, when called, creates the piece of `Equipment` - */ - protected def MakeTool(tdef : ToolDefinition, adef : AmmoBoxDefinition)() : Tool = MakeTool(tdef, List(adef)) - - /** - * Create a new `Tool` from provided `EquipmentDefinition` objects. - * Only use this function to create default `Tools` with the default parameters. - * For example, loadouts can retain `Tool` information that utilizes alternate, valid ammunition types; - * and, this method function will not construct a complete object if provided that information. - * @param tdef the `ToolDefinition` object - * @param adefs a `List` of `AmmoBoxDefinition` objects - * @return a curried function that, when called, creates the piece of `Equipment` - * @see `GlobalDefinitions` - * @see `OrderTerminalDefinition.BuildSimplifiedPattern` - */ - protected def MakeTool(tdef : ToolDefinition, adefs : List[AmmoBoxDefinition])() : Tool = { - val obj = Tool(tdef) - adefs match { - case _ :: _ => - LoadAmmunitionIntoWeapon(obj, adefs) - case Nil => ; //as-is - } - obj - } - - /** - * Given a weapon, and custom ammunition profiles, attempt to load those boxes of ammunition into the weapon.
- *
- * This is a customization function that should normally go unused. - * All of the information necessary to generate a `Tool` from a `Terminal` request should be available on the `ToolDefinition` object. - * The ammunition information, regardless of "customization," must satisfy the type limits of the original definition. - * As thus, to introduce very strange ammunition to a give `Tool`, - * either the definition must be modified or a different definition must be used. - * The custom ammunition is organized into order of ammunition slots based on the `FireModeDefinition` objects. - * That is: - * the first custom element is processed by the first ammunition slot; - * the second custom element is processed by the second ammunition slot; and, so forth. - * @param weapon the `Tool` object - * @param adefs a sequential `List` of ammunition to be loaded into weapon - * @see `AmmoBoxDefinition` - * @see `FireModeDefinition` - */ - private def LoadAmmunitionIntoWeapon(weapon : Tool, adefs : List[AmmoBoxDefinition]) : Unit = { - val definition = weapon.Definition - (0 until math.min(weapon.MaxAmmoSlot, adefs.length)).foreach(index => { - val ammoSlot = weapon.AmmoSlots(index) - adefs.lift(index) match { - case Some(aType) => - ammoSlot.AllAmmoTypes.indexOf(aType.AmmoType) match { - case -1 => - log.warn(s"terminal plans do not match definition: can not feed ${aType.AmmoType} ammunition into Tool (${definition.ObjectId} @ ammo $index)") - case n => - ammoSlot.AmmoTypeIndex = n - ammoSlot.Box = MakeAmmoBox(aType, Some(definition.FireModes(index).Magazine)) //make new internal magazine, full - } - case None => ; - } - }) - } - - /** - * Create a new `AmmoBox` from provided `EquipmentDefinition` objects. - * @param adef the `AmmoBoxDefinition` object - * @param capacity optional number of rounds in this `AmmoBox`, deviating from the `EquipmentDefinition`; - * necessary for constructing the magazine (`AmmoSlot`) of `Tool`s - * @return a curried function that, when called, creates the piece of `Equipment` - * @see `GlobalDefinitions` - */ - protected def MakeAmmoBox(adef : AmmoBoxDefinition, capacity : Option[Int] = None)() : AmmoBox = { - capacity match { - case Some(cap) => - AmmoBox(adef, cap) - case None => - AmmoBox(adef) - } - } - - /** - * Create a new `Kit` from provided `EquipmentDefinition` objects. - * @param kdef the `KitDefinition` object - * @return a curried function that, when called, creates the piece of `Equipment` - * @see `GlobalDefinitions` - */ - protected def MakeKit(kdef : KitDefinition)() : Kit = Kit(kdef) - - /** - * Create a new `SimpleItem` from provided `EquipmentDefinition` objects. - * @param sdef the `SimpleItemDefinition` object - * @return a curried function that, when called, creates the piece of `Equipment` - * @see `GlobalDefinitions` - */ - protected def MakeSimpleItem(sdef : SimpleItemDefinition)() : SimpleItem = SimpleItem(sdef) - - /** - * Create a new `ConstructionItem` from provided `EquipmentDefinition` objects. - * @param cdef the `ConstructionItemDefinition` object - * @return a curried function that, when called, creates the piece of `Equipment` - * @see `GlobalDefinitions` - */ - protected def MakeConstructionItem(cdef : ConstructionItemDefinition)() : ConstructionItem = ConstructionItem(cdef) - - /** - * Create a new `Vehicle` from provided `VehicleDefinition` objects. - * @param vdef the `VehicleDefinition` object - * @return a curried function that, when called, creates the `Vehicle` - * @see `GlobalDefinitions` - */ - protected def MakeVehicle(vdef : VehicleDefinition)() : Vehicle = Vehicle(vdef) } diff --git a/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalCombinedDefinition.scala b/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalCombinedDefinition.scala index c069545a4..bcbc35b34 100644 --- a/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalCombinedDefinition.scala +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalCombinedDefinition.scala @@ -1,19 +1,7 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.serverobject.terminals -import net.psforever.objects.Player -import net.psforever.packet.game.ItemTransactionMessage - -class VehicleTerminalCombinedDefinition extends TerminalDefinition(952) { - private val vehicles = groundVehicles ++ flight1Vehicles +class VehicleTerminalCombinedDefinition extends VehicleTerminalDefinition(952) { + vehicles = groundVehicles ++ flight1Vehicles Name = "vehicle_terminal_combined" - - def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { - vehicles.get(msg.item_name) match { - case Some(vehicle) => - Terminal.BuyVehicle(vehicle(), Nil) - case 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 new file mode 100644 index 000000000..a64fad155 --- /dev/null +++ b/common/src/main/scala/net/psforever/objects/serverobject/terminals/VehicleTerminalDefinition.scala @@ -0,0 +1,487 @@ +// Copyright (c) 2017 PSForever +package net.psforever.objects.serverobject.terminals + +import net.psforever.objects.definition.VehicleDefinition +import net.psforever.objects.{Player, Vehicle} +import net.psforever.objects.inventory.InventoryItem +import net.psforever.packet.game.ItemTransactionMessage + +abstract class VehicleTerminalDefinition(objId : Int) extends TerminalDefinition(objId) { + protected var vehicles : Map[String, ()=>Vehicle] = Map() + Name = "vehicle_terminal" + + import net.psforever.objects.GlobalDefinitions._ + import VehicleTerminalDefinition.MakeVehicle + /** + * A `Map` of operations for producing a ground-based `Vehicle`. + * key - an identification string sent by the client + * value - a curried function that builds the object + */ + protected val groundVehicles : Map[String, () => Vehicle] = Map( + "quadassault" -> MakeVehicle(quadassault), + "fury" -> MakeVehicle(fury), + "quadstealth" -> MakeVehicle(quadstealth), + "ant" -> MakeVehicle(ant), + "ams" -> MakeVehicle(ams), + "mediumtransport" -> MakeVehicle(mediumtransport), + "two_man_assault_buggy" -> MakeVehicle(two_man_assault_buggy), + "skyguard" -> MakeVehicle(skyguard), + "lightning" -> MakeVehicle(lightning), + "threemanheavybuggy" -> MakeVehicle(threemanheavybuggy), + "battlewagon" -> MakeVehicle(battlewagon), + "apc_tr" -> MakeVehicle(apc_tr), + "prowler" -> MakeVehicle(prowler), + "twomanheavybuggy" -> MakeVehicle(twomanheavybuggy), + "thunderer" -> MakeVehicle(thunderer), + "apc_nc" -> MakeVehicle(apc_nc), + "vanguard" -> MakeVehicle(vanguard), + "twomanhoverbuggy" -> MakeVehicle(twomanhoverbuggy), + "aurora" -> MakeVehicle(aurora), + "apc_vs" -> MakeVehicle(apc_vs), + "magrider" -> MakeVehicle(magrider), + "flail" -> MakeVehicle(flail), + "switchblade" -> MakeVehicle(switchblade), + "router" -> MakeVehicle(router) + ) + + /** + * A `Map` of operations for producing most flight-based `Vehicle`. + * key - an identification string sent by the client + * value - a curried function that builds the object + */ + protected val flight1Vehicles : Map[String, ()=>Vehicle] = Map( + "mosquito" -> MakeVehicle(mosquito), + "lightgunship" -> MakeVehicle(lightgunship), + "wasp" -> MakeVehicle(wasp), + "phantasm" -> MakeVehicle(phantasm), + "vulture" -> MakeVehicle(vulture), + "liberator" -> MakeVehicle(liberator) + ) + + /** + * A `Map` of operations for producing a flight-based `Vehicle` specific to the dropship terminal. + * key - an identification string sent by the client + * value - a curried function that builds the object + */ + protected val flight2Vehicles : Map[String, ()=>Vehicle] = Map( + "dropship" -> MakeVehicle(dropship), + "galaxy_gunship" -> MakeVehicle(galaxy_gunship), + "lodestar" -> MakeVehicle(lodestar) + ) + + /** + * A `Map` of operations for producing a ground-based `Vehicle` specific to the bfr terminal. + * key - an identification string sent by the client + * value - a curried function that builds the object + */ + protected val bfrVehicles : Map[String, ()=>Vehicle] = Map( + // "colossus_gunner" -> (()=>Unit), + // "colossus_flight" -> (()=>Unit), + // "peregrine_gunner" -> (()=>Unit), + // "peregrine_flight" -> (()=>Unit), + // "aphelion_gunner" -> (()=>Unit), + // "aphelion_flight" -> (()=>Unit) + ) + + import net.psforever.objects.{Loadout => _Loadout} //distinguish from Terminal.Loadout message + import _Loadout._ + /** + * A `Map` of the default contents of a `Vehicle` inventory, called the trunk. + * key - an identification string sent by the client (for the vehicle) + * value - a curried function that builds the object + */ + protected val trunk : Map[String, _Loadout] = { + val ammo_12mm = ShorthandAmmoBox(bullet_12mm, bullet_12mm.Capacity) + val ammo_15mm = ShorthandAmmoBox(bullet_15mm, bullet_15mm.Capacity) + val ammo_25mm = ShorthandAmmoBox(bullet_25mm, bullet_25mm.Capacity) + val ammo_35mm = ShorthandAmmoBox(bullet_35mm, bullet_35mm.Capacity) + val ammo_20mm = ShorthandAmmoBox(bullet_20mm, bullet_20mm.Capacity) + val ammo_75mm = ShorthandAmmoBox(bullet_75mm, bullet_75mm.Capacity) + val ammo_mortar = ShorthandAmmoBox(heavy_grenade_mortar, heavy_grenade_mortar.Capacity) + 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(), + List( + SimplifiedEntry(ammo_12mm, 30), + SimplifiedEntry(ammo_12mm, 34), + SimplifiedEntry(ammo_12mm, 74), + SimplifiedEntry(ammo_12mm, 78) + ) + ), + { + val ammo = ShorthandAmmoBox(hellfire_ammo, hellfire_ammo.Capacity) + "fury" -> _Loadout("default_fury", List(), + List( + SimplifiedEntry(ammo, 30), + SimplifiedEntry(ammo, 34), + SimplifiedEntry(ammo, 74), + SimplifiedEntry(ammo, 78) + ) + ) + }, + //"ant" -> _Loadout("default_ant", List(), List()), + //"ams" -> _Loadout("default_ams", List(), List()), + "two_man_assault_buggy" -> _Loadout("default_two_man_assault_buggy", List(), + List( + SimplifiedEntry(ammo_12mm, 30), + SimplifiedEntry(ammo_12mm, 34), + SimplifiedEntry(ammo_12mm, 38), + SimplifiedEntry(ammo_12mm, 90), + SimplifiedEntry(ammo_12mm, 94), + SimplifiedEntry(ammo_12mm, 98) + ) + ), + { + val ammo = ShorthandAmmoBox(skyguard_flak_cannon_ammo, skyguard_flak_cannon_ammo.Capacity) + "skyguard" -> _Loadout("default_skyguard", List(), + List( + SimplifiedEntry(ammo_12mm, 30), + SimplifiedEntry(ammo_12mm, 34), + SimplifiedEntry(ammo_12mm, 38), + SimplifiedEntry(ammo, 90), + SimplifiedEntry(ammo, 94), + SimplifiedEntry(ammo, 98) + ) + ) + }, + "threemanheavybuggy" -> _Loadout("default_threemanheavybuggy", List(), + List( + SimplifiedEntry(ammo_12mm, 30), + SimplifiedEntry(ammo_12mm, 34), + SimplifiedEntry(ammo_12mm, 38), + SimplifiedEntry(ammo_mortar, 90), + SimplifiedEntry(ammo_mortar, 94), + SimplifiedEntry(ammo_mortar, 98) + ) + ), + { + val ammo = ShorthandAmmoBox(firebird_missile, firebird_missile.Capacity) + "twomanheavybuggy" -> _Loadout("default_twomanheavybuggy", List(), + List( + SimplifiedEntry(ammo, 30), + SimplifiedEntry(ammo, 34), + SimplifiedEntry(ammo, 38), + SimplifiedEntry(ammo, 90), + SimplifiedEntry(ammo, 94), + SimplifiedEntry(ammo, 98) + ) + ) + }, + "twomanhoverbuggy" -> _Loadout("default_twomanhoverbuggy", List(), + List( + SimplifiedEntry(ammo_flux, 30), + SimplifiedEntry(ammo_flux, 34), + SimplifiedEntry(ammo_flux, 38), + SimplifiedEntry(ammo_flux, 90), + SimplifiedEntry(ammo_flux, 94), + SimplifiedEntry(ammo_flux, 98) + ) + ), + "mediumtransport" -> _Loadout("default_mediumtransport", List(), + List( + SimplifiedEntry(ammo_20mm, 30), + SimplifiedEntry(ammo_20mm, 34), + SimplifiedEntry(ammo_20mm, 38), + SimplifiedEntry(ammo_20mm, 90), + SimplifiedEntry(ammo_20mm, 94), + SimplifiedEntry(ammo_20mm, 98), + SimplifiedEntry(ammo_20mm, 150), + SimplifiedEntry(ammo_20mm, 154), + SimplifiedEntry(ammo_20mm, 158) + ) + ), + "battlewagon" -> _Loadout("default_battlewagon", List(), + List( + SimplifiedEntry(ammo_15mm, 30), + SimplifiedEntry(ammo_15mm, 34), + SimplifiedEntry(ammo_15mm, 38), + SimplifiedEntry(ammo_15mm, 90), + SimplifiedEntry(ammo_15mm, 94), + SimplifiedEntry(ammo_15mm, 98), + SimplifiedEntry(ammo_15mm, 150), + SimplifiedEntry(ammo_15mm, 154), + SimplifiedEntry(ammo_15mm, 158) + ) + ), + { + val ammo = ShorthandAmmoBox(gauss_cannon_ammo, gauss_cannon_ammo.Capacity) + "thunderer" -> _Loadout("default_thunderer", List(), + List( + SimplifiedEntry(ammo, 30), + SimplifiedEntry(ammo, 34), + SimplifiedEntry(ammo, 38), + SimplifiedEntry(ammo, 90), + SimplifiedEntry(ammo, 94), + SimplifiedEntry(ammo, 98), + SimplifiedEntry(ammo, 150), + SimplifiedEntry(ammo, 154), + SimplifiedEntry(ammo, 158) + ) + ) + }, + { + val ammo = ShorthandAmmoBox(fluxpod_ammo, fluxpod_ammo.Capacity) + "aurora" -> _Loadout("default_aurora", List(), + List( + SimplifiedEntry(ammo, 30), + SimplifiedEntry(ammo, 34), + SimplifiedEntry(ammo, 38), + SimplifiedEntry(ammo, 90), + SimplifiedEntry(ammo, 94), + SimplifiedEntry(ammo, 98), + SimplifiedEntry(ammo, 150), + SimplifiedEntry(ammo, 154), + SimplifiedEntry(ammo, 158) + ) + ) + }, + "apc_tr" -> _Loadout("default_apc_tr", List(), + List( + SimplifiedEntry(ammo_75mm, 30), + SimplifiedEntry(ammo_75mm, 34), + SimplifiedEntry(ammo_75mm, 38), + SimplifiedEntry(ammo_75mm, 42), + SimplifiedEntry(ammo_75mm, 46), + SimplifiedEntry(ammo_75mm, 110), + SimplifiedEntry(ammo_75mm, 114), + SimplifiedEntry(ammo_75mm, 118), + SimplifiedEntry(ammo_75mm, 122), + SimplifiedEntry(ammo_12mm, 126), + SimplifiedEntry(ammo_12mm, 190), + SimplifiedEntry(ammo_12mm, 194), + SimplifiedEntry(ammo_12mm, 198), + SimplifiedEntry(ammo_12mm, 202), + SimplifiedEntry(ammo_15mm, 206), + SimplifiedEntry(ammo_15mm, 270), + SimplifiedEntry(ammo_15mm, 274), + SimplifiedEntry(ammo_15mm, 278), + SimplifiedEntry(ammo_15mm, 282), + SimplifiedEntry(ammo_15mm, 286) + ) + ), + "apc_nc" -> _Loadout("default_apc_nc", List(), + List( + SimplifiedEntry(ammo_75mm, 30), + SimplifiedEntry(ammo_75mm, 34), + SimplifiedEntry(ammo_75mm, 38), + SimplifiedEntry(ammo_75mm, 42), + SimplifiedEntry(ammo_75mm, 46), + SimplifiedEntry(ammo_75mm, 110), + SimplifiedEntry(ammo_75mm, 114), + SimplifiedEntry(ammo_75mm, 118), + SimplifiedEntry(ammo_75mm, 122), + SimplifiedEntry(ammo_12mm, 126), + SimplifiedEntry(ammo_12mm, 190), + SimplifiedEntry(ammo_12mm, 194), + SimplifiedEntry(ammo_12mm, 198), + SimplifiedEntry(ammo_12mm, 202), + SimplifiedEntry(ammo_20mm, 206), + SimplifiedEntry(ammo_20mm, 270), + SimplifiedEntry(ammo_20mm, 274), + SimplifiedEntry(ammo_20mm, 278), + SimplifiedEntry(ammo_20mm, 282), + SimplifiedEntry(ammo_20mm, 286) + ) + ), + "apc_vs" -> _Loadout("default_apc_vs", List(), + List( + SimplifiedEntry(ammo_75mm, 30), + SimplifiedEntry(ammo_75mm, 34), + SimplifiedEntry(ammo_75mm, 38), + SimplifiedEntry(ammo_75mm, 42), + SimplifiedEntry(ammo_75mm, 46), + SimplifiedEntry(ammo_75mm, 110), + SimplifiedEntry(ammo_75mm, 114), + SimplifiedEntry(ammo_75mm, 118), + SimplifiedEntry(ammo_75mm, 122), + SimplifiedEntry(ammo_12mm, 126), + SimplifiedEntry(ammo_12mm, 190), + SimplifiedEntry(ammo_12mm, 194), + SimplifiedEntry(ammo_12mm, 198), + SimplifiedEntry(ammo_12mm, 202), + SimplifiedEntry(ammo_flux, 206), + SimplifiedEntry(ammo_flux, 270), + SimplifiedEntry(ammo_flux, 274), + SimplifiedEntry(ammo_flux, 278), + SimplifiedEntry(ammo_flux, 282), + SimplifiedEntry(ammo_flux, 286) + ) + ), + "lightning" -> _Loadout("default_lightning", List(), + List( + SimplifiedEntry(ammo_25mm, 30), + SimplifiedEntry(ammo_25mm, 34), + SimplifiedEntry(ammo_25mm, 38), + SimplifiedEntry(ammo_75mm, 90), + SimplifiedEntry(ammo_75mm, 94), + SimplifiedEntry(ammo_75mm, 98) + ) + ), + { + val ammo = ShorthandAmmoBox(bullet_105mm, bullet_105mm.Capacity) + "prowler" -> _Loadout("default_prowler", List(), + List( + SimplifiedEntry(ammo_15mm, 30), + SimplifiedEntry(ammo_15mm, 34), + SimplifiedEntry(ammo_15mm, 38), + SimplifiedEntry(ammo, 90), + SimplifiedEntry(ammo, 94), + SimplifiedEntry(ammo, 98) + ) + ) + }, + { + val ammo = ShorthandAmmoBox(bullet_150mm, bullet_150mm.Capacity) + "vanguard" -> _Loadout("default_vanguard", List(), + List( + SimplifiedEntry(ammo_20mm, 30), + SimplifiedEntry(ammo_20mm, 34), + SimplifiedEntry(ammo_20mm, 38), + SimplifiedEntry(ammo, 90), + SimplifiedEntry(ammo, 94), + SimplifiedEntry(ammo, 98) + ) + ) + }, + { + 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(), + List( + SimplifiedEntry(ammo1, 30), + SimplifiedEntry(ammo1, 34), + SimplifiedEntry(ammo1, 38), + SimplifiedEntry(ammo2, 90), + SimplifiedEntry(ammo2, 94), + SimplifiedEntry(ammo2, 98) + ) + ) + }, + //"flail" -> _Loadout("default_flail", List(), List()), + //"switchblade" -> _Loadout("default_switchblade", List(), List()), + //"router" -> _Loadout("default_router", List(), List()), + "mosquito" -> _Loadout("default_mosquito", List(), + List( + SimplifiedEntry(ammo_12mm, 30), + SimplifiedEntry(ammo_12mm, 34), + SimplifiedEntry(ammo_12mm, 74), + SimplifiedEntry(ammo_12mm, 78) + ) + ), + { + val ammo = ShorthandAmmoBox(reaver_rocket, reaver_rocket.Capacity) + "lightgunship" -> _Loadout("default_lightgunship", List(), + List( + SimplifiedEntry(ammo, 30), + SimplifiedEntry(ammo, 34), + SimplifiedEntry(ammo, 38), + SimplifiedEntry(ammo, 90), + SimplifiedEntry(ammo_20mm, 94), + SimplifiedEntry(ammo_20mm, 98) + ) + ) + }, + { + 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(), + List( + SimplifiedEntry(ammo1, 30), + SimplifiedEntry(ammo1, 34), + SimplifiedEntry(ammo2, 74), + SimplifiedEntry(ammo2, 78) + ) + ) + }, + "liberator" -> _Loadout("default_liberator", List(), + List( + SimplifiedEntry(ammo_35mm, 30), + SimplifiedEntry(ammo_35mm, 34), + SimplifiedEntry(ammo_25mm, 38), + SimplifiedEntry(ammo_25mm, 90), + SimplifiedEntry(ammo_bomb, 94), + SimplifiedEntry(ammo_bomb, 98), + SimplifiedEntry(ammo_bomb, 150), + SimplifiedEntry(ammo_bomb, 154), + SimplifiedEntry(ammo_bomb, 158) + ) + ), + "vulture" -> _Loadout("default_vulture", List(), + List( + SimplifiedEntry(ammo_35mm, 30), + SimplifiedEntry(ammo_35mm, 34), + SimplifiedEntry(ammo_35mm, 38), + SimplifiedEntry(ammo_25mm, 42), + SimplifiedEntry(ammo_25mm, 94), + SimplifiedEntry(ammo_bomb, 98), + SimplifiedEntry(ammo_bomb, 102), + SimplifiedEntry(ammo_bomb, 106) + ) //TODO confirm + ), + "dropship" -> _Loadout("default_dropship", List(), + List( + SimplifiedEntry(ammo_20mm, 30), + SimplifiedEntry(ammo_20mm, 34), + SimplifiedEntry(ammo_20mm, 38), + SimplifiedEntry(ammo_20mm, 42), + SimplifiedEntry(ammo_20mm, 94), + SimplifiedEntry(ammo_20mm, 98), + SimplifiedEntry(ammo_20mm, 102), + SimplifiedEntry(ammo_20mm, 106), + SimplifiedEntry(ammo_20mm, 158), + SimplifiedEntry(ammo_20mm, 162), + SimplifiedEntry(ammo_20mm, 166), + SimplifiedEntry(ammo_20mm, 170) + ) + ), + "galaxy_gunship" -> _Loadout("galaxy_gunship", List(), + List( + SimplifiedEntry(ammo_35mm, 30), + SimplifiedEntry(ammo_35mm, 34), + SimplifiedEntry(ammo_35mm, 38), + SimplifiedEntry(ammo_35mm, 42), + SimplifiedEntry(ammo_35mm, 102), + SimplifiedEntry(ammo_35mm, 106), + SimplifiedEntry(ammo_35mm, 110), + SimplifiedEntry(ammo_35mm, 114), + SimplifiedEntry(ammo_mortar, 174), + SimplifiedEntry(ammo_mortar, 178), + SimplifiedEntry(ammo_mortar, 182), + SimplifiedEntry(ammo_mortar, 186) + ) + ) + //"phantasm" -> _Loadout("default_phantasm", List(), List()), + //"lodestar" -> _Loadout("default_lodestar", List(), List()), + ) + } + + def Buy(player : Player, msg : ItemTransactionMessage) : Terminal.Exchange = { + vehicles.get(msg.item_name) match { + case Some(vehicle) => + val (weapons, inventory) = trunk.get(msg.item_name) match { + case Some(loadout) => + ( + loadout.VisibleSlots.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }), + loadout.Inventory.map(entry => { InventoryItem(EquipmentTerminalDefinition.BuildSimplifiedPattern(entry.item), entry.index) }) + ) + case None => + (List.empty, List.empty) + } + Terminal.BuyVehicle(vehicle(), weapons, inventory) + case None => + Terminal.NoDeal() + } + } +} + +object VehicleTerminalDefinition { + /** + * Create a new `Vehicle` from provided `VehicleDefinition` objects. + * @param vdef the `VehicleDefinition` object + * @return a curried function that, when called, creates the `Vehicle` + * @see `GlobalDefinitions` + */ + protected def MakeVehicle(vdef : VehicleDefinition)() : Vehicle = Vehicle(vdef) +} diff --git a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala index d41a702ea..016ce6319 100644 --- a/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala +++ b/common/src/main/scala/net/psforever/objects/vehicles/VehicleControl.scala @@ -1,8 +1,9 @@ // Copyright (c) 2017 PSForever package net.psforever.objects.vehicles +import akka.actor.Actor import net.psforever.objects.Vehicle -import net.psforever.objects.mount.MountableControl +import net.psforever.objects.mount.MountableBehavior /** * An `Actor` that handles messages being dispatched to a specific `Vehicle`.
@@ -11,8 +12,10 @@ import net.psforever.objects.mount.MountableControl * The latter is applicable only when the specific vehicle is being deconstructed. * @param vehicle the `Vehicle` object being governed */ -class VehicleControl(private val vehicle : Vehicle) extends MountableControl(vehicle) { - override def receive : Receive = super[MountableControl].receive.orElse { +class VehicleControl(private val vehicle : Vehicle) extends Actor with MountableBehavior { + override def MountableObject = vehicle + + def receive : Receive = mountableBehavior.orElse { case Vehicle.PrepareForDeletion => context.become(Disabled) diff --git a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala index bfc13b89f..f5814a857 100644 --- a/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala +++ b/common/src/main/scala/net/psforever/packet/GamePacketOpcode.scala @@ -446,7 +446,7 @@ object GamePacketOpcode extends Enumeration { case 0x69 => game.AvatarFirstTimeEventMessage.decode case 0x6a => noDecoder(AggravatedDamageMessage) case 0x6b => game.TriggerSoundMessage.decode - case 0x6c => noDecoder(LootItemMessage) + case 0x6c => game.LootItemMessage.decode case 0x6d => noDecoder(VehicleSubStateMessage) case 0x6e => noDecoder(SquadMembershipRequest) case 0x6f => noDecoder(SquadMembershipResponse) diff --git a/common/src/main/scala/net/psforever/packet/game/LootItemMessage.scala b/common/src/main/scala/net/psforever/packet/game/LootItemMessage.scala new file mode 100644 index 000000000..97b35d9db --- /dev/null +++ b/common/src/main/scala/net/psforever/packet/game/LootItemMessage.scala @@ -0,0 +1,27 @@ +// Copyright (c) 2017 PSForever +package net.psforever.packet.game + +import net.psforever.packet.{GamePacketOpcode, Marshallable, PlanetSideGamePacket} +import scodec.Codec +import scodec.codecs._ + +/** + * na + * @param item_guid the item being taken + * @param target_guid what is taking the item; + * in general, usually the player who is doing the taking + */ +final case class LootItemMessage(item_guid : PlanetSideGUID, + target_guid : PlanetSideGUID + ) extends PlanetSideGamePacket { + type Packet = LootItemMessage + def opcode = GamePacketOpcode.LootItemMessage + def encode = LootItemMessage.encode(this) +} + +object LootItemMessage extends Marshallable[LootItemMessage] { + implicit val codec : Codec[LootItemMessage] = ( + ("item_guid" | PlanetSideGUID.codec) :: + ("target_guid" | PlanetSideGUID.codec) + ).as[LootItemMessage] +} \ No newline at end of file diff --git a/common/src/test/scala/game/LootItemMessageTest.scala b/common/src/test/scala/game/LootItemMessageTest.scala new file mode 100644 index 000000000..ee9d1425e --- /dev/null +++ b/common/src/test/scala/game/LootItemMessageTest.scala @@ -0,0 +1,27 @@ +// Copyright (c) 2017 PSForever +package game + +import org.specs2.mutable._ +import net.psforever.packet._ +import net.psforever.packet.game._ +import scodec.bits._ + +class LootItemMessageTest extends Specification { + val string = hex"6C DD0D 5C14" + + "decode" in { + PacketCoding.DecodePacket(string).require match { + case LootItemMessage(item_guid, target_guid) => + item_guid mustEqual PlanetSideGUID(3549) + target_guid mustEqual PlanetSideGUID(5212) + case _ => + ko + } + } + + "encode" in { + val msg = LootItemMessage(PlanetSideGUID(3549),PlanetSideGUID(5212)) + val pkt = PacketCoding.EncodePacket(msg).require.toByteVector + pkt mustEqual string + } +} diff --git a/common/src/test/scala/objects/ContainerTest.scala b/common/src/test/scala/objects/ContainerTest.scala new file mode 100644 index 000000000..2ace560c5 --- /dev/null +++ b/common/src/test/scala/objects/ContainerTest.scala @@ -0,0 +1,71 @@ +// Copyright (c) 2017 PSForever +package objects + +import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} +import net.psforever.objects.{GlobalDefinitions, OffhandEquipmentSlot, Tool} +import net.psforever.packet.game.PlanetSideGUID +import org.specs2.mutable._ + +import scala.util.Success + +class ContainerTest extends Specification { + "Container" should { + "construct" in { + val obj = new ContainerTest.CObject + obj.VisibleSlots mustEqual (0 until 9).toSet + obj.Inventory.Size mustEqual 0 + obj.Inventory.Capacity mustEqual 9 + obj.Find(PlanetSideGUID(0)) mustEqual None + obj.Slot(0) mustEqual OffhandEquipmentSlot.BlockedSlot + obj.Collisions(0, 2, 2) mustEqual Success(List()) + } + + "Collisions can Find items in Inventory (default behavior)" in { + val obj = new ContainerTest.CObject + val weapon = Tool(GlobalDefinitions.beamer) + weapon.GUID = PlanetSideGUID(1) + + obj.Inventory += 0 -> weapon + obj.Find(PlanetSideGUID(1)) match { + case Some(index) => + obj.Inventory.Items(index).obj mustEqual weapon + case None => + ko + } + obj.Collisions(1,1,1) match { + case Success(items) => + items.length mustEqual 1 + items.head.obj mustEqual weapon + case _ =>; + ko + } + } + } +} + +object ContainerTest { + class CObject extends Container { + private val inv = GridInventory(3, 3) + + def Inventory : GridInventory = inv + + def Find(guid : PlanetSideGUID) : Option[Int] = { + Inventory.Items.find({ + case((_, item)) => + if(item.obj.HasGUID) { + item.obj.GUID == guid + } + else { + false + } + }) match { + case Some((index, _)) => + Some(index) + case None => + None + } + } + + def VisibleSlots :Set[Int] = Set[Int](0,1,2, 3,4,5, 6,7,8) + } +} diff --git a/common/src/test/scala/objects/EquipmentSlotTest.scala b/common/src/test/scala/objects/EquipmentSlotTest.scala new file mode 100644 index 000000000..2741225a4 --- /dev/null +++ b/common/src/test/scala/objects/EquipmentSlotTest.scala @@ -0,0 +1,148 @@ +// Copyright (c) 2017 PSForever +package objects + +import net.psforever.objects.{EquipmentSlot, OffhandEquipmentSlot, Tool} +import net.psforever.objects.equipment.EquipmentSize +import net.psforever.objects.GlobalDefinitions.{beamer, repeater, suppressor} +import org.specs2.mutable._ + +class EquipmentSlotTest extends Specification { + "EquipmentSlot" should { + "construct" in { + val obj = new EquipmentSlot() + obj.Size mustEqual EquipmentSize.Blocked + obj.Equipment mustEqual None + } + + "construct with a default size" in { + val obj = EquipmentSlot(EquipmentSize.Pistol) + obj.Size mustEqual EquipmentSize.Pistol + } + + "change size" in { + val obj = new EquipmentSlot() + obj.Size mustEqual EquipmentSize.Blocked + obj.Size = EquipmentSize.Pistol + obj.Size mustEqual EquipmentSize.Pistol + } + + "hold equipment" in { + val obj = new EquipmentSlot() + val equipment = Tool(beamer) + obj.Equipment = None + + beamer.Size mustEqual EquipmentSize.Pistol + obj.Size = EquipmentSize.Pistol + obj.Equipment = equipment + obj.Equipment match { + case Some(item) => + item.Definition mustEqual beamer + case None => + ko + } + } + + "put down previously held equipment" in { + val obj = EquipmentSlot(EquipmentSize.Pistol) + obj.Equipment = Tool(beamer) + + obj.Equipment match { + case Some(item) => + item.Definition mustEqual beamer + case None => + ko + } + obj.Equipment = None + obj.Equipment match { + case Some(_) => + ko + case None => + ok + } + } + + "not change size when holding equipment" in { + val obj = new EquipmentSlot() + obj.Size mustEqual EquipmentSize.Blocked + obj.Size = EquipmentSize.Pistol + obj.Equipment = Tool(beamer) + obj.Equipment match { + case Some(_) => ; + case None => ko + } + + obj.Size mustEqual EquipmentSize.Pistol + obj.Size = EquipmentSize.Rifle + obj.Size mustEqual EquipmentSize.Pistol + } + + "not hold wrong-sized equipment" in { + val obj = new EquipmentSlot() + val equipment = Tool(suppressor) + obj.Equipment = None + + beamer.Size mustEqual EquipmentSize.Pistol + obj.Size = EquipmentSize.Pistol + obj.Equipment = equipment + obj.Equipment mustEqual None + } + + "not switch to holding a second item in place of a first one" in { + val obj = EquipmentSlot(EquipmentSize.Pistol) + obj.Equipment = Tool(beamer) + + obj.Equipment match { + case Some(item) => + item.Definition mustEqual beamer + case None => + ko + } + repeater.Size mustEqual EquipmentSize.Pistol + obj.Equipment = Tool(repeater) //also a pistol + obj.Equipment match { + case Some(item) => + item.Definition mustEqual beamer + case None => + ko + } + } + } + + "OffhandEquipmentSLot" should { + "construct" in { + val obj = new OffhandEquipmentSlot(EquipmentSize.Pistol) + obj.Size mustEqual EquipmentSize.Pistol + obj.Equipment mustEqual None + } + + "hold equipment" in { + val obj = new OffhandEquipmentSlot(EquipmentSize.Pistol) + val equipment = Tool(beamer) + obj.Equipment = None + + beamer.Size mustEqual EquipmentSize.Pistol + obj.Equipment = equipment + obj.Equipment match { + case Some(item) => + item.Definition mustEqual beamer + case None => + ko + } + } + + "not change size after being constructed" in { + //see above test "EquipmentSlot should/not change size when holding equipment" + val obj = new OffhandEquipmentSlot(EquipmentSize.Pistol) + obj.Equipment mustEqual None + + obj.Size mustEqual EquipmentSize.Pistol + obj.Size = EquipmentSize.Rifle + obj.Size mustEqual EquipmentSize.Pistol + } + + "special Blocked size default" in { + OffhandEquipmentSlot.BlockedSlot.Size mustEqual EquipmentSize.Blocked + OffhandEquipmentSlot.BlockedSlot.Equipment mustEqual None + } + } +} diff --git a/common/src/test/scala/objects/LoadoutTest.scala b/common/src/test/scala/objects/LoadoutTest.scala new file mode 100644 index 000000000..b67ba9320 --- /dev/null +++ b/common/src/test/scala/objects/LoadoutTest.scala @@ -0,0 +1,196 @@ +// Copyright (c) 2017 PSForever +package objects + +import net.psforever.objects._ +import net.psforever.types.{CharacterGender, ExoSuitType, PlanetSideEmpire} +import net.psforever.objects.GlobalDefinitions._ +import net.psforever.objects.equipment.{Equipment, EquipmentSize} +import org.specs2.mutable._ + +class LoadoutTest extends Specification { + def CreatePlayer() : Player = { + val + player = Player("TestCharacter", PlanetSideEmpire.VS, CharacterGender.Female, 41, 1) + player.Slot(0).Equipment = Tool(beamer) + player.Slot(2).Equipment = Tool(suppressor) + player.Slot(4).Equipment = Tool(forceblade) + player.Slot(6).Equipment = AmmoBox(bullet_9mm) + player.Slot(9).Equipment = AmmoBox(bullet_9mm) + player.Slot(12).Equipment = AmmoBox(bullet_9mm) + player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) + player.Slot(36).Equipment = AmmoBox(energy_cell) + player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) + player + } + + "Player Loadout" should { + "test sample player" in { + val obj : Player = CreatePlayer() + obj.Holsters()(0).Equipment.get.Definition mustEqual beamer + obj.Holsters()(2).Equipment.get.Definition mustEqual suppressor + obj.Holsters()(4).Equipment.get.Definition mustEqual forceblade + obj.Slot(6).Equipment.get.Definition mustEqual bullet_9mm + obj.Slot(9).Equipment.get.Definition mustEqual bullet_9mm + obj.Slot(12).Equipment.get.Definition mustEqual bullet_9mm + obj.Slot(33).Equipment.get.Definition mustEqual bullet_9mm_AP + obj.Slot(36).Equipment.get.Definition mustEqual energy_cell + obj.Slot(39).Equipment.get.Definition mustEqual remote_electronics_kit + } + + "do not load, if never saved" in { + CreatePlayer().LoadLoadout(0) mustEqual None + } + + "save but incorrect load" in { + val obj : Player = CreatePlayer() + obj.SaveLoadout("test", 0) + + obj.LoadLoadout(1) mustEqual None + } + + "save and load" in { + val obj : Player = CreatePlayer() + obj.Slot(0).Equipment.get.asInstanceOf[Tool].Magazine = 1 //non-standard but legal + obj.Slot(2).Equipment.get.asInstanceOf[Tool].AmmoSlot.Magazine = 100 //non-standard (and out of range, real=25) + obj.SaveLoadout("test", 0) + + obj.LoadLoadout(0) match { + case Some(items) => + items.Label mustEqual "test" + items.ExoSuit mustEqual obj.ExoSuit + items.Subtype mustEqual 0 + + items.VisibleSlots.length mustEqual 3 + val holsters = items.VisibleSlots.sortBy(_.index) + holsters.head.index mustEqual 0 + holsters.head.item.asInstanceOf[Loadout.ShorthandTool].tdef mustEqual beamer + holsters.head.item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 1 + holsters(1).index mustEqual 2 + holsters(1).item.asInstanceOf[Loadout.ShorthandTool].tdef mustEqual suppressor + holsters(1).item.asInstanceOf[Loadout.ShorthandTool].ammo.head.ammo.capacity mustEqual 100 + holsters(2).index mustEqual 4 + holsters(2).item.asInstanceOf[Loadout.ShorthandTool].tdef mustEqual forceblade + + items.Inventory.length mustEqual 6 + val inventory = items.Inventory.sortBy(_.index) + inventory.head.index mustEqual 6 + inventory.head.item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm + inventory(1).index mustEqual 9 + inventory(1).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm + inventory(2).index mustEqual 12 + inventory(2).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm + inventory(3).index mustEqual 33 + inventory(3).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual bullet_9mm_AP + inventory(4).index mustEqual 36 + inventory(4).item.asInstanceOf[Loadout.ShorthandAmmoBox].adef mustEqual energy_cell + inventory(5).index mustEqual 39 + inventory(5).item.asInstanceOf[Loadout.ShorthandSimpleItem].sdef mustEqual remote_electronics_kit + case None => + ko + } + } + + "save without inventory contents" in { + val obj : Player = CreatePlayer() + obj.Inventory.Clear() + obj.SaveLoadout("test", 0) + + obj.LoadLoadout(0) match { + case Some(items) => + 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 => + ko + } + } + + "save without visible slot contents" in { + val obj : Player = CreatePlayer() + obj.Slot(0).Equipment = None + obj.Slot(2).Equipment = None + obj.Slot(4).Equipment = None + obj.SaveLoadout("test", 0) + + obj.LoadLoadout(0) match { + case Some(items) => + 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 => + ko + } + } + + "save (a construction item) and load" in { + val obj : Player = CreatePlayer() + obj.Inventory.Clear() + obj.Slot(6).Equipment = ConstructionItem(advanced_ace) + obj.SaveLoadout("test", 0) + + obj.LoadLoadout(0) match { + case Some(items) => + items.Inventory.length mustEqual 1 + items.Inventory.head.index mustEqual 6 + items.Inventory.head.item.asInstanceOf[Loadout.ShorthandConstructionItem].cdef mustEqual advanced_ace + case None => + ko + } + } + + "save (a kit) and load" in { + val obj : Player = CreatePlayer() + obj.Inventory.Clear() + obj.Slot(6).Equipment = Kit(medkit) + obj.SaveLoadout("test", 0) + + obj.LoadLoadout(0) match { + case Some(items) => + items.Inventory.length mustEqual 1 + items.Inventory.head.index mustEqual 6 + items.Inventory.head.item.asInstanceOf[Loadout.ShorthandKit].kdef mustEqual medkit + case None => + ko + } + } + + "save, load, delete" in { + val obj : Player = CreatePlayer() + obj.SaveLoadout("test", 0) + + obj.LoadLoadout(0) match { + case None => + ko + case Some(_) => ; //good; keep going + } + obj.DeleteLoadout(0) + obj.LoadLoadout(0) mustEqual None + } + + "distinguish MAX subtype information" in { + val obj : Player = CreatePlayer() + val slot = obj.Slot(2) + slot.Equipment = None //only an unequipped slot can have its Equipment Size changed (Rifle -> Max) + Player.SuitSetup(obj, ExoSuitType.MAX) + obj.SaveLoadout("generic", 0) //weaponless + slot.Equipment = None + slot.Equipment = Tool(trhev_dualcycler) + obj.SaveLoadout("cycler", 1) + slot.Equipment = None + slot.Equipment = Tool(trhev_pounder) + obj.SaveLoadout("pounder", 2) + slot.Equipment = None + slot.Equipment = Tool(trhev_burster) + obj.SaveLoadout("burster", 3) + + obj.LoadLoadout(0).get.Subtype mustEqual 0 + obj.LoadLoadout(1).get.Subtype mustEqual 1 + obj.LoadLoadout(2).get.Subtype mustEqual 2 + obj.LoadLoadout(3).get.Subtype mustEqual 3 + } + } +} diff --git a/common/src/test/scala/objects/MountableTest.scala b/common/src/test/scala/objects/MountableTest.scala index 88cce0e3e..3b33ecd3b 100644 --- a/common/src/test/scala/objects/MountableTest.scala +++ b/common/src/test/scala/objects/MountableTest.scala @@ -1,10 +1,10 @@ // Copyright (c) 2017 PSForever package objects -import akka.actor.{ActorRef, Props} +import akka.actor.{Actor, ActorRef, Props} import net.psforever.objects.Player import net.psforever.objects.definition.{ObjectDefinition, SeatDefinition} -import net.psforever.objects.mount.{Mountable, MountableControl} +import net.psforever.objects.mount.{Mountable, MountableBehavior} import net.psforever.objects.serverobject.PlanetSideServerObject import net.psforever.objects.vehicles.Seat import net.psforever.types.{CharacterGender, PlanetSideEmpire} @@ -83,5 +83,9 @@ object MountableTest { def Definition : ObjectDefinition = null //eh whatever } - class MountableTestControl(obj : Mountable) extends MountableControl(obj) + class MountableTestControl(obj : Mountable) extends Actor with MountableBehavior { + override def MountableObject = obj + + def receive : Receive = mountableBehavior + } } diff --git a/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala index ab1bcca82..ffab2595f 100644 --- a/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/AirVehicleTerminalTest.scala @@ -2,7 +2,7 @@ package objects.terminal import akka.actor.ActorRef -import net.psforever.objects.{GlobalDefinitions, Player, Tool} +import net.psforever.objects.{GlobalDefinitions, Player} import net.psforever.objects.serverobject.terminals.Terminal import net.psforever.packet.game.{ItemTransactionMessage, PlanetSideGUID} import net.psforever.types.{CharacterGender, PlanetSideEmpire, TransactionType} @@ -24,7 +24,14 @@ class AirVehicleTerminalTest extends Specification { reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true val reply2 = reply.asInstanceOf[Terminal.BuyVehicle] reply2.vehicle.Definition mustEqual GlobalDefinitions.lightgunship - reply2.loadout mustEqual Nil //TODO + reply2.weapons mustEqual Nil + reply2.inventory.length mustEqual 6 + reply2.inventory.head.obj.Definition mustEqual GlobalDefinitions.reaver_rocket + reply2.inventory(1).obj.Definition mustEqual GlobalDefinitions.reaver_rocket + reply2.inventory(2).obj.Definition mustEqual GlobalDefinitions.reaver_rocket + reply2.inventory(3).obj.Definition mustEqual GlobalDefinitions.reaver_rocket + reply2.inventory(4).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(5).obj.Definition mustEqual GlobalDefinitions.bullet_20mm } "player can not buy a fake vehicle ('reaver')" in { diff --git a/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala index 3446ec951..d7850c4e2 100644 --- a/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/DropshipVehicleTerminalTest.scala @@ -24,7 +24,20 @@ class DropshipVehicleTerminalTest extends Specification { reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true val reply2 = reply.asInstanceOf[Terminal.BuyVehicle] reply2.vehicle.Definition mustEqual GlobalDefinitions.dropship - reply2.loadout mustEqual Nil //TODO + reply2.weapons mustEqual Nil + reply2.inventory.length mustEqual 12 + reply2.inventory.head.obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(1).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(2).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(3).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(4).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(5).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(6).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(7).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(8).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(9).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(10).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(11).obj.Definition mustEqual GlobalDefinitions.bullet_20mm } "player can not buy a fake vehicle ('galaxy')" in { diff --git a/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala b/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala index 0a56463b9..c2207562f 100644 --- a/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala +++ b/common/src/test/scala/objects/terminal/GroundVehicleTerminalTest.scala @@ -24,7 +24,14 @@ class GroundVehicleTerminalTest extends Specification { reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true val reply2 = reply.asInstanceOf[Terminal.BuyVehicle] reply2.vehicle.Definition mustEqual GlobalDefinitions.two_man_assault_buggy - reply2.loadout mustEqual Nil //TODO + reply2.weapons mustEqual Nil + reply2.inventory.length mustEqual 6 + reply2.inventory.head.obj.Definition mustEqual GlobalDefinitions.bullet_12mm + reply2.inventory(1).obj.Definition mustEqual GlobalDefinitions.bullet_12mm + reply2.inventory(2).obj.Definition mustEqual GlobalDefinitions.bullet_12mm + reply2.inventory(3).obj.Definition mustEqual GlobalDefinitions.bullet_12mm + reply2.inventory(4).obj.Definition mustEqual GlobalDefinitions.bullet_12mm + reply2.inventory(5).obj.Definition mustEqual GlobalDefinitions.bullet_12mm } "player can not buy a fake vehicle ('harasser')" in { diff --git a/common/src/test/scala/objects/terminal/TerminalControlTest.scala b/common/src/test/scala/objects/terminal/TerminalControlTest.scala index ddb6ad521..b51062594 100644 --- a/common/src/test/scala/objects/terminal/TerminalControlTest.scala +++ b/common/src/test/scala/objects/terminal/TerminalControlTest.scala @@ -99,7 +99,14 @@ class VehicleTerminalControl1Test extends ActorTest() { assert(reply2.response.isInstanceOf[Terminal.BuyVehicle]) val reply3 = reply2.response.asInstanceOf[Terminal.BuyVehicle] assert(reply3.vehicle.Definition == GlobalDefinitions.two_man_assault_buggy) - assert(reply3.loadout == Nil) //TODO + assert(reply3.weapons == Nil) + assert(reply3.inventory.length == 6) //TODO + assert(reply3.inventory.head.obj.Definition == GlobalDefinitions.bullet_12mm) + assert(reply3.inventory(1).obj.Definition == GlobalDefinitions.bullet_12mm) + assert(reply3.inventory(2).obj.Definition == GlobalDefinitions.bullet_12mm) + assert(reply3.inventory(3).obj.Definition == GlobalDefinitions.bullet_12mm) + assert(reply3.inventory(4).obj.Definition == GlobalDefinitions.bullet_12mm) + assert(reply3.inventory(5).obj.Definition == GlobalDefinitions.bullet_12mm) } } diff --git a/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala b/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala index 722ec6adf..0bcb24d1b 100644 --- a/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala +++ b/common/src/test/scala/objects/terminal/VehicleTerminalCombinedTest.scala @@ -24,7 +24,14 @@ class VehicleTerminalCombinedTest extends Specification { reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true val reply2 = reply.asInstanceOf[Terminal.BuyVehicle] reply2.vehicle.Definition mustEqual GlobalDefinitions.two_man_assault_buggy - reply2.loadout mustEqual Nil //TODO + reply2.weapons mustEqual Nil + reply2.inventory.length mustEqual 6 + reply2.inventory.head.obj.Definition mustEqual GlobalDefinitions.bullet_12mm + reply2.inventory(1).obj.Definition mustEqual GlobalDefinitions.bullet_12mm + reply2.inventory(2).obj.Definition mustEqual GlobalDefinitions.bullet_12mm + reply2.inventory(3).obj.Definition mustEqual GlobalDefinitions.bullet_12mm + reply2.inventory(4).obj.Definition mustEqual GlobalDefinitions.bullet_12mm + reply2.inventory(5).obj.Definition mustEqual GlobalDefinitions.bullet_12mm } "player can buy a flying vehicle, the reaver ('lightgunship')" in { @@ -34,7 +41,14 @@ class VehicleTerminalCombinedTest extends Specification { reply.isInstanceOf[Terminal.BuyVehicle] mustEqual true val reply2 = reply.asInstanceOf[Terminal.BuyVehicle] reply2.vehicle.Definition mustEqual GlobalDefinitions.lightgunship - reply2.loadout mustEqual Nil //TODO + reply2.weapons mustEqual Nil + reply2.inventory.length mustEqual 6 + reply2.inventory.head.obj.Definition mustEqual GlobalDefinitions.reaver_rocket + reply2.inventory(1).obj.Definition mustEqual GlobalDefinitions.reaver_rocket + reply2.inventory(2).obj.Definition mustEqual GlobalDefinitions.reaver_rocket + reply2.inventory(3).obj.Definition mustEqual GlobalDefinitions.reaver_rocket + reply2.inventory(4).obj.Definition mustEqual GlobalDefinitions.bullet_20mm + reply2.inventory(5).obj.Definition mustEqual GlobalDefinitions.bullet_20mm } "player can not buy a fake vehicle ('harasser')" in { diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index 91128f8c1..f9b59404a 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -13,7 +13,7 @@ import services.ServiceManager.Lookup import net.psforever.objects._ import net.psforever.objects.equipment._ import net.psforever.objects.guid.{GUIDTask, Task, TaskResolver} -import net.psforever.objects.inventory.{GridInventory, InventoryItem} +import net.psforever.objects.inventory.{Container, GridInventory, InventoryItem} import net.psforever.objects.mount.Mountable import net.psforever.objects.serverobject.{CommonMessages, PlanetSideServerObject} import net.psforever.objects.serverobject.doors.Door @@ -61,7 +61,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(tplayer) => tplayer.VehicleSeated match { case Some(vehicle_guid) => - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 0, true)) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 0, true, vehicle_guid)) case None => ; } tplayer.VehicleOwned match { @@ -288,8 +288,15 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2))) } - case VehicleResponse.KickPassenger(unk1, unk2) => + case VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid) => sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(guid, unk1, unk2))) + if(guid == player.GUID) { + continent.GUID(vehicle_guid) match { + case Some(obj : Vehicle) => + UnAccessContents(obj) + case _ => ; + } + } case VehicleResponse.LoadVehicle(vehicle, vtype, vguid, vdata) => //this is not be suitable for vehicles with people who are seated in it before it spawns (if that is possible) @@ -308,9 +315,48 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, PlanetsideAttributeMessage(vehicle_guid, seat_group, permission))) } + case VehicleResponse.StowEquipment(vehicle_guid, slot, item_type, item_guid, item_data) => + if(player.GUID != guid) { + //TODO prefer ObjectAttachMessage, but how to force ammo pools to update properly? + sendResponse(PacketCoding.CreateGamePacket(0, + ObjectCreateDetailedMessage(item_type, item_guid, ObjectCreateMessageParent(vehicle_guid, slot), item_data) + )) +// sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(vehicle_guid, item_guid, slot))) + } + case VehicleResponse.UnloadVehicle(vehicle_guid) => sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(vehicle_guid, 0))) + case VehicleResponse.UnstowEquipment(item_guid) => + if(player.GUID != guid) { + //TODO prefer ObjectDetachMessage, but how to force ammo pools to update properly? + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(item_guid, 0))) +// sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(vehicle_guid, item_guid, Vector3(0f, 0f, 0f), 0f, 0f, 0f))) + //... +// continent.GUID(vehicle_guid) match { +// case Some(veh : Vehicle) => +// veh.PassengerInSeat(player) match { +// case Some(seat_num) => +// veh.Seat(seat_num).get.ControlledWeapon match { +// case Some(weapon_num) => +// veh.Weapons.get(weapon_num) match { +// case Some(mount) => +// mount.Equipment match { +// case Some(wep : Tool) => +// val ammo = wep.AmmoSlot +// sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(ammo.Box.GUID, wep.GUID, ammo.Magazine))) +// case _ => ; +// } +// case _ => ; +// } +// case _ => ; +// } +// case _ => ; +// } +// case _ => ; +// } + } + case VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) => if(player.GUID != guid) { sendResponse(PacketCoding.CreateGamePacket(0, VehicleStateMessage(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6))) @@ -374,18 +420,14 @@ class WorldSessionActor extends Actor with MDCContextAware { obj.WeaponControlledFromSeat(seat_num) match { case Some(weapon : Tool) => //update mounted weapon belonging to seat - val magazine = weapon.AmmoSlots(weapon.FireModeIndex).Box //update the magazine in the weapon, specifically - sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(magazine.GUID, 0, weapon.GUID, weapon.Magazine.toLong))) - //update all related ammunition objects in trunk - obj.Trunk.Items - .filter({ case ((_, item)) => item.obj.isInstanceOf[AmmoBox] && item.obj.asInstanceOf[AmmoBox].AmmoType == weapon.AmmoType }) - .foreach({ case ((_, item)) => - val box = item.obj.asInstanceOf[AmmoBox] - sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(box.GUID, 0, obj_guid, box.Capacity.toLong))) - }) + weapon.AmmoSlots.foreach(slot => { //update the magazine(s) in the weapon, specifically + val magazine = slot.Box + sendResponse(PacketCoding.CreateGamePacket(0, InventoryStateMessage(magazine.GUID, 0, weapon.GUID, magazine.Capacity.toLong))) + }) case _ => ; //no weapons to update } sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(obj_guid, player_guid, seat_num))) + AccessContents(obj) vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.MountVehicle(player_guid, obj_guid, seat_num)) case Mountable.CanMount(obj : Mountable, seat_num) => @@ -431,7 +473,7 @@ class WorldSessionActor extends Actor with MDCContextAware { afterHolsters.foreach({elem => tplayer.Slot(elem.start).Equipment = elem.obj }) val finalInventory = fillEmptyHolsters(tplayer.Holsters().iterator, toInventory ++ beforeInventory) //draw holsters - (0 until 5).foreach({index => + tplayer.VisibleSlots.foreach({index => tplayer.Slot(index).Equipment match { case Some(obj) => val definition = obj.Definition @@ -675,16 +717,34 @@ class WorldSessionActor extends Actor with MDCContextAware { sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(terminal_guid, TransactionType.Sell, false))) } - - case Terminal.BuyVehicle(vehicle, loadout) => + case Terminal.BuyVehicle(vehicle, weapons, trunk) => continent.Map.TerminalToSpawnPad.get(msg.terminal_guid.guid) match { case Some(pad_guid) => val pad = continent.GUID(pad_guid).get.asInstanceOf[VehicleSpawnPad] vehicle.Faction = tplayer.Faction vehicle.Position = pad.Position vehicle.Orientation = pad.Orientation + //default loadout, weapons + log.info(s"default weapons: ${weapons.size}") + val vWeapons = vehicle.Weapons + weapons.foreach(entry => { + val index = entry.start + vWeapons.get(index) match { + case Some(slot) => + slot.Equipment = None + slot.Equipment = entry.obj + case None => + log.warn(s"applying default loadout to $vehicle, can not find a mounted weapon @ $index") + } + }) + //default loadout, trunk + log.info(s"default trunk: ${trunk.size}") + val vTrunk = vehicle.Trunk + vTrunk.Clear() + trunk.foreach(entry => { vTrunk += entry.start -> entry.obj }) taskResolver ! RegisterNewVehicle(vehicle, pad) - sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Learn, true))) + sendResponse(PacketCoding.CreateGamePacket(0, ItemTransactionResultMessage(msg.terminal_guid, TransactionType.Buy, true))) + case None => log.error(s"$tplayer wanted to spawn a vehicle, but there was no spawn pad associated with terminal ${msg.terminal_guid} to accept it") } @@ -993,14 +1053,14 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Certifications += CertificationType.Phantasm AwardBattleExperiencePoints(player, 1000000L) // player.ExoSuit = ExoSuitType.MAX //TODO strange issue; divide number above by 10 when uncommenting - player.Slot(0).Equipment = Tool(beamer) + player.Slot(0).Equipment = Tool(GlobalDefinitions.StandardPistol(player.Faction)) player.Slot(2).Equipment = Tool(suppressor) - player.Slot(4).Equipment = Tool(forceblade) + player.Slot(4).Equipment = Tool(GlobalDefinitions.StandardMelee(player.Faction)) player.Slot(6).Equipment = AmmoBox(bullet_9mm) player.Slot(9).Equipment = AmmoBox(bullet_9mm) player.Slot(12).Equipment = AmmoBox(bullet_9mm) player.Slot(33).Equipment = AmmoBox(bullet_9mm_AP) - player.Slot(36).Equipment = AmmoBox(energy_cell) + player.Slot(36).Equipment = AmmoBox(GlobalDefinitions.StandardPistolAmmo(player.Faction)) player.Slot(39).Equipment = SimpleItem(remote_electronics_kit) player.Slot(5).Equipment.get.asInstanceOf[LockerContainer].Inventory += 0 -> SimpleItem(remote_electronics_kit) //TODO end temp player character auto-loading @@ -1346,75 +1406,106 @@ class WorldSessionActor extends Actor with MDCContextAware { log.info("ObjectDelete: " + msg) case msg @ MoveItemMessage(item_guid, source_guid, destination_guid, dest, unk1) => - player.Find(item_guid) match { - case Some(index) => - val indexSlot = player.Slot(index) - var itemOpt : Option[Equipment] = indexSlot.Equipment - //use this to short circuit - val item = itemOpt.get - val destSlot = player.Slot(dest) - val destItem = if((-1 < dest && dest < 5) || dest == Player.FreeHandSlot) { - destSlot.Equipment match { - case Some(found) => - Some(InventoryItem(found, dest)) - case None => - None - } - } - else { - val tile = item.Definition.Tile - player.Inventory.CheckCollisionsVar(dest, tile.Width, tile.Height) match { - case Success(Nil) => None //no item swap - case Success(entry :: Nil) => Some(entry) //one item to swap - case Success(_) | scala.util.Failure(_) => itemOpt = None; None //abort item move altogether - } - } - if(itemOpt.isDefined) { - log.info(s"MoveItem: $item_guid moved from $source_guid @ $index to $source_guid @ $dest") - indexSlot.Equipment = None - destItem match { - //do we have a swap item? - case Some(entry) => //yes, swap - val item2 = entry.obj - player.Slot(entry.start).Equipment = None //remove item2 to make room for item - destSlot.Equipment = item //in case dest and index could block each other - (indexSlot.Equipment = entry.obj) match { - case Some(_) => //item and item2 swapped places successfully - log.info(s"MoveItem: ${item2.GUID} swapped to $source_guid @ $index") - //we must shuffle items around cleanly to avoid causing icons to "disappear" - if(index == Player.FreeHandSlot) { - //temporarily put in safe location, A -> C - sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item.GUID, Vector3(0f, 0f, 0f), 0f, 0f, 0f))) //ground - } - else { - sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(player.GUID, item.GUID, Player.FreeHandSlot))) //free hand - } - sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(player.GUID, item2.GUID, index))) //B -> A - if(0 <= index && index < 5) { - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, index, item2)) - } - - case None => //item2 does not fit; drop on ground - val pos = player.Position - val playerOrient = player.Orientation - val orient : Vector3 = Vector3(0f, 0f, playerOrient.z) - continent.Actor ! Zone.DropItemOnGround(item2, pos, orient) - sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item2.GUID, pos, 0f, 0f, playerOrient.z))) //ground - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, pos, orient, item2)) + (continent.GUID(source_guid), continent.GUID(destination_guid), continent.GUID(item_guid)) match { + case (Some(source : Container), Some(destination : Container), Some(item : Equipment)) => + source.Find(item_guid) match { + case Some(index) => + val indexSlot = source.Slot(index) + val destSlot = destination.Slot(dest) + val destItem = destSlot.Equipment + if( { + val tile = item.Definition.Tile + destination.Collisions(dest, tile.Width, tile.Height) match { + case Success(Nil) => + destItem.isEmpty //no item swap; abort if encountering an unexpected item + case Success(entry :: Nil) => + destItem.contains(entry.obj) //one item to swap; abort if destination item is missing or is wrong + case Success(_) | scala.util.Failure(_) => + false //abort when too many items at destination or other failure case } + } && indexSlot.Equipment.contains(item)) { + log.info(s"MoveItem: $item_guid moved from $source_guid @ $index to $destination_guid @ $dest") + indexSlot.Equipment = None + destItem match { //do we have a swap item? + case Some(item2) => //yes, swap + destSlot.Equipment = None //remove item2 to make room for item + destSlot.Equipment = item + (indexSlot.Equipment = item2) match { + case Some(_) => //item and item2 swapped places successfully + log.info(s"MoveItem: ${item2.GUID} swapped to $source_guid @ $index") + //cleanly shuffle items around to avoid losing icons + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(source_guid, item_guid, Vector3(0f, 0f, 0f), 0f, 0f, 0f))) //ground; A -> C + sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(source_guid, item2.GUID, index))) //B -> A + source match { + case (obj : Vehicle) => + val player_guid = player.GUID + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item_guid)) + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, source_guid, index, item2)) + //TODO visible slot verification, in the case of BFR arms + case (_ : Player) => + if(source.VisibleSlots.contains(index)) { + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(source_guid, index, item2)) + } + case _ => ; + //TODO something? + } - case None => //just move item over - destSlot.Equipment = item - } - sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(source_guid, item_guid, dest))) - if(0 <= dest && dest < 5) { - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, dest, item)) - } + case None => //item2 does not fit; drop on ground + val pos = source.Position + val sourceOrientZ = source.Orientation.z + val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ) + continent.Actor ! Zone.DropItemOnGround(item2, pos, orient) + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(source_guid, item2.GUID, pos, 0f, 0f, sourceOrientZ))) //ground + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, pos, orient, item2)) + } + + case None => //just move item over + destSlot.Equipment = item + source match { + case (obj : Vehicle) => + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player.GUID, item_guid)) + //TODO visible slot verification, in the case of BFR arms + case _ => ; + //TODO something? + } + + } + sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(destination_guid, item_guid, dest))) + destination match { + case (obj : Vehicle) => + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player.GUID, destination_guid, dest, item)) + //TODO visible slot verification, in the case of BFR arms + case (_ : Player) => + if(destination.VisibleSlots.contains(dest)) { + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(destination_guid, dest, item)) + } + case _ => ; + //TODO something? + } + } + else if(indexSlot.Equipment.nonEmpty) { + log.error(s"MoveItem: wanted to move $item_guid, but unexpected item ${indexSlot.Equipment.get} at origin") + } + else { + log.error(s"MoveItem: wanted to move $item_guid, but unexpected item(s) at destination") + } + case _ => + log.error(s"MoveItem: wanted to move $item_guid, but could not find it") } - case None => - log.info(s"MoveItem: $source_guid wanted to move the item $item_guid but could not find it") + + case (None, _, _) => + log.error(s"MoveItem: wanted to move $item_guid from $source_guid, but could not find source") + case (_, None, _) => + log.error(s"MoveItem: wanted to move $item_guid from $source_guid to $destination_guid, but could not find destination") + case (_, _, None) => + log.error(s"MoveItem: wanted to move $item_guid, but could not find it") + case _ => + log.error(s"MoveItem: wanted to move $item_guid from $source_guid to $destination_guid, but multiple problems were encountered") } + case msg @ LootItemMessage(item_guid, target_guid) => + log.info("LootItem: " + msg) + case msg @ ChangeAmmoMessage(item_guid, unk1) => log.info("ChangeAmmo: " + msg) @@ -1463,14 +1554,35 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(obj : Vehicle) => if(obj.Faction == player.Faction) { - player.Slot(player.DrawnSlot).Equipment match { + val equipment = player.Slot(player.DrawnSlot).Equipment + if(equipment match { case Some(tool : Tool) => - if(tool.Definition == GlobalDefinitions.nano_dispenser) { - //TODO repairing behavior + tool.Definition match { + case GlobalDefinitions.nano_dispenser | GlobalDefinitions.remote_electronics_kit => false + case _ => true } - case Some(_) | None => - //TODO trunk access + case _ => true + }) { + //access to trunk + if(obj.AccessingTrunk.isEmpty) { + obj.AccessingTrunk = player.GUID + AccessContents(obj) sendResponse(PacketCoding.CreateGamePacket(0, UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType))) + } + else { + log.info(s"UseItem: $player can not cut in line while player ${obj.AccessingTrunk.get} is using $obj's trunk") + } + } + else if(equipment.isDefined) { + equipment.get.Definition match { + case GlobalDefinitions.nano_dispenser => + //TODO repairing behavior + + case GlobalDefinitions.remote_electronics_kit => + //TODO hacking behavior + + case _ => ; + } } } @@ -1487,8 +1599,17 @@ class WorldSessionActor extends Actor with MDCContextAware { case None => ; } - case msg @ UnuseItemMessage(player_guid, item) => + case msg @ UnuseItemMessage(player_guid, object_guid) => log.info("UnuseItem: " + msg) + continent.GUID(object_guid) match { + case Some(obj : Vehicle) => + if(obj.AccessingTrunk.contains(player.GUID)) { + obj.AccessingTrunk = None + UnAccessContents(obj) + } + + case _ =>; + } case msg @ DeployObjectMessage(guid, unk1, pos, roll, pitch, yaw, unk2) => log.info("DeployObject: " + msg) @@ -1559,12 +1680,17 @@ class WorldSessionActor extends Actor with MDCContextAware { //TODO optimize this later log.info(s"DismountVehicleMsg: $msg") if(player.GUID == player_guid) { + //normally disembarking from a seat + val previouslySeated = player.VehicleSeated + player.VehicleSeated = None + sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, unk1, unk2))) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, unk1, unk2)) //common warning for this section def dismountWarning(msg : String) : Unit = { log.warn(s"$msg; some vehicle might not know that a player is no longer sitting in it") } - //normally disembarking from a seat - player.VehicleSeated match { + //find vehicle seat and disembark it + previouslySeated match { case Some(obj_guid) => continent.GUID(obj_guid) match { case Some(obj : Mountable) => @@ -1580,6 +1706,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case (veh : Vehicle) => if(seats.count(seat => seat.isOccupied) == 0) { vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(veh, continent, 600L) //start vehicle decay (10m) + UnAccessContents(veh) } case _ => ; } @@ -1593,10 +1720,6 @@ class WorldSessionActor extends Actor with MDCContextAware { case None => dismountWarning(s"DismountVehicleMsg: player $player_guid not considered seated in a mountable entity") } - //should be safe - player.VehicleSeated = None - sendResponse(PacketCoding.CreateGamePacket(0, DismountVehicleMsg(player_guid, unk1, unk2))) - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.DismountVehicle(player_guid, unk1, unk2)) } else { //kicking someone else out of a seat; need to own that seat @@ -1612,7 +1735,7 @@ class WorldSessionActor extends Actor with MDCContextAware { case Some(seat) => seat.Occupant = None tplayer.VehicleSeated = None - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, unk1, unk2)) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(player_guid, unk1, unk2, vehicle_guid)) if(seats.count(seat => seat.isOccupied) == 0) { vehicleService ! VehicleServiceMessage.DelayedVehicleDeconstruction(obj, continent, 600L) //start vehicle decay (10m) } @@ -1673,7 +1796,7 @@ class WorldSessionActor extends Actor with MDCContextAware { if(vehicle.SeatPermissionGroup(seat_num).contains(group) && tplayer != player) { seat.Occupant = None tplayer.VehicleSeated = None - vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, false)) + vehicleService ! VehicleServiceMessage(continent.Id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, object_guid)) } case None => ; } @@ -1944,9 +2067,9 @@ class WorldSessionActor extends Actor with MDCContextAware { TaskResolver.GiveTask( new Task() { private val localVehicle = obj + private val localPad = pad.Actor private val localAnnounce = vehicleService private val localSession : String = sessionId.toString - private val localPad = pad.Actor private val localPlayer = player private val localVehicleService = vehicleService private val localZone = continent @@ -2137,6 +2260,32 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + def AccessContents(vehicle : Vehicle) : Unit = { + vehicleService ! Service.Join(s"${vehicle.Actor}") + val parent_guid = vehicle.GUID + vehicle.Trunk.Items.foreach({ + case ((_, entry)) => + val obj = entry.obj + val obj_def = obj.Definition + sendResponse(PacketCoding.CreateGamePacket(0, + ObjectCreateDetailedMessage( + obj_def.ObjectId, + obj.GUID, + ObjectCreateMessageParent(parent_guid, entry.start), + obj_def.Packet.DetailedConstructorData(obj).get + ) + )) + }) + } + + def UnAccessContents(vehicle : Vehicle) : Unit = { + vehicleService ! Service.Leave(Some(s"${vehicle.Actor}")) + vehicle.Trunk.Items.foreach({ + case ((_, entry)) => + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDeleteMessage(entry.obj.GUID, 0))) + }) + } + def failWithError(error : String) = { log.error(error) sendResponse(PacketCoding.CreateControlPacket(ConnectionClose())) diff --git a/pslogin/src/main/scala/services/Service.scala b/pslogin/src/main/scala/services/Service.scala index eed17c79a..0410e0fcf 100644 --- a/pslogin/src/main/scala/services/Service.scala +++ b/pslogin/src/main/scala/services/Service.scala @@ -9,7 +9,7 @@ object Service { final val defaultPlayerGUID : PlanetSideGUID = PlanetSideGUID(0) final case class Join(channel : String) - final case class Leave() + final case class Leave(channel : Option[String] = None) final case class LeaveAll() } diff --git a/pslogin/src/main/scala/services/avatar/AvatarService.scala b/pslogin/src/main/scala/services/avatar/AvatarService.scala index 5093c98d8..ac32c07e2 100644 --- a/pslogin/src/main/scala/services/avatar/AvatarService.scala +++ b/pslogin/src/main/scala/services/avatar/AvatarService.scala @@ -18,12 +18,18 @@ class AvatarService extends Actor { case Service.Join(channel) => val path = s"/$channel/Avatar" val who = sender() - log.info(s"$who has joined $path") - AvatarEvents.subscribe(who, path) - case Service.Leave() => + + case Service.Leave(None) => AvatarEvents.unsubscribe(sender()) + + case Service.Leave(Some(channel)) => + val path = s"/$channel/Avatar" + val who = sender() + log.info(s"$who has left $path") + AvatarEvents.unsubscribe(sender(), path) + case Service.LeaveAll() => AvatarEvents.unsubscribe(sender()) diff --git a/pslogin/src/main/scala/services/local/LocalService.scala b/pslogin/src/main/scala/services/local/LocalService.scala index bf5b109aa..b11eef4e4 100644 --- a/pslogin/src/main/scala/services/local/LocalService.scala +++ b/pslogin/src/main/scala/services/local/LocalService.scala @@ -22,8 +22,16 @@ class LocalService extends Actor { val who = sender() log.info(s"$who has joined $path") LocalEvents.subscribe(who, path) - case Service.Leave() => + + case Service.Leave(None) => LocalEvents.unsubscribe(sender()) + + case Service.Leave(Some(channel)) => + val path = s"/$channel/Local" + val who = sender() + log.info(s"$who has left $path") + LocalEvents.unsubscribe(who, path) + case Service.LeaveAll() => LocalEvents.unsubscribe(sender()) diff --git a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala index 03d700f43..7887a023b 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleAction.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleAction.scala @@ -2,6 +2,7 @@ package services.vehicle import net.psforever.objects.Vehicle +import net.psforever.objects.equipment.Equipment import net.psforever.objects.zones.Zone import net.psforever.packet.game.PlanetSideGUID import net.psforever.packet.game.objectcreate.ConstructorData @@ -13,10 +14,12 @@ object VehicleAction { final case class Awareness(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID) extends Action final case class ChildObjectState(player_guid : PlanetSideGUID, object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Action final case class DismountVehicle(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean) extends Action - final case class KickPassenger(player_guid : PlanetSideGUID, unk1 : Int, unk2 : Boolean) 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 final case class SeatPermissions(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Action + final case class StowEquipment(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, slot : Int, item : Equipment) extends Action final case class UnloadVehicle(player_guid : PlanetSideGUID, continent : Zone, vehicle : Vehicle) extends Action + final case class UnstowEquipment(player_guid : PlanetSideGUID, item_guid : PlanetSideGUID) extends Action final case class VehicleState(player_guid : PlanetSideGUID, vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Action } diff --git a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala index 8806e1920..1b7de780f 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleResponse.scala @@ -12,10 +12,12 @@ object VehicleResponse { final case class Awareness(vehicle_guid : PlanetSideGUID) extends Response final case class ChildObjectState(object_guid : PlanetSideGUID, pitch : Float, yaw : Float) extends Response final case class DismountVehicle(unk1 : Int, unk2 : Boolean) extends Response - final case class KickPassenger(unk1 : Int, unk2 : Boolean) 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 final case class SeatPermissions(vehicle_guid : PlanetSideGUID, seat_group : Int, permission : Long) extends Response + final case class StowEquipment(vehicle_guid : PlanetSideGUID, slot : Int, itype : Int, iguid : PlanetSideGUID, idata : ConstructorData) extends Response final case class UnloadVehicle(vehicle_guid : PlanetSideGUID) extends Response + final case class UnstowEquipment(item_guid : PlanetSideGUID) extends Response final case class VehicleState(vehicle_guid : PlanetSideGUID, unk1 : Int, pos : Vector3, ang : Vector3, vel : Option[Vector3], unk2 : Option[Int], unk3 : Int, unk4 : Int, wheel_direction : Int, unk5 : Boolean, unk6 : Boolean) extends Response } diff --git a/pslogin/src/main/scala/services/vehicle/VehicleService.scala b/pslogin/src/main/scala/services/vehicle/VehicleService.scala index 7f49f6eda..003c8dc3c 100644 --- a/pslogin/src/main/scala/services/vehicle/VehicleService.scala +++ b/pslogin/src/main/scala/services/vehicle/VehicleService.scala @@ -22,12 +22,18 @@ class VehicleService extends Actor { case Service.Join(channel) => val path = s"/$channel/Vehicle" val who = sender() - log.info(s"$who has joined $path") - VehicleEvents.subscribe(who, path) - case Service.Leave() => + + case Service.Leave(None) => VehicleEvents.unsubscribe(sender()) + + case Service.Leave(Some(channel)) => + val path = s"/$channel/Vehicle" + val who = sender() + log.info(s"$who has left $path") + VehicleEvents.unsubscribe(who, path) + case Service.LeaveAll() => VehicleEvents.unsubscribe(sender()) @@ -45,9 +51,9 @@ class VehicleService extends Actor { VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.DismountVehicle(unk1, unk2)) ) - case VehicleAction.KickPassenger(player_guid, unk1, unk2) => + case VehicleAction.KickPassenger(player_guid, unk1, unk2, vehicle_guid) => VehicleEvents.publish( - VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(unk1, unk2)) + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.KickPassenger(unk1, unk2, vehicle_guid)) ) case VehicleAction.LoadVehicle(player_guid, vehicle, vtype, vguid, vdata) => VehicleEvents.publish( @@ -61,6 +67,15 @@ class VehicleService extends Actor { VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.SeatPermissions(vehicle_guid, seat_group, permission)) ) + case VehicleAction.StowEquipment(player_guid, vehicle_guid, slot, item) => + val definition = item.Definition + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.StowEquipment(vehicle_guid, slot, definition.ObjectId, item.GUID, definition.Packet.DetailedConstructorData(item).get)) + ) + case VehicleAction.UnstowEquipment(player_guid, item_guid) => + VehicleEvents.publish( + VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.UnstowEquipment(item_guid)) + ) case VehicleAction.VehicleState(player_guid, vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6) => VehicleEvents.publish( VehicleServiceResponse(s"/$forChannel/Vehicle", player_guid, VehicleResponse.VehicleState(vehicle_guid, unk1, pos, ang, vel, unk2, unk3, unk4, wheel_direction, unk5, unk6)) diff --git a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala index 366f79da7..760cbf204 100644 --- a/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala +++ b/pslogin/src/main/scala/services/vehicle/support/DeconstructionActor.scala @@ -62,7 +62,7 @@ class DeconstructionActor extends Actor { seat.Occupant = None tplayer.VehicleSeated = None if(tplayer.HasGUID) { - context.parent ! VehicleServiceMessage(zone_id, VehicleAction.KickPassenger(tplayer.GUID, 4, false)) + context.parent ! VehicleServiceMessage(zone_id, VehicleAction.KickPassenger(tplayer.GUID, 4, false, vehicle.GUID)) } case None => ; }