Upload files to "/"
This commit is contained in:
+180
@@ -0,0 +1,180 @@
|
|||||||
|
-- Achievement detail popup — a movable/closable frame that shows a single
|
||||||
|
-- achievement's icon, name, description, points, and unlock date. Opened by
|
||||||
|
-- clicking an achievement chat link, the toast, or a row in the panel.
|
||||||
|
|
||||||
|
local popups = {}
|
||||||
|
local MAX_POPUPS = 6
|
||||||
|
|
||||||
|
local function FormatDate(t)
|
||||||
|
if not t then return "" end
|
||||||
|
return date("%m/%d/%y", t)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CreatePopup(index)
|
||||||
|
local f = CreateFrame("Frame", "RelationshipsAchievementDetailPopup"..index, UIParent)
|
||||||
|
f:SetWidth(340); f:SetHeight(96)
|
||||||
|
f:SetPoint("CENTER", UIParent, "CENTER", 0, -60 - ((index - 1) * 110))
|
||||||
|
f:SetFrameStrata("FULLSCREEN_DIALOG")
|
||||||
|
f:SetToplevel(true)
|
||||||
|
f:SetMovable(true); f:EnableMouse(true)
|
||||||
|
f:RegisterForDrag("LeftButton")
|
||||||
|
f:SetScript("OnDragStart", function() this:StartMoving() end)
|
||||||
|
f:SetScript("OnDragStop", function() this:StopMovingOrSizing() end)
|
||||||
|
f:Hide()
|
||||||
|
|
||||||
|
f:SetBackdrop({
|
||||||
|
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
|
||||||
|
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
|
||||||
|
tile = true, tileSize = 32, edgeSize = 16,
|
||||||
|
insets = { left = 5, right = 5, top = 5, bottom = 5 },
|
||||||
|
})
|
||||||
|
f:SetBackdropColor(0, 0, 0, 0.9)
|
||||||
|
|
||||||
|
local header = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
header:SetPoint("TOP", f, "TOP", 0, -8)
|
||||||
|
header:SetText("Achievement")
|
||||||
|
header:SetTextColor(1, 0.82, 0)
|
||||||
|
f.header = header
|
||||||
|
|
||||||
|
local icon = f:CreateTexture(nil, "ARTWORK")
|
||||||
|
icon:SetWidth(48); icon:SetHeight(48)
|
||||||
|
icon:SetPoint("TOPLEFT", f, "TOPLEFT", 14, -24)
|
||||||
|
f.icon = icon
|
||||||
|
|
||||||
|
local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
|
||||||
|
title:SetPoint("TOPLEFT", icon, "TOPRIGHT", 12, -2)
|
||||||
|
title:SetPoint("RIGHT", f, "RIGHT", -12, 0)
|
||||||
|
title:SetJustifyH("LEFT")
|
||||||
|
title:SetTextColor(1, 0.82, 0)
|
||||||
|
f.title = title
|
||||||
|
|
||||||
|
local desc = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||||||
|
desc:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -4)
|
||||||
|
desc:SetPoint("RIGHT", f, "RIGHT", -12, 0)
|
||||||
|
desc:SetJustifyH("LEFT")
|
||||||
|
desc:SetTextColor(1, 1, 1)
|
||||||
|
f.desc = desc
|
||||||
|
|
||||||
|
local date = f:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||||||
|
date:SetPoint("BOTTOMLEFT", f, "BOTTOMLEFT", 14, 8)
|
||||||
|
date:SetTextColor(0.7, 0.7, 0.7)
|
||||||
|
f.date = date
|
||||||
|
|
||||||
|
local points = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
points:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -12, 8)
|
||||||
|
points:SetTextColor(1, 0.82, 0)
|
||||||
|
f.points = points
|
||||||
|
|
||||||
|
local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
|
||||||
|
close:SetPoint("TOPRIGHT", f, "TOPRIGHT", -2, -2)
|
||||||
|
close:SetScript("OnClick", function() f:Hide() end)
|
||||||
|
|
||||||
|
if not UISpecialFrames then UISpecialFrames = {} end
|
||||||
|
tinsert(UISpecialFrames, "RelationshipsAchievementDetailPopup"..index)
|
||||||
|
|
||||||
|
return f
|
||||||
|
end
|
||||||
|
|
||||||
|
local function AcquirePopup()
|
||||||
|
for i = 1, table.getn(popups) do
|
||||||
|
if not popups[i]:IsVisible() then
|
||||||
|
return popups[i]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if table.getn(popups) < MAX_POPUPS then
|
||||||
|
local p = CreatePopup(table.getn(popups) + 1)
|
||||||
|
table.insert(popups, p)
|
||||||
|
return p
|
||||||
|
end
|
||||||
|
return popups[1]
|
||||||
|
end
|
||||||
|
|
||||||
|
function RelationshipsAchievements_ShowDetail(ach)
|
||||||
|
if not ach then return end
|
||||||
|
local f = AcquirePopup()
|
||||||
|
|
||||||
|
local unlocked = RelationshipsAchievements and RelationshipsAchievements:IsUnlocked(ach.id)
|
||||||
|
if unlocked then
|
||||||
|
f.header:SetText("Achievement Earned")
|
||||||
|
else
|
||||||
|
f.header:SetText("Achievement")
|
||||||
|
end
|
||||||
|
f.title:SetText(ach.name or "")
|
||||||
|
f.desc:SetText(ach.desc or "")
|
||||||
|
f.icon:SetTexture(ach.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
|
||||||
|
if ach.points and ach.points > 0 then
|
||||||
|
f.points:SetText(ach.points.." pts")
|
||||||
|
else
|
||||||
|
f.points:SetText("")
|
||||||
|
end
|
||||||
|
if unlocked then
|
||||||
|
local t = RelationshipsAchievements:GetUnlockTime(ach.id)
|
||||||
|
f.date:SetText(FormatDate(t))
|
||||||
|
else
|
||||||
|
f.date:SetText("Not yet earned")
|
||||||
|
end
|
||||||
|
f:Show()
|
||||||
|
f:Raise()
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============ Chat hyperlink handling ============
|
||||||
|
|
||||||
|
local function HandleAchievementLink(link)
|
||||||
|
if not link then return false end
|
||||||
|
if string.sub(link, 1, 6) ~= "relach" then return false end
|
||||||
|
-- link format: "relach:<id>"
|
||||||
|
local _, _, idStr = string.find(link, "^relach:(%d+)")
|
||||||
|
local id = tonumber(idStr)
|
||||||
|
if id and RelationshipsAchievements_ById and RelationshipsAchievements_ById[id] then
|
||||||
|
RelationshipsAchievements_ShowDetail(RelationshipsAchievements_ById[id])
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
|
||||||
|
local orig_SetItemRef = SetItemRef
|
||||||
|
function SetItemRef(link, text, button)
|
||||||
|
if HandleAchievementLink(link) then return end
|
||||||
|
if orig_SetItemRef then
|
||||||
|
return orig_SetItemRef(link, text, button)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Safely install a script handler. Vanilla 1.12 ChatFrames only support
|
||||||
|
-- OnHyperlinkClick; OnHyperlinkShow was added in later clients and setting
|
||||||
|
-- it here throws "ChatFrameN doesn't have a 'OnHyperlinkShow' script".
|
||||||
|
-- Wrapping in pcall lets us support both without erroring on 1.12.
|
||||||
|
local function SafeHookScript(frame, scriptName, newFn)
|
||||||
|
local prev
|
||||||
|
pcall(function() prev = frame:GetScript(scriptName) end)
|
||||||
|
local ok = pcall(function()
|
||||||
|
frame:SetScript(scriptName, function()
|
||||||
|
if HandleAchievementLink(arg1) then return end
|
||||||
|
if prev then prev() end
|
||||||
|
end)
|
||||||
|
end)
|
||||||
|
return ok
|
||||||
|
end
|
||||||
|
|
||||||
|
local function InstallChatHooks()
|
||||||
|
local num = NUM_CHAT_WINDOWS or 7
|
||||||
|
for i = 1, num do
|
||||||
|
local cf = getglobal("ChatFrame"..i)
|
||||||
|
if cf then
|
||||||
|
if cf.SetHyperlinksEnabled then
|
||||||
|
pcall(function() cf:SetHyperlinksEnabled(true) end)
|
||||||
|
end
|
||||||
|
SafeHookScript(cf, "OnHyperlinkClick")
|
||||||
|
SafeHookScript(cf, "OnHyperlinkShow")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
InstallChatHooks()
|
||||||
|
|
||||||
|
local _hookFrame = CreateFrame("Frame")
|
||||||
|
_hookFrame:RegisterEvent("PLAYER_LOGIN")
|
||||||
|
_hookFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
|
||||||
|
_hookFrame:SetScript("OnEvent", function()
|
||||||
|
InstallChatHooks()
|
||||||
|
end)
|
||||||
+683
@@ -0,0 +1,683 @@
|
|||||||
|
-- Event-driven achievement tracking, character scan, and hotkey binding.
|
||||||
|
|
||||||
|
local frame = CreateFrame("Frame", "RelationshipsAchievementsEventFrame", UIParent)
|
||||||
|
|
||||||
|
-- Turtle/OctoWoW builds expose slightly different event sets. Registering an
|
||||||
|
-- unknown event can abort an entire Lua file on some 1.12 clients, so optional
|
||||||
|
-- events are protected. The standard Vanilla events remain the primary path.
|
||||||
|
local function SafeRegister(name)
|
||||||
|
pcall(frame.RegisterEvent, frame, name)
|
||||||
|
end
|
||||||
|
local trackedEvents = {
|
||||||
|
"PLAYER_LOGIN", "PLAYER_ENTERING_WORLD", "PLAYER_LEVEL_UP", "PLAYER_DEAD",
|
||||||
|
"QUEST_COMPLETE", "QUEST_TURNED_IN", "ZONE_CHANGED_NEW_AREA", "ZONE_CHANGED",
|
||||||
|
"ZONE_CHANGED_INDOORS", "CHAT_MSG_COMBAT_HONOR_GAIN",
|
||||||
|
"CHAT_MSG_COMBAT_FACTION_CHANGE", "SKILL_LINES_CHANGED", "CHAT_MSG_LOOT",
|
||||||
|
"CHAT_MSG_MONEY", "CHAT_MSG_COMBAT_HOSTILE_DEATH", "CHAT_MSG_COMBAT_XP_GAIN",
|
||||||
|
"CHAT_MSG_SYSTEM", "CHAT_MSG_BG_SYSTEM_NEUTRAL", "CHAT_MSG_BG_SYSTEM_ALLIANCE", "CHAT_MSG_BG_SYSTEM_HORDE", "UPDATE_FACTION", "PARTY_MEMBERS_CHANGED", "FRIENDLIST_UPDATE",
|
||||||
|
"GUILD_ROSTER_UPDATE", "PLAYER_UNGHOST", "PLAYER_ALIVE", "CHAT_MSG_TEXT_EMOTE",
|
||||||
|
"CHAT_MSG_EMOTE", "PLAYER_PVP_RANK_CHANGED", "PLAYER_PVP_KILLS_CHANGED",
|
||||||
|
"PLAYER_PVPKILLS_CHANGED", "HONOR_CURRENCY_UPDATE", "HEARTHSTONE_BOUND",
|
||||||
|
"BANKFRAME_OPENED", "TRAINER_SHOW", "MAIL_SEND_SUCCESS", "PLAYER_EQUIPMENT_CHANGED",
|
||||||
|
"BAG_UPDATE", "CHARACTER_POINTS_CHANGED", "SPELLS_CHANGED", "UNIT_HEALTH", "PLAYER_MONEY"
|
||||||
|
}
|
||||||
|
for i = 1, table.getn(trackedEvents) do SafeRegister(trackedEvents[i]) end
|
||||||
|
|
||||||
|
-- ============ PvP rank scanning ============
|
||||||
|
-- Vanilla UnitPVPRank("player") returns 0 (no rank) or 5..19 where the
|
||||||
|
-- lowest ranked player is Private/Scout = 5. Subtract 4 to get 1..14.
|
||||||
|
local function ScanPvPRank()
|
||||||
|
if not UnitPVPRank then return end
|
||||||
|
local raw = UnitPVPRank("player") or 0
|
||||||
|
-- Lifetime stats preserve the highest rank ever earned, even after weekly
|
||||||
|
-- decay. Use whichever is higher so installing later still grants every
|
||||||
|
-- lower title in the chain.
|
||||||
|
if GetPVPLifetimeStats then
|
||||||
|
local _, _, highest = GetPVPLifetimeStats()
|
||||||
|
if highest and highest > raw then raw = highest end
|
||||||
|
end
|
||||||
|
if raw == 0 then return end
|
||||||
|
local rank = raw
|
||||||
|
if rank >= 5 then rank = rank - 4 end
|
||||||
|
if rank < 1 then return end
|
||||||
|
|
||||||
|
if rank >= 1 then RelationshipsAchievements:Unlock(2001) end -- Private / Scout
|
||||||
|
if rank >= 3 then RelationshipsAchievements:Unlock(2002) end -- Sergeant / Senior Sergeant
|
||||||
|
if rank >= 6 then RelationshipsAchievements:Unlock(2003) end -- Knight / Stone Guard
|
||||||
|
if rank >= 9 then RelationshipsAchievements:Unlock(2004) end -- Champion / Centurion
|
||||||
|
if rank >= 12 then RelationshipsAchievements:Unlock(2005) end -- Marshal / Legionnaire
|
||||||
|
if rank >= 13 then RelationshipsAchievements:Unlock(2006) end -- Field Marshal / Warlord
|
||||||
|
if rank >= 14 then
|
||||||
|
local _, faction = UnitFactionGroup("player")
|
||||||
|
if faction == "Horde" then
|
||||||
|
RelationshipsAchievements:Unlock(1003) -- High Warlord
|
||||||
|
else
|
||||||
|
RelationshipsAchievements:Unlock(1002) -- Grand Marshal
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ScanPvPKills()
|
||||||
|
if not GetPVPLifetimeStats then return end
|
||||||
|
local honorable = GetPVPLifetimeStats() or 0
|
||||||
|
RelationshipsAchievements:UpdateStat("honorKills", honorable)
|
||||||
|
if honorable >= 1 then RelationshipsAchievements:Unlock(501) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ScanTalents()
|
||||||
|
if not GetNumTalentTabs or not GetNumTalents or not GetTalentInfo then return end
|
||||||
|
local spent = 0
|
||||||
|
for tab = 1, GetNumTalentTabs() do
|
||||||
|
for talent = 1, GetNumTalents(tab) do
|
||||||
|
local _, _, _, _, rank = GetTalentInfo(tab, talent)
|
||||||
|
spent = spent + (rank or 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if spent >= 1 then RelationshipsAchievements:Unlock(1111) end
|
||||||
|
if spent >= 51 then RelationshipsAchievements:Unlock(1112) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local previousTalentSpent
|
||||||
|
local function ScanTalentChange()
|
||||||
|
if not GetNumTalentTabs or not GetNumTalents or not GetTalentInfo then return end
|
||||||
|
local spent = 0
|
||||||
|
for tab = 1, GetNumTalentTabs() do
|
||||||
|
for talent = 1, GetNumTalents(tab) do
|
||||||
|
local _, _, _, _, rank = GetTalentInfo(tab, talent)
|
||||||
|
spent = spent + (rank or 0)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if previousTalentSpent and previousTalentSpent > 0 and spent == 0 then RelationshipsAchievements:Unlock(1113) end
|
||||||
|
previousTalentSpent = spent
|
||||||
|
ScanTalents()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ScanEquipmentAndBags()
|
||||||
|
if GetInventoryItemLink then
|
||||||
|
if GetInventoryItemLink("player", 4) then RelationshipsAchievements:Unlock(1203) end
|
||||||
|
if GetInventoryItemLink("player", 19) then RelationshipsAchievements:Unlock(109) end
|
||||||
|
local allRare, haveGear, epic = true, false, false
|
||||||
|
for slot = 1, 18 do
|
||||||
|
if slot ~= 4 and slot ~= 18 then
|
||||||
|
local link = GetInventoryItemLink("player", slot)
|
||||||
|
if link then
|
||||||
|
haveGear = true
|
||||||
|
local _, _, quality = GetItemInfo(link)
|
||||||
|
if quality and quality >= 4 then epic = true end
|
||||||
|
if not quality or quality < 3 then allRare = false end
|
||||||
|
local itemName = GetItemInfo(link)
|
||||||
|
if itemName == "Atiesh, Greatstaff of the Guardian" then RelationshipsAchievements:Unlock(1004) end
|
||||||
|
if itemName == "Thunderfury, Blessed Blade of the Windseeker" then RelationshipsAchievements:Unlock(1005) end
|
||||||
|
if itemName == "Sulfuras, Hand of Ragnaros" then RelationshipsAchievements:Unlock(1006) end
|
||||||
|
else
|
||||||
|
allRare = false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if haveGear and allRare then RelationshipsAchievements:Unlock(1208) end
|
||||||
|
if epic then RelationshipsAchievements:Unlock(1209) end
|
||||||
|
end
|
||||||
|
if GetContainerNumSlots then
|
||||||
|
local bags, has16 = 0, false
|
||||||
|
for bag = 1, 4 do
|
||||||
|
local slots = GetContainerNumSlots(bag) or 0
|
||||||
|
if slots > 0 then bags = bags + 1 end
|
||||||
|
if slots >= 16 then has16 = true end
|
||||||
|
end
|
||||||
|
if bags >= 4 then RelationshipsAchievements:Unlock(1202) end
|
||||||
|
if has16 then RelationshipsAchievements:Unlock(1201) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ScanSpellbook()
|
||||||
|
if not GetSpellName then return end
|
||||||
|
local mounts, pets = 0, 0
|
||||||
|
local i = 1
|
||||||
|
while true do
|
||||||
|
local name, rank = GetSpellName(i, BOOKTYPE_SPELL or "spell")
|
||||||
|
if not name then break end
|
||||||
|
local low = string.lower(name)
|
||||||
|
if string.find(low, "summon ") and (string.find(low, "horse") or string.find(low, "ram")
|
||||||
|
or string.find(low, "tiger") or string.find(low, "raptor") or string.find(low, "wolf")
|
||||||
|
or string.find(low, "kodo") or string.find(low, "mechanostrider")) then mounts = mounts + 1 end
|
||||||
|
if string.find(low, "summon ") and not string.find(low, "demon") and not string.find(low, "elemental") then pets = pets + 1 end
|
||||||
|
i = i + 1
|
||||||
|
end
|
||||||
|
RelationshipsAchievements:UpdateStat("mountCount", mounts)
|
||||||
|
RelationshipsAchievements:UpdateStat("petCount", pets)
|
||||||
|
if mounts >= 1 then RelationshipsAchievements:Unlock(211) end
|
||||||
|
if mounts >= 1 and (UnitLevel("player") or 1) >= 60 then RelationshipsAchievements:Unlock(212) end
|
||||||
|
if pets >= 1 then RelationshipsAchievements:Unlock(1204) end
|
||||||
|
end
|
||||||
|
|
||||||
|
function RelationshipsAchievements_ScanCharacter()
|
||||||
|
RelationshipsAchievements:Init()
|
||||||
|
|
||||||
|
local lvl = UnitLevel("player") or 1
|
||||||
|
if lvl >= 5 then RelationshipsAchievements:Unlock(216) end
|
||||||
|
if lvl >= 10 then RelationshipsAchievements:Unlock(201) end
|
||||||
|
if lvl >= 15 then RelationshipsAchievements:Unlock(218) end
|
||||||
|
if lvl >= 20 then RelationshipsAchievements:Unlock(202) end
|
||||||
|
if lvl >= 25 then RelationshipsAchievements:Unlock(219) end
|
||||||
|
if lvl >= 30 then RelationshipsAchievements:Unlock(203) end
|
||||||
|
if lvl >= 35 then RelationshipsAchievements:Unlock(220) end
|
||||||
|
if lvl >= 40 then RelationshipsAchievements:Unlock(204) end
|
||||||
|
if lvl >= 45 then RelationshipsAchievements:Unlock(221) end
|
||||||
|
if lvl >= 50 then RelationshipsAchievements:Unlock(205) end
|
||||||
|
if lvl >= 55 then RelationshipsAchievements:Unlock(222) end
|
||||||
|
if lvl >= 60 then RelationshipsAchievements:Unlock(206) end
|
||||||
|
|
||||||
|
-- Custom races and OctoWoW level milestone.
|
||||||
|
local race = UnitRace("player")
|
||||||
|
if race == "High Elf" or race == "HighElf" then RelationshipsAchievements:Unlock(1302) end
|
||||||
|
if race == "Goblin" then RelationshipsAchievements:Unlock(1303) end
|
||||||
|
if lvl >= 60 then RelationshipsAchievements:Unlock(1307) end
|
||||||
|
|
||||||
|
-- Class-at-60 achievements
|
||||||
|
if lvl >= 60 then
|
||||||
|
local _, class = UnitClass("player")
|
||||||
|
if class == "WARRIOR" then RelationshipsAchievements:Unlock(1101)
|
||||||
|
elseif class == "PALADIN" then RelationshipsAchievements:Unlock(1102)
|
||||||
|
elseif class == "HUNTER" then RelationshipsAchievements:Unlock(1103)
|
||||||
|
elseif class == "ROGUE" then RelationshipsAchievements:Unlock(1104)
|
||||||
|
elseif class == "PRIEST" then RelationshipsAchievements:Unlock(1105)
|
||||||
|
elseif class == "SHAMAN" then RelationshipsAchievements:Unlock(1106)
|
||||||
|
elseif class == "MAGE" then RelationshipsAchievements:Unlock(1107)
|
||||||
|
elseif class == "WARLOCK" then RelationshipsAchievements:Unlock(1108)
|
||||||
|
elseif class == "DRUID" then RelationshipsAchievements:Unlock(1109)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if GetGuildInfo and GetGuildInfo("player") then
|
||||||
|
RelationshipsAchievements:Unlock(103)
|
||||||
|
if GetNumGuildMembers and GetNumGuildMembers() >= 10 then RelationshipsAchievements:Unlock(1703) end
|
||||||
|
end
|
||||||
|
|
||||||
|
if GetNumPartyMembers and GetNumPartyMembers() > 0 then
|
||||||
|
RelationshipsAchievements:Unlock(105)
|
||||||
|
end
|
||||||
|
|
||||||
|
if GetNumFriends and GetNumFriends() > 0 then
|
||||||
|
RelationshipsAchievements:Unlock(102)
|
||||||
|
RelationshipsAchievements:UpdateStat("friendCount", GetNumFriends())
|
||||||
|
end
|
||||||
|
|
||||||
|
if GetInventoryItemLink and GetInventoryItemLink("player", 19) then
|
||||||
|
RelationshipsAchievements:Unlock(109)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Gold-in-bag achievements (retroactive on scan)
|
||||||
|
if GetMoney then
|
||||||
|
local copper = GetMoney() or 0
|
||||||
|
if copper >= 100 then RelationshipsAchievements:Unlock(1901) end -- 1 silver
|
||||||
|
if copper >= 10000 then RelationshipsAchievements:Unlock(1902) end -- 1 gold
|
||||||
|
if copper >= 5000000 then RelationshipsAchievements:Unlock(223) end -- 500g
|
||||||
|
if copper >= 10000000 then RelationshipsAchievements:Unlock(1903) end -- 1000g
|
||||||
|
if copper >= 50000000 then RelationshipsAchievements:Unlock(224) end -- 5000g
|
||||||
|
if copper >= 100000000 then RelationshipsAchievements:Unlock(1904) end -- 10000g
|
||||||
|
end
|
||||||
|
|
||||||
|
local zc = 0
|
||||||
|
for _ in pairs(RelationshipsAchievementsCharDB.zones) do zc = zc + 1 end
|
||||||
|
RelationshipsAchievements:UpdateStat("zoneCount", zc)
|
||||||
|
|
||||||
|
if GetNumSkillLines then
|
||||||
|
local highestProf = 0
|
||||||
|
local highestWeapon = 0
|
||||||
|
for i = 1, GetNumSkillLines() do
|
||||||
|
local name, isHeader, _, rank = GetSkillLineInfo(i)
|
||||||
|
if not isHeader and rank and rank > 0 then
|
||||||
|
if name == "Swords" or name == "Two-Handed Swords"
|
||||||
|
or name == "Axes" or name == "Two-Handed Axes"
|
||||||
|
or name == "Maces" or name == "Two-Handed Maces"
|
||||||
|
or name == "Daggers" or name == "Fist Weapons"
|
||||||
|
or name == "Polearms" or name == "Staves"
|
||||||
|
or name == "Bows" or name == "Crossbows" or name == "Guns"
|
||||||
|
or name == "Wands" or name == "Thrown" then
|
||||||
|
if rank > highestWeapon then highestWeapon = rank end
|
||||||
|
elseif name == "Cooking" then
|
||||||
|
RelationshipsAchievements:UpdateStat("cookingSkill", rank)
|
||||||
|
elseif name == "Fishing" then
|
||||||
|
RelationshipsAchievements:UpdateStat("fishingSkill", rank)
|
||||||
|
elseif name == "First Aid" then
|
||||||
|
if rank >= 300 then RelationshipsAchievements:Unlock(707) end
|
||||||
|
else
|
||||||
|
if rank > highestProf then highestProf = rank end
|
||||||
|
if rank >= 300 then
|
||||||
|
if name == "Alchemy" then RelationshipsAchievements:Unlock(708) end
|
||||||
|
if name == "Blacksmithing" then RelationshipsAchievements:Unlock(709) end
|
||||||
|
if name == "Enchanting" then RelationshipsAchievements:Unlock(710) end
|
||||||
|
if name == "Engineering" then RelationshipsAchievements:Unlock(711) end
|
||||||
|
if name == "Herbalism" then RelationshipsAchievements:Unlock(712) end
|
||||||
|
if name == "Leatherworking"then RelationshipsAchievements:Unlock(713) end
|
||||||
|
if name == "Mining" then RelationshipsAchievements:Unlock(714) end
|
||||||
|
if name == "Skinning" then RelationshipsAchievements:Unlock(715) end
|
||||||
|
if name == "Tailoring" then RelationshipsAchievements:Unlock(716) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
RelationshipsAchievements:UpdateStat("profSkill", highestProf)
|
||||||
|
RelationshipsAchievements:UpdateStat("weaponSkill", highestWeapon)
|
||||||
|
end
|
||||||
|
|
||||||
|
if GetNumFactions then
|
||||||
|
local exalted = 0
|
||||||
|
local hasFriendly, hasHonored, hasRevered, hasExalted = false, false, false, false
|
||||||
|
for i = 1, GetNumFactions() do
|
||||||
|
local name, _, standing = GetFactionInfo(i)
|
||||||
|
if standing then
|
||||||
|
if standing >= 5 then hasFriendly = true end
|
||||||
|
if standing >= 6 then hasHonored = true end
|
||||||
|
if standing >= 7 then hasRevered = true end
|
||||||
|
if standing >= 8 then
|
||||||
|
hasExalted = true
|
||||||
|
exalted = exalted + 1
|
||||||
|
if name and RelationshipsAchievements_FactionMap[name] then
|
||||||
|
RelationshipsAchievements:Unlock(RelationshipsAchievements_FactionMap[name])
|
||||||
|
end
|
||||||
|
RelationshipsAchievementsCharDB.factionsExalted[name or "?"] = true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if hasFriendly then RelationshipsAchievements:Unlock(801) end
|
||||||
|
if hasHonored then RelationshipsAchievements:Unlock(802) end
|
||||||
|
if hasRevered then RelationshipsAchievements:Unlock(803) end
|
||||||
|
if hasExalted then RelationshipsAchievements:Unlock(804) end
|
||||||
|
RelationshipsAchievements:UpdateStat("exaltedCount", exalted)
|
||||||
|
end
|
||||||
|
|
||||||
|
ScanPvPRank()
|
||||||
|
ScanPvPKills()
|
||||||
|
ScanTalents()
|
||||||
|
ScanEquipmentAndBags()
|
||||||
|
ScanSpellbook()
|
||||||
|
|
||||||
|
RelationshipsAchievements_RefreshUI = RelationshipsAchievements_RefreshUI or function() end
|
||||||
|
if RelationshipsAchievementFrame and RelationshipsAchievementFrame:IsVisible() then
|
||||||
|
RelationshipsAchievements_RefreshUI()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function BindHotkey()
|
||||||
|
if not SetBinding then return end
|
||||||
|
local existing = GetBindingAction and GetBindingAction("Y")
|
||||||
|
if existing and existing ~= "" and existing ~= "TOGGLERELATIONSHIPSACHIEVEMENT" then
|
||||||
|
return
|
||||||
|
end
|
||||||
|
SetBinding("Y", "TOGGLERELATIONSHIPSACHIEVEMENT")
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============ SendChatMessage hook (Chatty achievement) ============
|
||||||
|
-- Wrapping SendChatMessage is more reliable than reading CHAT_MSG_SAY back,
|
||||||
|
-- because whispers, guild chat, and channels each fire different events and
|
||||||
|
-- some don't echo the sender field consistently on Turtle / Vanilla.
|
||||||
|
local _origSendChatMessage = SendChatMessage
|
||||||
|
if _origSendChatMessage then SendChatMessage = function(msg, chatType, language, target)
|
||||||
|
_origSendChatMessage(msg, chatType, language, target)
|
||||||
|
if msg and msg ~= "" and RelationshipsAchievements and RelationshipsAchievements.AddStat then
|
||||||
|
RelationshipsAchievements:AddStat("chatCount", 1)
|
||||||
|
end
|
||||||
|
end end
|
||||||
|
|
||||||
|
local _origSendMail = SendMail
|
||||||
|
if _origSendMail then SendMail = function(name, subject, body)
|
||||||
|
_origSendMail(name, subject, body)
|
||||||
|
RelationshipsAchievements:Unlock(110)
|
||||||
|
end end
|
||||||
|
|
||||||
|
local _origPostAuction = PostAuction
|
||||||
|
if _origPostAuction then PostAuction = function(minBid, buyoutPrice, runTime, stackSize, numStacks)
|
||||||
|
_origPostAuction(minBid, buyoutPrice, runTime, stackSize, numStacks)
|
||||||
|
RelationshipsAchievements:AddStat("auctionsListed", numStacks or 1)
|
||||||
|
end end
|
||||||
|
|
||||||
|
local _origBuyoutAuction = BuyoutAuction
|
||||||
|
if _origBuyoutAuction then BuyoutAuction = function(index)
|
||||||
|
_origBuyoutAuction(index)
|
||||||
|
RelationshipsAchievements:Unlock(107)
|
||||||
|
RelationshipsAchievements:AddStat("auctionsWon", 1)
|
||||||
|
end end
|
||||||
|
|
||||||
|
local OnQuestComplete
|
||||||
|
|
||||||
|
-- Hook ConfirmBinder(): the innkeeper popup's OnAccept calls this API and it
|
||||||
|
-- fires exactly once per successful bind. The HEARTHSTONE_BOUND event does not
|
||||||
|
-- exist in vanilla 1.12, and the CHAT_MSG_SYSTEM string fallback below is
|
||||||
|
-- localised / worded differently on some Turtle/Octo builds, so hooking the
|
||||||
|
-- API is the only reliable trigger for re-binding to a new inn.
|
||||||
|
local _origConfirmBinder = ConfirmBinder
|
||||||
|
if _origConfirmBinder then ConfirmBinder = function()
|
||||||
|
_origConfirmBinder()
|
||||||
|
if RelationshipsAchievements and RelationshipsAchievements.Unlock then
|
||||||
|
RelationshipsAchievements:Unlock(108)
|
||||||
|
end
|
||||||
|
if RelationshipsAchievementsCharDB then
|
||||||
|
RelationshipsAchievementsCharDB.pendingInnBind = nil
|
||||||
|
end
|
||||||
|
end end
|
||||||
|
|
||||||
|
-- Hook GetQuestReward(): fires exactly once when the player accepts a quest
|
||||||
|
-- reward. Much more reliable than QUEST_COMPLETE (which fires each time the
|
||||||
|
-- reward window opens) for tracking completed-quest counters.
|
||||||
|
local _origGetQuestReward = GetQuestReward
|
||||||
|
if _origGetQuestReward then GetQuestReward = function(choice)
|
||||||
|
OnQuestComplete()
|
||||||
|
return _origGetQuestReward(choice)
|
||||||
|
end end
|
||||||
|
|
||||||
|
-- ============ DoEmote hook (Say Hello / Dance Off) ============
|
||||||
|
-- Hooking DoEmote is the cleanest way: the token is stable ("wave", "dance")
|
||||||
|
-- across all locales, unlike the CHAT_MSG_TEXT_EMOTE text which is localized.
|
||||||
|
local _origDoEmote = DoEmote
|
||||||
|
if _origDoEmote then
|
||||||
|
DoEmote = function(token, target)
|
||||||
|
_origDoEmote(token, target)
|
||||||
|
if not token then return end
|
||||||
|
local t = string.lower(token)
|
||||||
|
if t == "wave" then
|
||||||
|
RelationshipsAchievements:Unlock(1701)
|
||||||
|
elseif t == "dance" then
|
||||||
|
RelationshipsAchievements:Unlock(1702)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local loginScanDone = false
|
||||||
|
|
||||||
|
local function OnLogin()
|
||||||
|
RelationshipsAchievements:Init()
|
||||||
|
RelationshipsAchievements:Unlock(101)
|
||||||
|
RelationshipsAchievements:Unlock(1301)
|
||||||
|
BindHotkey()
|
||||||
|
if not loginScanDone then
|
||||||
|
loginScanDone = true
|
||||||
|
local scanner = CreateFrame("Frame")
|
||||||
|
local acc = 0
|
||||||
|
scanner:SetScript("OnUpdate", function()
|
||||||
|
acc = acc + (arg1 or 0)
|
||||||
|
if acc >= 2 then
|
||||||
|
RelationshipsAchievements_ScanCharacter()
|
||||||
|
if ShowFriends then ShowFriends() end
|
||||||
|
if GuildRoster then GuildRoster() end
|
||||||
|
this:SetScript("OnUpdate", nil)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function OnLevelUp()
|
||||||
|
RelationshipsAchievements_ScanCharacter()
|
||||||
|
end
|
||||||
|
|
||||||
|
OnQuestComplete = function()
|
||||||
|
-- QUEST_COMPLETE is the reliable 1.12 turn-in event. Custom clients that
|
||||||
|
-- also emit QUEST_TURNED_IN are de-duplicated by time stamp below.
|
||||||
|
local now = time()
|
||||||
|
if RelationshipsAchievementsCharDB.lastQuestCredit == now then return end
|
||||||
|
RelationshipsAchievementsCharDB.lastQuestCredit = now
|
||||||
|
RelationshipsAchievements:AddStat("questCount", 1)
|
||||||
|
local title = GetTitleText and GetTitleText() or ""
|
||||||
|
local low = string.lower(title or "")
|
||||||
|
if string.find(low, "attunement to the core") then RelationshipsAchievements:Unlock(683) end
|
||||||
|
if string.find(low, "blackhand's command") then RelationshipsAchievements:Unlock(682) end
|
||||||
|
if string.find(low, "onyxia") or string.find(low, "great masquerade") then RelationshipsAchievements:Unlock(684) end
|
||||||
|
if string.find(low, "head of onyxia") or string.find(low, "victory for the alliance") or string.find(low, "victory for the horde") then RelationshipsAchievements:Unlock(2104) end
|
||||||
|
if string.find(low, "silithyst") then RelationshipsAchievements:AddStat("silithystTurnIns", 1) end
|
||||||
|
if string.find(low, "class") then RelationshipsAchievements:Unlock(312) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function OnZoneChanged()
|
||||||
|
local zone = GetRealZoneText() or GetZoneText()
|
||||||
|
if not zone or zone == "" then return end
|
||||||
|
|
||||||
|
if RelationshipsAchievements_CityMap and RelationshipsAchievements_CityMap[zone] then
|
||||||
|
RelationshipsAchievements:Unlock(RelationshipsAchievements_CityMap[zone])
|
||||||
|
RelationshipsAchievementsCharDB.cities = RelationshipsAchievementsCharDB.cities or {}
|
||||||
|
if not RelationshipsAchievementsCharDB.cities[zone] then
|
||||||
|
RelationshipsAchievementsCharDB.cities[zone] = true
|
||||||
|
local a, h = 0, 0
|
||||||
|
for c in pairs(RelationshipsAchievementsCharDB.cities) do
|
||||||
|
if RelationshipsAchievements_AllianceCities[c] then a = a + 1 end
|
||||||
|
if RelationshipsAchievements_HordeCities[c] then h = h + 1 end
|
||||||
|
end
|
||||||
|
RelationshipsAchievements:UpdateStat("allianceCitiesVisited", a)
|
||||||
|
RelationshipsAchievements:UpdateStat("hordeCitiesVisited", h)
|
||||||
|
if a >= 3 then RelationshipsAchievements:Unlock(1807) end
|
||||||
|
if h >= 3 then RelationshipsAchievements:Unlock(1808) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
local lowZone = string.lower(zone)
|
||||||
|
if string.find(lowZone, "karazhan crypt") then RelationshipsAchievements:Unlock(1305) end
|
||||||
|
if string.find(lowZone, "emerald sanctum") then RelationshipsAchievements:Unlock(1308) end
|
||||||
|
|
||||||
|
if not RelationshipsAchievementsCharDB.zones[zone] then
|
||||||
|
RelationshipsAchievementsCharDB.zones[zone] = true
|
||||||
|
local id = RelationshipsAchievements_ZoneMap[zone]
|
||||||
|
if id then RelationshipsAchievements:Unlock(id) end
|
||||||
|
local count = 0
|
||||||
|
for _ in pairs(RelationshipsAchievementsCharDB.zones) do count = count + 1 end
|
||||||
|
RelationshipsAchievements:UpdateStat("zoneCount", count)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function OnHonorGain(msg)
|
||||||
|
-- Objective/bonus honor uses this event too, so never increment blindly.
|
||||||
|
-- Lifetime stats give the authoritative HK total and cannot double-count.
|
||||||
|
if GetPVPLifetimeStats then
|
||||||
|
local before = RelationshipsAchievements:GetStat("honorKills")
|
||||||
|
ScanPvPKills()
|
||||||
|
local after = RelationshipsAchievements:GetStat("honorKills")
|
||||||
|
if after > before then
|
||||||
|
RelationshipsAchievements:Unlock(501)
|
||||||
|
RelationshipsAchievements:UpdateStat("pvpKills", after)
|
||||||
|
end
|
||||||
|
elseif msg and string.find(string.lower(msg), "honorable kill") then
|
||||||
|
RelationshipsAchievements:Unlock(501)
|
||||||
|
RelationshipsAchievements:AddStat("honorKills", 1)
|
||||||
|
RelationshipsAchievements:AddStat("pvpKills", 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function OnFactionChange(msg)
|
||||||
|
if not msg then return end
|
||||||
|
-- Recompute from the faction table. Incrementing from chat inflated the
|
||||||
|
-- count when duplicate messages fired and could award 5/10 Exalted early.
|
||||||
|
RelationshipsAchievements_ScanCharacter()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function OnSkillLinesChanged()
|
||||||
|
RelationshipsAchievements_ScanCharacter()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function OnLootMessage(msg)
|
||||||
|
if not msg then return end
|
||||||
|
local clean = string.gsub(msg, ",", "")
|
||||||
|
local _, _, g = string.find(clean, "(%d+) Gold")
|
||||||
|
if g then
|
||||||
|
RelationshipsAchievements:AddStat("goldLooted", tonumber(g))
|
||||||
|
end
|
||||||
|
local low = string.lower(msg)
|
||||||
|
if string.find(low, "fish") or string.find(low, "snapper") or string.find(low, "trout")
|
||||||
|
or string.find(low, "salmon") or string.find(low, "catfish") or string.find(low, "eel") then
|
||||||
|
RelationshipsAchievements:Unlock(1401)
|
||||||
|
RelationshipsAchievements:AddStat("fishCount", 1)
|
||||||
|
end
|
||||||
|
if string.find(low, "you create") and (string.find(low, "food") or string.find(low, "meal")
|
||||||
|
or string.find(low, "roast") or string.find(low, "stew")) then RelationshipsAchievements:Unlock(1501) end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function OnEnemyDeath(msg)
|
||||||
|
if not msg then return end
|
||||||
|
if string.find(msg, "You have slain") or string.find(msg, "slain by you") then
|
||||||
|
RelationshipsAchievements:AddStat("killCount", 1)
|
||||||
|
end
|
||||||
|
local bossMap = RelationshipsAchievements_BossMap or {}
|
||||||
|
for boss, info in pairs(bossMap) do
|
||||||
|
if string.find(msg, boss, 1, true) then
|
||||||
|
RelationshipsAchievements:Unlock(info.id)
|
||||||
|
local seen = info.raid and RelationshipsAchievementsCharDB.raidBosses or RelationshipsAchievementsCharDB.dungeonBosses
|
||||||
|
if not seen[boss] then
|
||||||
|
seen[boss] = true
|
||||||
|
RelationshipsAchievements:AddStat(info.raid and "raidsCleared" or "dungeonsCleared", 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function OnSystemMessage(msg)
|
||||||
|
if not msg then return end
|
||||||
|
local _, _, winner = string.find(msg, "^(.-) has defeated ")
|
||||||
|
if winner and winner == UnitName("player") then
|
||||||
|
RelationshipsAchievements:AddStat("duelWins", 1)
|
||||||
|
end
|
||||||
|
if string.find(msg, "home is now") or string.find(msg, "bound to") then
|
||||||
|
RelationshipsAchievements:Unlock(108)
|
||||||
|
RelationshipsAchievementsCharDB.pendingInnBind = nil
|
||||||
|
end
|
||||||
|
local low = string.lower(msg)
|
||||||
|
if string.find(low, "learned a new") or string.find(low, "you have learned") then RelationshipsAchievements:Unlock(1110) end
|
||||||
|
if string.find(low, "darkmoon faire") then RelationshipsAchievements:Unlock(911) 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, "winter veil") then RelationshipsAchievements:Unlock(901) end
|
||||||
|
-- Battleground victories. On 1.12 the win message reads "The Alliance wins!"
|
||||||
|
-- or "The Horde wins!" via CHAT_MSG_BG_SYSTEM_NEUTRAL and does NOT include
|
||||||
|
-- the map name, so we look up the current BG from GetRealZoneText() and
|
||||||
|
-- credit the player's faction only. Guard with pendingBgWin so duplicate
|
||||||
|
-- messages (both faction and neutral system chat) don't double-count.
|
||||||
|
if string.find(msg, "wins!") or string.find(msg, "[Vv]ictory") then
|
||||||
|
local zone = (GetRealZoneText and GetRealZoneText()) or ""
|
||||||
|
local _, myFaction = UnitFactionGroup("player")
|
||||||
|
local winner
|
||||||
|
if string.find(msg, "Alliance") then winner = "Alliance"
|
||||||
|
elseif string.find(msg, "Horde") then winner = "Horde"
|
||||||
|
end
|
||||||
|
local key = zone .. "|" .. (winner or "?")
|
||||||
|
if RelationshipsAchievementsCharDB and RelationshipsAchievementsCharDB.lastBgWinKey ~= key then
|
||||||
|
RelationshipsAchievementsCharDB.lastBgWinKey = key
|
||||||
|
if not winner or winner == myFaction then
|
||||||
|
if string.find(zone, "Warsong Gulch") then
|
||||||
|
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
|
||||||
|
|
||||||
|
-- Emote handler: catch /wave and /dance in every locale by matching the
|
||||||
|
-- token via DoEmote hook AND as a fallback matching the English text here.
|
||||||
|
local function OnTextEmote(msg, sender)
|
||||||
|
if not msg then return end
|
||||||
|
local me = UnitName("player")
|
||||||
|
-- vanilla emote text always begins with "You " when it's the local player,
|
||||||
|
-- or the player's name otherwise.
|
||||||
|
local mine = (sender and sender == me) or string.find(msg, "^You ")
|
||||||
|
if not mine then return end
|
||||||
|
if string.find(string.lower(msg), "wave") then
|
||||||
|
RelationshipsAchievements:Unlock(1701)
|
||||||
|
end
|
||||||
|
if string.find(string.lower(msg), "dance") then
|
||||||
|
RelationshipsAchievements:Unlock(1702)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
frame:SetScript("OnEvent", function()
|
||||||
|
if event == "PLAYER_LOGIN" then
|
||||||
|
OnLogin()
|
||||||
|
elseif event == "PLAYER_ENTERING_WORLD" then
|
||||||
|
RelationshipsAchievements:Init()
|
||||||
|
BindHotkey()
|
||||||
|
OnZoneChanged()
|
||||||
|
ScanPvPRank()
|
||||||
|
elseif event == "PLAYER_LEVEL_UP" then
|
||||||
|
OnLevelUp()
|
||||||
|
elseif event == "PLAYER_DEAD" or event == "PLAYER_UNGHOST" or event == "PLAYER_ALIVE" then
|
||||||
|
if event == "PLAYER_DEAD" then
|
||||||
|
RelationshipsAchievements:Unlock(207)
|
||||||
|
RelationshipsAchievements:AddStat("deaths", 1)
|
||||||
|
end
|
||||||
|
elseif event == "QUEST_COMPLETE" or event == "QUEST_TURNED_IN" then
|
||||||
|
OnQuestComplete()
|
||||||
|
elseif event == "ZONE_CHANGED_NEW_AREA" or event == "ZONE_CHANGED" or event == "ZONE_CHANGED_INDOORS" then
|
||||||
|
OnZoneChanged()
|
||||||
|
elseif event == "CHAT_MSG_COMBAT_HONOR_GAIN" then
|
||||||
|
OnHonorGain(arg1)
|
||||||
|
elseif event == "CHAT_MSG_COMBAT_FACTION_CHANGE" then
|
||||||
|
OnFactionChange(arg1)
|
||||||
|
elseif event == "SKILL_LINES_CHANGED" then
|
||||||
|
OnSkillLinesChanged()
|
||||||
|
elseif event == "CHAT_MSG_LOOT" or event == "CHAT_MSG_MONEY" then
|
||||||
|
OnLootMessage(arg1)
|
||||||
|
elseif event == "CHAT_MSG_COMBAT_HOSTILE_DEATH" or event == "CHAT_MSG_COMBAT_XP_GAIN" then
|
||||||
|
OnEnemyDeath(arg1)
|
||||||
|
elseif event == "CHAT_MSG_SYSTEM"
|
||||||
|
or event == "CHAT_MSG_BG_SYSTEM_NEUTRAL"
|
||||||
|
or event == "CHAT_MSG_BG_SYSTEM_ALLIANCE"
|
||||||
|
or event == "CHAT_MSG_BG_SYSTEM_HORDE" then
|
||||||
|
OnSystemMessage(arg1)
|
||||||
|
elseif event == "CHAT_MSG_TEXT_EMOTE" or event == "CHAT_MSG_EMOTE" then
|
||||||
|
OnTextEmote(arg1, arg2)
|
||||||
|
elseif event == "PLAYER_PVP_RANK_CHANGED"
|
||||||
|
or event == "HONOR_CURRENCY_UPDATE"
|
||||||
|
or event == "PLAYER_PVPKILLS_CHANGED" or event == "PLAYER_PVP_KILLS_CHANGED" then
|
||||||
|
ScanPvPRank()
|
||||||
|
ScanPvPKills()
|
||||||
|
elseif event == "HEARTHSTONE_BOUND" then
|
||||||
|
RelationshipsAchievements:Unlock(108)
|
||||||
|
RelationshipsAchievementsCharDB.pendingInnBind = nil
|
||||||
|
elseif event == "BANKFRAME_OPENED" then
|
||||||
|
RelationshipsAchievements:Unlock(106)
|
||||||
|
elseif event == "TRAINER_SHOW" then
|
||||||
|
RelationshipsAchievements:Unlock(111)
|
||||||
|
elseif event == "MAIL_SEND_SUCCESS" then
|
||||||
|
RelationshipsAchievements:Unlock(110)
|
||||||
|
elseif event == "PLAYER_EQUIPMENT_CHANGED" or event == "BAG_UPDATE" then
|
||||||
|
ScanEquipmentAndBags()
|
||||||
|
elseif event == "CHARACTER_POINTS_CHANGED" then
|
||||||
|
ScanTalentChange()
|
||||||
|
elseif event == "SPELLS_CHANGED" then
|
||||||
|
ScanSpellbook()
|
||||||
|
elseif event == "PLAYER_MONEY" then
|
||||||
|
if GetMoney then
|
||||||
|
local copper = GetMoney() or 0
|
||||||
|
if copper >= 100 then RelationshipsAchievements:Unlock(1901) end
|
||||||
|
if copper >= 10000 then RelationshipsAchievements:Unlock(1902) end
|
||||||
|
if copper >= 5000000 then RelationshipsAchievements:Unlock(223) end
|
||||||
|
if copper >= 10000000 then RelationshipsAchievements:Unlock(1903) end
|
||||||
|
if copper >= 50000000 then RelationshipsAchievements:Unlock(224) end
|
||||||
|
if copper >= 100000000 then RelationshipsAchievements:Unlock(1904) end
|
||||||
|
end
|
||||||
|
elseif event == "UNIT_HEALTH" and arg1 == "player" then
|
||||||
|
local maxHealth = UnitHealthMax("player") or 0
|
||||||
|
if maxHealth > 0 and UnitHealth("player") > 0 and UnitHealth("player") * 10 < maxHealth then
|
||||||
|
RelationshipsAchievements:Unlock(1605)
|
||||||
|
end
|
||||||
|
elseif event == "UPDATE_FACTION" then
|
||||||
|
RelationshipsAchievements_ScanCharacter()
|
||||||
|
elseif event == "PARTY_MEMBERS_CHANGED" then
|
||||||
|
if GetNumPartyMembers and GetNumPartyMembers() > 0 then
|
||||||
|
RelationshipsAchievements:Unlock(105)
|
||||||
|
end
|
||||||
|
elseif event == "FRIENDLIST_UPDATE" then
|
||||||
|
if GetNumFriends and GetNumFriends() > 0 then
|
||||||
|
RelationshipsAchievements:Unlock(102)
|
||||||
|
RelationshipsAchievements:UpdateStat("friendCount", GetNumFriends())
|
||||||
|
end
|
||||||
|
elseif event == "GUILD_ROSTER_UPDATE" then
|
||||||
|
if GetGuildInfo and GetGuildInfo("player") then
|
||||||
|
RelationshipsAchievements:Unlock(103)
|
||||||
|
if GetNumGuildMembers and GetNumGuildMembers() >= 10 then RelationshipsAchievements:Unlock(1703) end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
## Interface: 11200
|
||||||
|
## Title: Relationships Achievements
|
||||||
|
## Notes: WotLK-style achievement system for OctoWoW
|
||||||
|
## Author: Lovable
|
||||||
|
## Version: 1.0
|
||||||
|
## SavedVariables: RelationshipsAchievementsDB
|
||||||
|
## SavedVariablesPerCharacter: RelationshipsAchievementsCharDB
|
||||||
|
## DefaultState: Enabled
|
||||||
|
## OptionalDeps:
|
||||||
|
|
||||||
|
Database.lua
|
||||||
|
Core.lua
|
||||||
|
Toast.lua
|
||||||
|
Detail.lua
|
||||||
|
UI.lua
|
||||||
|
Events.lua
|
||||||
|
Bindings.xml
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
-- Achievement toast popup (the "gold banner" that appears on unlock)
|
||||||
|
|
||||||
|
local TOAST_DURATION = 5.0
|
||||||
|
local FADE_DURATION = 1.0
|
||||||
|
|
||||||
|
local toast
|
||||||
|
|
||||||
|
local function CreateToast()
|
||||||
|
local f = CreateFrame("Button", "RelationshipsAchievementToast", UIParent)
|
||||||
|
f:SetWidth(320)
|
||||||
|
f:SetHeight(80)
|
||||||
|
f:SetPoint("TOP", UIParent, "TOP", 0, -120)
|
||||||
|
f:SetFrameStrata("HIGH")
|
||||||
|
f:EnableMouse(true)
|
||||||
|
f:RegisterForClicks("LeftButtonUp")
|
||||||
|
f:SetScript("OnClick", function()
|
||||||
|
if this.ach and RelationshipsAchievements_ShowDetail then
|
||||||
|
RelationshipsAchievements_ShowDetail(this.ach)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
f:Hide()
|
||||||
|
|
||||||
|
f:SetBackdrop({
|
||||||
|
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
|
||||||
|
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
|
||||||
|
tile = true, tileSize = 32, edgeSize = 16,
|
||||||
|
insets = { left = 5, right = 5, top = 5, bottom = 5 },
|
||||||
|
})
|
||||||
|
f:SetBackdropColor(0, 0, 0, 0.9)
|
||||||
|
|
||||||
|
local header = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
header:SetPoint("TOP", f, "TOP", 0, -8)
|
||||||
|
header:SetText("Achievement Unlocked!")
|
||||||
|
header:SetTextColor(1, 0.82, 0)
|
||||||
|
f.header = header
|
||||||
|
|
||||||
|
local icon = f:CreateTexture(nil, "ARTWORK")
|
||||||
|
icon:SetWidth(48); icon:SetHeight(48)
|
||||||
|
icon:SetPoint("LEFT", f, "LEFT", 14, -6)
|
||||||
|
f.icon = icon
|
||||||
|
|
||||||
|
local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
|
||||||
|
title:SetPoint("TOPLEFT", icon, "TOPRIGHT", 14, -2)
|
||||||
|
title:SetPoint("RIGHT", f, "RIGHT", -10, 0)
|
||||||
|
title:SetJustifyH("LEFT")
|
||||||
|
title:SetTextColor(1, 1, 1)
|
||||||
|
f.title = title
|
||||||
|
|
||||||
|
local points = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
points:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -10, 8)
|
||||||
|
points:SetTextColor(1, 0.82, 0)
|
||||||
|
f.points = points
|
||||||
|
|
||||||
|
f.state = "hidden"
|
||||||
|
f.stateTime = 0
|
||||||
|
|
||||||
|
f:SetScript("OnUpdate", function()
|
||||||
|
local elapsed = arg1 or 0
|
||||||
|
this.stateTime = this.stateTime + elapsed
|
||||||
|
if this.state == "showing" then
|
||||||
|
local a = this.stateTime / 0.4
|
||||||
|
if a >= 1 then a = 1; this.state = "shown"; this.stateTime = 0 end
|
||||||
|
this:SetAlpha(a)
|
||||||
|
elseif this.state == "shown" then
|
||||||
|
if this.stateTime >= TOAST_DURATION then
|
||||||
|
this.state = "hiding"; this.stateTime = 0
|
||||||
|
end
|
||||||
|
elseif this.state == "hiding" then
|
||||||
|
local a = 1 - (this.stateTime / FADE_DURATION)
|
||||||
|
if a <= 0 then
|
||||||
|
a = 0
|
||||||
|
this:Hide()
|
||||||
|
this.state = "hidden"
|
||||||
|
end
|
||||||
|
this:SetAlpha(a)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
return f
|
||||||
|
end
|
||||||
|
|
||||||
|
function RelationshipsAchievements_ShowToast(ach)
|
||||||
|
if not ach then return end
|
||||||
|
if not toast then toast = CreateToast() end
|
||||||
|
toast.ach = ach
|
||||||
|
toast.title:SetText(ach.name)
|
||||||
|
toast.icon:SetTexture(ach.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
|
||||||
|
if ach.points and ach.points > 0 then
|
||||||
|
toast.points:SetText(ach.points.." points")
|
||||||
|
else
|
||||||
|
toast.points:SetText("")
|
||||||
|
end
|
||||||
|
toast:SetAlpha(0)
|
||||||
|
toast:Show()
|
||||||
|
toast.state = "showing"
|
||||||
|
toast.stateTime = 0
|
||||||
|
PlaySoundFile("Sound\\Interface\\LevelUp.wav")
|
||||||
|
end
|
||||||
@@ -0,0 +1,528 @@
|
|||||||
|
-- WotLK-style Relationships Achievement UI
|
||||||
|
-- Left column: categories with per-category progress (Summary + real categories).
|
||||||
|
-- Right side: either a summary landing panel or a scrolling list of achievements.
|
||||||
|
|
||||||
|
local FRAME_W, FRAME_H = 840, 620
|
||||||
|
local ROW_H = 72
|
||||||
|
local VISIBLE_ROWS = 7
|
||||||
|
local RECENT_COUNT = 14
|
||||||
|
local RECENT_ROW_H = 34
|
||||||
|
|
||||||
|
local selectedCategory = 0
|
||||||
|
local scrollOffset = 0
|
||||||
|
|
||||||
|
local rows = {}
|
||||||
|
local catButtons = {}
|
||||||
|
local displayCats = {}
|
||||||
|
local summaryPanel
|
||||||
|
local listPanel
|
||||||
|
|
||||||
|
local function FormatDate(t)
|
||||||
|
if not t then return "" end
|
||||||
|
return date("%m/%d/%y", t)
|
||||||
|
end
|
||||||
|
|
||||||
|
local function GetAchievementsForCategory(catId)
|
||||||
|
local out = {}
|
||||||
|
for i = 1, table.getn(RelationshipsAchievements_List) do
|
||||||
|
if RelationshipsAchievements_List[i].cat == catId then
|
||||||
|
table.insert(out, RelationshipsAchievements_List[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
local function BuildDisplayCats()
|
||||||
|
displayCats = { { id = 0, name = "Summary" } }
|
||||||
|
for i = 1, table.getn(RelationshipsAchievements_Categories) do
|
||||||
|
table.insert(displayCats, RelationshipsAchievements_Categories[i])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============ Progress bar helper ============
|
||||||
|
-- Text is parented to the StatusBar itself at OVERLAY so it always renders
|
||||||
|
-- ABOVE the green fill texture (which is on the bar at ARTWORK layer).
|
||||||
|
local function CreateProgressBar(parent, width, height)
|
||||||
|
local wrap = CreateFrame("Frame", nil, parent)
|
||||||
|
if width then wrap:SetWidth(width) end
|
||||||
|
if height then wrap:SetHeight(height) end
|
||||||
|
wrap:SetBackdrop({
|
||||||
|
bgFile = "Interface\\Buttons\\WHITE8x8",
|
||||||
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||||
|
edgeSize = 8,
|
||||||
|
insets = { left = 2, right = 2, top = 2, bottom = 2 },
|
||||||
|
})
|
||||||
|
wrap:SetBackdropColor(0, 0, 0, 0.7)
|
||||||
|
wrap:SetBackdropBorderColor(0.4, 0.3, 0.1, 1)
|
||||||
|
|
||||||
|
local bar = CreateFrame("StatusBar", nil, wrap)
|
||||||
|
bar:SetPoint("TOPLEFT", wrap, "TOPLEFT", 3, -3)
|
||||||
|
bar:SetPoint("BOTTOMRIGHT", wrap, "BOTTOMRIGHT", -3, 3)
|
||||||
|
bar:SetStatusBarTexture("Interface\\TargetingFrame\\UI-StatusBar")
|
||||||
|
bar:SetStatusBarColor(0.15, 0.65, 0.15)
|
||||||
|
bar:SetMinMaxValues(0, 1)
|
||||||
|
bar:SetValue(0)
|
||||||
|
|
||||||
|
-- Text is a child of the bar at OVERLAY so it draws above the fill.
|
||||||
|
local text = bar:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||||||
|
text:SetPoint("CENTER", bar, "CENTER", 0, 0)
|
||||||
|
text:SetTextColor(1, 1, 1)
|
||||||
|
text:SetDrawLayer("OVERLAY", 7)
|
||||||
|
|
||||||
|
wrap.bar = bar
|
||||||
|
wrap.text = text
|
||||||
|
wrap.SetMinMaxValues = function(self, a, b) bar:SetMinMaxValues(a, b) end
|
||||||
|
wrap.SetValue = function(self, v) bar:SetValue(v) end
|
||||||
|
wrap.SetStatusBarColor = function(self, r,g,b,a) bar:SetStatusBarColor(r,g,b,a) end
|
||||||
|
|
||||||
|
return wrap
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============ Category buttons ============
|
||||||
|
|
||||||
|
local function UpdateCategoryButtons()
|
||||||
|
for i = 1, table.getn(displayCats) do
|
||||||
|
local btn = catButtons[i]
|
||||||
|
local cat = displayCats[i]
|
||||||
|
if btn then
|
||||||
|
if cat.id == 0 then
|
||||||
|
btn.text:SetText("Summary")
|
||||||
|
else
|
||||||
|
local done, total = RelationshipsAchievements:CategoryProgress(cat.id)
|
||||||
|
btn.text:SetText(cat.name.." ("..done.."/"..total..")")
|
||||||
|
end
|
||||||
|
if cat.id == selectedCategory then
|
||||||
|
btn.highlight:Show()
|
||||||
|
btn.text:SetTextColor(1, 0.82, 0)
|
||||||
|
else
|
||||||
|
btn.highlight:Hide()
|
||||||
|
btn.text:SetTextColor(1, 1, 1)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============ Achievement rows ============
|
||||||
|
|
||||||
|
local function UpdateRows()
|
||||||
|
local list = GetAchievementsForCategory(selectedCategory)
|
||||||
|
local n = table.getn(list)
|
||||||
|
if scrollOffset < 0 then scrollOffset = 0 end
|
||||||
|
if scrollOffset > math.max(0, n - VISIBLE_ROWS) then
|
||||||
|
scrollOffset = math.max(0, n - VISIBLE_ROWS)
|
||||||
|
end
|
||||||
|
|
||||||
|
for i = 1, VISIBLE_ROWS do
|
||||||
|
local row = rows[i]
|
||||||
|
local ach = list[i + scrollOffset]
|
||||||
|
if ach then
|
||||||
|
row.ach = ach
|
||||||
|
row.icon:SetTexture(ach.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
|
||||||
|
row.title:SetText(ach.name)
|
||||||
|
row.desc:SetText(ach.desc or "")
|
||||||
|
if ach.points and ach.points > 0 then
|
||||||
|
row.points:SetText(ach.points)
|
||||||
|
else
|
||||||
|
row.points:SetText("")
|
||||||
|
end
|
||||||
|
|
||||||
|
local unlocked = RelationshipsAchievements:IsUnlocked(ach.id)
|
||||||
|
if unlocked then
|
||||||
|
row.icon:SetVertexColor(1, 1, 1)
|
||||||
|
row.title:SetTextColor(0.15, 0.75, 0.15)
|
||||||
|
row.desc:SetTextColor(1, 1, 1)
|
||||||
|
row.date:SetText(FormatDate(RelationshipsAchievements:GetUnlockTime(ach.id)))
|
||||||
|
row.bg:SetVertexColor(0.2, 0.15, 0.05, 0.8)
|
||||||
|
else
|
||||||
|
row.icon:SetVertexColor(0.35, 0.35, 0.35)
|
||||||
|
row.title:SetTextColor(0.6, 0.6, 0.6)
|
||||||
|
row.desc:SetTextColor(0.5, 0.5, 0.5)
|
||||||
|
row.date:SetText("")
|
||||||
|
row.bg:SetVertexColor(0.1, 0.1, 0.1, 0.6)
|
||||||
|
end
|
||||||
|
|
||||||
|
if ach.account then row.acctTag:Show() else row.acctTag:Hide() end
|
||||||
|
|
||||||
|
local cur, max = RelationshipsAchievements:GetProgress(ach.id)
|
||||||
|
if cur and max and not unlocked then
|
||||||
|
row.progressBar:SetMinMaxValues(0, max)
|
||||||
|
row.progressBar:SetValue(cur)
|
||||||
|
row.progressBar.text:SetText(cur.."/"..max)
|
||||||
|
row.progressBar:Show()
|
||||||
|
else
|
||||||
|
row.progressBar:Hide()
|
||||||
|
end
|
||||||
|
|
||||||
|
row:Show()
|
||||||
|
else
|
||||||
|
row:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============ Summary landing page ============
|
||||||
|
|
||||||
|
local function CollectRecentUnlocks(limit)
|
||||||
|
local all = {}
|
||||||
|
for i = 1, table.getn(RelationshipsAchievements_List) do
|
||||||
|
local a = RelationshipsAchievements_List[i]
|
||||||
|
local t = RelationshipsAchievements:GetUnlockTime(a.id)
|
||||||
|
if t then
|
||||||
|
table.insert(all, { ach = a, time = t })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
table.sort(all, function(x, y) return x.time > y.time end)
|
||||||
|
local out = {}
|
||||||
|
for i = 1, math.min(limit, table.getn(all)) do
|
||||||
|
table.insert(out, all[i])
|
||||||
|
end
|
||||||
|
return out
|
||||||
|
end
|
||||||
|
|
||||||
|
local function UpdateSummary()
|
||||||
|
if not summaryPanel then return end
|
||||||
|
|
||||||
|
local recent = CollectRecentUnlocks(RECENT_COUNT)
|
||||||
|
for i = 1, RECENT_COUNT do
|
||||||
|
local r = summaryPanel.recentRows[i]
|
||||||
|
local entry = recent[i]
|
||||||
|
if entry then
|
||||||
|
r.icon:SetTexture(entry.ach.icon or "Interface\\Icons\\INV_Misc_QuestionMark")
|
||||||
|
r.title:SetText(entry.ach.name)
|
||||||
|
r.date:SetText(FormatDate(entry.time))
|
||||||
|
r.points:SetText((entry.ach.points and entry.ach.points > 0) and entry.ach.points or "")
|
||||||
|
r.ach = entry.ach
|
||||||
|
r:Show()
|
||||||
|
else
|
||||||
|
r:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
if table.getn(recent) == 0 and summaryPanel.emptyLabel then
|
||||||
|
summaryPanel.emptyLabel:Show()
|
||||||
|
elseif summaryPanel.emptyLabel then
|
||||||
|
summaryPanel.emptyLabel:Hide()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ApplySelectionVisibility()
|
||||||
|
if selectedCategory == 0 then
|
||||||
|
if summaryPanel then summaryPanel:Show() end
|
||||||
|
if listPanel then listPanel:Hide() end
|
||||||
|
else
|
||||||
|
if summaryPanel then summaryPanel:Hide() end
|
||||||
|
if listPanel then listPanel:Show() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function RelationshipsAchievements_RefreshUI()
|
||||||
|
UpdateCategoryButtons()
|
||||||
|
ApplySelectionVisibility()
|
||||||
|
if selectedCategory == 0 then
|
||||||
|
UpdateSummary()
|
||||||
|
else
|
||||||
|
UpdateRows()
|
||||||
|
end
|
||||||
|
if RelationshipsAchievementFrame then
|
||||||
|
local total = RelationshipsAchievements:TotalAvailable()
|
||||||
|
local done = RelationshipsAchievements:TotalUnlocked()
|
||||||
|
local pts = RelationshipsAchievements:TotalPoints()
|
||||||
|
if RelationshipsAchievementFrame.summary then
|
||||||
|
RelationshipsAchievementFrame.summary:SetText(done.."/"..total.." achievements |cffffd200"..pts.."|r points")
|
||||||
|
end
|
||||||
|
if RelationshipsAchievementFrame.overallBar then
|
||||||
|
RelationshipsAchievementFrame.overallBar:SetMinMaxValues(0, total)
|
||||||
|
RelationshipsAchievementFrame.overallBar:SetValue(done)
|
||||||
|
RelationshipsAchievementFrame.overallBar.text:SetText(done.." / "..total)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- Clicking a row in the main list should NOT open the popup box; the row
|
||||||
|
-- is already visible in the panel. Left intentionally as a no-op so future
|
||||||
|
-- selection/highlight logic can hook in without re-introducing the popup.
|
||||||
|
local function OnRowClick()
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CreateRow(parent, index)
|
||||||
|
local row = CreateFrame("Button", nil, parent)
|
||||||
|
row:SetWidth(600); row:SetHeight(ROW_H - 4)
|
||||||
|
row:SetPoint("TOPLEFT", parent, "TOPLEFT", 6, -((index - 1) * ROW_H) - 6)
|
||||||
|
row:RegisterForClicks("LeftButtonUp")
|
||||||
|
row:SetScript("OnClick", OnRowClick)
|
||||||
|
|
||||||
|
local bg = row:CreateTexture(nil, "BACKGROUND")
|
||||||
|
bg:SetAllPoints(row)
|
||||||
|
bg:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||||
|
bg:SetVertexColor(0.1, 0.1, 0.1, 0.6)
|
||||||
|
row.bg = bg
|
||||||
|
|
||||||
|
local icon = row:CreateTexture(nil, "ARTWORK")
|
||||||
|
icon:SetWidth(44); icon:SetHeight(44)
|
||||||
|
icon:SetPoint("TOPLEFT", row, "TOPLEFT", 8, -6)
|
||||||
|
row.icon = icon
|
||||||
|
|
||||||
|
local title = row:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
title:SetPoint("TOPLEFT", icon, "TOPRIGHT", 10, -2)
|
||||||
|
title:SetJustifyH("LEFT")
|
||||||
|
row.title = title
|
||||||
|
|
||||||
|
local desc = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||||||
|
desc:SetPoint("TOPLEFT", title, "BOTTOMLEFT", 0, -2)
|
||||||
|
desc:SetPoint("RIGHT", row, "RIGHT", -60, 0)
|
||||||
|
desc:SetJustifyH("LEFT")
|
||||||
|
desc:SetHeight(18)
|
||||||
|
row.desc = desc
|
||||||
|
|
||||||
|
local pbar = CreateProgressBar(row, nil, 16)
|
||||||
|
pbar:SetHeight(16)
|
||||||
|
pbar:SetPoint("BOTTOMLEFT", row, "BOTTOMLEFT", 62, 6)
|
||||||
|
pbar:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -60, 6)
|
||||||
|
pbar:Hide()
|
||||||
|
row.progressBar = pbar
|
||||||
|
|
||||||
|
local points = row:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
|
||||||
|
points:SetPoint("TOPRIGHT", row, "TOPRIGHT", -10, -6)
|
||||||
|
points:SetTextColor(1, 0.82, 0)
|
||||||
|
row.points = points
|
||||||
|
|
||||||
|
local date = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||||||
|
date:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -10, 6)
|
||||||
|
date:SetTextColor(0.7, 0.7, 0.7)
|
||||||
|
row.date = date
|
||||||
|
|
||||||
|
local acctTag = row:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||||||
|
acctTag:SetPoint("BOTTOMRIGHT", row, "BOTTOMRIGHT", -110, 6)
|
||||||
|
acctTag:SetTextColor(0.4, 0.7, 1)
|
||||||
|
acctTag:SetText("Account-wide")
|
||||||
|
row.acctTag = acctTag
|
||||||
|
|
||||||
|
return row
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CreateCategoryButton(parent, index, cat)
|
||||||
|
local btn = CreateFrame("Button", nil, parent)
|
||||||
|
btn:SetWidth(170); btn:SetHeight(22)
|
||||||
|
btn:SetPoint("TOPLEFT", parent, "TOPLEFT", 4, -((index - 1) * 24) - 4)
|
||||||
|
|
||||||
|
local hl = btn:CreateTexture(nil, "BACKGROUND")
|
||||||
|
hl:SetAllPoints(btn)
|
||||||
|
hl:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||||
|
hl:SetVertexColor(0.4, 0.3, 0.1, 0.7)
|
||||||
|
hl:Hide()
|
||||||
|
btn.highlight = hl
|
||||||
|
|
||||||
|
local t = btn:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
t:SetPoint("LEFT", btn, "LEFT", 8, 0)
|
||||||
|
t:SetJustifyH("LEFT")
|
||||||
|
btn.text = t
|
||||||
|
|
||||||
|
btn:SetScript("OnClick", function()
|
||||||
|
selectedCategory = cat.id
|
||||||
|
scrollOffset = 0
|
||||||
|
RelationshipsAchievements_RefreshUI()
|
||||||
|
end)
|
||||||
|
|
||||||
|
return btn
|
||||||
|
end
|
||||||
|
|
||||||
|
-- ============ Summary panel construction ============
|
||||||
|
|
||||||
|
local function CreateSummaryPanel(parent)
|
||||||
|
local panel = CreateFrame("Frame", nil, parent)
|
||||||
|
panel:SetPoint("TOPLEFT", parent, "TOPLEFT", 0, 0)
|
||||||
|
panel:SetPoint("BOTTOMRIGHT", parent, "BOTTOMRIGHT", 0, 0)
|
||||||
|
panel:SetBackdrop({
|
||||||
|
bgFile = "Interface\\Buttons\\WHITE8x8",
|
||||||
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||||
|
tile = false, edgeSize = 12,
|
||||||
|
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||||||
|
})
|
||||||
|
panel:SetBackdropColor(0, 0, 0, 0.6)
|
||||||
|
panel:SetBackdropBorderColor(0.4, 0.3, 0.1, 1)
|
||||||
|
|
||||||
|
local RIGHT_INSET = 14
|
||||||
|
local recentHeader = panel:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
|
||||||
|
recentHeader:SetPoint("TOPLEFT", panel, "TOPLEFT", 14, -12)
|
||||||
|
recentHeader:SetText("Most Recent Achievements")
|
||||||
|
recentHeader:SetTextColor(1, 0.82, 0)
|
||||||
|
|
||||||
|
panel.recentRows = {}
|
||||||
|
for i = 1, RECENT_COUNT do
|
||||||
|
local r = CreateFrame("Button", nil, panel)
|
||||||
|
r:SetHeight(RECENT_ROW_H - 2)
|
||||||
|
r:SetPoint("TOPLEFT", recentHeader, "BOTTOMLEFT", 0, -6 - ((i - 1) * RECENT_ROW_H))
|
||||||
|
r:SetPoint("RIGHT", panel, "RIGHT", -RIGHT_INSET, 0)
|
||||||
|
r:RegisterForClicks("LeftButtonUp")
|
||||||
|
|
||||||
|
local bg = r:CreateTexture(nil, "BACKGROUND")
|
||||||
|
bg:SetAllPoints(r)
|
||||||
|
bg:SetTexture("Interface\\Buttons\\WHITE8x8")
|
||||||
|
bg:SetVertexColor(0.2, 0.15, 0.05, 0.7)
|
||||||
|
|
||||||
|
local icon = r:CreateTexture(nil, "ARTWORK")
|
||||||
|
icon:SetWidth(20); icon:SetHeight(20)
|
||||||
|
icon:SetPoint("LEFT", r, "LEFT", 4, 0)
|
||||||
|
r.icon = icon
|
||||||
|
|
||||||
|
local title = r:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
title:SetPoint("LEFT", icon, "RIGHT", 8, 0)
|
||||||
|
title:SetTextColor(0.15, 0.75, 0.15)
|
||||||
|
r.title = title
|
||||||
|
|
||||||
|
-- Fixed width + right-justify so the LEFT edge of the points field
|
||||||
|
-- stays put whether the value is "5", "10", "25" or "50". Without a
|
||||||
|
-- fixed width, a shorter number shifts the date column right and
|
||||||
|
-- knocks the row out of alignment.
|
||||||
|
local pts = r:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
pts:SetPoint("RIGHT", r, "RIGHT", -10, 0)
|
||||||
|
pts:SetWidth(28)
|
||||||
|
pts:SetJustifyH("RIGHT")
|
||||||
|
pts:SetTextColor(1, 0.82, 0)
|
||||||
|
r.points = pts
|
||||||
|
|
||||||
|
local date = r:CreateFontString(nil, "OVERLAY", "GameFontHighlightSmall")
|
||||||
|
date:SetPoint("RIGHT", pts, "LEFT", -14, 0)
|
||||||
|
date:SetWidth(64)
|
||||||
|
date:SetJustifyH("RIGHT")
|
||||||
|
date:SetTextColor(0.7, 0.7, 0.7)
|
||||||
|
r.date = date
|
||||||
|
|
||||||
|
r:SetScript("OnClick", function()
|
||||||
|
if this and this.ach then
|
||||||
|
local ach = this.ach
|
||||||
|
selectedCategory = ach.cat or 0
|
||||||
|
local list = GetAchievementsForCategory(selectedCategory)
|
||||||
|
local idx = 1
|
||||||
|
for j = 1, table.getn(list) do
|
||||||
|
if list[j] == ach then idx = j; break end
|
||||||
|
end
|
||||||
|
scrollOffset = idx - 1
|
||||||
|
local maxOff = math.max(0, table.getn(list) - VISIBLE_ROWS)
|
||||||
|
if scrollOffset > maxOff then scrollOffset = maxOff end
|
||||||
|
RelationshipsAchievements_RefreshUI()
|
||||||
|
-- Do not open the popup; the user is taken to the achievement
|
||||||
|
-- inside the main UI (category selected + scrolled into view).
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
panel.recentRows[i] = r
|
||||||
|
end
|
||||||
|
|
||||||
|
local empty = panel:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||||||
|
empty:SetPoint("TOPLEFT", recentHeader, "BOTTOMLEFT", 4, -10)
|
||||||
|
empty:SetText("No achievements earned yet. Get out there!")
|
||||||
|
empty:SetTextColor(0.7, 0.7, 0.7)
|
||||||
|
empty:Hide()
|
||||||
|
panel.emptyLabel = empty
|
||||||
|
|
||||||
|
|
||||||
|
return panel
|
||||||
|
end
|
||||||
|
|
||||||
|
local function CreateAchievementFrame()
|
||||||
|
BuildDisplayCats()
|
||||||
|
|
||||||
|
local f = CreateFrame("Frame", "RelationshipsAchievementFrame", UIParent)
|
||||||
|
f:SetWidth(FRAME_W); f:SetHeight(FRAME_H)
|
||||||
|
f:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
|
||||||
|
f:SetFrameStrata("DIALOG")
|
||||||
|
f:SetMovable(false); f:EnableMouse(true)
|
||||||
|
f:SetClampedToScreen(true)
|
||||||
|
f:Hide()
|
||||||
|
|
||||||
|
f:SetBackdrop({
|
||||||
|
bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background",
|
||||||
|
edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Gold-Border",
|
||||||
|
tile = true, tileSize = 32, edgeSize = 32,
|
||||||
|
insets = { left = 11, right = 12, top = 12, bottom = 11 },
|
||||||
|
})
|
||||||
|
|
||||||
|
local titleBg = f:CreateTexture(nil, "ARTWORK")
|
||||||
|
titleBg:SetTexture("Interface\\DialogFrame\\UI-DialogBox-Header")
|
||||||
|
titleBg:SetWidth(460); titleBg:SetHeight(64)
|
||||||
|
titleBg:SetPoint("TOP", f, "TOP", 0, 12)
|
||||||
|
|
||||||
|
-- Center vertically inside the gold title header texture (which is 64px tall
|
||||||
|
-- anchored 12px above the frame top, so its visual center sits ~20px above f:TOP).
|
||||||
|
local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormal")
|
||||||
|
title:SetPoint("CENTER", f, "TOP", 0, -8)
|
||||||
|
title:SetText("Relationships Achievements")
|
||||||
|
title:SetTextColor(1, 0.82, 0)
|
||||||
|
|
||||||
|
local summary = f:CreateFontString(nil, "OVERLAY", "GameFontHighlight")
|
||||||
|
summary:SetPoint("TOP", f, "TOP", 0, -28)
|
||||||
|
f.summary = summary
|
||||||
|
|
||||||
|
local overall = CreateProgressBar(f, FRAME_W - 60, 16)
|
||||||
|
overall:SetPoint("TOP", f, "TOP", 0, -48)
|
||||||
|
f.overallBar = overall
|
||||||
|
|
||||||
|
local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
|
||||||
|
close:SetPoint("TOPRIGHT", f, "TOPRIGHT", -4, -4)
|
||||||
|
close:SetScript("OnClick", function() f:Hide() end)
|
||||||
|
|
||||||
|
-- Category panel
|
||||||
|
local catPanel = CreateFrame("Frame", nil, f)
|
||||||
|
catPanel:SetWidth(180); catPanel:SetHeight(FRAME_H - 105)
|
||||||
|
catPanel:SetPoint("TOPLEFT", f, "TOPLEFT", 16, -76)
|
||||||
|
catPanel:SetBackdrop({
|
||||||
|
bgFile = "Interface\\Buttons\\WHITE8x8",
|
||||||
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||||
|
tile = false, edgeSize = 12,
|
||||||
|
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||||||
|
})
|
||||||
|
catPanel:SetBackdropColor(0, 0, 0, 0.6)
|
||||||
|
catPanel:SetBackdropBorderColor(0.4, 0.3, 0.1, 1)
|
||||||
|
|
||||||
|
for i = 1, table.getn(displayCats) do
|
||||||
|
catButtons[i] = CreateCategoryButton(catPanel, i, displayCats[i])
|
||||||
|
end
|
||||||
|
|
||||||
|
local host = CreateFrame("Frame", nil, f)
|
||||||
|
host:SetPoint("TOPLEFT", catPanel, "TOPRIGHT", 8, 0)
|
||||||
|
host:SetPoint("BOTTOMRIGHT", f, "BOTTOMRIGHT", -16, 29)
|
||||||
|
|
||||||
|
listPanel = CreateFrame("Frame", nil, host)
|
||||||
|
listPanel:SetPoint("TOPLEFT", host, "TOPLEFT", 0, 0)
|
||||||
|
listPanel:SetPoint("BOTTOMRIGHT", host, "BOTTOMRIGHT", 0, 0)
|
||||||
|
listPanel:SetBackdrop({
|
||||||
|
bgFile = "Interface\\Buttons\\WHITE8x8",
|
||||||
|
edgeFile = "Interface\\Tooltips\\UI-Tooltip-Border",
|
||||||
|
tile = false, edgeSize = 12,
|
||||||
|
insets = { left = 3, right = 3, top = 3, bottom = 3 },
|
||||||
|
})
|
||||||
|
listPanel:SetBackdropColor(0, 0, 0, 0.6)
|
||||||
|
listPanel:SetBackdropBorderColor(0.4, 0.3, 0.1, 1)
|
||||||
|
|
||||||
|
for i = 1, VISIBLE_ROWS do
|
||||||
|
rows[i] = CreateRow(listPanel, i)
|
||||||
|
end
|
||||||
|
|
||||||
|
listPanel:EnableMouseWheel(true)
|
||||||
|
listPanel:SetScript("OnMouseWheel", function()
|
||||||
|
local delta = arg1 or 0
|
||||||
|
scrollOffset = scrollOffset - delta
|
||||||
|
UpdateRows()
|
||||||
|
end)
|
||||||
|
|
||||||
|
summaryPanel = CreateSummaryPanel(host)
|
||||||
|
|
||||||
|
tinsert(UISpecialFrames, "RelationshipsAchievementFrame")
|
||||||
|
|
||||||
|
return f
|
||||||
|
end
|
||||||
|
|
||||||
|
function RelationshipsAchievements_Toggle()
|
||||||
|
if not RelationshipsAchievementFrame then
|
||||||
|
CreateAchievementFrame()
|
||||||
|
end
|
||||||
|
if RelationshipsAchievementFrame:IsVisible() then
|
||||||
|
RelationshipsAchievementFrame:Hide()
|
||||||
|
else
|
||||||
|
RelationshipsAchievementFrame:Show()
|
||||||
|
if RelationshipsAchievements and not RelationshipsAchievements:IsUnlocked(104) then
|
||||||
|
RelationshipsAchievements:Unlock(104)
|
||||||
|
end
|
||||||
|
RelationshipsAchievements_RefreshUI()
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user