Upload files to "/"

This commit is contained in:
2026-07-23 03:37:22 +00:00
parent 9f6f4b6f27
commit 01dff34305
2 changed files with 208 additions and 100 deletions
+4 -4
View File
@@ -48,7 +48,7 @@ RelationshipsAchievements_List = {
{ id = 106, cat = 1, name = "Money in the Bank", desc = "Use a bank for the first time.", points = 10, icon = "Interface\\Icons\\INV_Misc_Bag_15" },
{ id = 107, cat = 1, name = "Big Spender", desc = "Purchase an item from an auction house.", points = 10, icon = "Interface\\Icons\\INV_Misc_Coin_02" },
{ id = 108, cat = 1, name = "Take a Chill Pill", desc = "Bind to an inn.", points = 5, icon = "Interface\\Icons\\INV_Misc_Rune_01" },
{ id = 109, cat = 1, name = "Represent", desc = "Equip a tabard.", points = 10, icon = "Interface\\Icons\\INV_Shirt_GuildTabard_01" },
{ id = 109, cat = 1, name = "Represent", desc = "Equip a tabard.", points = 10, icon = "Interface\\Icons\\INV_Shirt_Guildtabard_01" },
{ id = 110, cat = 1, name = "Mailbox Hero", desc = "Send your first piece of mail.", points = 5, icon = "Interface\\Icons\\INV_Letter_04" },
{ id = 111, cat = 1, name = "Trainee", desc = "Visit a class trainer.", points = 5, icon = "Interface\\Icons\\INV_Misc_Book_11" },
@@ -223,7 +223,7 @@ RelationshipsAchievements_List = {
{ id = 707, cat = 7, name = "Master of the Bandage",desc = "Reach 300 in First Aid.", points = 10, icon = "Interface\\Icons\\INV_Misc_Bandage_15" },
{ id = 708, cat = 7, name = "Master Alchemist", desc = "Reach 300 in Alchemy.", points = 10, icon = "Interface\\Icons\\Trade_Alchemy" },
{ id = 709, cat = 7, name = "Master Blacksmith", desc = "Reach 300 in Blacksmithing.", points = 10, icon = "Interface\\Icons\\Trade_BlackSmithing" },
{ id = 710, cat = 7, name = "Master Enchanter", desc = "Reach 300 in Enchanting.", points = 10, icon = "Interface\\Icons\\INV_Enchant_ShardBrilliantLarge" },
{ id = 710, cat = 7, name = "Master Enchanter", desc = "Reach 300 in Enchanting.", points = 10, icon = "Interface\\Icons\\Trade_Engraving" },
{ id = 711, cat = 7, name = "Master Engineer", desc = "Reach 300 in Engineering.", points = 10, icon = "Interface\\Icons\\Trade_Engineering" },
{ id = 712, cat = 7, name = "Master Herbalist", desc = "Reach 300 in Herbalism.", points = 10, icon = "Interface\\Icons\\INV_Misc_Herb_07" },
{ id = 713, cat = 7, name = "Master Leatherworker", desc = "Reach 300 in Leatherworking.", points = 10, icon = "Interface\\Icons\\Trade_LeatherWorking" },
@@ -335,14 +335,14 @@ RelationshipsAchievements_List = {
{ id = 1601, cat = 16, name = "One Life to Live", desc = "Roll a Hardcore character.", points = 10, icon = "Interface\\Icons\\Spell_Holy_GreaterHeal" },
{ id = 1602, cat = 16, name = "Survivor", desc = "Reach level 20 on a Hardcore character.", points = 15, icon = "Interface\\Icons\\Spell_Holy_Heal" },
{ id = 1603, cat = 16, name = "Untouchable", desc = "Reach level 40 on a Hardcore character.", points = 25, icon = "Interface\\Icons\\Spell_Holy_Renew" },
{ id = 1604, cat = 16, name = "Hardcore Hero", desc = "Reach level 60 on a Hardcore character.", points = 50, icon = "Interface\\Icons\\Spell_Holy_Resurrection" },
{ id = 1604, cat = 16, name = "Hardcore Hero", desc = "Reach level 60 on a Hardcore character.", points = 50, icon = "Interface\\Icons\\Spell_Holy_GuardianSpirit" },
{ id = 1605, cat = 16, name = "Close Call", desc = "Survive dropping below 10% health.", points = 10, icon = "Interface\\Icons\\Spell_Shadow_Twilight" },
{ id = 1606, cat = 16, name = "No Retreat", desc = "Defeat an elite of your level solo.", points = 15, icon = "Interface\\Icons\\Ability_Warrior_Innerrage" },
-- ============ SOCIAL ============
{ id = 1701, cat = 17, name = "Say Hello", desc = "Emote /wave at another player.", points = 5, icon = "Interface\\Icons\\INV_Misc_Head_Human_02" },
{ id = 1702, cat = 17, name = "Dance Off", desc = "Use the /dance emote.", points = 5, icon = "Interface\\Icons\\INV_Shirt_GuildTabard_01" },
{ id = 1703, cat = 17, name = "Guildmate", desc = "Have 10 members in your guild.", points = 10, icon = "Interface\\Icons\\INV_Shirt_GuildTabard_01" },
{ id = 1703, cat = 17, name = "Guildmate", desc = "Have 10 members in your guild.", points = 10, icon = "Interface\\Icons\\INV_Shirt_Guildtabard_01" },
{ id = 1704, cat = 17, name = "Popular", desc = "Add 5 friends to your friends list.", points = 10, icon = "Interface\\Icons\\INV_Misc_Head_Human_02",
progress = { stat = "friendCount", max = 5 } },
{ id = 1705, cat = 17, name = "Chatty", desc = "Send 100 messages in chat.", points = 10, icon = "Interface\\Icons\\INV_Letter_04",
+187 -79
View File
@@ -14,12 +14,14 @@ local trackedEvents = {
"ZONE_CHANGED_INDOORS", "CHAT_MSG_COMBAT_HONOR_GAIN",
"CHAT_MSG_COMBAT_FACTION_CHANGE", "SKILL_LINES_CHANGED", "CHAT_MSG_LOOT",
"CHAT_MSG_MONEY", "CHAT_MSG_COMBAT_HOSTILE_DEATH", "CHAT_MSG_COMBAT_XP_GAIN",
"CHAT_MSG_SYSTEM", "CHAT_MSG_BG_SYSTEM_NEUTRAL", "CHAT_MSG_BG_SYSTEM_ALLIANCE", "CHAT_MSG_BG_SYSTEM_HORDE", "UPDATE_FACTION", "PARTY_MEMBERS_CHANGED", "FRIENDLIST_UPDATE",
"CHAT_MSG_SYSTEM", "UPDATE_FACTION", "PARTY_MEMBERS_CHANGED", "FRIENDLIST_UPDATE",
"GUILD_ROSTER_UPDATE", "PLAYER_UNGHOST", "PLAYER_ALIVE", "CHAT_MSG_TEXT_EMOTE",
"CHAT_MSG_EMOTE", "PLAYER_PVP_RANK_CHANGED", "PLAYER_PVP_KILLS_CHANGED",
"PLAYER_PVPKILLS_CHANGED", "HONOR_CURRENCY_UPDATE", "HEARTHSTONE_BOUND",
"BANKFRAME_OPENED", "TRAINER_SHOW", "MAIL_SEND_SUCCESS", "PLAYER_EQUIPMENT_CHANGED",
"BAG_UPDATE", "CHARACTER_POINTS_CHANGED", "SPELLS_CHANGED", "UNIT_HEALTH", "PLAYER_MONEY"
"BAG_UPDATE", "CHARACTER_POINTS_CHANGED", "SPELLS_CHANGED", "UNIT_HEALTH", "PLAYER_MONEY",
-- Vanilla 1.12 event names (PLAYER_EQUIPMENT_CHANGED does not exist here).
"UNIT_INVENTORY_CHANGED", "ADDON_LOADED", "MERCHANT_SHOW", "MERCHANT_CLOSED"
}
for i = 1, table.getn(trackedEvents) do SafeRegister(trackedEvents[i]) end
@@ -92,29 +94,48 @@ local function ScanTalentChange()
ScanTalents()
end
-- Extract item quality from a link's color code. Robust when GetItemInfo has
-- not cached the item yet (common in 1.12 right after login or equip).
local QUALITY_BY_COLOR = {
["ff9d9d9d"] = 0, ["ffffffff"] = 1, ["ff1eff00"] = 2,
["ff0070dd"] = 3, ["ffa335ee"] = 4, ["ffff8000"] = 5,
}
local function QualityFromLink(link)
if not link then return nil end
local _, _, color = string.find(link, "|c(%x%x%x%x%x%x%x%x)")
if not color then return nil end
return QUALITY_BY_COLOR[string.lower(color)]
end
local function ScanEquipmentAndBags()
if GetInventoryItemLink then
if GetInventoryItemLink("player", 4) then RelationshipsAchievements:Unlock(1203) end
if GetInventoryItemLink("player", 19) then RelationshipsAchievements:Unlock(109) end
local allRare, haveGear, epic = true, false, false
for slot = 1, 18 do
if slot ~= 4 and slot ~= 18 then
-- Well Equipped: every equipped armor/weapon slot must be Rare+.
-- Slots that are legitimately optional on a spec (shirt, tabard,
-- ranged, off-hand on 2H users) are skipped from the "all filled"
-- check but still count when present.
local requiredSlots = { 1,2,3,5,6,7,8,9,10,11,12,13,14,15,16 }
local optionalSlots = { 17, 18 } -- off-hand, ranged
local allRare, missing, epic = true, false, false
local function eval(slot, required)
local link = GetInventoryItemLink("player", slot)
if link then
haveGear = true
local _, _, quality = GetItemInfo(link)
if not link then
if required then missing = true end
return
end
local _, _, q = GetItemInfo(link)
local quality = q or QualityFromLink(link)
if quality and quality >= 4 then epic = true end
if not quality or quality < 3 then allRare = false end
if required and (not quality or quality < 3) then allRare = false end
local itemName = GetItemInfo(link)
if itemName == "Atiesh, Greatstaff of the Guardian" then RelationshipsAchievements:Unlock(1004) end
if itemName == "Thunderfury, Blessed Blade of the Windseeker" then RelationshipsAchievements:Unlock(1005) end
if itemName == "Sulfuras, Hand of Ragnaros" then RelationshipsAchievements:Unlock(1006) end
else
allRare = false
end
end
end
if haveGear and allRare then RelationshipsAchievements:Unlock(1208) end
for i = 1, table.getn(requiredSlots) do eval(requiredSlots[i], true) end
for i = 1, table.getn(optionalSlots) do eval(optionalSlots[i], false) end
if not missing and allRare then RelationshipsAchievements:Unlock(1208) end
if epic then RelationshipsAchievements:Unlock(1209) end
end
if GetContainerNumSlots then
@@ -129,24 +150,85 @@ local function ScanEquipmentAndBags()
end
end
-- Hidden tooltip used for spellbook scanning. Vanilla 1.12 does not expose
-- mount / non-combat-pet flags on spells, so we read the tooltip text — it
-- reliably distinguishes them across every locale-agnostic edge case we care
-- about because the mount / pet tooltip prefixes are stable in enUS clients.
local RA_ScanTip = CreateFrame("GameTooltip", "RelationshipsAchievementsScanTip", UIParent, "GameTooltipTemplate")
RA_ScanTip:SetOwner(UIParent, "ANCHOR_NONE")
local function TooltipText(index, book)
RA_ScanTip:ClearLines()
RA_ScanTip:SetSpell(index, book)
local text = ""
for line = 1, RA_ScanTip:NumLines() do
local fs = getglobal("RelationshipsAchievementsScanTipTextLeft"..line)
if fs then
local t = fs:GetText()
if t then text = text .. "\n" .. t end
end
end
return string.lower(text)
end
local function ScanSpellbook()
if not GetSpellName then return end
local book = BOOKTYPE_SPELL or "spell"
local mounts, pets = 0, 0
local hasEpicMount = false
local seen = {}
local i = 1
while true do
local name, rank = GetSpellName(i, BOOKTYPE_SPELL or "spell")
local name, rank = GetSpellName(i, book)
if not name then break end
local low = string.lower(name)
if string.find(low, "summon ") and (string.find(low, "horse") or string.find(low, "ram")
or string.find(low, "tiger") or string.find(low, "raptor") or string.find(low, "wolf")
or string.find(low, "kodo") or string.find(low, "mechanostrider")) then mounts = mounts + 1 end
if string.find(low, "summon ") and not string.find(low, "demon") and not string.find(low, "elemental") then pets = pets + 1 end
local key = name .. "||" .. (rank or "")
if not seen[key] then
seen[key] = true
local tip = TooltipText(i, book)
-- Mounts: tooltip includes "mount speed" or "riding" descriptor.
-- We match both the classic "increases speed by X%" line and the
-- explicit "summons and dismisses a rideable" phrasing.
local isMount = string.find(tip, "mount speed", 1, true)
or string.find(tip, "rideable", 1, true)
or string.find(tip, "summons and dismisses a rideable", 1, true)
-- Non-combat pets: tooltip contains "right click to dismiss"
-- or the classic "companion" descriptor. Explicitly exclude
-- combat pets (hunter/warlock), which say "summons your pet".
local isPet = (string.find(tip, "companion", 1, true)
or string.find(tip, "right click to dismiss", 1, true))
and not string.find(tip, "summons your", 1, true)
and not string.find(tip, "combat pet", 1, true)
if isMount then
mounts = mounts + 1
-- Epic ground mounts grant +100% speed in vanilla.
if string.find(tip, "100%", 1, true) or string.find(tip, "epic", 1, true) then
hasEpicMount = true
end
elseif isPet then
pets = pets + 1
end
end
i = i + 1
end
-- Fallback: Riding skill unambiguously proves mount ownership even if
-- tooltip scanning missed a custom mount (Turtle/OctoWoW variants).
if GetNumSkillLines then
for s = 1, GetNumSkillLines() do
local skill, header, _, rank = GetSkillLineInfo(s)
if not header and skill == "Riding" and rank and rank > 0 then
if mounts < 1 then mounts = 1 end
if rank >= 150 then hasEpicMount = true end
end
end
end
RelationshipsAchievements:UpdateStat("mountCount", mounts)
RelationshipsAchievements:UpdateStat("petCount", pets)
if mounts >= 1 then RelationshipsAchievements:Unlock(211) end
if mounts >= 1 and (UnitLevel("player") or 1) >= 60 then RelationshipsAchievements:Unlock(212) end
if hasEpicMount and (UnitLevel("player") or 1) >= 60 then
RelationshipsAchievements:Unlock(212)
end
if pets >= 1 then RelationshipsAchievements:Unlock(1204) end
end
@@ -326,44 +408,79 @@ if _origSendMail then SendMail = function(name, subject, body)
RelationshipsAchievements:Unlock(110)
end end
local _origPostAuction = PostAuction
if _origPostAuction then PostAuction = function(minBid, buyoutPrice, runTime, stackSize, numStacks)
_origPostAuction(minBid, buyoutPrice, runTime, stackSize, numStacks)
-- Auction House API is provided by Blizzard_AuctionUI, which is Load-on-Demand
-- in 1.12. Hooking at file-load time silently no-ops because the globals are
-- still nil. HookAuctionHouse() runs again on ADDON_LOADED "Blizzard_AuctionUI"
-- so the hooks attach as soon as the AH frame is opened for the first time.
local auctionHooked = false
local function HookAuctionHouse()
if auctionHooked then return end
if not PostAuction and not BuyoutAuction and not PlaceAuctionBid then return end
auctionHooked = true
if PostAuction then
local _orig = PostAuction
PostAuction = function(minBid, buyoutPrice, runTime, stackSize, numStacks)
_orig(minBid, buyoutPrice, runTime, stackSize, numStacks)
RelationshipsAchievements:AddStat("auctionsListed", numStacks or 1)
end end
local _origBuyoutAuction = BuyoutAuction
if _origBuyoutAuction then BuyoutAuction = function(index)
_origBuyoutAuction(index)
end
end
if BuyoutAuction then
local _orig = BuyoutAuction
BuyoutAuction = function(index)
_orig(index)
RelationshipsAchievements:Unlock(107)
RelationshipsAchievements:AddStat("auctionsWon", 1)
end end
local OnQuestComplete
-- Hook ConfirmBinder(): the innkeeper popup's OnAccept calls this API and it
-- fires exactly once per successful bind. The HEARTHSTONE_BOUND event does not
-- exist in vanilla 1.12, and the CHAT_MSG_SYSTEM string fallback below is
-- localised / worded differently on some Turtle/Octo builds, so hooking the
-- API is the only reliable trigger for re-binding to a new inn.
local _origConfirmBinder = ConfirmBinder
if _origConfirmBinder then ConfirmBinder = function()
_origConfirmBinder()
if RelationshipsAchievements and RelationshipsAchievements.Unlock then
RelationshipsAchievements:Unlock(108)
end
if RelationshipsAchievementsCharDB then
RelationshipsAchievementsCharDB.pendingInnBind = nil
end
-- Many AH addons (and the default UI in some 1.12 builds) route the
-- Buyout button through PlaceAuctionBid(list, index, bid) with bid ==
-- buyout price. Hook it too so wins are always counted.
if PlaceAuctionBid then
local _orig = PlaceAuctionBid
PlaceAuctionBid = function(listType, index, bid)
_orig(listType, index, bid)
local _, _, _, _, _, _, _, _, buyout = GetAuctionItemInfo(listType, index)
if buyout and bid and bid >= buyout and buyout > 0 then
RelationshipsAchievements:Unlock(107)
RelationshipsAchievements:AddStat("auctionsWon", 1)
end
end
end
end
HookAuctionHouse() -- attempt immediately in case the UI is already loaded.
-- ============ Vendor sales tracking ============
-- The merchant window is always the default UI; hooks are safe to install at
-- file-load time. Two vectors cover every sell path:
-- * UseContainerItem while MerchantFrame is visible = shift-click / right-
-- click sell from the bag.
-- * Drag-and-drop onto the merchant → PickupContainerItem then a click on
-- the merchant, which resolves via UseContainerItem too, so one hook
-- catches both cases.
local function MerchantOpen()
return MerchantFrame and MerchantFrame:IsVisible()
end
local _origUseContainerItem = UseContainerItem
if _origUseContainerItem then UseContainerItem = function(bag, slot, target)
local sellable = false
if MerchantOpen() and GetContainerItemLink then
local link = GetContainerItemLink(bag, slot)
-- Skip if slot is empty or item is bound-cannot-sell? A no-op sell is
-- harmless — the vendor simply refuses — so we count only when the
-- API had something to act on.
if link then sellable = true end
end
_origUseContainerItem(bag, slot, target)
if sellable then
RelationshipsAchievements:AddStat("vendorSales", 1)
end
end end
-- Hook GetQuestReward(): fires exactly once when the player accepts a quest
-- reward. Much more reliable than QUEST_COMPLETE (which fires each time the
-- reward window opens) for tracking completed-quest counters.
local _origGetQuestReward = GetQuestReward
if _origGetQuestReward then GetQuestReward = function(choice)
OnQuestComplete()
return _origGetQuestReward(choice)
local _origStaticPopupShow = StaticPopup_Show
if _origStaticPopupShow then StaticPopup_Show = function(which, text_arg1, text_arg2, data)
local popup = _origStaticPopupShow(which, text_arg1, text_arg2, data)
if which == "CONFIRM_BINDER" then RelationshipsAchievementsCharDB.pendingInnBind = true end
return popup
end end
-- ============ DoEmote hook (Say Hello / Dance Off) ============
@@ -410,7 +527,7 @@ local function OnLevelUp()
RelationshipsAchievements_ScanCharacter()
end
OnQuestComplete = function()
local function OnQuestComplete()
-- QUEST_COMPLETE is the reliable 1.12 turn-in event. Custom clients that
-- also emit QUEST_TURNED_IN are de-duplicated by time stamp below.
local now = time()
@@ -541,38 +658,22 @@ local function OnSystemMessage(msg)
if string.find(low, "trick or treat") then RelationshipsAchievements:Unlock(903) end
if string.find(low, "lunar festival") then RelationshipsAchievements:Unlock(909) end
if string.find(low, "winter veil") then RelationshipsAchievements:Unlock(901) end
-- Battleground victories. On 1.12 the win message reads "The Alliance wins!"
-- or "The Horde wins!" via CHAT_MSG_BG_SYSTEM_NEUTRAL and does NOT include
-- the map name, so we look up the current BG from GetRealZoneText() and
-- credit the player's faction only. Guard with pendingBgWin so duplicate
-- messages (both faction and neutral system chat) don't double-count.
if string.find(msg, "wins!") or string.find(msg, "[Vv]ictory") then
local zone = (GetRealZoneText and GetRealZoneText()) or ""
local _, myFaction = UnitFactionGroup("player")
local winner
if string.find(msg, "Alliance") then winner = "Alliance"
elseif string.find(msg, "Horde") then winner = "Horde"
end
local key = zone .. "|" .. (winner or "?")
if RelationshipsAchievementsCharDB and RelationshipsAchievementsCharDB.lastBgWinKey ~= key then
RelationshipsAchievementsCharDB.lastBgWinKey = key
if not winner or winner == myFaction then
if string.find(zone, "Warsong Gulch") then
-- Battleground victories (system messages contain "Warsong Gulch", etc.)
if string.find(msg, "[Vv]ictory") or string.find(msg, "wins!") then
if string.find(msg, "Warsong Gulch") then
RelationshipsAchievements:Unlock(505)
RelationshipsAchievements:AddStat("wsgWins", 1)
RelationshipsAchievements:AddStat("bgWins", 1)
elseif string.find(zone, "Alterac Valley") then
elseif string.find(msg, "Alterac Valley") then
RelationshipsAchievements:Unlock(506)
RelationshipsAchievements:AddStat("avWins", 1)
RelationshipsAchievements:AddStat("bgWins", 1)
elseif string.find(zone, "Arathi Basin") then
elseif string.find(msg, "Arathi Basin") then
RelationshipsAchievements:Unlock(507)
RelationshipsAchievements:AddStat("abWins", 1)
RelationshipsAchievements:AddStat("bgWins", 1)
end
end
end
end
end
-- Emote handler: catch /wave and /dance in every locale by matching the
@@ -621,10 +722,7 @@ frame:SetScript("OnEvent", function()
OnLootMessage(arg1)
elseif event == "CHAT_MSG_COMBAT_HOSTILE_DEATH" or event == "CHAT_MSG_COMBAT_XP_GAIN" then
OnEnemyDeath(arg1)
elseif event == "CHAT_MSG_SYSTEM"
or event == "CHAT_MSG_BG_SYSTEM_NEUTRAL"
or event == "CHAT_MSG_BG_SYSTEM_ALLIANCE"
or event == "CHAT_MSG_BG_SYSTEM_HORDE" then
elseif event == "CHAT_MSG_SYSTEM" then
OnSystemMessage(arg1)
elseif event == "CHAT_MSG_TEXT_EMOTE" or event == "CHAT_MSG_EMOTE" then
OnTextEmote(arg1, arg2)
@@ -642,8 +740,18 @@ frame:SetScript("OnEvent", function()
RelationshipsAchievements:Unlock(111)
elseif event == "MAIL_SEND_SUCCESS" then
RelationshipsAchievements:Unlock(110)
elseif event == "PLAYER_EQUIPMENT_CHANGED" or event == "BAG_UPDATE" then
elseif event == "PLAYER_EQUIPMENT_CHANGED"
or event == "UNIT_INVENTORY_CHANGED"
or event == "BAG_UPDATE" then
if event == "UNIT_INVENTORY_CHANGED" and arg1 ~= "player" then return end
ScanEquipmentAndBags()
elseif event == "ADDON_LOADED" then
if arg1 == "Blizzard_AuctionUI" then HookAuctionHouse() end
elseif event == "MERCHANT_SHOW" then
-- Rescan equipment when opening a vendor — buying gear or repairs
-- often coincides with equipping the item immediately after.
ScanEquipmentAndBags()
ScanSpellbook()
elseif event == "CHARACTER_POINTS_CHANGED" then
ScanTalentChange()
elseif event == "SPELLS_CHANGED" then