Skip to content

Commit 774df8d

Browse files
committed
UseEquipmentSet unequips slots the set wants empty
Previously only `GUID_IGNORED` and `GUID_EMPTY` were both treated as "skip this slot," so equipment sets with cleared (but not ignored) slots couldn't be used to unequip — running a "naked" set returned `true` but left the player's gear on. Now `GUID_EMPTY` triggers a paperdoll-to-bag move. Implementation: - `Item::Swap::ToBag` — new atomic primitive (paperdoll → bag) using the same `FUN_INVENTORY_SWAP` engine helper as the equip variants, just with player+slot as the source side and a bag-encoded destination. - `Script_UseEquipmentSet` snapshots all empty bag slots at entry and consumes them sequentially. Snapshotting up front avoids the client-side stale-state collision where iterating `ResolveBag` per unequip would keep picking the same "first empty" slot — the client's bag state doesn't update between packet sends, so the second packet would target a slot the first had already filled, and the server would try to swap the previously-unequipped item back onto the wrong paperdoll slot ("This item cannot be equipped").
1 parent 36e1aa8 commit 774df8d

3 files changed

Lines changed: 85 additions & 1 deletion

File tree

src/equipmentset/Api.cpp

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
#include "Offsets.h"
3636
#include "Set.h"
3737
#include "event/Custom.h"
38+
#include "item/Location.h"
3839
#include "item/Swap.h"
3940

4041
#include <cstdint>
@@ -401,11 +402,54 @@ int __fastcall Script_UseEquipmentSet(void *L) {
401402
uint64_t guids[SLOT_COUNT];
402403
std::memcpy(guids, s->items, sizeof(guids));
403404

405+
// Snapshot empty bag slots up front. The CGItem state doesn't
406+
// update between packet sends (server has to acknowledge each
407+
// swap), so iterating the bag walk fresh per unequip would
408+
// repeatedly pick the same "first empty" slot — second packet
409+
// collides with what packet 1 just put there, and the server
410+
// tries to swap that item back onto the wrong paperdoll slot
411+
// → "This item cannot be equipped".
412+
struct FreeSlot {
413+
int bag;
414+
int slot;
415+
};
416+
FreeSlot freeSlots[64];
417+
int freeSlotCount = 0;
418+
for (int bag = 0; bag <= 4 && freeSlotCount < 64; ++bag) {
419+
const int slots = Item::Location::GetBagSlotCount(bag);
420+
for (int slot = 1; slot <= slots && freeSlotCount < 64; ++slot) {
421+
if (Item::Location::ResolveBag(L, bag, slot) == nullptr)
422+
freeSlots[freeSlotCount++] = {bag, slot};
423+
}
424+
}
425+
int nextFreeSlot = 0;
426+
404427
for (int i = 0; i < SLOT_COUNT; ++i) {
405428
const int targetSlot = i + 1;
406429
const uint64_t g = guids[i];
407-
if (g == GUID_EMPTY || g == GUID_IGNORED)
430+
431+
// GUID_IGNORED: leave whatever the player has in this slot
432+
// as-is. Distinct from GUID_EMPTY (= 0), which means "this
433+
// slot should be empty after the swap" — handled below.
434+
if (g == GUID_IGNORED)
435+
continue;
436+
437+
if (g == GUID_EMPTY) {
438+
// Set wants this slot empty. If it already is, no-op.
439+
// Otherwise unequip whatever's there into the next bag
440+
// slot we reserved during the pre-loop snapshot. If
441+
// we've exhausted the snapshot, silently skip — matches
442+
// the modern API which surfaces no per-slot reason.
443+
const uint8_t *equipped =
444+
Item::Location::ResolveEquipmentSlot(targetSlot);
445+
if (equipped == nullptr)
446+
continue;
447+
if (nextFreeSlot >= freeSlotCount)
448+
continue;
449+
const FreeSlot &fs = freeSlots[nextFreeSlot++];
450+
Item::Swap::ToBag(equipped, targetSlot, fs.bag, fs.slot);
408451
continue;
452+
}
409453

410454
const int loc = Locations::FindGUID(g);
411455
if (loc == 0)

src/item/Swap.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,38 @@ bool FromPaperdoll(const void *cgItem, int srcPaperdollSlot, int dstPaperdollSlo
199199
dstPaperdollSlot);
200200
}
201201

202+
bool ToBag(const void *cgItem, int srcPaperdollSlot, int dstBagID, int dstSlotInBag) {
203+
if (srcPaperdollSlot < 1 || srcPaperdollSlot > 19)
204+
return false;
205+
if (cgItem == nullptr)
206+
return false;
207+
208+
void *player = ResolvePlayer();
209+
if (player == nullptr)
210+
return false;
211+
uint32_t playerLo = 0, playerHi = 0;
212+
if (!ReadGuid(player, &playerLo, &playerHi))
213+
return false;
214+
215+
uint32_t dstContainerLo = 0, dstContainerHi = 0, dstLinear = 0;
216+
if (!EncodeBagSlot(dstBagID, dstSlotInBag,
217+
&dstContainerLo, &dstContainerHi, &dstLinear))
218+
return false;
219+
220+
uint32_t itemLo = 0, itemHi = 0;
221+
if (!ReadGuid(cgItem, &itemLo, &itemHi))
222+
return false;
223+
224+
auto fn = reinterpret_cast<SwapFn_t>(Offsets::FUN_INVENTORY_SWAP);
225+
fn(player,
226+
itemLo, itemHi,
227+
playerLo, playerHi,
228+
static_cast<uint32_t>(srcPaperdollSlot - 1),
229+
dstContainerLo, dstContainerHi, dstLinear,
230+
0);
231+
return true;
232+
}
233+
202234
bool MoveCount(void *L, int srcBag, int srcSlot, int dstBag, int dstSlot, int count) {
203235
// Vanilla protocol writes count as a single byte. Clamp at 255;
204236
// anything larger would either truncate silently (255 sent) or

src/item/Swap.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ namespace Item::Swap {
3232
// paperdoll slot `srcSlot` (1-based). Used for paperdoll-to-
3333
// paperdoll swaps like rings 11/12, trinkets 13/14.
3434
//
35+
// - `ToBag(item, srcPaperdollSlot, dstBag, dstSlot)` — item currently
36+
// in paperdoll slot, moving to a bag slot. The "unequip" direction
37+
// `UseEquipmentSet` needs for slots the set wants empty. Uses the
38+
// same atomic swap packet — server-side accept rules are identical
39+
// (bagID 0..4, dst slot must be valid for that bag).
40+
//
3541
// All slot args are 1-based Lua-facing; helpers do the
3642
// linear-slot conversion. Returns `false` on bad args (slot out of
3743
// range, no player object, missing instance block on the item or
@@ -44,6 +50,8 @@ bool FromBag(const void *cgItem, int bagID, int slotInBag, int dstPaperdollSlot)
4450

4551
bool FromPaperdoll(const void *cgItem, int srcPaperdollSlot, int dstPaperdollSlot);
4652

53+
bool ToBag(const void *cgItem, int srcPaperdollSlot, int dstBagID, int dstSlotInBag);
54+
4755
// General bag-to-bag swap — neither side is constrained to the
4856
// paperdoll. Same engine helper (`FUN_INVENTORY_SWAP`), which routes
4957
// to opcode 0x10D (same-container slot swap, e.g. backpack ↔ backpack)

0 commit comments

Comments
 (0)