Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d0b8fff6f9 | |||
| 9bbbf4ac45 | |||
| 5542971559 | |||
| e634c4fad2 | |||
| 6d6cabadb6 | |||
| 8149862f76 | |||
| 6438daee4b | |||
| 01dff34305 | |||
| 9f6f4b6f27 |
@@ -226,12 +226,31 @@ SlashCmdList["RELATIONSHIPSACHIEVEMENTS"] = function(msg)
|
|||||||
if RelationshipsAchievements_ShowToast and RelationshipsAchievements_ById[206] then
|
if RelationshipsAchievements_ShowToast and RelationshipsAchievements_ById[206] then
|
||||||
RelationshipsAchievements_ShowToast(RelationshipsAchievements_ById[206])
|
RelationshipsAchievements_ShowToast(RelationshipsAchievements_ById[206])
|
||||||
end
|
end
|
||||||
|
elseif cmd == "minimap" then
|
||||||
|
local sub = string.lower(rest or "")
|
||||||
|
if sub == "show" then
|
||||||
|
if RelationshipsAchievements_Minimap_Show then RelationshipsAchievements_Minimap_Show() end
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Minimap button shown.|r")
|
||||||
|
elseif sub == "hide" then
|
||||||
|
if RelationshipsAchievements_Minimap_Hide then RelationshipsAchievements_Minimap_Hide() end
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Minimap button hidden.|r")
|
||||||
|
elseif sub == "reset" then
|
||||||
|
if RelationshipsAchievements_Minimap_Reset then RelationshipsAchievements_Minimap_Reset() end
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Minimap button position reset.|r")
|
||||||
|
else
|
||||||
|
if RelationshipsAchievements_Minimap_Toggle then RelationshipsAchievements_Minimap_Toggle() end
|
||||||
|
end
|
||||||
|
elseif cmd == "played" then
|
||||||
|
if RequestTimePlayed then RequestTimePlayed() end
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage("|cff00ff00Relationships Achievements:|r reading /played for quest count...")
|
||||||
elseif cmd == "help" then
|
elseif cmd == "help" then
|
||||||
DEFAULT_CHAT_FRAME:AddMessage("|cffffd200Relationships Achievements commands:|r")
|
DEFAULT_CHAT_FRAME:AddMessage("|cffffd200Relationships Achievements commands:|r")
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(" /rach - toggle window")
|
DEFAULT_CHAT_FRAME:AddMessage(" /rach - toggle window")
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(" /rach unlock <id> - unlock achievement by id")
|
DEFAULT_CHAT_FRAME:AddMessage(" /rach unlock <id> - unlock achievement by id")
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(" /rach reset - wipe all progress")
|
DEFAULT_CHAT_FRAME:AddMessage(" /rach reset - wipe all progress")
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(" /rach points - show total points")
|
DEFAULT_CHAT_FRAME:AddMessage(" /rach points - show total points")
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage(" /rach minimap [show|hide|reset] - control minimap button")
|
||||||
|
DEFAULT_CHAT_FRAME:AddMessage(" /rach played - sync quest count from /played")
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(" /rach scan - rescan character stats")
|
DEFAULT_CHAT_FRAME:AddMessage(" /rach scan - rescan character stats")
|
||||||
DEFAULT_CHAT_FRAME:AddMessage(" /rach test - show a test toast")
|
DEFAULT_CHAT_FRAME:AddMessage(" /rach test - show a test toast")
|
||||||
else
|
else
|
||||||
|
|||||||
+4
-4
@@ -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 = 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 = 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 = 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 = 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" },
|
{ 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 = 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 = 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 = 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 = 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 = 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" },
|
{ 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 = 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 = 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 = 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 = 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" },
|
{ id = 1606, cat = 16, name = "No Retreat", desc = "Defeat an elite of your level solo.", points = 15, icon = "Interface\\Icons\\Ability_Warrior_Innerrage" },
|
||||||
|
|
||||||
-- ============ SOCIAL ============
|
-- ============ 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 = 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 = 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",
|
{ 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 } },
|
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",
|
{ id = 1705, cat = 17, name = "Chatty", desc = "Send 100 messages in chat.", points = 10, icon = "Interface\\Icons\\INV_Letter_04",
|
||||||
|
|||||||
+768
-96
@@ -14,12 +14,17 @@ local trackedEvents = {
|
|||||||
"ZONE_CHANGED_INDOORS", "CHAT_MSG_COMBAT_HONOR_GAIN",
|
"ZONE_CHANGED_INDOORS", "CHAT_MSG_COMBAT_HONOR_GAIN",
|
||||||
"CHAT_MSG_COMBAT_FACTION_CHANGE", "SKILL_LINES_CHANGED", "CHAT_MSG_LOOT",
|
"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_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",
|
"GUILD_ROSTER_UPDATE", "PLAYER_UNGHOST", "PLAYER_ALIVE", "CHAT_MSG_TEXT_EMOTE",
|
||||||
"CHAT_MSG_EMOTE", "PLAYER_PVP_RANK_CHANGED", "PLAYER_PVP_KILLS_CHANGED",
|
"CHAT_MSG_EMOTE", "PLAYER_PVP_RANK_CHANGED", "PLAYER_PVP_KILLS_CHANGED",
|
||||||
"PLAYER_PVPKILLS_CHANGED", "HONOR_CURRENCY_UPDATE", "HEARTHSTONE_BOUND",
|
"PLAYER_PVPKILLS_CHANGED", "HONOR_CURRENCY_UPDATE", "HEARTHSTONE_BOUND",
|
||||||
"BANKFRAME_OPENED", "TRAINER_SHOW", "MAIL_SEND_SUCCESS", "PLAYER_EQUIPMENT_CHANGED",
|
"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",
|
||||||
|
"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
|
for i = 1, table.getn(trackedEvents) do SafeRegister(trackedEvents[i]) end
|
||||||
|
|
||||||
@@ -92,29 +97,48 @@ local function ScanTalentChange()
|
|||||||
ScanTalents()
|
ScanTalents()
|
||||||
end
|
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()
|
local function ScanEquipmentAndBags()
|
||||||
if GetInventoryItemLink then
|
if GetInventoryItemLink then
|
||||||
if GetInventoryItemLink("player", 4) then RelationshipsAchievements:Unlock(1203) end
|
if GetInventoryItemLink("player", 4) then RelationshipsAchievements:Unlock(1203) end
|
||||||
if GetInventoryItemLink("player", 19) then RelationshipsAchievements:Unlock(109) end
|
if GetInventoryItemLink("player", 19) then RelationshipsAchievements:Unlock(109) end
|
||||||
local allRare, haveGear, epic = true, false, false
|
-- Well Equipped: every equipped armor/weapon slot must be Rare+.
|
||||||
for slot = 1, 18 do
|
-- Slots that are legitimately optional on a spec (shirt, tabard,
|
||||||
if slot ~= 4 and slot ~= 18 then
|
-- ranged, off-hand on 2H users) are skipped from the "all filled"
|
||||||
local link = GetInventoryItemLink("player", slot)
|
-- check but still count when present.
|
||||||
if link then
|
local requiredSlots = { 1,2,3,5,6,7,8,9,10,11,12,13,14,15,16 }
|
||||||
haveGear = true
|
local optionalSlots = { 17, 18 } -- off-hand, ranged
|
||||||
local _, _, quality = GetItemInfo(link)
|
local allRare, missing, epic = true, false, false
|
||||||
if quality and quality >= 4 then epic = true end
|
local function eval(slot, required)
|
||||||
if not quality or quality < 3 then allRare = false end
|
local link = GetInventoryItemLink("player", slot)
|
||||||
local itemName = GetItemInfo(link)
|
if not link then
|
||||||
if itemName == "Atiesh, Greatstaff of the Guardian" then RelationshipsAchievements:Unlock(1004) end
|
if required then missing = true end
|
||||||
if itemName == "Thunderfury, Blessed Blade of the Windseeker" then RelationshipsAchievements:Unlock(1005) end
|
return
|
||||||
if itemName == "Sulfuras, Hand of Ragnaros" then RelationshipsAchievements:Unlock(1006) end
|
|
||||||
else
|
|
||||||
allRare = false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
local _, _, q = GetItemInfo(link)
|
||||||
|
local quality = q or QualityFromLink(link)
|
||||||
|
if quality and quality >= 4 then epic = true 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
|
||||||
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
|
if epic then RelationshipsAchievements:Unlock(1209) end
|
||||||
end
|
end
|
||||||
if GetContainerNumSlots then
|
if GetContainerNumSlots then
|
||||||
@@ -129,27 +153,569 @@ local function ScanEquipmentAndBags()
|
|||||||
end
|
end
|
||||||
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")
|
||||||
|
|
||||||
|
-- ============ 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)
|
||||||
|
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
|
||||||
|
|
||||||
|
-- ============ 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()
|
local function ScanSpellbook()
|
||||||
if not GetSpellName then return end
|
if not GetSpellName then return end
|
||||||
|
local book = BOOKTYPE_SPELL or "spell"
|
||||||
local mounts, pets = 0, 0
|
local mounts, pets = 0, 0
|
||||||
|
local hasEpicMount = false
|
||||||
|
local seen = {}
|
||||||
local i = 1
|
local i = 1
|
||||||
while true do
|
while true do
|
||||||
local name, rank = GetSpellName(i, BOOKTYPE_SPELL or "spell")
|
local name, rank = GetSpellName(i, book)
|
||||||
if not name then break end
|
if not name then break end
|
||||||
local low = string.lower(name)
|
local key = name .. "||" .. (rank or "")
|
||||||
if string.find(low, "summon ") and (string.find(low, "horse") or string.find(low, "ram")
|
if not seen[key] then
|
||||||
or string.find(low, "tiger") or string.find(low, "raptor") or string.find(low, "wolf")
|
seen[key] = true
|
||||||
or string.find(low, "kodo") or string.find(low, "mechanostrider")) then mounts = mounts + 1 end
|
local tip = TooltipText(i, book)
|
||||||
if string.find(low, "summon ") and not string.find(low, "demon") and not string.find(low, "elemental") then pets = pets + 1 end
|
-- 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".
|
||||||
|
-- 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
|
i = i + 1
|
||||||
end
|
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
|
||||||
|
|
||||||
|
-- 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("mountCount", mounts)
|
||||||
RelationshipsAchievements:UpdateStat("petCount", pets)
|
RelationshipsAchievements:UpdateStat("petCount", pets)
|
||||||
if mounts >= 1 then RelationshipsAchievements:Unlock(211) end
|
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
|
if pets >= 1 then RelationshipsAchievements:Unlock(1204) end
|
||||||
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()
|
function RelationshipsAchievements_ScanCharacter()
|
||||||
RelationshipsAchievements:Init()
|
RelationshipsAchievements:Init()
|
||||||
|
|
||||||
@@ -292,6 +858,9 @@ function RelationshipsAchievements_ScanCharacter()
|
|||||||
ScanTalents()
|
ScanTalents()
|
||||||
ScanEquipmentAndBags()
|
ScanEquipmentAndBags()
|
||||||
ScanSpellbook()
|
ScanSpellbook()
|
||||||
|
if RelationshipsAchievements_ScanCompanions then
|
||||||
|
RelationshipsAchievements_ScanCompanions()
|
||||||
|
end
|
||||||
|
|
||||||
RelationshipsAchievements_RefreshUI = RelationshipsAchievements_RefreshUI or function() end
|
RelationshipsAchievements_RefreshUI = RelationshipsAchievements_RefreshUI or function() end
|
||||||
if RelationshipsAchievementFrame and RelationshipsAchievementFrame:IsVisible() then
|
if RelationshipsAchievementFrame and RelationshipsAchievementFrame:IsVisible() then
|
||||||
@@ -326,44 +895,99 @@ if _origSendMail then SendMail = function(name, subject, body)
|
|||||||
RelationshipsAchievements:Unlock(110)
|
RelationshipsAchievements:Unlock(110)
|
||||||
end end
|
end end
|
||||||
|
|
||||||
local _origPostAuction = PostAuction
|
-- Auction House API is provided by Blizzard_AuctionUI, which is Load-on-Demand
|
||||||
if _origPostAuction then PostAuction = function(minBid, buyoutPrice, runTime, stackSize, numStacks)
|
-- in 1.12. Hooking at file-load time silently no-ops because the globals are
|
||||||
_origPostAuction(minBid, buyoutPrice, runTime, stackSize, numStacks)
|
-- still nil. HookAuctionHouse() runs again on ADDON_LOADED "Blizzard_AuctionUI"
|
||||||
RelationshipsAchievements:AddStat("auctionsListed", numStacks or 1)
|
-- so the hooks attach as soon as the AH frame is opened for the first time.
|
||||||
end end
|
local auctionHooked = false
|
||||||
|
local function HookAuctionHouse()
|
||||||
local _origBuyoutAuction = BuyoutAuction
|
if auctionHooked then return end
|
||||||
if _origBuyoutAuction then BuyoutAuction = function(index)
|
-- Vanilla 1.12 uses StartAuction(minBid, buyoutPrice, runTime) to list the
|
||||||
_origBuyoutAuction(index)
|
-- item currently in the auction sell slot. PostAuction is a TBC+ API and
|
||||||
RelationshipsAchievements:Unlock(107)
|
-- does not exist on Turtle/Octowow, which is why listings were never
|
||||||
RelationshipsAchievements:AddStat("auctionsWon", 1)
|
-- being counted. Hook both so the addon keeps working if Turtle ever
|
||||||
end end
|
-- back-ports PostAuction.
|
||||||
|
if not StartAuction and not PostAuction and not BuyoutAuction and not PlaceAuctionBid then return end
|
||||||
local OnQuestComplete
|
auctionHooked = true
|
||||||
|
if StartAuction then
|
||||||
-- Hook ConfirmBinder(): the innkeeper popup's OnAccept calls this API and it
|
local _orig = StartAuction
|
||||||
-- fires exactly once per successful bind. The HEARTHSTONE_BOUND event does not
|
StartAuction = function(minBid, buyoutPrice, runTime)
|
||||||
-- exist in vanilla 1.12, and the CHAT_MSG_SYSTEM string fallback below is
|
_orig(minBid, buyoutPrice, runTime)
|
||||||
-- localised / worded differently on some Turtle/Octo builds, so hooking the
|
-- GetAuctionSellItemInfo returns (name, texture, count, ...); count is the stack size.
|
||||||
-- API is the only reliable trigger for re-binding to a new inn.
|
local count = 1
|
||||||
local _origConfirmBinder = ConfirmBinder
|
if GetAuctionSellItemInfo then
|
||||||
if _origConfirmBinder then ConfirmBinder = function()
|
local _, _, c = GetAuctionSellItemInfo()
|
||||||
_origConfirmBinder()
|
if c and c > 0 then count = c end
|
||||||
if RelationshipsAchievements and RelationshipsAchievements.Unlock then
|
end
|
||||||
RelationshipsAchievements:Unlock(108)
|
RelationshipsAchievements:AddStat("auctionsListed", 1)
|
||||||
|
-- (We count one auction per listing action; `count` is stack size, not # of auctions.)
|
||||||
|
local _ = count
|
||||||
|
end
|
||||||
end
|
end
|
||||||
if RelationshipsAchievementsCharDB then
|
if PostAuction then
|
||||||
RelationshipsAchievementsCharDB.pendingInnBind = nil
|
local _orig = PostAuction
|
||||||
|
PostAuction = function(minBid, buyoutPrice, runTime, stackSize, numStacks)
|
||||||
|
_orig(minBid, buyoutPrice, runTime, stackSize, numStacks)
|
||||||
|
RelationshipsAchievements:AddStat("auctionsListed", numStacks or 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if BuyoutAuction then
|
||||||
|
local _orig = BuyoutAuction
|
||||||
|
BuyoutAuction = function(index)
|
||||||
|
_orig(index)
|
||||||
|
RelationshipsAchievements:Unlock(107)
|
||||||
|
RelationshipsAchievements:AddStat("auctionsWon", 1)
|
||||||
|
end
|
||||||
|
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 end
|
end end
|
||||||
|
|
||||||
-- Hook GetQuestReward(): fires exactly once when the player accepts a quest
|
local _origStaticPopupShow = StaticPopup_Show
|
||||||
-- reward. Much more reliable than QUEST_COMPLETE (which fires each time the
|
if _origStaticPopupShow then StaticPopup_Show = function(which, text_arg1, text_arg2, data)
|
||||||
-- reward window opens) for tracking completed-quest counters.
|
local popup = _origStaticPopupShow(which, text_arg1, text_arg2, data)
|
||||||
local _origGetQuestReward = GetQuestReward
|
if which == "CONFIRM_BINDER" then RelationshipsAchievementsCharDB.pendingInnBind = true end
|
||||||
if _origGetQuestReward then GetQuestReward = function(choice)
|
return popup
|
||||||
OnQuestComplete()
|
|
||||||
return _origGetQuestReward(choice)
|
|
||||||
end end
|
end end
|
||||||
|
|
||||||
-- ============ DoEmote hook (Say Hello / Dance Off) ============
|
-- ============ DoEmote hook (Say Hello / Dance Off) ============
|
||||||
@@ -410,7 +1034,7 @@ local function OnLevelUp()
|
|||||||
RelationshipsAchievements_ScanCharacter()
|
RelationshipsAchievements_ScanCharacter()
|
||||||
end
|
end
|
||||||
|
|
||||||
OnQuestComplete = function()
|
local function OnQuestComplete()
|
||||||
-- QUEST_COMPLETE is the reliable 1.12 turn-in event. Custom clients that
|
-- 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.
|
-- also emit QUEST_TURNED_IN are de-duplicated by time stamp below.
|
||||||
local now = time()
|
local now = time()
|
||||||
@@ -525,8 +1149,31 @@ local function OnEnemyDeath(msg)
|
|||||||
end
|
end
|
||||||
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)
|
local function OnSystemMessage(msg)
|
||||||
if not msg then return end
|
if not msg then return end
|
||||||
|
TryParseQuestCount(msg)
|
||||||
local _, _, winner = string.find(msg, "^(.-) has defeated ")
|
local _, _, winner = string.find(msg, "^(.-) has defeated ")
|
||||||
if winner and winner == UnitName("player") then
|
if winner and winner == UnitName("player") then
|
||||||
RelationshipsAchievements:AddStat("duelWins", 1)
|
RelationshipsAchievements:AddStat("duelWins", 1)
|
||||||
@@ -541,36 +1188,20 @@ local function OnSystemMessage(msg)
|
|||||||
if string.find(low, "trick or treat") then RelationshipsAchievements:Unlock(903) end
|
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, "lunar festival") then RelationshipsAchievements:Unlock(909) end
|
||||||
if string.find(low, "winter veil") then RelationshipsAchievements:Unlock(901) end
|
if string.find(low, "winter veil") then RelationshipsAchievements:Unlock(901) end
|
||||||
-- Battleground victories. On 1.12 the win message reads "The Alliance wins!"
|
-- Battleground victories (system messages contain "Warsong Gulch", etc.)
|
||||||
-- or "The Horde wins!" via CHAT_MSG_BG_SYSTEM_NEUTRAL and does NOT include
|
if string.find(msg, "[Vv]ictory") or string.find(msg, "wins!") then
|
||||||
-- the map name, so we look up the current BG from GetRealZoneText() and
|
if string.find(msg, "Warsong Gulch") then
|
||||||
-- credit the player's faction only. Guard with pendingBgWin so duplicate
|
RelationshipsAchievements:Unlock(505)
|
||||||
-- messages (both faction and neutral system chat) don't double-count.
|
RelationshipsAchievements:AddStat("wsgWins", 1)
|
||||||
if string.find(msg, "wins!") or string.find(msg, "[Vv]ictory") then
|
RelationshipsAchievements:AddStat("bgWins", 1)
|
||||||
local zone = (GetRealZoneText and GetRealZoneText()) or ""
|
elseif string.find(msg, "Alterac Valley") then
|
||||||
local _, myFaction = UnitFactionGroup("player")
|
RelationshipsAchievements:Unlock(506)
|
||||||
local winner
|
RelationshipsAchievements:AddStat("avWins", 1)
|
||||||
if string.find(msg, "Alliance") then winner = "Alliance"
|
RelationshipsAchievements:AddStat("bgWins", 1)
|
||||||
elseif string.find(msg, "Horde") then winner = "Horde"
|
elseif string.find(msg, "Arathi Basin") then
|
||||||
end
|
RelationshipsAchievements:Unlock(507)
|
||||||
local key = zone .. "|" .. (winner or "?")
|
RelationshipsAchievements:AddStat("abWins", 1)
|
||||||
if RelationshipsAchievementsCharDB and RelationshipsAchievementsCharDB.lastBgWinKey ~= key then
|
RelationshipsAchievements:AddStat("bgWins", 1)
|
||||||
RelationshipsAchievementsCharDB.lastBgWinKey = key
|
|
||||||
if not winner or winner == myFaction then
|
|
||||||
if string.find(zone, "Warsong Gulch") then
|
|
||||||
RelationshipsAchievements:Unlock(505)
|
|
||||||
RelationshipsAchievements:AddStat("wsgWins", 1)
|
|
||||||
RelationshipsAchievements:AddStat("bgWins", 1)
|
|
||||||
elseif string.find(zone, "Alterac Valley") then
|
|
||||||
RelationshipsAchievements:Unlock(506)
|
|
||||||
RelationshipsAchievements:AddStat("avWins", 1)
|
|
||||||
RelationshipsAchievements:AddStat("bgWins", 1)
|
|
||||||
elseif string.find(zone, "Arathi Basin") then
|
|
||||||
RelationshipsAchievements:Unlock(507)
|
|
||||||
RelationshipsAchievements:AddStat("abWins", 1)
|
|
||||||
RelationshipsAchievements:AddStat("bgWins", 1)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -621,10 +1252,7 @@ frame:SetScript("OnEvent", function()
|
|||||||
OnLootMessage(arg1)
|
OnLootMessage(arg1)
|
||||||
elseif event == "CHAT_MSG_COMBAT_HOSTILE_DEATH" or event == "CHAT_MSG_COMBAT_XP_GAIN" then
|
elseif event == "CHAT_MSG_COMBAT_HOSTILE_DEATH" or event == "CHAT_MSG_COMBAT_XP_GAIN" then
|
||||||
OnEnemyDeath(arg1)
|
OnEnemyDeath(arg1)
|
||||||
elseif event == "CHAT_MSG_SYSTEM"
|
elseif event == "CHAT_MSG_SYSTEM" then
|
||||||
or event == "CHAT_MSG_BG_SYSTEM_NEUTRAL"
|
|
||||||
or event == "CHAT_MSG_BG_SYSTEM_ALLIANCE"
|
|
||||||
or event == "CHAT_MSG_BG_SYSTEM_HORDE" then
|
|
||||||
OnSystemMessage(arg1)
|
OnSystemMessage(arg1)
|
||||||
elseif event == "CHAT_MSG_TEXT_EMOTE" or event == "CHAT_MSG_EMOTE" then
|
elseif event == "CHAT_MSG_TEXT_EMOTE" or event == "CHAT_MSG_EMOTE" then
|
||||||
OnTextEmote(arg1, arg2)
|
OnTextEmote(arg1, arg2)
|
||||||
@@ -642,8 +1270,18 @@ frame:SetScript("OnEvent", function()
|
|||||||
RelationshipsAchievements:Unlock(111)
|
RelationshipsAchievements:Unlock(111)
|
||||||
elseif event == "MAIL_SEND_SUCCESS" then
|
elseif event == "MAIL_SEND_SUCCESS" then
|
||||||
RelationshipsAchievements:Unlock(110)
|
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()
|
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
|
elseif event == "CHARACTER_POINTS_CHANGED" then
|
||||||
ScanTalentChange()
|
ScanTalentChange()
|
||||||
elseif event == "SPELLS_CHANGED" then
|
elseif event == "SPELLS_CHANGED" then
|
||||||
@@ -679,5 +1317,39 @@ frame:SetScript("OnEvent", function()
|
|||||||
RelationshipsAchievements:Unlock(103)
|
RelationshipsAchievements:Unlock(103)
|
||||||
if GetNumGuildMembers and GetNumGuildMembers() >= 10 then RelationshipsAchievements:Unlock(1703) end
|
if GetNumGuildMembers and GetNumGuildMembers() >= 10 then RelationshipsAchievements:Unlock(1703) end
|
||||||
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
|
||||||
end)
|
end)
|
||||||
|
|||||||
+147
@@ -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,3 +1,5 @@
|
|||||||
|
<img src="https://i.ibb.co/z1W5z0T/a.gif"/>
|
||||||
|
|
||||||
# Relationships Achievements
|
# Relationships Achievements
|
||||||
|
|
||||||
A **WotLK-style achievement system** for **OctoWoW**.
|
A **WotLK-style achievement system** for **OctoWoW**.
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
## Interface: 11200
|
## Interface: 11200
|
||||||
## Title: Relationships Achievements
|
## Title: Relationships Achievements
|
||||||
## Notes: WotLK-style achievement system for OctoWoW
|
## Notes: WotLK-style achievement system for OctoWoW
|
||||||
## Author: Lovable
|
## Author: Relationship
|
||||||
## Version: 1.0
|
## Version: 1.1
|
||||||
## SavedVariables: RelationshipsAchievementsDB
|
## SavedVariables: RelationshipsAchievementsDB
|
||||||
## SavedVariablesPerCharacter: RelationshipsAchievementsCharDB
|
## SavedVariablesPerCharacter: RelationshipsAchievementsCharDB
|
||||||
## DefaultState: Enabled
|
## DefaultState: Enabled
|
||||||
@@ -13,5 +13,6 @@ Core.lua
|
|||||||
Toast.lua
|
Toast.lua
|
||||||
Detail.lua
|
Detail.lua
|
||||||
UI.lua
|
UI.lua
|
||||||
|
Minimap.lua
|
||||||
Events.lua
|
Events.lua
|
||||||
Bindings.xml
|
Bindings.xml
|
||||||
|
|||||||
Reference in New Issue
Block a user