diff --git a/common/src/main/scala/net/psforever/objects/LockerContainer.scala b/common/src/main/scala/net/psforever/objects/LockerContainer.scala index f8b0de836..7793a6a5c 100644 --- a/common/src/main/scala/net/psforever/objects/LockerContainer.scala +++ b/common/src/main/scala/net/psforever/objects/LockerContainer.scala @@ -18,8 +18,6 @@ class LockerContainer extends Equipment with Container { def VisibleSlots : Set[Int] = Set.empty[Int] - def Fit(obj : Equipment) : Option[Int] = inventory.Fit(obj.Definition.Tile) - def Definition : EquipmentDefinition = GlobalDefinitions.locker_container } diff --git a/common/src/main/scala/net/psforever/objects/Player.scala b/common/src/main/scala/net/psforever/objects/Player.scala index 3a36445c9..f5642611e 100644 --- a/common/src/main/scala/net/psforever/objects/Player.scala +++ b/common/src/main/scala/net/psforever/objects/Player.scala @@ -154,7 +154,7 @@ class Player(private val core : Avatar) extends PlanetSideGameObject with Factio def Locker : LockerContainer = core.Locker - def Fit(obj : Equipment) : Option[Int] = { + override def Fit(obj : Equipment) : Option[Int] = { recursiveHolsterFit(holsters.iterator, obj.Size) match { case Some(index) => Some(index) diff --git a/common/src/main/scala/net/psforever/objects/inventory/Container.scala b/common/src/main/scala/net/psforever/objects/inventory/Container.scala index e216c5fd4..724500763 100644 --- a/common/src/main/scala/net/psforever/objects/inventory/Container.scala +++ b/common/src/main/scala/net/psforever/objects/inventory/Container.scala @@ -42,6 +42,10 @@ trait Container { */ def Find(guid : PlanetSideGUID) : Option[Int] = Inventory.Find(guid) + def Fit(obj : Equipment) : Option[Int] = Fit(obj.Definition.Tile) + + def Fit(tile : InventoryTile) : Option[Int] = Inventory.Fit(tile) + /** * A(n imperfect) reference to a generalized pool of the contained objects.
*
diff --git a/pslogin/src/main/scala/WorldSessionActor.scala b/pslogin/src/main/scala/WorldSessionActor.scala index ca46235ba..bc4efd582 100644 --- a/pslogin/src/main/scala/WorldSessionActor.scala +++ b/pslogin/src/main/scala/WorldSessionActor.scala @@ -2113,117 +2113,7 @@ class WorldSessionActor extends Actor with MDCContextAware { 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") - val player_guid = player.GUID - val sourceIsNotDestination : Boolean = source != destination //if source is destination, OCDM style is not required - //remove item from source - indexSlot.Equipment = None - source match { - case obj : Vehicle => - vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item_guid)) - case obj : Player => - if(obj.isBackpack || source.VisibleSlots.contains(index)) { //corpse being looted, or item was in hands - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item_guid)) - } - case _ => ; - } - - destItemEntry match { //do we have a swap item in the destination slot? - case Some(InventoryItem(item2, destIndex)) => //yes, swap - //cleanly shuffle items around to avoid losing icons - //the next ObjectDetachMessage is necessary to avoid icons being lost, but only as part of this swap - sendResponse(ObjectDetachMessage(source_guid, item_guid, Vector3.Zero, 0f, 0f, 0f)) - val item2_guid = item2.GUID - destination.Slot(destIndex).Equipment = None //remove the swap item from destination - (indexSlot.Equipment = item2) match { - case Some(_) => //item and item2 swapped places successfully - log.info(s"MoveItem: $item2_guid swapped to $source_guid @ $index") - //remove item2 from destination - sendResponse(ObjectDetachMessage(destination_guid, item2_guid, Vector3.Zero, 0f, 0f, 0f)) - destination match { - case obj : Vehicle => - vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid)) - case obj : Player => - if(obj.isBackpack || destination.VisibleSlots.contains(dest)) { //corpse being looted, or item was in hands - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item2_guid)) - } - case _ => ; - } - //display item2 in source - if(sourceIsNotDestination && player == source) { - val objDef = item2.Definition - sendResponse( - ObjectCreateDetailedMessage( - objDef.ObjectId, - item2_guid, - ObjectCreateMessageParent(source_guid, index), - objDef.Packet.DetailedConstructorData(item2).get - ) - ) - } - else { - sendResponse(ObjectAttachMessage(source_guid, item2_guid, index)) - } - source match { - case obj : Vehicle => - vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, source_guid, index, item2)) - case obj : Player => - if(source.VisibleSlots.contains(index)) { //item is put in hands - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(player_guid, source_guid, index, item2)) - } - else if(obj.isBackpack) { //corpse being given item - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.StowEquipment(player_guid, source_guid, index, item2)) - } - case _ => ; - } - - case None => //item2 does not fit; drop on ground - log.info(s"MoveItem: $item2_guid can not fit in swap location; dropping on ground @ ${source.Position}") - val pos = source.Position - val sourceOrientZ = source.Orientation.z - val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ) - continent.Actor ! Zone.DropItemOnGround(item2, pos, orient) - sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, 0f, 0f, sourceOrientZ)) //ground - val objDef = item2.Definition - destination match { - case obj : Vehicle => - vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid)) - case _ => ; - //Player does not require special case; the act of dropping forces the item and icon to change - } - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentOnGround(player_guid, pos, orient, objDef.ObjectId, item2_guid, objDef.Packet.ConstructorData(item2).get)) - } - - case None => ; - } - //move item into destination slot - destination.Slot(dest).Equipment = item - if(sourceIsNotDestination && player == destination) { - val objDef = item.Definition - sendResponse( - ObjectCreateDetailedMessage( - objDef.ObjectId, - item_guid, - ObjectCreateMessageParent(destination_guid, dest), - objDef.Packet.DetailedConstructorData(item).get - ) - ) - } - else { - sendResponse(ObjectAttachMessage(destination_guid, item_guid, dest)) - } - destination match { - case obj : Vehicle => - vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, destination_guid, dest, item)) - case obj : Player => - if(destination.VisibleSlots.contains(dest)) { //item is put in hands - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(player_guid, destination_guid, dest, item)) - } - else if(obj.isBackpack) { //corpse being given item - avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.StowEquipment(player_guid, destination_guid, dest, item)) - } - case _ => ; - } + PerformMoveItem(item, source, index, destination, dest, destItemEntry) } else if(!indexSlot.Equipment.contains(item)) { log.error(s"MoveItem: wanted to move $item_guid, but found unexpected ${indexSlot.Equipment.get} at source location") @@ -2250,7 +2140,39 @@ class WorldSessionActor extends Actor with MDCContextAware { } case msg @ LootItemMessage(item_guid, target_guid) => - log.info("LootItem: " + msg) + log.info(s"LootItem: $msg") + (continent.GUID(item_guid), continent.GUID(target_guid)) match { + case (Some(item : Equipment), Some(target : Container)) => + //figure out the source + ( + { + val findFunc : PlanetSideGameObject with Container => Option[(PlanetSideGameObject with Container, Option[Int])] = FindInLocalContainer(item_guid) + findFunc(player.Locker) + .orElse(findFunc(player)) + .orElse(accessedContainer match { + case Some(parent) => + findFunc(parent) + case None => + None + } + ) + }, target.Fit(item)) match { + case (Some((source, Some(index))), Some(dest)) => + PerformMoveItem(item, source, index, target, dest, None) + case (None, _) => + log.error(s"LootItem: can not find where $item is put currently") + case (_, None) => + log.error(s"LootItem: can not find somwhere to put $item in $target") + case _ => + log.error(s"LootItem: wanted to move $item_guid to $target_guid, but multiple problems were encountered") + } + case (Some(obj), _) => + log.warn(s"LootItem: item $obj is (probably) not lootable") + case (None, _) => + log.warn(s"LootItem: can not find $item_guid") + case (_, None) => + log.warn(s"LootItem: can not find where to put $item_guid") + } case msg @ AvatarImplantMessage(_, _, _, _) => //(player_guid, unk1, unk2, implant) => log.info("AvatarImplantMessage: " + msg) @@ -2286,6 +2208,13 @@ class WorldSessionActor extends Actor with MDCContextAware { } } + case Some(obj : Player) => + if(obj.isBackpack) { + log.info(s"UseItem: $player looting the corpse of $obj") + sendResponse(UseItemMessage(avatar_guid, unk1, object_guid, unk2, unk3, unk4, unk5, unk6, unk7, unk8, itemType)) + accessedContainer = Some(obj) + } + case Some(obj : Locker) => if(player.Faction == obj.Faction) { log.info(s"UseItem: $player accessing a locker") @@ -2385,7 +2314,8 @@ class WorldSessionActor extends Actor with MDCContextAware { } case msg @ UnuseItemMessage(player_guid, object_guid) => - log.info("UnuseItem: " + msg) + log.info(s"UnuseItem: $msg") + //TODO check for existing accessedContainer value? continent.GUID(object_guid) match { case Some(obj : Vehicle) => if(obj.AccessingTrunk.contains(player.GUID)) { @@ -3485,6 +3415,150 @@ class WorldSessionActor extends Actor with MDCContextAware { ) } + /** + * Given an item, and two places, one where the item currently is and one where the item will be moved, + * perform a controlled transfer of the item. + * If something exists at the `destination` side of the transfer in the position that `item` will occupy, + * resolve its location as well by swapping it with where `item` originally was positioned.
+ *
+ * Parameter checks will not be performed. + * Do perform checks before sending data to this function. + * Do not call with incorrect or unverified data, e.g., `item` not actually being at `source` @ `index`. + * @param item the item being moved + * @param source the container in which `item` is currently located + * @param index the index position in `source` where `item` is currently located + * @param destination the container where `item` is being moved + * @param dest the index position in `destination` where `item` is being moved + * @param destinationCollisionEntry information about the contents in an area of `destination` starting at index `dest` + */ + private def PerformMoveItem(item : Equipment, + source : PlanetSideGameObject with Container, + index : Int, + destination : PlanetSideGameObject with Container, + dest : Int, + destinationCollisionEntry : Option[InventoryItem]) : Unit = { + val item_guid = item.GUID + val source_guid = source.GUID + val destination_guid = destination.GUID + val indexSlot = source.Slot(index) + val player_guid = player.GUID + val sourceIsNotDestination : Boolean = source != destination //if source is destination, OCDM style is not required + if(sourceIsNotDestination) { + log.info(s"MoveItem: $item moved from $source @ $index to $destination @ $dest") + } + else { + log.info(s"MoveItem: $item moved from $index to $dest in $source") + } + //remove item from source + indexSlot.Equipment = None + source match { + case obj : Vehicle => + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item_guid)) + case obj : Player => + if(obj.isBackpack || source.VisibleSlots.contains(index)) { //corpse being looted, or item was in hands + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item_guid)) + } + case _ => ; + } + + destinationCollisionEntry match { //do we have a swap item in the destination slot? + case Some(InventoryItem(item2, destIndex)) => //yes, swap + //cleanly shuffle items around to avoid losing icons + //the next ObjectDetachMessage is necessary to avoid icons being lost, but only as part of this swap + sendResponse(ObjectDetachMessage(source_guid, item_guid, Vector3.Zero, 0f, 0f, 0f)) + val item2_guid = item2.GUID + destination.Slot(destIndex).Equipment = None //remove the swap item from destination + (indexSlot.Equipment = item2) match { + case Some(_) => //item and item2 swapped places successfully + log.info(s"MoveItem: $item2 swapped to $source @ $index") + //remove item2 from destination + sendResponse(ObjectDetachMessage(destination_guid, item2_guid, Vector3.Zero, 0f, 0f, 0f)) + destination match { + case obj : Vehicle => + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid)) + case obj : Player => + if(obj.isBackpack || destination.VisibleSlots.contains(dest)) { //corpse being looted, or item was in hands + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.ObjectDelete(player_guid, item2_guid)) + } + case _ => ; + } + //display item2 in source + if(sourceIsNotDestination && player == source) { + val objDef = item2.Definition + sendResponse( + ObjectCreateDetailedMessage( + objDef.ObjectId, + item2_guid, + ObjectCreateMessageParent(source_guid, index), + objDef.Packet.DetailedConstructorData(item2).get + ) + ) + } + else { + sendResponse(ObjectAttachMessage(source_guid, item2_guid, index)) + } + source match { + case obj : Vehicle => + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, source_guid, index, item2)) + case obj : Player => + if(source.VisibleSlots.contains(index)) { //item is put in hands + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(player_guid, source_guid, index, item2)) + } + else if(obj.isBackpack) { //corpse being given item + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.StowEquipment(player_guid, source_guid, index, item2)) + } + case _ => ; + } + + case None => //item2 does not fit; drop on ground + log.info(s"MoveItem: $item2 can not fit in swap location; dropping on ground @ ${source.Position}") + val pos = source.Position + val sourceOrientZ = source.Orientation.z + val orient : Vector3 = Vector3(0f, 0f, sourceOrientZ) + continent.Actor ! Zone.DropItemOnGround(item2, pos, orient) + sendResponse(ObjectDetachMessage(destination_guid, item2_guid, pos, 0f, 0f, sourceOrientZ)) //ground + val objDef = item2.Definition + destination match { + case obj : Vehicle => + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.UnstowEquipment(player_guid, item2_guid)) + case _ => ; + //Player does not require special case; the act of dropping forces the item and icon to change + } + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentOnGround(player_guid, pos, orient, objDef.ObjectId, item2_guid, objDef.Packet.ConstructorData(item2).get)) + } + + case None => ; + } + //move item into destination slot + destination.Slot(dest).Equipment = item + if(sourceIsNotDestination && player == destination) { + val objDef = item.Definition + sendResponse( + ObjectCreateDetailedMessage( + objDef.ObjectId, + item_guid, + ObjectCreateMessageParent(destination_guid, dest), + objDef.Packet.DetailedConstructorData(item).get + ) + ) + } + else { + sendResponse(ObjectAttachMessage(destination_guid, item_guid, dest)) + } + destination match { + case obj : Vehicle => + vehicleService ! VehicleServiceMessage(s"${obj.Actor}", VehicleAction.StowEquipment(player_guid, destination_guid, dest, item)) + case obj : Player => + if(destination.VisibleSlots.contains(dest)) { //item is put in hands + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.EquipmentInHand(player_guid, destination_guid, dest, item)) + } + else if(obj.isBackpack) { //corpse being given item + avatarService ! AvatarServiceMessage(continent.Id, AvatarAction.StowEquipment(player_guid, destination_guid, dest, item)) + } + case _ => ; + } + } + /** * After a weapon has finished shooting, determine if it needs to be sorted in a special way. * @param tool a weapon