Add OctoQuestRewards 1.0.0 addon source

This commit is contained in:
2026-10-07 18:45:52 +00:00
parent 06e5431ff7
commit 1cc4726299
5 changed files with 4082 additions and 0 deletions
+189
View File
@@ -0,0 +1,189 @@
OctoQuestRewards = {}
local A = OctoQuestRewards
local G = getfenv(0)
-- A raw custom base XP cross-check, not the website's potentially scaled Gains.
-- https://octowow.st/db/?quest=41677 (RewXP=2150, level=32, checked 2026-10-05)
local octoBase = { [41677] = {32, 2150} }
-- Only documented, present functions are called. No modern XP API is assumed.
function A.Call(fn, a)
if type(fn) ~= "function" then return nil end
local ok, value
if a == nil then ok, value = pcall(fn) else ok, value = pcall(fn, a) end
if ok then return value end
end
local function QuestTables()
local q = pfDB and pfDB.quests
return q and q.data, q and (q.loc or q[GetLocale()] or q.enUS)
end
local titleIndex, indexedLocale
local function MatchTitle(title, level, description, objectives)
local data, loc = QuestTables()
if not data or not loc or not title then return nil end
if indexedLocale ~= loc then
titleIndex, indexedLocale = {}, loc
for id, row in pairs(loc) do
if type(row) == "table" and row.T and data[id] then
titleIndex[row.T] = titleIndex[row.T] or {}
table.insert(titleIndex[row.T], id)
end
end
end
local found, count = nil, 0
for _, id in ipairs(titleIndex[title] or {}) do
local row = data[id]
if not level or level == row.lvl or row.lvl == -1 then
found, count = id, count + 1
end
end
if count == 1 then return found, "pfQuest title/level match (inferred ID)" end
-- Duplicate titles need exact quest text; never pick the first guessed ID.
found, count = nil, 0
if pfDatabase and pfDatabase.FormatQuestText then
for _, id in ipairs(titleIndex[title] or {}) do
local row = loc[id]
local d = row.D and pfDatabase:FormatQuestText(row.D)
local o = row.O and pfDatabase:FormatQuestText(row.O)
if (not level or level == data[id].lvl or data[id].lvl == -1)
and ((description and description ~= "" and description == d)
or (objectives and objectives ~= "" and objectives == o)) then
found, count = id, count + 1
end
end
end
if count == 1 then return found, "pfQuest exact text match (inferred ID)" end
end
local function PositiveID(id)
id = tonumber(id)
if id and id > 0 then return id end
end
function A.LogTitle(index)
if pfQuestCompat and pfQuestCompat.GetQuestLogTitle then
return pfQuestCompat.GetQuestLogTitle(index)
end
return GetQuestLogTitle(index)
end
function A.LogID(index, title, level, description, objectives)
if C_QuestLog and C_QuestLog.GetQuestIDForLogIndex then
local id = PositiveID(A.Call(C_QuestLog.GetQuestIDForLogIndex, index))
if id then return id, "C_QuestLog.GetQuestIDForLogIndex" end
end
local link = A.Call(G.GetQuestLink, index)
if type(link) == "string" then
local _, _, id = string.find(link, "quest:(%d+)")
if PositiveID(id) then return tonumber(id), "GetQuestLink" end
end
return MatchTitle(title, level, description, objectives)
end
-- Vanilla degradation. Octo's server rate/scaling is not verified.
-- Level alone cannot determine base XP: quests at the same level vary widely.
function A.EstimateXP(base, questLevel, playerLevel)
if type(base) ~= "number" or not questLevel then return nil end
if playerLevel >= 60 then return 0 end
if questLevel < 1 then questLevel = playerLevel end
local difference = playerLevel - questLevel
local factor = 1
if A.db.scaling == "full25" then
-- Optional explicit assumption for Turtle-like custom scaling.
if difference > 25 then return nil end
elseif difference >= 10 then factor = 0.1
elseif difference > 5 then factor = (10 - difference) * 0.2 end
return math.ceil(base * factor - 0.000001)
end
function A.GetRecord(kind)
local r = { playerLevel = UnitLevel("player"), kind = kind }
local index
if kind == "log" then
index = GetQuestLogSelection()
local header
local tag
r.title, r.level, tag, header = A.LogTitle(index)
if not r.title or header then return nil end
local description, objectives = GetQuestLogQuestText()
r.id, r.idSource = A.LogID(index, r.title, r.level, description, objectives)
r.money = A.Call(G.GetQuestLogRewardMoney)
r.moneySource = "GetQuestLogRewardMoney (server cache)"
else
r.title = GetTitleText()
if not r.title or r.title == "" then return nil end
-- VoiceOver installs GetQuestID inside its own namespace, not the engine.
r.id = PositiveID(A.Call(G.GetQuestID))
if r.id then r.idSource = "GetQuestID (provider unverified)" end
if not r.id then
r.id, r.idSource = MatchTitle(r.title, nil,
kind == "detail" and GetQuestText() or nil,
kind == "detail" and GetObjectiveText() or nil)
end
if kind == "progress" then
-- REQUEST_ITEMS packets don't supply reward money: GetRewardMoney here
-- can be stale. Read only a uniquely matching accepted quest instead.
local match, count = nil, 0
for i = 1, GetNumQuestLogEntries() do
local title, level, tag, header = A.LogTitle(i)
if title == r.title and not header then match, count = i, count + 1 end
end
if count == 1 then
local old = GetQuestLogSelection()
SelectQuestLogEntry(match)
local log = A.GetRecord("log")
SelectQuestLogEntry(old)
if log then
r.id, r.idSource, r.level = log.id, log.idSource, log.level
r.money, r.moneySource = log.money, log.moneySource
end
end
else
r.money = A.Call(G.GetRewardMoney)
r.moneySource = "GetRewardMoney (NPC server packet)"
end
end
local data = QuestTables()
if not r.level and data and r.id and data[r.id] then r.level = data[r.id].lvl end
local cache
if r.id and C_QuestLog and C_QuestLog.GetQuestDetails then
cache = A.Call(C_QuestLog.GetQuestDetails, r.id)
if type(cache) == "table" then
r.level = cache.level or r.level
if r.money == nil and r.playerLevel < 60 then
r.money, r.moneySource = cache.rewardMoney, "C_QuestLog.GetQuestDetails.rewardMoney"
end
end
end
if r.money and r.money < 0 then r.money = 0 end -- A cost isn't a reward.
local reward = r.id and PfExtend_QuestRewards and PfExtend_QuestRewards[r.id]
local baseline = r.id and (octoBase[r.id] or (A.VanillaXP and A.VanillaXP[r.id]))
local base
if baseline and (baseline[1] == r.level or baseline[1] == -1) then
base = baseline[2]
r.xpSource = (octoBase[r.id] and "Octo DB raw RewXP" or "LibQuestXP Vanilla baseline")
.. " + " .. A.db.scaling .. " scaling; rate assumed 1x; Octo match unverified"
elseif not reward and type(cache) == "table" and type(cache.rewardMoneyAtMaxLevel) == "number" then
-- Classic cache has no XP field. This is the historical Vanilla conversion,
-- NOT a directly supplied XP value, and may differ on custom quests.
if r.level and r.level <= 60 then
base = cache.rewardMoneyAtMaxLevel / 0.6
r.xpSource = "ClassicAPI max-level money / 0.6 + " .. A.db.scaling .. " scaling (estimated)"
end
end
r.baseXP = base
r.xp = A.EstimateXP(base, r.level, r.playerLevel)
if base == nil and reward and type(reward.xp) == "number" then
-- The installed pfExtend scraper copies displayed Gains, not raw RewXP.
-- We cannot undo unknown website scaling. Use it only as a near-level
-- reference, with no second scaling; suppress it on overlevelled quests.
r.xpSource = "pfExtend website Gains reference; base XP/scaling unverified"
if r.playerLevel >= 60 then r.xp = 0
elseif r.level and (r.level < 1 or r.playerLevel <= r.level + 5) then r.xp = reward.xp end
end
r.idSource = r.idSource or "unresolved/ambiguous ID"
r.xpSource = r.xpSource or "unavailable"
if r.id then A.predictions[r.id] = r end
return r
end