From 09d834eafd32092b6c36c1338ead528f824d9bd5 Mon Sep 17 00:00:00 2001 From: Andrew James Date: Sun, 20 Oct 2024 16:54:33 +1100 Subject: [PATCH 1/4] Move anonymous namespace functions to the top of the file --- Source/inv.cpp | 163 +++++++++++++++++++++++++------------------------ 1 file changed, 84 insertions(+), 79 deletions(-) diff --git a/Source/inv.cpp b/Source/inv.cpp index 13292624af6..c066be281d1 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -631,6 +631,90 @@ std::optional FindSlotUnderCursor(Point cursorPosition) return {}; } +/** + * @brief Checks whether an item of the given size can be placed on the specified player's inventory slot. + * @param player The player whose inventory will be checked. + * @param slotIndex The 0-based index of the slot to put the item on. + * @param itemSize The size of the item to be checked. + * @return 'True' in case the item can be placed on the specified player's inventory slot and 'False' otherwise. + */ +bool CheckItemFitsInInventorySlot(const Player &player, int slotIndex, const Size &itemSize) +{ + int yy = (slotIndex > 0) ? (10 * (slotIndex / 10)) : 0; + + for (int j = 0; j < itemSize.height; j++) { + if (yy >= InventoryGridCells) { + return false; + } + int xx = (slotIndex > 0) ? (slotIndex % 10) : 0; + for (int i = 0; i < itemSize.width; i++) { + if (xx >= 10 || player.InvGrid[xx + yy] != 0) { + // The item is too wide to fit in the specified column, or one of the cells is occupied + return false; + } + xx++; + } + yy += 10; + } + return true; +} + +/** + * @brief Finds the first slot that could fit an item of the given size + * @param player Player whose inventory will be checked. + * @param itemSize Dimensions of the item. + * @return The first slot that could fit the item or an empty optional. + */ +std::optional FindSlotForItem(const Player &player, const Size &itemSize) +{ + if (itemSize.height == 1) { + for (int i = 30; i <= 39; i++) { + if (CheckItemFitsInInventorySlot(player, i, itemSize)) + return i; + } + for (int x = 9; x >= 0; x--) { + for (int y = 2; y >= 0; y--) { + if (CheckItemFitsInInventorySlot(player, 10 * y + x, itemSize)) + return 10 * y + x; + } + } + return {}; + } + + if (itemSize.height == 2) { + for (int x = 10 - itemSize.width; x >= 0; x--) { + for (int y = 0; y < 3; y++) { + if (CheckItemFitsInInventorySlot(player, 10 * y + x, itemSize)) + return 10 * y + x; + } + } + return {}; + } + + if (itemSize == Size { 1, 3 }) { + for (int i = 0; i < 20; i++) { + if (CheckItemFitsInInventorySlot(player, i, itemSize)) + return i; + } + return {}; + } + + if (itemSize == Size { 2, 3 }) { + for (int i = 0; i < 9; i++) { + if (CheckItemFitsInInventorySlot(player, i, itemSize)) + return i; + } + + for (int i = 10; i < 19; i++) { + if (CheckItemFitsInInventorySlot(player, i, itemSize)) + return i; + } + return {}; + } + + app_fatal(StrCat("Unknown item size: ", itemSize.width, "x", itemSize.height)); +} + void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool dropItem) { if (player._pmode > PM_WALK_SIDEWAYS) { @@ -1246,85 +1330,6 @@ bool AutoEquipEnabled(const Player &player, const Item &item) return true; } -namespace { -/** - * @brief Checks whether an item of the given size can be placed on the specified player's inventory slot. - * @param player The player whose inventory will be checked. - * @param slotIndex The 0-based index of the slot to put the item on. - * @param itemSize The size of the item to be checked. - * @return 'True' in case the item can be placed on the specified player's inventory slot and 'False' otherwise. - */ -bool CheckItemFitsInInventorySlot(const Player &player, int slotIndex, const Size &itemSize) -{ - int yy = (slotIndex > 0) ? (10 * (slotIndex / 10)) : 0; - - for (int j = 0; j < itemSize.height; j++) { - if (yy >= InventoryGridCells) { - return false; - } - int xx = (slotIndex > 0) ? (slotIndex % 10) : 0; - for (int i = 0; i < itemSize.width; i++) { - if (xx >= 10 || player.InvGrid[xx + yy] != 0) { - return false; - } - xx++; - } - yy += 10; - } - return true; -} - -std::optional FindSlotForItem(const Player &player, const Size &itemSize) -{ - if (itemSize.height == 1) { - for (int i = 30; i <= 39; i++) { - if (CheckItemFitsInInventorySlot(player, i, itemSize)) - return i; - } - for (int x = 9; x >= 0; x--) { - for (int y = 2; y >= 0; y--) { - if (CheckItemFitsInInventorySlot(player, 10 * y + x, itemSize)) - return 10 * y + x; - } - } - return {}; - } - - if (itemSize.height == 2) { - for (int x = 10 - itemSize.width; x >= 0; x--) { - for (int y = 0; y < 3; y++) { - if (CheckItemFitsInInventorySlot(player, 10 * y + x, itemSize)) - return 10 * y + x; - } - } - return {}; - } - - if (itemSize == Size { 1, 3 }) { - for (int i = 0; i < 20; i++) { - if (CheckItemFitsInInventorySlot(player, i, itemSize)) - return i; - } - return {}; - } - - if (itemSize == Size { 2, 3 }) { - for (int i = 0; i < 9; i++) { - if (CheckItemFitsInInventorySlot(player, i, itemSize)) - return i; - } - - for (int i = 10; i < 19; i++) { - if (CheckItemFitsInInventorySlot(player, i, itemSize)) - return i; - } - return {}; - } - - app_fatal(StrCat("Unknown item size: ", itemSize.width, "x", itemSize.height)); -} -} // namespace - bool CanFitItemInInventory(const Player &player, const Item &item) { return static_cast(FindSlotForItem(player, GetInventorySize(item))); From 38edb84ded0e2f05177688f4ddfa4b514e5dd8b1 Mon Sep 17 00:00:00 2001 From: Andrew James Date: Sun, 20 Oct 2024 21:08:17 +1100 Subject: [PATCH 2/4] Only use holditem if we're actually picking the item to hand --- Source/inv.cpp | 122 ++++++++++++++++++++++++++++--------------------- 1 file changed, 70 insertions(+), 52 deletions(-) diff --git a/Source/inv.cpp b/Source/inv.cpp index c066be281d1..7d20922cffd 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -735,20 +735,25 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool Item &holdItem = player.HoldItem; holdItem.clear(); + bool attemptedMove = false; bool automaticallyMoved = false; - bool automaticallyEquipped = false; - bool automaticallyUnequip = false; + SfxID successSound = SfxID::None; + HeroSpeech failedSpeech = HeroSpeech::ICantDoThat; // Default message if the player attempts to automove an item that can't go anywhere else if (r >= SLOTXY_HEAD && r <= SLOTXY_CHEST) { inv_body_loc invloc = MapSlotToInvBodyLoc(r); if (!player.InvBody[invloc].isEmpty()) { - holdItem = player.InvBody[invloc]; if (automaticMove) { - automaticallyUnequip = true; - automaticallyMoved = automaticallyEquipped = AutoPlaceItemInInventory(player, holdItem); - } - - if (!automaticMove || automaticallyMoved) { + attemptedMove = true; + automaticallyMoved = AutoPlaceItemInInventory(player, player.InvBody[invloc]); + if (automaticallyMoved) { + successSound = ItemInvSnds[ItemCAnimTbl[player.InvBody[invloc]._iCurs]]; + RemoveEquipment(player, invloc, false); + } else { + failedSpeech = HeroSpeech::IHaveNoRoom; + } + } else { + holdItem = player.InvBody[invloc]; RemoveEquipment(player, invloc, false); } } @@ -758,19 +763,28 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool unsigned ig = r - SLOTXY_INV_FIRST; int iv = std::abs(player.InvGrid[ig]) - 1; if (iv >= 0) { - holdItem = player.InvList[iv]; if (automaticMove) { - if (CanBePlacedOnBelt(player, holdItem)) { - automaticallyMoved = AutoPlaceItemInBelt(player, holdItem, true, &player == MyPlayer); - } else if (CanEquip(holdItem)) { + attemptedMove = true; + if (CanBePlacedOnBelt(player, player.InvList[iv])) { + automaticallyMoved = AutoPlaceItemInBelt(player, player.InvList[iv], true, &player == MyPlayer); + if (automaticallyMoved) { + successSound = SfxID::GrabItem; + player.RemoveInvItem(iv, false); + } else { + failedSpeech = HeroSpeech::IHaveNoRoom; + } + } else if (CanEquip(player.InvList[iv])) { + failedSpeech = HeroSpeech::IHaveNoRoom; // Default to saying "I have no room" if auto-equip fails + /* - * Move the respective InvBodyItem to inventory before moving the item from inventory - * to InvBody with AutoEquip. AutoEquip requires the InvBody slot to be empty. - * First identify the correct InvBody slot and store it in invloc. + * If the player shift-clicks an item in the inventory we want to swap it with whatever item may be + * equipped in the target slot. Lifting the item to the hand unconditionally would be ideal, except + * we don't want to leave the item on the hand if the equip attempt failed. We would end up + * generating wasteful network messages if we did the lift first. Instead we work out whatever slot + * needs to be unequipped (if any): */ - automaticallyUnequip = true; // Switch to say "I have no room when inventory is too full" int invloc = NUM_INVLOC; - switch (player.GetItemLocation(holdItem)) { + switch (player.GetItemLocation(player.InvList[iv])) { case ILOC_ARMOR: invloc = INVLOC_CHEST; break; @@ -782,17 +796,19 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool break; case ILOC_ONEHAND: if (!player.InvBody[INVLOC_HAND_LEFT].isEmpty() - && (holdItem._iClass == player.InvBody[INVLOC_HAND_LEFT]._iClass + && (player.InvList[iv]._iClass == player.InvBody[INVLOC_HAND_LEFT]._iClass || player.GetItemLocation(player.InvBody[INVLOC_HAND_LEFT]) == ILOC_TWOHAND)) { // The left hand is not empty and we're either trying to equip the same type of item or // it's holding a two handed weapon, so it must be unequipped invloc = INVLOC_HAND_LEFT; - } else if (!player.InvBody[INVLOC_HAND_RIGHT].isEmpty() && holdItem._iClass == player.InvBody[INVLOC_HAND_RIGHT]._iClass) { + } else if (!player.InvBody[INVLOC_HAND_RIGHT].isEmpty() && player.InvList[iv]._iClass == player.InvBody[INVLOC_HAND_RIGHT]._iClass) { // The right hand is not empty and we're trying to equip the same type of item, so we need // to unequip that item invloc = INVLOC_HAND_RIGHT; } - // otherwise one hand is empty so we can let the auto-equip code put the target item into that hand. + // otherwise one hand is empty (and we can let the auto-equip code put the target item into + // that hand) or we're playing a bard with two swords equipped and we're trying to auto-equip + // a shield (in which case the attempt will fail). break; case ILOC_TWOHAND: // Moving a two-hand item from inventory to InvBody requires emptying both hands. @@ -824,10 +840,11 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool } break; default: - automaticallyUnequip = false; // Switch to say "I can't do that" + // If the player is trying to equip a ring we want to say "I can't do that" if they don't already have a ring slot free. + failedSpeech = HeroSpeech::ICantDoThat; break; } - // Empty the identified InvBody slot (invloc) and hand over to AutoEquip + // Then empty the identified InvBody slot (invloc) and hand over to AutoEquip if (invloc != NUM_INVLOC) { if (!player.InvBody[invloc].isEmpty()) { if (AutoPlaceItemInInventory(player, player.InvBody[invloc])) { @@ -835,11 +852,14 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool } } } - automaticallyMoved = automaticallyEquipped = AutoEquip(player, holdItem, true, &player == MyPlayer); + automaticallyMoved = AutoEquip(player, player.InvList[iv], true, &player == MyPlayer); + if (automaticallyMoved) { + successSound = ItemInvSnds[ItemCAnimTbl[player.InvList[iv]._iCurs]]; + player.RemoveInvItem(iv, false); + } } - } - - if (!automaticMove || automaticallyMoved) { + } else { + holdItem = player.InvList[iv]; player.RemoveInvItem(iv, false); } } @@ -848,12 +868,17 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool if (r >= SLOTXY_BELT_FIRST) { Item &beltItem = player.SpdList[r - SLOTXY_BELT_FIRST]; if (!beltItem.isEmpty()) { - holdItem = beltItem; if (automaticMove) { - automaticallyMoved = AutoPlaceItemInInventory(player, holdItem); - } - - if (!automaticMove || automaticallyMoved) { + attemptedMove = true; + automaticallyMoved = AutoPlaceItemInInventory(player, beltItem); + if (automaticallyMoved) { + successSound = SfxID::GrabItem; + player.RemoveSpdBarItem(r - SLOTXY_BELT_FIRST); + } else { + failedSpeech = HeroSpeech::IHaveNoRoom; + } + } else { + holdItem = beltItem; player.RemoveSpdBarItem(r - SLOTXY_BELT_FIRST); } } @@ -868,31 +893,24 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool holdItem._iStatFlag = player.CanUseItem(holdItem); if (&player == MyPlayer) { - if (automaticallyEquipped) { - PlaySFX(ItemInvSnds[ItemCAnimTbl[holdItem._iCurs]]); - } else if (!automaticMove || automaticallyMoved) { - PlaySFX(SfxID::GrabItem); - } - - if (automaticMove) { - if (!automaticallyMoved) { - if (CanBePlacedOnBelt(player, holdItem) || automaticallyUnequip) { - player.SaySpecific(HeroSpeech::IHaveNoRoom); - } else { - player.SaySpecific(HeroSpeech::ICantDoThat); - } - } - - holdItem.clear(); + PlaySFX(SfxID::GrabItem); + NewCursor(holdItem); + } + if (dropItem) { + TryDropItem(); + } + } else if (automaticMove) { + if (automaticallyMoved) { + CalcPlrInv(player, true); + } + if (attemptedMove && &player == MyPlayer) { + if (automaticallyMoved) { + PlaySFX(successSound); } else { - NewCursor(holdItem); + player.SaySpecific(failedSpeech); } } } - - if (dropItem && !holdItem.isEmpty()) { - TryDropItem(); - } } void TryCombineNaKrulNotes(Player &player, Item ¬eItem) From 1012595e385028cc5fdc9b7ca976eb0898d76a7b Mon Sep 17 00:00:00 2001 From: Andrew James Date: Sun, 20 Oct 2024 21:08:40 +1100 Subject: [PATCH 3/4] Shuffle items around when auto-equipping so less inv space is required --- Source/inv.cpp | 54 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/Source/inv.cpp b/Source/inv.cpp index 7d20922cffd..ba9aef94090 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -636,9 +636,10 @@ std::optional FindSlotUnderCursor(Point cursorPosition) * @param player The player whose inventory will be checked. * @param slotIndex The 0-based index of the slot to put the item on. * @param itemSize The size of the item to be checked. + * @param itemIndexToIgnore can be used to check if an item of the given size would fit if the item with the given (positive) ID was removed. * @return 'True' in case the item can be placed on the specified player's inventory slot and 'False' otherwise. */ -bool CheckItemFitsInInventorySlot(const Player &player, int slotIndex, const Size &itemSize) +bool CheckItemFitsInInventorySlot(const Player &player, int slotIndex, const Size &itemSize, int itemIndexToIgnore) { int yy = (slotIndex > 0) ? (10 * (slotIndex / 10)) : 0; @@ -648,8 +649,8 @@ bool CheckItemFitsInInventorySlot(const Player &player, int slotIndex, const Siz } int xx = (slotIndex > 0) ? (slotIndex % 10) : 0; for (int i = 0; i < itemSize.width; i++) { - if (xx >= 10 || player.InvGrid[xx + yy] != 0) { - // The item is too wide to fit in the specified column, or one of the cells is occupied + if (xx >= 10 || !(player.InvGrid[xx + yy] == 0 || std::abs(player.InvGrid[xx + yy]) - 1 == itemIndexToIgnore)) { + // The item is too wide to fit in the specified column, or one of the cells is occupied (and not by the item we're planning on removing) return false; } xx++; @@ -663,18 +664,19 @@ bool CheckItemFitsInInventorySlot(const Player &player, int slotIndex, const Siz * @brief Finds the first slot that could fit an item of the given size * @param player Player whose inventory will be checked. * @param itemSize Dimensions of the item. + * @param itemIndexToIgnore Can be used if you want to find whether the new item would fit with this item removed, without performing unnecessary actions. * @return The first slot that could fit the item or an empty optional. */ -std::optional FindSlotForItem(const Player &player, const Size &itemSize) +std::optional FindSlotForItem(const Player &player, const Size &itemSize, int itemIndexToIgnore = -1) { if (itemSize.height == 1) { for (int i = 30; i <= 39; i++) { - if (CheckItemFitsInInventorySlot(player, i, itemSize)) + if (CheckItemFitsInInventorySlot(player, i, itemSize, itemIndexToIgnore)) return i; } for (int x = 9; x >= 0; x--) { for (int y = 2; y >= 0; y--) { - if (CheckItemFitsInInventorySlot(player, 10 * y + x, itemSize)) + if (CheckItemFitsInInventorySlot(player, 10 * y + x, itemSize, itemIndexToIgnore)) return 10 * y + x; } } @@ -684,7 +686,7 @@ std::optional FindSlotForItem(const Player &player, const Size &itemSize) if (itemSize.height == 2) { for (int x = 10 - itemSize.width; x >= 0; x--) { for (int y = 0; y < 3; y++) { - if (CheckItemFitsInInventorySlot(player, 10 * y + x, itemSize)) + if (CheckItemFitsInInventorySlot(player, 10 * y + x, itemSize, itemIndexToIgnore)) return 10 * y + x; } } @@ -693,7 +695,7 @@ std::optional FindSlotForItem(const Player &player, const Size &itemSize) if (itemSize == Size { 1, 3 }) { for (int i = 0; i < 20; i++) { - if (CheckItemFitsInInventorySlot(player, i, itemSize)) + if (CheckItemFitsInInventorySlot(player, i, itemSize, itemIndexToIgnore)) return i; } return {}; @@ -701,12 +703,12 @@ std::optional FindSlotForItem(const Player &player, const Size &itemSize) if (itemSize == Size { 2, 3 }) { for (int i = 0; i < 9; i++) { - if (CheckItemFitsInInventorySlot(player, i, itemSize)) + if (CheckItemFitsInInventorySlot(player, i, itemSize, itemIndexToIgnore)) return i; } for (int i = 10; i < 19; i++) { - if (CheckItemFitsInInventorySlot(player, i, itemSize)) + if (CheckItemFitsInInventorySlot(player, i, itemSize, itemIndexToIgnore)) return i; } return {}; @@ -715,6 +717,18 @@ std::optional FindSlotForItem(const Player &player, const Size &itemSize) app_fatal(StrCat("Unknown item size: ", itemSize.width, "x", itemSize.height)); } +/** + * @brief Checks if the given item could be placed on the specified players inventory if the other item was removed. + * @param player The player whose inventory will be checked. + * @param item The item to be checked. + * @param itemIndexToIgnore The inventory index of the item that we assume will be removed. + * @return 'True' if the item could fit with the other item removed and 'False' otherwise. + */ +bool CouldFitItemInInventory(const Player &player, const Item &item, int itemIndexToIgnore) +{ + return static_cast(FindSlotForItem(player, GetInventorySize(item), itemIndexToIgnore)); +} + void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool dropItem) { if (player._pmode > PM_WALK_SIDEWAYS) { @@ -829,7 +843,7 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool // No space to move right hand item to inventory, abort. break; } - if (!CanFitItemInInventory(player, player.InvBody[INVLOC_HAND_LEFT])) { + if (!CouldFitItemInInventory(player, player.InvBody[INVLOC_HAND_LEFT], iv)) { // No space for left item. Move back right item to right hand and abort. player.InvBody[INVLOC_HAND_RIGHT] = player.InvList[player._pNumInv - 1]; player.RemoveInvItem(player._pNumInv - 1, false); @@ -845,17 +859,23 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool break; } // Then empty the identified InvBody slot (invloc) and hand over to AutoEquip - if (invloc != NUM_INVLOC) { - if (!player.InvBody[invloc].isEmpty()) { - if (AutoPlaceItemInInventory(player, player.InvBody[invloc])) { - player.InvBody[invloc].clear(); - } - } + if (invloc != NUM_INVLOC + && !player.InvBody[invloc].isEmpty() + && CouldFitItemInInventory(player, player.InvBody[invloc], iv)) { + holdItem = player.InvBody[invloc].pop(); } automaticallyMoved = AutoEquip(player, player.InvList[iv], true, &player == MyPlayer); if (automaticallyMoved) { successSound = ItemInvSnds[ItemCAnimTbl[player.InvList[iv]._iCurs]]; player.RemoveInvItem(iv, false); + + // If we're holding an item at this point we just lifted it from a body slot to make room for the original item, so we need to put it into the inv + if (!holdItem.isEmpty() && AutoPlaceItemInInventory(player, holdItem)) { + holdItem.clear(); + } // there should never be a situation where holdItem is not empty but we fail to place it into the inventory given the checks earlier... leave it on the hand in this case. + } else if (!holdItem.isEmpty()) { + // We somehow failed to equip the item in the slot we already checked should hold it? Better put this item back... + player.InvBody[invloc] = holdItem.pop(); } } } else { From ee3baf2e942d90dc0faa92a2788475761cec0c21 Mon Sep 17 00:00:00 2001 From: Andrew James Date: Mon, 21 Oct 2024 20:59:30 +1100 Subject: [PATCH 4/4] allow auto-equipping two-handed weapons with both hands occupied specifically where the larger item is in the right hand, this would fail previously in certain circumstances. --- Source/inv.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Source/inv.cpp b/Source/inv.cpp index ba9aef94090..6a88617c86a 100644 --- a/Source/inv.cpp +++ b/Source/inv.cpp @@ -837,20 +837,25 @@ void CheckInvCut(Player &player, Point cursorPosition, bool automaticMove, bool // the left hand), invloc isn't used there. invloc = INVLOC_HAND_RIGHT; } else { - // Both hands are holding items, we must unequip the right hand item and check that there's - // space for the left before trying to auto-equip - if (!AutoPlaceItemInInventory(player, player.InvBody[INVLOC_HAND_RIGHT])) { - // No space to move right hand item to inventory, abort. - break; + // Both hands are holding items, we must unequip one of the items and check that there's + // space for the other before trying to auto-equip + inv_body_loc mainHand = INVLOC_HAND_LEFT; + inv_body_loc offHand = INVLOC_HAND_RIGHT; + if (!AutoPlaceItemInInventory(player, player.InvBody[offHand])) { + // No space to move right hand item to inventory, can we move the left instead? + std::swap(mainHand, offHand); + if (!AutoPlaceItemInInventory(player, player.InvBody[offHand])) { + break; + } } - if (!CouldFitItemInInventory(player, player.InvBody[INVLOC_HAND_LEFT], iv)) { - // No space for left item. Move back right item to right hand and abort. - player.InvBody[INVLOC_HAND_RIGHT] = player.InvList[player._pNumInv - 1]; + if (!CouldFitItemInInventory(player, player.InvBody[mainHand], iv)) { + // No space for the main hand item. Move the other item back to the off hand and abort. + player.InvBody[offHand] = player.InvList[player._pNumInv - 1]; player.RemoveInvItem(player._pNumInv - 1, false); break; } - RemoveEquipment(player, INVLOC_HAND_RIGHT, false); - invloc = INVLOC_HAND_LEFT; + RemoveEquipment(player, offHand, false); + invloc = mainHand; } break; default: