Upload files to "/"

This commit is contained in:
2026-07-23 12:06:26 +00:00
parent 6d6cabadb6
commit e634c4fad2
3 changed files with 696 additions and 4 deletions
+548 -4
View File
@@ -21,7 +21,10 @@ local trackedEvents = {
"BANKFRAME_OPENED", "TRAINER_SHOW", "MAIL_SEND_SUCCESS", "PLAYER_EQUIPMENT_CHANGED",
"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"
"UNIT_INVENTORY_CHANGED", "ADDON_LOADED", "MERCHANT_SHOW", "MERCHANT_CLOSED",
"TIME_PLAYED_MSG", "UNIT_PET", "UNIT_MODEL_CHANGED", "LEARNED_SPELL_IN_TAB",
-- OctoWoW / Turtle backported Companion API (Pet menu "P" -> Companions tab)
"COMPANION_LEARNED", "COMPANION_UPDATE", "COMPANION_UNLEARNED", "PET_UI_UPDATE"
}
for i = 1, table.getn(trackedEvents) do SafeRegister(trackedEvents[i]) end
@@ -157,6 +160,128 @@ end
local RA_ScanTip = CreateFrame("GameTooltip", "RelationshipsAchievementsScanTip", UIParent, "GameTooltipTemplate")
RA_ScanTip:SetOwner(UIParent, "ANCHOR_NONE")
-- ============ Known companion names (Vanilla + TurtleWoW / OctoWoW) ============
-- Any spell whose name matches one of these entries is credited as a
-- non-combat pet even when its tooltip doesn't contain the standard
-- keywords. Extend this list as new companions are added to the server.
local RA_KNOWN_COMPANIONS_LIST = {
-- Vanilla non-combat pets
"Ancona Chicken", "Azure Whelpling", "Bombay Cat", "Black Kingsnake",
"Black Tabby", "Blue Dragonhawk Hatchling", "Brown Prairie Dog",
"Brown Rabbit", "Brown Snake", "Calico Cat", "Cat Carrier (Bombay)",
"Cat Carrier (Black Tabby)", "Cat Carrier (Cornish Rex)",
"Cat Carrier (Orange Tabby)", "Cat Carrier (Siamese)",
"Cat Carrier (Silver Tabby)", "Cat Carrier (White Kitten)",
"Chicken Egg", "Cockroach", "Cornish Rex", "Crimson Whelpling",
"Dark Whelpling", "Disgusting Oozeling", "Emerald Whelpling",
"Firefly", "Frog", "Golden Dragonhawk Hatchling", "Great Horned Owl",
"Green Wing Macaw", "Hawk Owl", "Hyacinth Macaw", "Island Frog",
"Jubling", "Lifelike Mechanical Toad", "Lifelike Toad",
"Little Fawn", "Mana Wyrmling", "Mechanical Squirrel Box",
"Mini Diablo", "Mulgore Hatchling", "Orange Tabby",
"Panda Cub", "Parrot Cage (Green Wing Macaw)",
"Parrot Cage (Hyacinth Macaw)", "Parrot Cage (Senegal)",
"Peddlefeet", "Personal World Destroyer", "Prairie Dog Whistle",
"Red Dragonhawk Hatchling", "Sprite Darter Hatchling",
"Senegal", "Siamese", "Silver Tabby", "Smolderweb Hatchling",
"Snowshoe Rabbit", "Snowy Owl", "Speedy", "Stinker",
"Tiny Green Dragon", "Tiny Red Dragon", "Tranquil Mechanical Yeti",
"Tree Frog Box", "Turquoise Turtle", "Wanderer's Companion",
"Westfall Chicken", "White Kitten", "Whiskers the Rat",
"Winter Reindeer", "Winter's Little Helper", "Worg Carrier",
"Worg Pup", "Wolpertinger", "Zergling",
-- Turtle WoW / OctoWoW additions (community-known companions)
"Baby Blizzard Bear", "Baby Crocolisk", "Baby Murloc",
"Baby Ogre", "Baby Shark", "Baby Tallstrider", "Baby Turtle",
"Bat Cub", "Bloodsail Cannonball", "Blue Moth", "Bronze Whelpling",
"Captured Firefly", "Clockwork Rocket Bot", "Cobra Hatchling",
"Coral Whelpling", "Corrupt Whelpling", "Deviate Hatchling",
"Elekk Plushie", "Ethereal Soul-Trader", "Fel Whelpling",
"Forest Frog", "Fox Kit", "Ghost Cat", "Ghostly Skull",
"Gorilla Hatchling", "Grunty", "Hippogryph Hatchling",
"Ivory Raptor", "Jade Whelpling", "Lil' Alexstrasza",
"Lil' Deathwing", "Lil' K.T.", "Lil' Ragnaros", "Lil' Smoky",
"Lil' Tarecgosa", "Lil' Timmy", "Lil' Wickerman", "Lil' XT",
"Lucky Cricket", "Magical Crawdad", "Mechanical Chicken",
"Micro Sentry", "Mini Tyrael", "Moonkin Hatchling",
"Murky", "Naga Hatchling", "Netherwhelp", "Nightsaber Cub",
"Onyx Whelpling", "Pandaren Monk", "Peanut", "Perky Pug",
"Piglet's Collar", "Pink Elekk", "Plague Rat", "Polar Bear Cub",
"Purple Turtle", "Quilen Cub", "Raptor Hatchling", "Red Panda",
"Reef Wanderer", "Riding Turtle Hatchling", "Sand Kitten",
"Sandbox Tiger", "Sandstone Drake", "Sea Pony", "Shore Crab",
"Silithid Hatchling", "Snowball", "Snowy Rabbit",
"Spectral Tiger Cub", "Stormwind Rat", "Swamp Frog",
"Tiny Emerald Whelpling", "Tiny Sporebat", "Tricorne",
"Turkey", "Undercity Cockroach", "Wisp", "Withers",
"Ziggy the Sifaka",
-- OctoWoW Companions tab (as shown in the "P" menu)
"Green Steam Tonk", "Purple Steam Tonk", "Blue Steam Tonk",
"Red Steam Tonk", "Yellow Steam Tonk", "Steam Tonk",
"Steam Tonk Controller", "Control Console",
"Lulu", "Mr. Wiggles", "Pet Bombling", "Lifelike Mechanical Toad",
"Tranquil Mechanical Yeti", "Mechanical Squirrel",
"Green Kitten", "Black Kitten", "Orange Kitten", "Cornish Rex Cat",
"Bombay Cat", "Siamese Cat", "Silver Tabby Cat",
}
local RA_KNOWN_COMPANIONS_SET = {}
for _, n in ipairs(RA_KNOWN_COMPANIONS_LIST) do
RA_KNOWN_COMPANIONS_SET[string.lower(n)] = true
end
-- Substring patterns that identify a companion regardless of colour / prefix.
-- Any spellbook or summoned pet name containing one of these (case-insensitive)
-- is treated as a known companion. Covers the OctoWoW / Turtle families where
-- every colour variant shares the same base name (e.g. "Green Steam Tonk",
-- "Purple Steam Tonk", "White Steam Tonk" all match "steam tonk").
local RA_COMPANION_PATTERNS = {
"steam tonk", "kitten", "whelpling", "hatchling", "cub",
"prairie dog", "rabbit", "squirrel", "toad", "frog",
"cockroach", "cat carrier", "mechanical squirrel",
"lil'", "lil ", "mini ", "tiny ", "baby ",
"companion", "critter",
}
function RA_IsKnownCompanionName(name)
if type(name) ~= "string" or name == "" then return false end
local lower = string.lower(name)
if RA_KNOWN_COMPANIONS_SET[lower] == true then return true end
for _, pat in ipairs(RA_COMPANION_PATTERNS) do
if string.find(lower, pat, 1, true) then return true end
end
return false
end
function RA_AddKnownCompanion(name)
if type(name) == "string" and name ~= "" then
RA_KNOWN_COMPANIONS_SET[string.lower(name)] = true
end
end
-- Credit a companion by name into the same store the tooltip path uses so
-- the total petCount stays consistent no matter which detection path found it.
function RA_CreditCompanionByName(name)
if not RA_IsKnownCompanionName(name) then return end
if not RelationshipsAchievementsCharDB then return end
if not RelationshipsAchievementsCharDB.countedPets then
RelationshipsAchievementsCharDB.countedPets = {}
end
RelationshipsAchievementsCharDB.countedPets[name] = true
local n = 0
for _ in pairs(RelationshipsAchievementsCharDB.countedPets) do n = n + 1 end
local companionPets = 0
if RelationshipsAchievementsCharDB.countedCompanions then
for _ in pairs(RelationshipsAchievementsCharDB.countedCompanions) do
companionPets = companionPets + 1
end
end
local total = n
if companionPets > total then total = companionPets end
RelationshipsAchievements:UpdateStat("petCount", total)
if total >= 1 then RelationshipsAchievements:Unlock(1204) end
if total >= 10 then RelationshipsAchievements:Unlock(1205) end
end
local function TooltipText(index, book)
RA_ScanTip:ClearLines()
RA_ScanTip:SetSpell(index, book)
@@ -171,6 +296,123 @@ local function TooltipText(index, book)
return string.lower(text)
end
-- ============ Known mount names (Vanilla + TurtleWoW / OctoWoW) ============
-- Same rationale as the companion list: some private-server mounts (e.g. the
-- OctoWoW "Zebra") have tooltips that don't include the classic "mount speed"
-- / "rideable" phrasing, so tooltip scanning misses them. A name whitelist
-- guarantees credit whenever such a spell appears in the spellbook or is
-- summoned by the player.
local RA_KNOWN_MOUNTS_LIST = {
-- Horde racial ground mounts (Vanilla)
"Brown Wolf", "Dire Wolf", "Timber Wolf", "Red Wolf", "Winter Wolf",
"Frostwolf", "Black Wolf", "Arctic Wolf", "Swift Brown Wolf",
"Swift Timber Wolf", "Swift Gray Wolf", "Swift Frostwolf",
"Black Skeletal Horse", "Blue Skeletal Horse", "Brown Skeletal Horse",
"Red Skeletal Horse", "Green Skeletal Warhorse", "Purple Skeletal Warhorse",
"Red Skeletal Warhorse", "Grey Kodo", "Brown Kodo", "Green Kodo",
"Teal Kodo", "Great White Kodo", "Great Grey Kodo", "Great Brown Kodo",
"Great Green Kodo", "Ivory Raptor", "Emerald Raptor", "Turquoise Raptor",
"Violet Raptor", "Obsidian Raptor", "Swift Blue Raptor", "Swift Olive Raptor",
"Swift Orange Raptor", "Whistle of the Emerald Raptor",
"Whistle of the Ivory Raptor", "Whistle of the Turquoise Raptor",
"Whistle of the Violet Raptor", "Whistle of the Mottled Red Raptor",
-- Alliance racial ground mounts (Vanilla)
"Pinto", "Palomino", "Chestnut Mare", "Brown Horse", "White Stallion",
"Black Stallion", "Swift Brown Steed", "Swift Palomino", "Swift White Steed",
"Gray Ram", "Brown Ram", "White Ram", "Black Ram", "Frost Ram",
"Swift Gray Ram", "Swift Brown Ram", "Swift White Ram",
"Mechanostrider", "Blue Mechanostrider", "Green Mechanostrider",
"Red Mechanostrider", "White Mechanostrider", "Yellow Mechanostrider",
"Unpainted Mechanostrider", "Swift Green Mechanostrider",
"Swift White Mechanostrider", "Swift Yellow Mechanostrider",
"Striped Frostsaber", "Striped Nightsaber", "Spotted Frostsaber",
"Striped Dawnsaber", "Swift Frostsaber", "Swift Mistsaber",
"Swift Stormsaber", "Reins of the Striped Frostsaber",
"Reins of the Striped Nightsaber", "Reins of the Spotted Frostsaber",
-- Class mounts (Vanilla)
"Summon Warhorse", "Summon Charger", "Summon Felsteed", "Summon Dreadsteed",
"Warhorse", "Charger", "Felsteed", "Dreadsteed",
-- PvP / faction / reputation / rare mounts (Vanilla)
"Black Battlestrider", "Black War Kodo", "Black War Ram", "Black War Raptor",
"Black War Steed", "Black War Tiger", "Black War Wolf", "Horn of the Black War Wolf",
"Horn of the Black Wolf", "Horn of the Brown Wolf", "Horn of the Dire Wolf",
"Horn of the Red Wolf", "Horn of the Timber Wolf", "Horn of the Arctic Wolf",
"Horn of the Frostwolf", "Horn of the Swift Brown Wolf",
"Horn of the Swift Gray Wolf", "Horn of the Swift Timber Wolf",
"Rivendare's Deathcharger", "Deathcharger's Reins", "Reins of the Rivendare's Deathcharger",
"Winterspring Frostsaber", "Reins of the Winterspring Frostsaber",
"Swift Zulian Tiger", "Reins of the Swift Zulian Tiger",
"Swift Razzashi Raptor", "Reins of the Swift Razzashi Raptor",
"Qiraji Battle Tank", "Reins of the Blue Qiraji Battle Tank",
"Reins of the Green Qiraji Battle Tank", "Reins of the Red Qiraji Battle Tank",
"Reins of the Yellow Qiraji Battle Tank",
"Tawny Sabercat", "Reins of the Tawny Sabercat",
"Cenarion War Hippogryph", "Reins of the Cenarion War Hippogryph",
-- Turtle WoW / OctoWoW custom mounts (community-known)
"Zebra", "Reins of the Zebra", "Whistle of the Zebra",
"Zhevra", "Reins of the Zhevra",
"Silver Riding Turtle", "Reins of the Silver Riding Turtle",
"Riding Turtle", "Reins of the Riding Turtle",
"Sea Turtle", "Reins of the Sea Turtle",
"Brown Ostrich", "White Ostrich", "Black Ostrich", "Reins of the Ostrich",
"Swift Ostrich", "Reins of the Swift Ostrich",
"Camel", "Brown Camel", "Tan Camel", "White Camel", "Reins of the Camel",
"Swift Camel", "Reins of the Swift Camel",
"Reindeer", "Winter Reindeer", "Reins of the Reindeer",
"Great Elk", "Reins of the Great Elk",
"Moose", "Reins of the Moose", "Swift Moose",
"Boar", "War Boar", "Reins of the War Boar", "Swift War Boar",
"Bear Mount", "Riding Bear", "Reins of the Riding Bear",
"Swift Bear", "Polar Bear", "Reins of the Polar Bear",
"Cheetah", "Reins of the Cheetah", "Swift Cheetah",
"Lion", "White Lion", "Reins of the White Lion",
"Steam Tank Mount", "Steamscale", "Mechano-Hog",
"Goblin Trike", "Trike", "Reins of the Trike",
"Snow Leopard", "Reins of the Snow Leopard",
"Ochre Skeletal Warhorse", "Ancona Chicken Mount",
"Turtle Mount", "Great Sea Turtle",
"Black Panther", "Reins of the Black Panther",
"Giraffe", "Reins of the Giraffe",
"Hyena", "Reins of the Hyena",
"Ram of Ironforge", "Kodo of Thunder Bluff",
}
local RA_KNOWN_MOUNTS_SET = {}
for _, n in ipairs(RA_KNOWN_MOUNTS_LIST) do
RA_KNOWN_MOUNTS_SET[string.lower(n)] = true
end
function RA_IsKnownMountName(name)
if type(name) ~= "string" or name == "" then return false end
return RA_KNOWN_MOUNTS_SET[string.lower(name)] == true
end
function RA_AddKnownMount(name)
if type(name) == "string" and name ~= "" then
RA_KNOWN_MOUNTS_SET[string.lower(name)] = true
end
end
-- Persistent per-character store so name-detected mounts survive reloads and
-- merge with tooltip-detected ones without double counting.
function RA_CreditMountByName(name)
if not RA_IsKnownMountName(name) then return end
if not RelationshipsAchievementsCharDB then return end
if not RelationshipsAchievementsCharDB.countedMounts then
RelationshipsAchievementsCharDB.countedMounts = {}
end
RelationshipsAchievementsCharDB.countedMounts[string.lower(name)] = true
end
local function CountStoredMounts()
if not RelationshipsAchievementsCharDB
or not RelationshipsAchievementsCharDB.countedMounts then
return 0
end
local n = 0
for _ in pairs(RelationshipsAchievementsCharDB.countedMounts) do n = n + 1 end
return n
end
local function ScanSpellbook()
if not GetSpellName then return end
local book = BOOKTYPE_SPELL or "spell"
@@ -194,19 +436,48 @@ local function ScanSpellbook()
-- 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)
-- Non-combat pets: several tooltip phrasings exist across
-- Vanilla / TurtleWoW / OctoWoW. Match any of them, then filter
-- out 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)
or string.find(tip, "small pet", 1, true)
or string.find(tip, "critter", 1, true)
or string.find(tip, "summons and dismisses a", 1, true)
or string.find(tip, "summons and dismisses your", 1, true)
or string.find(tip, "summon a small", 1, true)
or string.find(tip, "vanity pet", 1, true)
or string.find(tip, "non-combat pet", 1, true)
)
and not string.find(tip, "summons your pet", 1, true)
and not string.find(tip, "combat pet", 1, true)
and not isMount
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
if RA_CreditMountByName then RA_CreditMountByName(name) end
elseif isPet then
pets = pets + 1
end
-- Name-based fast path: known vanilla + Turtle/Octo companions.
-- Ensures custom pets whose tooltips don't match keywords still count.
if (not isPet) and (not isMount)
and RA_IsKnownCompanionName and RA_IsKnownCompanionName(name) then
pets = pets + 1
if RA_CreditCompanionByName then RA_CreditCompanionByName(name) end
end
-- Name-based fast path: known vanilla + Turtle/Octo mounts.
-- Catches custom mounts (e.g. Zebra) whose tooltips omit the
-- classic "mount speed" / "rideable" keywords.
if (not isMount) and (not isPet)
and RA_IsKnownMountName and RA_IsKnownMountName(name) then
mounts = mounts + 1
if RA_CreditMountByName then RA_CreditMountByName(name) end
end
end
i = i + 1
end
@@ -223,6 +494,12 @@ local function ScanSpellbook()
end
end
-- Merge with the persistent name-detected mount store so counts never
-- regress if a mount was seen previously but is currently unavailable
-- from the spellbook scan (e.g. server API hiccup).
local storedMounts = CountStoredMounts()
if storedMounts > mounts then mounts = storedMounts end
RelationshipsAchievements:UpdateStat("mountCount", mounts)
RelationshipsAchievements:UpdateStat("petCount", pets)
if mounts >= 1 then RelationshipsAchievements:Unlock(211) end
@@ -232,6 +509,213 @@ local function ScanSpellbook()
if pets >= 1 then RelationshipsAchievements:Unlock(1204) end
end
-- Expose so slash command / minimap can force a rescan.
RelationshipsAchievements_ScanSpellbook = ScanSpellbook
-- ============ Companion API scanning (OctoWoW / Turtle) ============
-- OctoWoW's Pet menu ("P") has a Companions tab powered by the backported
-- Companion API (GetNumCompanions/GetCompanionInfo/CallCompanion). This is
-- the authoritative source for non-combat pet ownership on those clients
-- and is far more reliable than tooltip scanning the spellbook.
local function EnsureCountedCompanions()
if not RelationshipsAchievementsCharDB then return nil end
if not RelationshipsAchievementsCharDB.countedCompanions then
RelationshipsAchievementsCharDB.countedCompanions = {}
end
return RelationshipsAchievementsCharDB.countedCompanions
end
local function CountTable(t)
local n = 0
if t then for _ in pairs(t) do n = n + 1 end end
return n
end
local function ScanCompanions()
if not GetNumCompanions then return end
local counted = EnsureCountedCompanions()
if not counted then return end
-- Different builds/servers register the companion tab under different
-- type strings. Try each so we always sync with the "P" menu Companions
-- tab regardless of which naming OctoWoW / Turtle uses.
local types = { "CRITTER", "COMPANION", "COMPANIONS", "PET", "MINIPET" }
for _, ctype in ipairs(types) do
local ok, num = pcall(GetNumCompanions, ctype)
if ok and num and num > 0 then
for i = 1, num do
local ok2, _, name = pcall(GetCompanionInfo, ctype, i)
if ok2 and name and name ~= "" then
counted[name] = true
else
counted[ctype.."_"..i] = true
end
end
end
end
local total = CountTable(counted)
-- Merge with tooltip-scanned pets so we always report the higher value.
local spellPets = (RelationshipsAchievementsCharDB.stats and RelationshipsAchievementsCharDB.stats.petCount) or 0
if total < spellPets then total = spellPets end
RelationshipsAchievements:UpdateStat("petCount", total)
if total >= 1 then RelationshipsAchievements:Unlock(1204) end
if total >= 10 then RelationshipsAchievements:Unlock(1205) end
end
RelationshipsAchievements_ScanCompanions = ScanCompanions
-- Backup path: hook CallCompanion so a summon always credits the pet, even
-- if the addon loaded before the Companion API populated.
if CallCompanion then
local _origCallCompanion = CallCompanion
CallCompanion = function(ctype, index)
_origCallCompanion(ctype, index)
if ctype == "CRITTER" then
local counted = EnsureCountedCompanions()
if counted then
local ok, _, name = pcall(GetCompanionInfo, "CRITTER", index)
local key = (ok and name and name ~= "") and name or ("companion_"..index)
counted[key] = true
local n = CountTable(counted)
RelationshipsAchievements:UpdateStat("petCount", n)
RelationshipsAchievements:Unlock(1204)
if n >= 10 then RelationshipsAchievements:Unlock(1205) end
end
end
end
end
-- Backup pet-count tracking: if a player casts a spell whose tooltip matches
-- pet criteria, ensure it counts. This catches Turtle/Octo custom companions
-- whose tooltips ScanSpellbook may not have visited yet.
local function EnsureCountedPets()
if not RelationshipsAchievementsCharDB then return nil end
if not RelationshipsAchievementsCharDB.countedPets then
RelationshipsAchievementsCharDB.countedPets = {}
end
return RelationshipsAchievementsCharDB.countedPets
end
local function TooltipTextByName(spellName)
if not spellName or spellName == "" then return "" end
RA_ScanTip:ClearLines()
-- SetSpell by index only; iterate spellbook once to find the name.
if not GetSpellName then return "" end
local book = BOOKTYPE_SPELL or "spell"
local i = 1
while true do
local name = GetSpellName(i, book)
if not name then break end
if name == spellName then
return TooltipText(i, book)
end
i = i + 1
end
return ""
end
local function CreditPetSpell(spellName)
if not spellName or spellName == "" then return end
local counted = EnsureCountedPets()
if not counted then return end
if counted[spellName] then return end
-- Known-companion fast path (no tooltip required).
if RA_IsKnownCompanionName and RA_IsKnownCompanionName(spellName) then
counted[spellName] = true
local n = 0
for _ in pairs(counted) do n = n + 1 end
local companionPets = 0
if RelationshipsAchievementsCharDB and RelationshipsAchievementsCharDB.countedCompanions then
for _ in pairs(RelationshipsAchievementsCharDB.countedCompanions) do
companionPets = companionPets + 1
end
end
local total = n
if companionPets > total then total = companionPets end
RelationshipsAchievements:UpdateStat("petCount", total)
RelationshipsAchievements:Unlock(1204)
if total >= 10 then RelationshipsAchievements:Unlock(1205) end
return
end
local tip = TooltipTextByName(spellName)
if tip == "" then return end
local isPet = (
string.find(tip, "companion", 1, true)
or string.find(tip, "right click to dismiss", 1, true)
or string.find(tip, "small pet", 1, true)
or string.find(tip, "critter", 1, true)
or string.find(tip, "summons and dismisses a", 1, true)
or string.find(tip, "summons and dismisses your", 1, true)
or string.find(tip, "vanity pet", 1, true)
or string.find(tip, "non-combat pet", 1, true)
)
and not string.find(tip, "summons your pet", 1, true)
and not string.find(tip, "mount speed", 1, true)
and not string.find(tip, "rideable", 1, true)
if isPet then
counted[spellName] = true
local n = 0
for _ in pairs(counted) do n = n + 1 end
RelationshipsAchievements:UpdateStat("petCount", n)
RelationshipsAchievements:Unlock(1204)
end
end
-- Credit a mount cast by name. Mirrors CreditPetSpell but for mounts,
-- using the known-mount whitelist so custom Turtle/OctoWoW mounts (e.g.
-- "Zebra") count the moment the player summons them, even if the client's
-- spellbook scan hadn't picked them up yet.
local function CreditMountSpell(spellName)
if not spellName or spellName == "" then return end
if not RA_IsKnownMountName or not RA_IsKnownMountName(spellName) then return end
if not RelationshipsAchievementsCharDB then return end
if not RelationshipsAchievementsCharDB.countedMounts then
RelationshipsAchievementsCharDB.countedMounts = {}
end
local key = string.lower(spellName)
if RelationshipsAchievementsCharDB.countedMounts[key] then return end
RelationshipsAchievementsCharDB.countedMounts[key] = true
local n = 0
for _ in pairs(RelationshipsAchievementsCharDB.countedMounts) do n = n + 1 end
-- Never regress the stat: prefer whichever count is higher.
local prev = 0
if RelationshipsAchievementsCharDB.stats
and RelationshipsAchievementsCharDB.stats.mountCount then
prev = RelationshipsAchievementsCharDB.stats.mountCount
end
if n < prev then n = prev end
RelationshipsAchievements:UpdateStat("mountCount", n)
RelationshipsAchievements:Unlock(211)
end
local _origCastSpellByName = CastSpellByName
if _origCastSpellByName then CastSpellByName = function(name, onSelf)
_origCastSpellByName(name, onSelf)
CreditPetSpell(name)
CreditMountSpell(name)
end end
local _origCastSpell = CastSpell
if _origCastSpell then CastSpell = function(spellId, bookType)
_origCastSpell(spellId, bookType)
if GetSpellName then
local n = GetSpellName(spellId, bookType or BOOKTYPE_SPELL or "spell")
CreditPetSpell(n)
CreditMountSpell(n)
end
end end
local _origUseAction = UseAction
if _origUseAction then UseAction = function(slot, checkCursor, onSelf)
_origUseAction(slot, checkCursor, onSelf)
if GetActionText then
local txt = GetActionText(slot)
if txt then
CreditPetSpell(txt)
CreditMountSpell(txt)
end
end
end end
function RelationshipsAchievements_ScanCharacter()
RelationshipsAchievements:Init()
@@ -374,6 +858,9 @@ function RelationshipsAchievements_ScanCharacter()
ScanTalents()
ScanEquipmentAndBags()
ScanSpellbook()
if RelationshipsAchievements_ScanCompanions then
RelationshipsAchievements_ScanCompanions()
end
RelationshipsAchievements_RefreshUI = RelationshipsAchievements_RefreshUI or function() end
if RelationshipsAchievementFrame and RelationshipsAchievementFrame:IsVisible() then
@@ -642,8 +1129,31 @@ local function OnEnemyDeath(msg)
end
end
-- Match Turtle/Octo /played extension lines that expose the character's
-- completed-quest total. Multiple phrasings observed across custom builds.
local function TryParseQuestCount(msg)
if not msg then return end
local patterns = {
"[Qq]uests? [Cc]ompleted:?%s*(%d+)",
"[Cc]ompleted [Qq]uests?:?%s*(%d+)",
"[Tt]otal [Qq]uests?:?%s*(%d+)",
"[Qq]uests?[^%d]-(%d+)%s*completed",
}
for i = 1, table.getn(patterns) do
local _, _, num = string.find(msg, patterns[i])
if num then
local n = tonumber(num)
if n and n > 0 then
RelationshipsAchievements:UpdateStat("questCount", n)
return true
end
end
end
end
local function OnSystemMessage(msg)
if not msg then return end
TryParseQuestCount(msg)
local _, _, winner = string.find(msg, "^(.-) has defeated ")
if winner and winner == UnitName("player") then
RelationshipsAchievements:AddStat("duelWins", 1)
@@ -787,5 +1297,39 @@ frame:SetScript("OnEvent", function()
RelationshipsAchievements:Unlock(103)
if GetNumGuildMembers and GetNumGuildMembers() >= 10 then RelationshipsAchievements:Unlock(1703) end
end
elseif event == "TIME_PLAYED_MSG" then
-- Some /played variants (Turtle/Octo) also emit "Quests completed: N"
-- as separate CHAT_MSG_SYSTEM lines around this event. Rescan the
-- spellbook too so any pets learned since last scan are counted.
if RelationshipsAchievements_ScanSpellbook then
RelationshipsAchievements_ScanSpellbook()
end
elseif event == "UNIT_PET" or event == "UNIT_MODEL_CHANGED" then
-- A pet/companion just spawned or despawned. Rescan the spellbook so
-- newly-learned companion spells count immediately.
if arg1 == "player" and RelationshipsAchievements_ScanSpellbook then
RelationshipsAchievements_ScanSpellbook()
if RelationshipsAchievements_ScanCompanions then
RelationshipsAchievements_ScanCompanions()
end
-- Name-based fallback: if the summoned pet's unit name matches a
-- known companion, credit it directly.
if UnitName and RA_CreditCompanionByName then
local petName = UnitName("pet")
if petName then RA_CreditCompanionByName(petName) end
end
end
elseif event == "COMPANION_LEARNED" or event == "COMPANION_UPDATE"
or event == "COMPANION_UNLEARNED" or event == "PET_UI_UPDATE" then
if RelationshipsAchievements_ScanCompanions then
RelationshipsAchievements_ScanCompanions()
end
elseif event == "LEARNED_SPELL_IN_TAB" then
if RelationshipsAchievements_ScanSpellbook then
RelationshipsAchievements_ScanSpellbook()
end
if RelationshipsAchievements_ScanCompanions then
RelationshipsAchievements_ScanCompanions()
end
end
end)
+147
View File
@@ -0,0 +1,147 @@
-- Movable / hideable minimap button.
-- Named "RelationshipsAchievementsMinimapButton" and parented to Minimap so
-- MinimapButtonBag and MinimapButtonFrame automatically discover and manage it.
local MMB = CreateFrame("Button", "RelationshipsAchievementsMinimapButton", Minimap)
MMB:SetFrameStrata("MEDIUM")
MMB:SetWidth(31); MMB:SetHeight(31)
MMB:SetFrameLevel(8)
MMB:RegisterForClicks("LeftButtonUp", "RightButtonUp")
MMB:RegisterForDrag("LeftButton")
MMB:SetMovable(true)
MMB:SetClampedToScreen(true)
-- Ring (matches other minimap addons like Cartographer / Titan)
local overlay = MMB:CreateTexture(nil, "OVERLAY")
overlay:SetWidth(53); overlay:SetHeight(53)
overlay:SetTexture("Interface\\Minimap\\MiniMap-TrackingBorder")
overlay:SetPoint("TOPLEFT", MMB, "TOPLEFT", 0, 0)
-- Icon
local icon = MMB:CreateTexture(nil, "BACKGROUND")
icon:SetWidth(20); icon:SetHeight(20)
icon:SetTexture("Interface\\Icons\\Achievement_General")
icon:SetPoint("CENTER", MMB, "CENTER", 0, 1)
MMB.icon = icon
-- Highlight
local hi = MMB:CreateTexture(nil, "HIGHLIGHT")
hi:SetTexture("Interface\\Minimap\\UI-Minimap-ZoomButton-Highlight")
hi:SetWidth(31); hi:SetHeight(31)
hi:SetPoint("CENTER", MMB, "CENTER", 0, 1)
hi:SetBlendMode("ADD")
local function GetDB()
RelationshipsAchievementsDB = RelationshipsAchievementsDB or {}
if not RelationshipsAchievementsDB.minimap then
RelationshipsAchievementsDB.minimap = { angle = 200, radius = 80, hide = false, free = false, x = 0, y = 0 }
end
local m = RelationshipsAchievementsDB.minimap
if m.angle == nil then m.angle = 200 end
if m.radius == nil then m.radius = 80 end
if m.hide == nil then m.hide = false end
if m.free == nil then m.free = false end
return m
end
local function ApplyPosition()
local m = GetDB()
MMB:ClearAllPoints()
if m.free then
MMB:SetPoint("CENTER", UIParent, "CENTER", m.x or 0, m.y or 0)
else
local a = math.rad(m.angle or 200)
local r = m.radius or 80
MMB:SetPoint("CENTER", Minimap, "CENTER",
math.cos(a) * r, math.sin(a) * r)
end
end
local function ApplyVisibility()
local m = GetDB()
if m.hide then MMB:Hide() else MMB:Show() end
end
-- Drag around the minimap ring (or free-drag anywhere with Shift held).
local function MoveOnUpdate()
local m = GetDB()
local mx, my = Minimap:GetCenter()
if not mx then return end
local scale = Minimap:GetEffectiveScale()
local cx, cy = GetCursorPosition()
cx, cy = cx / scale, cy / scale
if IsShiftKeyDown() then
m.free = true
local ux, uy = UIParent:GetCenter()
local uscale = UIParent:GetEffectiveScale()
local px, py = GetCursorPosition()
m.x = px / uscale - ux
m.y = py / uscale - uy
else
m.free = false
local dx, dy = cx - mx, cy - my
m.angle = math.deg(math.atan2(dy, dx))
end
ApplyPosition()
end
MMB:SetScript("OnDragStart", function()
this:LockHighlight()
this:SetScript("OnUpdate", MoveOnUpdate)
end)
MMB:SetScript("OnDragStop", function()
this:UnlockHighlight()
this:SetScript("OnUpdate", nil)
end)
MMB:SetScript("OnClick", function()
if arg1 == "RightButton" then
if RelationshipsAchievements_MinimapMenu then
RelationshipsAchievements_MinimapMenu()
else
local m = GetDB()
m.hide = true
ApplyVisibility()
DEFAULT_CHAT_FRAME:AddMessage("|cffffd200Relationships Achievements:|r minimap button hidden. Type |cff00ff00/rach minimap show|r to restore.")
end
else
if RelationshipsAchievements_Toggle then RelationshipsAchievements_Toggle() end
end
end)
MMB:SetScript("OnEnter", function()
GameTooltip:SetOwner(this, "ANCHOR_LEFT")
GameTooltip:AddLine("Relationships Achievements")
local pts = RelationshipsAchievements and RelationshipsAchievements.TotalPoints and RelationshipsAchievements:TotalPoints() or 0
GameTooltip:AddLine("|cffffd200Points:|r "..pts, 1, 1, 1)
GameTooltip:AddLine(" ")
GameTooltip:AddLine("Left-click: open panel", 0.6, 0.8, 1)
GameTooltip:AddLine("Right-click: hide button", 0.6, 0.8, 1)
GameTooltip:AddLine("Drag: move around minimap", 0.6, 0.8, 1)
GameTooltip:AddLine("Shift+Drag: free-place anywhere", 0.6, 0.8, 1)
GameTooltip:Show()
end)
MMB:SetScript("OnLeave", function() GameTooltip:Hide() end)
local init = CreateFrame("Frame")
init:RegisterEvent("PLAYER_LOGIN")
init:RegisterEvent("PLAYER_ENTERING_WORLD")
init:SetScript("OnEvent", function()
ApplyPosition()
ApplyVisibility()
end)
function RelationshipsAchievements_Minimap_Show()
local m = GetDB(); m.hide = false; ApplyVisibility()
end
function RelationshipsAchievements_Minimap_Hide()
local m = GetDB(); m.hide = true; ApplyVisibility()
end
function RelationshipsAchievements_Minimap_Toggle()
local m = GetDB(); m.hide = not m.hide; ApplyVisibility()
end
function RelationshipsAchievements_Minimap_Reset()
RelationshipsAchievementsDB = RelationshipsAchievementsDB or {}
RelationshipsAchievementsDB.minimap = { angle = 200, radius = 80, hide = false, free = false, x = 0, y = 0 }
ApplyPosition(); ApplyVisibility()
end
+1
View File
@@ -13,5 +13,6 @@ Core.lua
Toast.lua
Detail.lua
UI.lua
Minimap.lua
Events.lua
Bindings.xml