Files
Bagshui/Components/GameInfo.lua
T
OEVG 896f46f0d1
enUS Localization Update Announcement / localization-update-announcement (push) Canceled after 0s
Bagshui 1.6.4
2026-10-30 15:55:43 +00:00

387 lines
16 KiB
Lua

-- Bagshui WoW Game Information Loader
-- Exposes: BsGameInfo (and Bagshui.components.GameInfo)
-- Raises: BAGSHUI_GAME_UPDATE
Bagshui:AddComponent(function()
-- Information constructed here is used for localization and generation of default categories.
Bagshui:AddConstants({
BS_INVENTORY_SLOT_NAMES = {
HeadSlot = _G.INVTYPE_HEAD,
NeckSlot = _G.INVTYPE_NECK,
ShoulderSlot = _G.INVTYPE_SHOULDER,
BackSlot = _G.INVTYPE_CLOAK,
ChestSlot = _G.INVTYPE_CHEST,
ShirtSlot = _G.INVTYPE_SHIELD,
TabardSlot = _G.INVTYPE_TABARD,
WristSlot = _G.INVTYPE_WRIST,
HandsSlot = _G.INVTYPE_HAND,
WaistSlot = _G.INVTYPE_WAIST,
LegsSlot = _G.INVTYPE_LEGS,
FeetSlot = _G.INVTYPE_FEET,
Finger0Slot = _G.INVTYPE_FINGER,
Finger1Slot = _G.INVTYPE_FINGER,
Trinket0Slot = _G.INVTYPE_TRINKET,
Trinket1Slot = _G.INVTYPE_TRINKET,
MainHandSlot = _G.INVTYPE_WEAPONMAINHAND,
SecondaryHandSlot = _G.INVTYPE_WEAPONOFFHAND,
RangedSlot = _G.INVTYPE_RANGED,
},
-- Map item subclasses to the skills they require.
-- Only needed when the subclass doesn't match the skill name.
BS_ITEM_SUBCLASS_TO_SKILL = {
["Fishing Pole"] = "Fishing",
["One-Handed Axes"] = "Axes",
["One-Handed Maces"] = "Maces",
["One-Handed Swords"] = "Swords",
["Plate"] = "Plate Mail",
["Shields"] = "Shield",
},
})
-- Prepare storage of game information.
local GameInfo = {
-- Character classes.
-- Keys: English UPPERCASE names
-- Values: English Proper Noun Case names
characterClasses = {
["DRUID"] = "Druid",
["HUNTER"] = "Hunter",
["MAGE"] = "Mage",
["PALADIN"] = "Paladin",
["PRIEST"] = "Priest",
["ROGUE"] = "Rogue",
["SHAMAN"] = "Shaman",
["WARLOCK"] = "Warlock",
["WARRIOR"] = "Warrior",
},
-- Item classes added here are not covered by GetAuctionItemClasses and
-- need to be localized manually in the locale file.
-- Key case must match the case of the item class as returned by the game.
itemClasses = {
["Key"] = "Key",
["Miscellaneous"] = "Miscellaneous",
["Quest"] = "Quest",
["Trade Goods"] = "Trade Goods",
},
-- Item subclasses added here are not covered by GetAuctionItemSubClasses.
itemSubclasses = {},
-- Need these mappings to call GetAuctionItemSubClasses.
itemSubClassIds = {
Weapon = 1,
Armor = 2,
Container = 3,
Projectile = 6,
Quiver = 7,
Recipe = 8,
},
-- List of inventory slot locations, used to generate rule function templates.
inventorySlots = {},
-- Class translation helper tables.
reverseTranslatedCharacterClasses = {},
lowercaseReverseTranslatedCharacterClasses = {},
lowercaseToNormalCaseReverseTranslatedCharacterClasses = {},
lowercaseLocalizedCharacterClasses = {},
lowercaseToNormalCaseLocalizedCharacterClasses = {},
-- Game state tracking.
currentZone = "",
currentRealZone = "",
currentSubZone = "",
currentMinimapZone = "",
playerGroupType = BS_GAME_PLAYER_GROUP_TYPE.SOLO,
isInInstance = nil, -- Game returns 1/nil so we're just going with it here.
instanceType = "none",
lootMethod = "freeforall",
masterLooterPartyId = nil,
}
Bagshui.environment.BsGameInfo = GameInfo
Bagshui.components.GameInfo = GameInfo
-- Populate item classes and subclasses.
GameInfo.itemClasses["Weapon"], GameInfo.itemClasses["Armor"], GameInfo.itemClasses["Container"], GameInfo.itemClasses["Consumable"], GameInfo.itemClasses["Trade Goods"], GameInfo.itemClasses["Projectile"], GameInfo.itemClasses["Quiver"], GameInfo.itemClasses["Recipe"], GameInfo.itemClasses["Reagent"], GameInfo.itemClasses["Miscellaneous"] =
_G.GetAuctionItemClasses()
for itemClass, localizedItemClass in pairs(GameInfo.itemClasses) do
GameInfo.itemSubclasses[itemClass] = {}
end
GameInfo.itemSubclasses["Weapon"]["One-Handed Axes"], GameInfo.itemSubclasses["Weapon"]["Two-Handed Axes"], GameInfo.itemSubclasses["Weapon"]["Bows"], GameInfo.itemSubclasses["Weapon"]["Guns"], GameInfo.itemSubclasses["Weapon"]["One-Handed Maces"], GameInfo.itemSubclasses["Weapon"]["Two-Handed Maces"], GameInfo.itemSubclasses["Weapon"]["Polearms"], GameInfo.itemSubclasses["Weapon"]["One-Handed Swords"], GameInfo.itemSubclasses["Weapon"]["Two-Handed Swords"], GameInfo.itemSubclasses["Weapon"]["Staves"], GameInfo.itemSubclasses["Weapon"]["Fist Weapons"], GameInfo.itemSubclasses["Weapon"]["Miscellaneous"], GameInfo.itemSubclasses["Weapon"]["Daggers"], GameInfo.itemSubclasses["Weapon"]["Thrown"], GameInfo.itemSubclasses["Weapon"]["Crossbows"], GameInfo.itemSubclasses["Weapon"]["Wands"], GameInfo.itemSubclasses["Weapon"]["Fishing Pole"] =
_G.GetAuctionItemSubClasses(GameInfo.itemSubClassIds.Weapon)
GameInfo.itemSubclasses["Armor"]["Miscellaneous"], GameInfo.itemSubclasses["Armor"]["Cloth"], GameInfo.itemSubclasses["Armor"]["Leather"], GameInfo.itemSubclasses["Armor"]["Mail"], GameInfo.itemSubclasses["Armor"]["Plate"], GameInfo.itemSubclasses["Armor"]["Shields"], GameInfo.itemSubclasses["Armor"]["Librams"], GameInfo.itemSubclasses["Armor"]["Idols"], GameInfo.itemSubclasses["Armor"]["Totems"] =
_G.GetAuctionItemSubClasses(GameInfo.itemSubClassIds.Armor)
GameInfo.itemSubclasses["Container"]["Bag"], GameInfo.itemSubclasses["Container"]["Soul Bag"], GameInfo.itemSubclasses["Container"]["Herb Bag"], GameInfo.itemSubclasses["Container"]["Enchanting Bag"] =
_G.GetAuctionItemSubClasses(GameInfo.itemSubClassIds.Container)
GameInfo.itemSubclasses["Projectile"]["Arrow"], GameInfo.itemSubclasses["Projectile"]["Bullet"] =
_G.GetAuctionItemSubClasses(GameInfo.itemSubClassIds.Projectile)
GameInfo.itemSubclasses["Quiver"]["Quiver"], GameInfo.itemSubclasses["Quiver"]["Ammo Pouch"] =
_G.GetAuctionItemSubClasses(GameInfo.itemSubClassIds.Quiver)
GameInfo.itemSubclasses["Recipe"]["Book"], GameInfo.itemSubclasses["Recipe"]["Leatherworking"], GameInfo.itemSubclasses["Recipe"]["Tailoring"], GameInfo.itemSubclasses["Recipe"]["Engineering"], GameInfo.itemSubclasses["Recipe"]["Blacksmithing"], GameInfo.itemSubclasses["Recipe"]["Cooking"], GameInfo.itemSubclasses["Recipe"]["Alchemy"], GameInfo.itemSubclasses["Recipe"]["First Aid"], GameInfo.itemSubclasses["Recipe"]["Enchanting"], GameInfo.itemSubclasses["Recipe"]["Fishing"] =
_G.GetAuctionItemSubClasses(GameInfo.itemSubClassIds.Recipe)
-- Item subclasses not provided by GetAuctionItemClasses which need to be localized manually in the locale file.
GameInfo.itemSubclasses["Key"]["Key"] = "Key"
GameInfo.itemSubclasses["Miscellaneous"]["Junk"] = "Junk"
GameInfo.itemSubclasses["Quest"]["Quest"] = "Quest"
GameInfo.itemSubclasses["Trade Goods"]["Devices"] = "Devices"
GameInfo.itemSubclasses["Trade Goods"]["Explosives"] = "Explosives"
GameInfo.itemSubclasses["Trade Goods"]["Parts"] = "Parts"
GameInfo.itemSubclasses["Trade Goods"]["Trade Goods"] = "Trade Goods"
-- Populate Inventory Slots.
-- Automatically build the English -> localized translations of inventory slot names.
-- (INVTYPE_X -> localized doesn't need to be stored because it's always available in global variables).
for globalVariable, englishName in pairs({
-- Armor.
INVTYPE_BODY = "Shirt",
INVTYPE_CHEST = "Chest",
INVTYPE_CLOAK = "Back",
INVTYPE_FEET = "Feet",
INVTYPE_FINGER = "Finger",
INVTYPE_HAND = "Hands",
INVTYPE_HEAD = "Head",
INVTYPE_LEGS = "Legs",
INVTYPE_NECK = "Neck",
INVTYPE_ROBE = "Chest",
INVTYPE_SHOULDER = "Shoulder",
INVTYPE_WAIST = "Waist",
INVTYPE_WRIST = "Wrist",
-- Weapons.
INVTYPE_RANGED = "Ranged",
INVTYPE_WEAPON = "One-Hand",
INVTYPE_2HWEAPON = "Two-Hand",
INVTYPE_WEAPONMAINHAND = "Main Hand",
INVTYPE_WEAPONOFFHAND = "Off Hand",
INVTYPE_SHIELD = "Off Hand",
INVTYPE_HOLDABLE = "IHeld In Off-hand",
INVTYPE_RELIC = "Relic",
-- Other.
INVTYPE_BAG = "Bag",
INVTYPE_TABARD = "Tabard",
INVTYPE_TRINKET = "Trinket",
}) do
GameInfo.inventorySlots[englishName] = _G[globalVariable]
end
--- Some game information can't be built until the Bagshui localization has been loaded.
function GameInfo:PopulatePostLocalizationInfo()
-- Professions to profession bags.
self.professionsToBags = {
[L.Enchanting] = L["Enchanting Bag"],
[L.Herbalism] = L["Herb Bag"],
}
-- Alphabetically sorted list of player classes.
self.sortedCharacterClasses = {}
for uppercaseClass, nounCaseClass in pairs(self.characterClasses) do
self.reverseTranslatedCharacterClasses[L[nounCaseClass]] = nounCaseClass
table.insert(self.sortedCharacterClasses, uppercaseClass)
end
table.sort(self.sortedCharacterClasses, function(a, b)
return L[self.characterClasses[a]] < L[self.characterClasses[b]]
end)
-- Provide a menu template for player classes.
-- Used by Category Editor and Inventory Edit Mode Direct Assignment menu.
self.characterClassMenu = {}
for _, class in ipairs(BsGameInfo.sortedCharacterClasses) do
table.insert(self.characterClassMenu, {
text = L[BsGameInfo.characterClasses[class]],
value = class,
})
end
-- Used by RequiresClass.
for _, nounCaseClass in pairs(self.characterClasses) do
self.lowercaseReverseTranslatedCharacterClasses[string.lower(L[nounCaseClass])] = string.lower(nounCaseClass)
self.lowercaseToNormalCaseReverseTranslatedCharacterClasses[string.lower(L[nounCaseClass])] = nounCaseClass
end
for _, nounCaseClass in pairs(self.characterClasses) do
self.lowercaseLocalizedCharacterClasses[string.lower(nounCaseClass)] = string.lower(L[nounCaseClass])
self.lowercaseToNormalCaseLocalizedCharacterClasses[string.lower(nounCaseClass)] = L[nounCaseClass]
end
end
--- Storage helper for `GameInfo:UpdateLocation()`.
--- Returns nil if the string is empty.
---@param str any
---@return any
local function nilForEmptyString(str)
return string.len(str or "") > 0 and str or nil
end
-- Tracking variables used to determine whether UpdateLocation() should raise BAGSHUI_GAME_UPDATE.
local oldZone, oldRealZone, oldSubZone, oldMinimapZone, oldInInstance, oldInstanceType
--- Store current location data.
function GameInfo:UpdateLocation()
oldZone = self.currentZone
oldRealZone = self.currentRealZone
oldSubZone = self.currentSubZone
oldMinimapZone = self.currentMinimapZone
oldInInstance = self.isInInstance
oldInstanceType = self.instanceType
self.currentZone = nilForEmptyString(_G.GetZoneText())
self.currentRealZone = nilForEmptyString(_G.GetRealZoneText())
self.currentSubZone = nilForEmptyString(_G.GetSubZoneText())
self.currentMinimapZone = nilForEmptyString(_G.GetMinimapZoneText())
self.isInInstance, self.instanceType = _G.IsInInstance()
if
oldZone ~= self.currentZone
or oldRealZone ~= self.currentRealZone
or oldSubZone ~= self.currentSubZone
or oldMinimapZone ~= self.currentMinimapZone
or oldInInstance ~= self.isInInstance
or oldInstanceType ~= self.instanceType
then
Bagshui:RaiseEvent("BAGSHUI_GAME_UPDATE")
end
end
-- Tracking variables used to determine whether UpdateLocation() should raise BAGSHUI_GAME_UPDATE.
local oldPlayerGroupType, oldLootMethod, oldMasterLooter
-- Reusable variable for `GetLootMethod()` return value.
local masterLooterPartyId
--- Store current group (as in party/raid) data.
function GameInfo:UpdateGroup()
oldPlayerGroupType = self.playerGroupType
oldLootMethod = self.lootMethod
oldMasterLooter = self.isMasterLooter
-- There's no GetGroupType() but we can infer it.=
self.playerGroupType = (
(_G.GetNumRaidMembers() or 0) > 0 and BS_GAME_PLAYER_GROUP_TYPE.RAID
or (_G.GetNumPartyMembers() or 0) > 0 and BS_GAME_PLAYER_GROUP_TYPE.PARTY
or BS_GAME_PLAYER_GROUP_TYPE.SOLO
)
-- Don't need the third return value from GetLootMethod() because
-- the second is 0 if the current player is the master looter.
self.lootMethod, masterLooterPartyId = _G.GetLootMethod()
self.isMasterLooter = (self.lootMethod == "master" and masterLooterPartyId == 0)
if
oldPlayerGroupType ~= self.playerGroupType
or oldLootMethod ~= self.lootMethod
or oldMasterLooter ~= self.isMasterLooter
then
Bagshui:RaiseEvent("BAGSHUI_GAME_UPDATE")
end
end
--- Zone output helper for the GameInfo slash handler.
--- Only prints both zones if they're different and non-nil.
---@param z1 string? First zone.
---@param z2 string? Second zone.
local function printNonNilZones(z1, z2)
local mainZone = z1 or z2
if not mainZone then
return
end
Bs:PrintBare(BS_INDENT .. mainZone)
if mainZone == z2 or not z2 then
return
end
Bs:PrintBare(BS_INDENT .. z2)
end
--- Event handling.
---@param event string Event identifier.
---@param arg1 any? Argument 1 from the event.
function GameInfo:OnEvent(event, arg1)
if event == "PLAYER_ENTERING_WORLD" then
self:UpdateLocation()
self:UpdateGroup()
return
end
if string.find(event, "ZONE") then
self:UpdateLocation()
return
end
if string.find(event, "PARTY") or string.find(event, "RAID") then
self:UpdateGroup()
return
end
if event == "BAGSHUI_LOCALIZATION_LOADED" then
self:PopulatePostLocalizationInfo()
return
end
end
--- Slash command helper called from `ItemInfo`'s slash handler.
--- This lets us have `/Bagshui Info Location` etc.
---@param tokens table Tokenized input from slash command.
---@return boolean handled `true` if ItemInfo shouldn't continue processing.
function GameInfo:HandleInfoSlash(tokens)
if
BsUtil.MatchLocalizedOrNon(tokens[2], "location")
or BsUtil.MatchLocalizedOrNon(tokens[2], "instance")
or BsUtil.MatchLocalizedOrNon(tokens[2], "zone")
then
Bs:Print(string.format(L.Symbol_Colon, L.Location))
Bs:PrintBare(string.format(L.Symbol_Colon, L.Zone))
printNonNilZones(self.currentZone, self.currentSubZone)
Bs:PrintBare(string.format(L.Symbol_Colon, L.Subzone))
printNonNilZones(self.currentSubZone, self.currentMinimapZone)
Bs:PrintBare(string.format(L.Symbol_Colon, L.Instance))
if not self.isInInstance or self.instanceType == "none" then
Bs:PrintBare(BS_INDENT .. L.NoneParenthesis)
else
Bs:PrintBare(BS_INDENT .. self.instanceType)
end
return true
elseif
BsUtil.MatchLocalizedOrNon(tokens[2], "group")
or BsUtil.MatchLocalizedOrNon(tokens[2], "party")
or BsUtil.MatchLocalizedOrNon(tokens[2], "loot")
or BsUtil.MatchLocalizedOrNon(tokens[2], "raid")
then
Bs:Print(string.format(L.Symbol_Colon, _G.GROUP))
Bs:PrintBare(BS_INDENT .. self.playerGroupType)
Bs:PrintBare(string.format(L.Symbol_Colon, _G.LOOT))
Bs:PrintBare(BS_INDENT .. string.format(L.Symbol_Colon, _G.LOOT_METHOD) .. " " .. self.lootMethod)
Bs:PrintBare(BS_INDENT .. "IsLootMaster? " .. tostring(self.isMasterLooter))
return true
end
end
Bagshui:RegisterEvent("BAGSHUI_LOCALIZATION_LOADED", GameInfo)
-- Location events.
Bagshui:RegisterEvent("PLAYER_ENTERING_WORLD", GameInfo)
Bagshui:RegisterEvent("MINIMAP_ZONE_CHANGED", GameInfo)
Bagshui:RegisterEvent("ZONE_CHANGED", GameInfo)
Bagshui:RegisterEvent("ZONE_CHANGED_NEW_AREA", GameInfo)
Bagshui:RegisterEvent("ZONE_CHANGED_INDOORS", GameInfo)
-- Group events.
Bagshui:RegisterEvent("PARTY_LOOT_METHOD_CHANGED", GameInfo)
Bagshui:RegisterEvent("PARTY_MEMBERS_CHANGED", GameInfo)
Bagshui:RegisterEvent("RAID_ROSTER_UPDATE", GameInfo)
end)