From 02ee06fd97c3189435b96e51ad5a01e8063ef3d8 Mon Sep 17 00:00:00 2001 From: FateJH Date: Fri, 18 Aug 2017 09:40:29 -0400 Subject: [PATCH] corrected missing Tile size for CUD and Laze Pointer; changed MoveItem tests to account for spatial inconsistency between modular holster slots and regional inventory cells --- .../net/psforever/objects/EquipmentSlot.scala | 14 ++ .../psforever/objects/GlobalDefinitions.scala | 2 + .../objects/inventory/GridInventory.scala | 52 +++-- .../inventory/InventoryEquipmentSlot.scala | 55 +++-- .../test/scala/objects/InventoryTest.scala | 198 +++++++++++++++--- .../src/main/scala/WorldSessionActor.scala | 101 +++++---- 6 files changed, 314 insertions(+), 108 deletions(-) diff --git a/common/src/main/scala/net/psforever/objects/EquipmentSlot.scala b/common/src/main/scala/net/psforever/objects/EquipmentSlot.scala index 7cb698b1b..816c1ec16 100644 --- a/common/src/main/scala/net/psforever/objects/EquipmentSlot.scala +++ b/common/src/main/scala/net/psforever/objects/EquipmentSlot.scala @@ -21,12 +21,26 @@ class EquipmentSlot { Size } + /** + * Determine what `Equipment` is stowed in the given position. + * @return the `Equipment` in this slot + */ def Equipment : Option[Equipment] = tool + /** + * Attempt to stow an item at the given position. + * @param assignEquipment the change in `Equipment` for this slot + * @return the `Equipment` in this slot + */ def Equipment_=(assignEquipment : Equipment) : Option[Equipment] = { Equipment = Some(assignEquipment) } + /** + * Attempt to stow an item at the given position. + * @param assignEquipment the change in `Equipment` for this slot + * @return the `Equipment` in this slot + */ def Equipment_=(assignEquipment : Option[Equipment]) : Option[Equipment] = { if(assignEquipment.isDefined) { //if new equipment is defined, don't put it in the slot if the slot is being used if(tool.isEmpty && EquipmentSize.isEqual(size, assignEquipment.get.Size)) { diff --git a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala index 4f3df693c..34976c0c2 100644 --- a/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala +++ b/common/src/main/scala/net/psforever/objects/GlobalDefinitions.scala @@ -1117,10 +1117,12 @@ object GlobalDefinitions { val flail_targeting_laser = SimpleItemDefinition(SItem.flail_targeting_laser) flail_targeting_laser.Packet = new CommandDetonaterConverter + flail_targeting_laser.Tile = InventoryTile.Tile33 val command_detonater = SimpleItemDefinition(SItem.command_detonater) command_detonater.Packet = new CommandDetonaterConverter + command_detonater.Tile = InventoryTile.Tile33 val ace = ConstructionItemDefinition(CItem.Unit.ace) diff --git a/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala b/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala index 8307cea26..b513ea553 100644 --- a/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala +++ b/common/src/main/scala/net/psforever/objects/inventory/GridInventory.scala @@ -116,12 +116,7 @@ class GridInventory { } /** - * Test whether a given piece of `Equipment` would collide with any stowed content in the inventory.
- *
- * If there are fewer items stored in the inventory than there are cells required to represent the testing item, - * test the collision by iterating through the list of items. - * If there are more items, check that each cell that would be used for the testing items tile does not collide. - * The "testing item" in this case has already been transformed into its tile dimensions. + * Test whether a given piece of `Equipment` would collide with any stowed content in the inventory. * @param start the cell index to test this `Equipment` for insertion * @param w the width of the `Equipment` to be tested * @param h the height of the `Equipment` to be tested @@ -132,8 +127,33 @@ class GridInventory { Success(List.empty[Int]) } else { - def check : (Int,Int,Int) => Try[List[Int]] = if(items.size < w * h) { CheckCollisionsAsList } else { CheckCollisionsAsGrid } - check(start, w, h) + CheckCollisionsVar(start, w, h) match { + case Success(list) => + Success(list.map({ f => f.obj.GUID.guid })) + case Failure(ex) => + Failure(ex) + } + } + } + + /** + * Test whether a given piece of `Equipment` would collide with any stowed content in the inventory.
+ *
+ * If there are fewer items stored in the inventory than there are cells required to represent the testing item, + * test the collision by iterating through the list of items. + * If there are more items, check that each cell that would be used for the testing items tile does not collide. + * The "testing item" in this case has already been transformed into its tile dimensions. + * @param start the cell index to test this `Equipment` for insertion + * @param w the width of the `Equipment` to be tested + * @param h the height of the `Equipment` to be tested + * @return a `List` of existing items that an item of this scale would overlap if inserted + */ + def CheckCollisionsVar(start : Int, w : Int, h : Int) : Try[List[InventoryItem]] = { + if(items.size < w * h) { + CheckCollisionsAsList(start, w, h) + } + else { + CheckCollisionsAsGrid(start, w, h) } } @@ -145,10 +165,10 @@ class GridInventory { * @param start the cell index to test this `Equipment` for insertion * @param w the width of the `Equipment` to be tested * @param h the height of the `Equipment` to be tested - * @return a `List` of GUID values for all existing contents that this item would overlap if inserted + * @return a `List` of existing items that an item of this scale would overlap if inserted * @throws IndexOutOfBoundsException if the region extends outside of the grid boundaries */ - def CheckCollisionsAsList(start : Int, w : Int, h : Int) : Try[List[Int]] = { + def CheckCollisionsAsList(start : Int, w : Int, h : Int) : Try[List[InventoryItem]] = { val actualSlot = start - offset val startx : Int = actualSlot % width val starty : Int = actualSlot / width @@ -159,7 +179,7 @@ class GridInventory { Failure(new IndexOutOfBoundsException(s"requested region escapes the $bounds edge of the grid inventory - $startx, $starty; $w x $h")) } else { - val collisions : mutable.Set[Int] = mutable.Set[Int]() + val collisions : mutable.Set[InventoryItem] = mutable.Set[InventoryItem]() items.values.foreach({ item : InventoryItem => val actualItemStart : Int = item.start - offset val itemx : Int = actualItemStart % width @@ -168,7 +188,7 @@ class GridInventory { val clipsOnX : Boolean = if(itemx < startx) { itemx + tile.width > startx } else { itemx <= startw } val clipsOnY : Boolean = if(itemy < starty) { itemy + tile.height > starty } else { itemy <= starth } if(clipsOnX && clipsOnY) { - collisions += item.GUID.guid + collisions += item } }) Success(collisions.toList) @@ -183,10 +203,10 @@ class GridInventory { * @param start the cell index to test this `Equipment` for insertion * @param w the width of the `Equipment` to be tested * @param h the height of the `Equipment` to be tested - * @return a `List` of GUID values for all existing contents that this item would overlap if inserted + * @return a `List` of existing items that an item of this scale would overlap if inserted * @throws IndexOutOfBoundsException if the region extends outside of the grid boundaries */ - def CheckCollisionsAsGrid(start : Int, w : Int, h : Int) : Try[List[Int]] = { + def CheckCollisionsAsGrid(start : Int, w : Int, h : Int) : Try[List[InventoryItem]] = { val actualSlot = start - offset if(actualSlot < 0 || actualSlot >= grid.length || (actualSlot % width) + w > width || (actualSlot / width) + h > height) { val startx : Int = actualSlot % width @@ -196,12 +216,12 @@ class GridInventory { Failure(new IndexOutOfBoundsException(s"requested region escapes the $bounds edge of the grid inventory - $startx, $starty; $w x $h")) } else { - val collisions : mutable.Set[Int] = mutable.Set[Int]() + val collisions : mutable.Set[InventoryItem] = mutable.Set[InventoryItem]() var curr = actualSlot for(_ <- 0 until h) { for(col <- 0 until w) { if(grid(curr + col) > -1) { - collisions += items(grid(curr + col)).GUID.guid + collisions += items(grid(curr + col)) } } curr += width diff --git a/common/src/main/scala/net/psforever/objects/inventory/InventoryEquipmentSlot.scala b/common/src/main/scala/net/psforever/objects/inventory/InventoryEquipmentSlot.scala index 5e4515ed5..575a43a10 100644 --- a/common/src/main/scala/net/psforever/objects/inventory/InventoryEquipmentSlot.scala +++ b/common/src/main/scala/net/psforever/objects/inventory/InventoryEquipmentSlot.scala @@ -4,40 +4,57 @@ package net.psforever.objects.inventory import net.psforever.objects.OffhandEquipmentSlot import net.psforever.objects.equipment.{Equipment, EquipmentSize} +import scala.util.{Failure, Success} + /** * A slot-like interface for a specific grid position in an inventory. - * The size is bound to anything that can be stowed, which encompasses most all `Equipment`. - * Furthermore, rather than operating on a fixed-size slot, this "slot" represents an inventory region that either includes `slot` or starts at `slot`. - * An object added to the underlying inventory from here can only be added with its initial point at `slot`. - * An object found at `slot`, however, can be removed even if the starting cell is prior to `slot.` + * The size is typically bound to anything that can be stowed which encompasses most all `Equipment`. + * The capacity of this `EquipmentSlot` is essentially treated as 1x1. + * Upon insertions, however, the capacity temporarily is treated as the size of the item being inserted (unless `None`). + * This allows a proper check for insertion collision.
+ *
+ * Rather than operating on a fixed-size slot, this "slot" represents an inventory region that either includes `slot` or starts at `slot`. + * When determining the contents of the inventory at `slot`, only that singular cell is checked. + * When removing an item from `slot`, the item in inventory only has to be positioned in such a way that overlaps with `slot`. + * When adding an item to `slot`, `slot` is treated as the upper left corner (the initial point) of a larger capacity region.
+ *
+ * The following diagrams demonstrate the coordinate association:
+ * `    - - - - -     - - - - -     - - - - -`
+ * `    - - - - -     - r r x -     - - - - -`
+ * `    - - s - -     - r r x -     - - i i -`
+ * `    - - - - -     - x x x -     - - i i -`
+ * `    - - - - -     - - - - -     - - - - -`
+ * ... where 's' is the 1x1 slot, + * 'r' is the corner of any 2x2 item that can be removed ('x' is a potential affected edge), + * and 'i' is the region checked for a 2x2 insertion into `slot`. */ class InventoryEquipmentSlot(private val slot : Int, private val inv : GridInventory) extends OffhandEquipmentSlot(EquipmentSize.Inventory) { - /** - * Attempt to stow an item into the inventory at the given position. - * @param assignEquipment the change in `Equipment` for this slot - * @return the `Equipment` in this slot - */ override def Equipment_=(assignEquipment : Option[Equipment]) : Option[Equipment] = { assignEquipment match { case Some(equip) => - inv += slot -> equip + val tile = equip.Definition.Tile + inv.CheckCollisionsVar(slot, tile.Width, tile.Height) match { + case Success(Nil) => inv += slot -> equip + case _ => ; + } + case None => inv -= slot } Equipment } - /** - * Determine what `Equipment`, if any, is stowed in the inventory in the given position. - * @return the `Equipment` in this slot - */ override def Equipment : Option[Equipment] = { - inv.Items.find({ case ((_, item : InventoryItem)) => item.start == slot }) match { - case Some((_, item : InventoryItem)) => - Some(item.obj) - case None => + inv.CheckCollisionsAsGrid(slot,1,1) match { + case Success(list) => + list.headOption match { + case Some(found) => + Some(found.obj) + case None => + None + } + case Failure(_) => None } } } - diff --git a/common/src/test/scala/objects/InventoryTest.scala b/common/src/test/scala/objects/InventoryTest.scala index 52698fa5e..3af8a011d 100644 --- a/common/src/test/scala/objects/InventoryTest.scala +++ b/common/src/test/scala/objects/InventoryTest.scala @@ -59,14 +59,30 @@ class InventoryTest extends Specification { obj.Capacity mustEqual 45 val w = bullet9mmBox2.Tile.width val h = bullet9mmBox2.Tile.height - obj.CheckCollisionsAsList(0, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(1, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(2, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(3, w, h) mustEqual Success(Nil) - obj.CheckCollisionsAsGrid(0, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(1, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(2, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(3, w, h) mustEqual Success(Nil) + val list0 = obj.CheckCollisionsAsList(0, w, h) + list0 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list1 = obj.CheckCollisionsAsList(1, w, h) + list1 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list2 = obj.CheckCollisionsAsList(2, w, h) + list2 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list3 = obj.CheckCollisionsAsList(3, w, h) + list3 match { + case scala.util.Success(list) => list.isEmpty mustEqual true + case scala.util.Failure(_) => ko + } + obj.CheckCollisionsAsGrid(0, w, h) mustEqual list0 + obj.CheckCollisionsAsGrid(1, w, h) mustEqual list1 + obj.CheckCollisionsAsGrid(2, w, h) mustEqual list2 + obj.CheckCollisionsAsGrid(3, w, h) mustEqual list3 obj.Clear() ok } @@ -77,14 +93,30 @@ class InventoryTest extends Specification { obj.Capacity mustEqual 45 val w = bullet9mmBox2.Tile.width val h = bullet9mmBox2.Tile.height - obj.CheckCollisionsAsList(3, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(2, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(1, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(0, w, h) mustEqual Success(Nil) - obj.CheckCollisionsAsGrid(3, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(2, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(1, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(0, w, h) mustEqual Success(Nil) + val list0 = obj.CheckCollisionsAsList(3, w, h) + list0 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list1 = obj.CheckCollisionsAsList(2, w, h) + list1 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list2 = obj.CheckCollisionsAsList(1, w, h) + list2 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list3 = obj.CheckCollisionsAsList(0, w, h) + list3 match { + case scala.util.Success(list) => list.isEmpty mustEqual true + case scala.util.Failure(_) => ko + } + obj.CheckCollisionsAsGrid(3, w, h) mustEqual list0 + obj.CheckCollisionsAsGrid(2, w, h) mustEqual list1 + obj.CheckCollisionsAsGrid(1, w, h) mustEqual list2 + obj.CheckCollisionsAsGrid(0, w, h) mustEqual list3 obj.Clear() ok } @@ -95,14 +127,30 @@ class InventoryTest extends Specification { obj.Capacity mustEqual 45 val w = bullet9mmBox2.Tile.width val h = bullet9mmBox2.Tile.height - obj.CheckCollisionsAsList(0, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(9, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(18, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(27, w, h) mustEqual Success(Nil) - obj.CheckCollisionsAsGrid(0, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(9, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(18, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(27, w, h) mustEqual Success(Nil) + val list0 = obj.CheckCollisionsAsList(0, w, h) + list0 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list1 = obj.CheckCollisionsAsList(9, w, h) + list1 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list2 = obj.CheckCollisionsAsList(18, w, h) + list2 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list3 = obj.CheckCollisionsAsList(27, w, h) + list3 match { + case scala.util.Success(list) => list.isEmpty mustEqual true + case scala.util.Failure(_) => ko + } + obj.CheckCollisionsAsGrid(0, w, h) mustEqual list0 + obj.CheckCollisionsAsGrid(9, w, h) mustEqual list1 + obj.CheckCollisionsAsGrid(18, w, h) mustEqual list2 + obj.CheckCollisionsAsGrid(27, w, h) mustEqual list3 obj.Clear() ok } @@ -113,14 +161,100 @@ class InventoryTest extends Specification { obj.Capacity mustEqual 45 val w = bullet9mmBox2.Tile.width val h = bullet9mmBox2.Tile.height - obj.CheckCollisionsAsList(27, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(19, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(9, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsList(0, w, h) mustEqual Success(Nil) - obj.CheckCollisionsAsGrid(27, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(19, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(9, w, h) mustEqual Success(1 :: Nil) - obj.CheckCollisionsAsGrid(0, w, h) mustEqual Success(Nil) + val list0 = obj.CheckCollisionsAsList(27, w, h) + list0 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list1 = obj.CheckCollisionsAsList(18, w, h) + list1 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list2 = obj.CheckCollisionsAsList(9, w, h) + list2 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list3 = obj.CheckCollisionsAsList(0, w, h) + list3 match { + case scala.util.Success(list) => list.isEmpty mustEqual true + case scala.util.Failure(_) => ko + } + obj.CheckCollisionsAsGrid(27, w, h) mustEqual list0 + obj.CheckCollisionsAsGrid(18, w, h) mustEqual list1 + obj.CheckCollisionsAsGrid(9, w, h) mustEqual list2 + obj.CheckCollisionsAsGrid(0, w, h) mustEqual list3 + obj.Clear() + ok + } + + "check for item collision (diagonal insert)" in { + /* + Number indicates upper-left corner of attempted 3x3 insertion by list# + 0 - - - - - 2 - - - - - + - 1 - - - 3 - - - - - - + - - - - - - - - - - - - + - - - X X X - - - - - - + - - - X X X - - - - - - + - 5 - X X 7 - - - - - - + 4 - - - - - 6 - - - - - + - - - - - - - - - - - - + - - - - - - - - - - - - + */ + val obj : GridInventory = GridInventory(12, 9) + obj += 39 -> bullet9mmBox1 + obj.Capacity mustEqual 99 //108 - 9 + val w = bullet9mmBox2.Tile.width + val h = bullet9mmBox2.Tile.height + val list0 = obj.CheckCollisionsAsList(0, w, h) + list0 match { + case scala.util.Success(list) => list.isEmpty mustEqual true + case scala.util.Failure(_) => ko + } + val list1 = obj.CheckCollisionsAsList(13, w, h) + list1 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list2 = obj.CheckCollisionsAsList(6, w, h) + list2 match { + case scala.util.Success(list) =>list.isEmpty mustEqual true + case scala.util.Failure(_) => ko + } + val list3 = obj.CheckCollisionsAsList(17, w, h) + list3 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list4 = obj.CheckCollisionsAsList(72, w, h) + list4 match { + case scala.util.Success(list) => list.isEmpty mustEqual true + case scala.util.Failure(_) => ko + } + val list5 = obj.CheckCollisionsAsList(61, w, h) + list5 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + val list6 = obj.CheckCollisionsAsList(78, w, h) + list6 match { + case scala.util.Success(list) => list.isEmpty mustEqual true + case scala.util.Failure(_) => ko + } + val list7 = obj.CheckCollisionsAsList(65, w, h) + list7 match { + case scala.util.Success(list) => list.length mustEqual 1 + case scala.util.Failure(_) => ko + } + obj.CheckCollisionsAsGrid(0, w, h) mustEqual list0 + obj.CheckCollisionsAsGrid(13, w, h) mustEqual list1 + obj.CheckCollisionsAsGrid(6, w, h) mustEqual list2 + obj.CheckCollisionsAsGrid(17, w, h) mustEqual list3 + obj.CheckCollisionsAsGrid(72, w, h) mustEqual list4 + obj.CheckCollisionsAsGrid(61, w, h) mustEqual list5 + obj.CheckCollisionsAsGrid(78, w, h) mustEqual list6 + obj.CheckCollisionsAsGrid(65, w, h) mustEqual list7 obj.Clear() ok } diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index ae61a4cf3..52094defe 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -21,6 +21,7 @@ import net.psforever.packet.game.objectcreate._ import net.psforever.types._ import scala.annotation.tailrec +import scala.util.Success class WorldSessionActor extends Actor with MDCContextAware { private[this] val log = org.log4s.getLogger @@ -857,50 +858,68 @@ class WorldSessionActor extends Actor with MDCContextAware { player.Find(item_guid) match { case Some(index) => val indexSlot = player.Slot(index) + var itemOpt = indexSlot.Equipment //use this to short circuit + val item = itemOpt.get val destSlot = player.Slot(dest) - val item = indexSlot.Equipment.get - val destItem = destSlot.Equipment - indexSlot.Equipment = None - destSlot.Equipment = None - (destSlot.Equipment = item) match { - case Some(_) => //move item - log.info(s"MoveItem: $item_guid moved from $avatar_guid_1 @ $index to $avatar_guid_1 @ $dest") - //continue on to the code following the next match statement after resolving the match statement - destItem match { - case Some(item2) => //second item to swap? - (indexSlot.Equipment = destItem) match { - case Some(_) => //yes, swap - log.info(s"MoveItem: ${item2.GUID} swapped to $avatar_guid_1 @ $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 => //can't complete the swap; drop the other item on the ground - sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item2.GUID, player.Position, 0f, 0f, player.Orientation.z))) //ground - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, player.Position, player.Orientation, item2)) - } - - case None => ; //just move item over - } - sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(avatar_guid_1, item_guid, dest))) - if(0 <= dest && dest < 5) { - avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, dest, item)) - } - - case None => //restore original contents - indexSlot.Equipment = item - destSlot.Equipment = destItem + val destItem = if((-1 < dest && dest < 5) || dest == Player.FreeHandSlot) { + destSlot.Equipment match { + case Some(found) => + Some(InventoryItem(found, dest)) + case None => + None + } } - case 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 $avatar_guid_1 @ $index to $avatar_guid_1 @ $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 $avatar_guid_1 @ $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 orient = player.Orientation + DropItemOnGround(item2, pos, player.Orientation) + sendResponse(PacketCoding.CreateGamePacket(0, ObjectDetachMessage(player.GUID, item2.GUID, pos, 0f, 0f, orient.z))) //ground + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentOnGround(player.GUID, pos, orient, item2)) + } + + case None => //just move item over + destSlot.Equipment = item + } + sendResponse(PacketCoding.CreateGamePacket(0, ObjectAttachMessage(avatar_guid_1, item_guid, dest))) + if(0 <= dest && dest < 5) { + avatarService ! AvatarServiceMessage(player.Continent, AvatarAction.EquipmentInHand(player.GUID, dest, item)) + } + } + case None => + log.info(s"MoveItem: $avatar_guid_1 wanted to move the item $item_guid but could not find it") } case msg @ ChangeAmmoMessage(item_guid, unk1) =>