From f34cd33e2f4beb23b4a78463274fb80f6ebefcf5 Mon Sep 17 00:00:00 2001 From: Altoholic Local <5999549+uga@users.noreply.github.com> Date: Wed, 5 Aug 2026 19:19:52 +0200 Subject: [PATCH 1/2] Label every bag with the type it really is A user reported that Altoholic does not recognise some of the higher-end bags, the Satchel of Cenarius (a 24-slot herb bag) among them. It turned out that no bag type was ever resolved correctly, and that the ones which did show a type were showing the wrong one: a warlock's Soul Pouch was labelled "(Ammo Pouch)", while a hunter's Quickdraw Quiver got no label at all. ScanBag saves the item family as a bit index, because the field is only 5 bits wide and the raw family of a mining bag (1024) would never fit: if bagType and bagType > 0 then bagType = Log2(bagType) end but bagTypeStrings was keyed on the raw family instead: [4] = ... -- "Soul Bag", [32] = ... -- "Herb Bag" so the lookup in _GetContainerInfo could only ever match by accident. A soul bag saved 2 and picked up "Ammo Pouch", an inscription bag saved 4 and picked up "Soul Bag", and every other type saved a value the table had no key for, hence no label. Plain bags were fine only because they save a 0 that matches nothing. Key both tables on what ScanBag actually saves. The saved value is now offset by BAG_TYPE_OFFSET, for two reasons: - 0 has to keep meaning "no specific type", but a quiver is family 1, whose Log2 is also 0, so every ordinary bag would become a quiver; - data saved by earlier versions holds the plain Log2, 0 to 10. Placing the new range above it means those values match no key and read as "no type", which is what they already displayed, rather than picking up a wrong label until the character is scanned again. The highest value is now 26, so the 5 bits still hold it and the layout of bag.info is unchanged. Bags are rescanned on any BAG_UPDATE, so each character corrects itself on its next login. Co-Authored-By: Claude Opus 5 --- DataStore_Containers/DataStore_Containers.lua | 56 +++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/DataStore_Containers/DataStore_Containers.lua b/DataStore_Containers/DataStore_Containers.lua index 2a5baaf..3ff9788 100644 --- a/DataStore_Containers/DataStore_Containers.lua +++ b/DataStore_Containers/DataStore_Containers.lua @@ -75,6 +75,10 @@ local function Log2(n) return log(n) / log(2) end +-- Bag types are saved as Log2(item family) + this offset, so that they occupy 16 to 26 in the +-- 5 bits (0 to 31) reserved for them. See ScanBag for why the range starts there. +local BAG_TYPE_OFFSET = 16 + -- *** Scanning functions *** local function EmptyContainer(bagID) local bag = GetContainer(bagID) @@ -199,9 +203,15 @@ local function ScanBag(bagID) -- https://wowpedia.fandom.com/wiki/ItemFamily -- bag type will be 1024 for a mining bag for instance, it's just bit 11 in the item family (2^bit-1) -- we'll just save 10 using the Log2 - + -- .. shifted by BAG_TYPE_OFFSET, for two reasons: + -- - 0 must keep meaning "no specific type", but a quiver (family 1) would also save a 0 + -- - earlier versions saved the plain Log2 (0 to 10) while bagTypeStrings was keyed on the + -- item family itself, so those keys never matched. Staying clear of that range means + -- data saved by an earlier version reads as "no type" (what it already displayed) + -- instead of picking up a wrong label, until that character is scanned again. + if bagType and bagType > 0 then - bagType = Log2(bagType) + bagType = Log2(bagType) + BAG_TYPE_OFFSET end bag.info = (rarity or 0) -- bits 0-2 : rarity @@ -354,17 +364,18 @@ if interfaceVersion >= 110200 then end if isRetail then + -- keys are what ScanBag saves : the bit index of the item family, offset by BAG_TYPE_OFFSET bagTypeStrings = { - -- [1] = "Quiver", - -- [2] = "Ammo Pouch", - [4] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 1), -- "Soul Bag", - [8] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 7), -- "Leatherworking Bag", - [16] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 8), -- "Inscription Bag", - [32] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 2), -- "Herb Bag" - [64] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 3), -- "Enchanting Bag", - [128] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 4), -- "Engineering Bag", - [512] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 5), -- "Gem Bag", - [1024] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 6), -- "Mining Bag", + -- [BAG_TYPE_OFFSET + 0] = "Quiver", -- family 1 + -- [BAG_TYPE_OFFSET + 1] = "Ammo Pouch", -- family 2 + [BAG_TYPE_OFFSET + 2] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 1), -- "Soul Bag", family 4 + [BAG_TYPE_OFFSET + 3] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 7), -- "Leatherworking Bag", family 8 + [BAG_TYPE_OFFSET + 4] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 8), -- "Inscription Bag", family 16 + [BAG_TYPE_OFFSET + 5] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 2), -- "Herb Bag" family 32 + [BAG_TYPE_OFFSET + 6] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 3), -- "Enchanting Bag", family 64 + [BAG_TYPE_OFFSET + 7] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 4), -- "Engineering Bag", family 128 + [BAG_TYPE_OFFSET + 9] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 5), -- "Gem Bag", family 512 + [BAG_TYPE_OFFSET + 10] = C_Item.GetItemSubClassInfo(Enum.ItemClass.Container, 6), -- "Mining Bag", family 1024 } -- CharacterBankTab_x : 6 to 11, AccountBankTab_x : 12 to 16 @@ -373,17 +384,18 @@ if isRetail then -- end else + -- keys are what ScanBag saves : the bit index of the item family, offset by BAG_TYPE_OFFSET bagTypeStrings = { - [1] = "Quiver", - [2] = "Ammo Pouch", - [4] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 1), -- "Soul Bag", - [8] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 7), -- "Leatherworking Bag", - [16] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 8), -- "Inscription Bag", - [32] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 2), -- "Herb Bag" - [64] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 3), -- "Enchanting Bag", - [128] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 4), -- "Engineering Bag", - [512] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 5), -- "Gem Bag", - [1024] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 6), -- "Mining Bag", + [BAG_TYPE_OFFSET + 0] = "Quiver", -- family 1 + [BAG_TYPE_OFFSET + 1] = "Ammo Pouch", -- family 2 + [BAG_TYPE_OFFSET + 2] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 1), -- "Soul Bag", family 4 + [BAG_TYPE_OFFSET + 3] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 7), -- "Leatherworking Bag", family 8 + [BAG_TYPE_OFFSET + 4] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 8), -- "Inscription Bag", family 16 + [BAG_TYPE_OFFSET + 5] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 2), -- "Herb Bag" family 32 + [BAG_TYPE_OFFSET + 6] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 3), -- "Enchanting Bag", family 64 + [BAG_TYPE_OFFSET + 7] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 4), -- "Engineering Bag", family 128 + [BAG_TYPE_OFFSET + 9] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 5), -- "Gem Bag", family 512 + [BAG_TYPE_OFFSET + 10] = C_Item.GetItemSubClassInfo(LE_ITEM_CLASS_CONTAINER or Enum.ItemClass.Container, 6), -- "Mining Bag", family 1024 } end From 5f11a1620a2188ce4513958c5362ae2146cfdb37 Mon Sep 17 00:00:00 2001 From: Altoholic Local <5999549+uga@users.noreply.github.com> Date: Wed, 5 Aug 2026 20:58:33 +0200 Subject: [PATCH 2/2] Leave profession bags out of the free slot totals A hunter carrying a full quiver was reported as having eighty-four slots with twenty-one free, sixteen of which were quiver slots that hold nothing but ammunition. A warlock's soul pouch added twenty more of the same kind. The totals were arithmetically right and told the user something that was not true: how much room there is for ordinary things. The bag type is saved correctly now, so the two kinds can be told apart. Bags and bank bags that only take one family of items no longer count towards the totals of either. Nothing is hidden: each bag is still listed in the tooltip with its size, its free slots and, since the type is read properly, its name. A character's totals are rewritten by the scan that runs a few seconds after their next login. Co-Authored-By: Claude Opus 5 --- DataStore_Containers/DataStore_Containers.lua | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/DataStore_Containers/DataStore_Containers.lua b/DataStore_Containers/DataStore_Containers.lua index 3ff9788..eb1fd64 100644 --- a/DataStore_Containers/DataStore_Containers.lua +++ b/DataStore_Containers/DataStore_Containers.lua @@ -150,9 +150,16 @@ local function ScanBagSlotsInfo() for bagID = 0, COMMON_NUM_BAG_SLOTS, 1 do -- 5 Slots to include Reagent Bag local bag = GetContainer(bagID) local info = bag.info or 0 - - numSlots = numSlots + bit64:GetBits(info, 3, 7) -- bits 3-9 : bag size - freeSlots = freeSlots + bit64:GetBits(info, 10, 7) -- bits 10-16 : number of free slots in this bag + + -- A profession bag only takes what it was made for, so counting its slots here said a + -- character could carry more than they can : a hunter with a full quiver read as having + -- sixteen slots of room, a warlock's soul pouch added twenty. The bag type is saved + -- now, so the two kinds can be told apart. Each bag is still listed with its size and + -- its type in the tooltip, only these totals change. + if bit64:GetBits(info, 17, 5) == 0 then + numSlots = numSlots + bit64:GetBits(info, 3, 7) -- bits 3-9 : bag size + freeSlots = freeSlots + bit64:GetBits(info, 10, 7) -- bits 10-16 : number of free slots in this bag + end end char.bagInfo = numSlots -- bits 0-9 : num bag slots @@ -173,9 +180,13 @@ local function ScanBankSlotsInfo() -- Retail : 6 to 11, MoP : 6 to 12 for bagID = COMMON_NUM_BAG_SLOTS + 1, COMMON_NUM_BAG_SLOTS + (NUM_BANKBAGSLOTS or 6) do -- 6 to 12 local bag = GetContainer(bagID) - - numSlots = numSlots + bit64:GetBits(bag.info, 3, 7) -- bits 3-9 : bag size - freeSlots = freeSlots + bit64:GetBits(bag.info, 10, 7) -- bits 10-16 : number of free slots in this bag + local info = bag.info or 0 -- a bank bag slot that was never filled has none + + -- same as the bags : a herb bag in the bank is not room for anything else + if bit64:GetBits(info, 17, 5) == 0 then + numSlots = numSlots + bit64:GetBits(info, 3, 7) -- bits 3-9 : bag size + freeSlots = freeSlots + bit64:GetBits(info, 10, 7) -- bits 10-16 : number of free slots in this bag + end end local numPurchasedSlots = isRetail