feat: Questie-X v1.1.4 - plugin architecture, monorepo, rebranding

- Repoint remote to Xurkon/Questie-X
- Add QuestiePluginAPI, QuestieServer, QuestieLearnerComms modules
- Add Questie-X.toc, Questie-X-Classic.toc, Questie-X-TBC.toc, Questie-X-Turtle.toc
- Remove embedded Database/Ascension and Database/Ebonhold (migrated to plugins)
- Add Plugins/ directory with NTFS junctions for Questie-X-AscensionDB and Questie-X-EbonholdDB
- Add LibDeflate, XXH_Lua_Lib, LibDBIcon-1.0, LibDataBroker-1.1
- Update README: Questie-X branding, logo, plugin install guide, plugin API docs
- Update CHANGELOG: v1.1.4 entry documenting all architectural changes
- Update .gitignore: exclude plugin junctions, __pycache__, .agents, debug files
- Various module updates: corrections, map, tracker, tooltips, quest, options
This commit is contained in:
Xurkon
2026-03-14 06:10:17 -05:00
parent 89c3b3173d
commit c4d003e58f
77 changed files with 10883 additions and 8264 deletions
+15 -9
View File
@@ -9,8 +9,9 @@ local QuestieLib = QuestieLoader:ImportModule("QuestieLib")
local Sounds = QuestieLoader:ImportModule("Sounds")
--- COMPATIBILITY ---
local QuestieCompat = QuestieCompat -- Ensure it's loaded
local GetQuestLogTitle = QuestieCompat.GetQuestLogTitle
local C_QuestLog_GetQuestObjectives = QuestieCompat.C_QuestLog.GetQuestObjectives
local C_QuestLog_GetQuestObjectives = QuestieCompat.C_QuestLog and QuestieCompat.C_QuestLog.GetQuestObjectives
local HaveQuestData = QuestieCompat.HaveQuestData
local stringByte = string.byte
@@ -88,7 +89,7 @@ local function GetNewObjectives(questId, oldObjectives, questLogIndex)
local changedObjIds -- not assigning {} for easier nil when nothing changed
local objectives = C_QuestLog_GetQuestObjectives(questId, questLogIndex)
for objIndex = 1, #objectives do -- iterate manually to be sure getting those in order
for objIndex = 1, table.getn(objectives) do -- iterate manually to be sure getting those in order
local oldObj = oldObjectives[objIndex]
local newObj = objectives[objIndex]
-- Check if objective.text is in game's cache
@@ -102,7 +103,7 @@ local function GetNewObjectives(questId, oldObjectives, questLogIndex)
if (not changedObjIds) then
changedObjIds = { objIndex }
else
changedObjIds[#changedObjIds + 1] = objIndex
changedObjIds[table.getn(changedObjIds) + 1] = objIndex
end
if oldObj and newObj and oldObj.numRequired ~= oldObj.numFulfilled and newObj.numRequired == newObj.numFulfilled then
@@ -177,15 +178,15 @@ function QuestLogCache.CheckForChanges(questIdsToCheck)
local newObjectives, changedObjIds = GetNewObjectives(questId, cachedObjectives, questLogIndex)
if newObjectives then
if (not cachedQuest) or (#cachedObjectives == #newObjectives and #cachedObjectives > 0 and
if (not cachedQuest) or (table.getn(cachedObjectives) == table.getn(newObjectives) and table.getn(cachedObjectives) > 0 and
(cachedQuest.title ~= title or cachedQuest.questTag ~= questTag or cachedQuest.isComplete ~= isComplete)) then
changedObjIds = {}
for i = 1, #newObjectives do
for i = 1, table.getn(newObjectives) do
changedObjIds[i] = i
end
if isComplete == 1 then
for i = 1, #newObjectives do
for i = 1, table.getn(newObjectives) do
local o = newObjectives[i]
o.finished = true
o.numFulfilled = o.numRequired
@@ -235,6 +236,11 @@ end
function QuestLogCache.RemoveQuest(questId)
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestLogCache.RemoveQuest] remove questId:", questId)
cache[questId] = nil
-- Also evict any live fallback object so stale tracker entries don't persist.
local QuestieDB = QuestieLoader:ImportModule("QuestieDB")
if QuestieDB and QuestieDB.InvalidateLiveQuest then
QuestieDB.InvalidateLiveQuest(questId)
end
end
--- Tests if game client's cache has all quest log quests and objectives cached.
@@ -249,7 +255,7 @@ function QuestLogCache.TestGameCache()
if HaveQuestData(questId) then
local objectives = C_QuestLog_GetQuestObjectives(questId, questLogIndex)
for objIndex = 1, #objectives do
for objIndex = 1, table.getn(objectives) do
local text = objectives[objIndex].text
-- Check if objective.text is not in game's cache
if (not text) or (stringByte(text, 1) == 32) then
@@ -298,12 +304,12 @@ end
---@param o table @objective
local function DebugPrintObjective(q, i, o)
if (o.raw_numFulfilled == o.numFulfilled) and (o.raw_finished == o.finished) then
print(" ", i .. "/" .. #q.objectives .. ":",
print(" ", i .. "/" .. table.getn(q.objectives) .. ":",
o.numFulfilled .. "/" .. o.numRequired .. "=" .. tostring(o.finished),
o.type,
"\"" .. o.raw_text .. "\" \"" .. o.text .. "\"")
else
print(" ", i .. "/" .. #q.objectives .. ":",
print(" ", i .. "/" .. table.getn(q.objectives) .. ":",
o.raw_numFulfilled .. "/" .. o.numRequired .. "=" .. tostring(o.raw_finished),
"FIX:", o.numFulfilled .. "/" .. o.numRequired .. "=" .. tostring(o.finished),
o.type,
+60 -4
View File
@@ -1636,14 +1636,68 @@ function QuestieQuest:PopulateQuestLogInfo(quest)
quest.isComplete = true
end
-- Live fallback quests (no static DB entry) manage their own Objectives.
-- Their per-objective Update() functions read directly from QuestLogCache.
if quest._isLogFallback then
for _, obj in pairs(quest.Objectives) do
obj:Update()
end
return true
end
--Uses the category order to draw the quests and trusts the database order.
local questObjectives = QuestieQuest:GetAllLeaderBoardDetails(quest.Id) or {} -- DO NOT MODIFY THE RETURNED TABLE
for objectiveIndex, objective in pairs(questObjectives) do
if objective.type and string.len(objective.type) > 1 then
if (not quest.ObjectiveData) or (not quest.ObjectiveData[objectiveIndex]) then
Questie:Error(l10n("Missing objective data for quest "), quest.Id, " ", objective.text)
if (not quest.ObjectiveData) or (not quest.ObjectiveData[objectiveIndex]) or (not quest.ObjectiveData[objectiveIndex].Id) then
Questie:Debug(Questie.DEBUG_DEVELOP, "[QuestieQuest] Missing objective data for quest", quest.Id, objective.text, "creating fallback objective")
if not quest.Objectives[objectiveIndex] then
local fallbackSpawnList = {}
local objType = objective.type or "monster"
if objType == "monster" then
local l10n = QuestieLoader:ImportModule("l10n")
local zoneId = quest.zoneOrSort and quest.zoneOrSort > 0 and quest.zoneOrSort or nil
if zoneId and l10n and l10n.raresByZone and l10n.raresByZone[zoneId] then
local function _GetIconScaleForMonster()
return Questie.db.profile.monsterScale or 1
end
for _, npcId in ipairs(l10n.raresByZone[zoneId]) do
local npcName = QuestieDB.QueryNPCSingle(npcId, "name")
local npcSpawns = QuestieDB.QueryNPCSingle(npcId, "spawns")
if npcName and npcSpawns and npcSpawns[zoneId] then
fallbackSpawnList[npcId] = {
Id = npcId,
Name = npcName,
Spawns = { [zoneId] = npcSpawns[zoneId] },
Waypoints = {},
Hostile = true,
Icon = Questie.ICON_TYPE_SLAY,
GetIconScale = _GetIconScaleForMonster,
IconScale = _GetIconScaleForMonster(),
TooltipKey = "m_" .. npcId,
}
end
end
end
end
quest.Objectives[objectiveIndex] = {
Id = 0, -- Dynamic fallback, no static DB ID known
Index = objectiveIndex,
questId = quest.Id,
_lastUpdate = 0,
Description = objective.text,
spawnList = fallbackSpawnList,
AlreadySpawned = {},
Update = _QuestieQuest.ObjectiveUpdate,
Type = objType,
}
end
quest.Objectives[objectiveIndex]:Update()
else
if not quest.Objectives[objectiveIndex] then
quest.Objectives[objectiveIndex] = {
@@ -1651,7 +1705,7 @@ function QuestieQuest:PopulateQuestLogInfo(quest)
Index = objectiveIndex,
questId = quest.Id,
_lastUpdate = 0,
Description = objective.text,
Description = (objective.text and objective.text ~= "") and objective.text or (quest.ObjectiveData and quest.ObjectiveData[objectiveIndex] and quest.ObjectiveData[objectiveIndex].Text) or "",
spawnList = {},
AlreadySpawned = {},
Update = _QuestieQuest.ObjectiveUpdate,
@@ -1731,7 +1785,9 @@ function _QuestieQuest.ObjectiveUpdate(self)
local finished = obj.finished or false -- ensure its boolean false and not nil (hack)
self.Type = obj.type;
self.Description = obj.text
local quest = QuestieDB.GetQuest(self.questId)
local fallbackText = quest and quest.ObjectiveData and quest.ObjectiveData[self.Index] and quest.ObjectiveData[self.Index].Text or ""
self.Description = (obj.text and obj.text ~= "") and obj.text or fallbackText
self.Collected = tonumber(numFulfilled);
self.Needed = tonumber(numRequired);
self.Completed = (self.Needed == self.Collected and self.Needed > 0) or