mirror of
https://github.com/Blaststar/Questie-Octo-Vanilla.git
synced 2026-10-19 11:50:50 +00:00
Force pushing all my changes
This commit is contained in:
@@ -42,6 +42,9 @@ function A:Validate()
|
||||
queryQuestsCompleted = type(QueryQuestsCompleted)=="function",
|
||||
questFlaggedCompleted = type(IsQuestFlaggedCompleted)=="function" or Has(C_QuestLog,"IsQuestFlaggedCompleted"),
|
||||
mapWorldSize = Has(C_Map,"GetMapWorldSize"),
|
||||
instanceInfo = type(GetInstanceInfo)=="function",
|
||||
leaderboardObjectiveID = type(GetQuestLogLeaderBoardID)=="function",
|
||||
areaTriggerInfo = Has(C_Map,"GetAreaTriggerInfo"),
|
||||
}
|
||||
|
||||
return self.valid
|
||||
@@ -109,6 +112,51 @@ function A:GetBestMapForPlayer()
|
||||
return C_Map.GetBestMapForUnit("player")
|
||||
end
|
||||
|
||||
-- ClassicAPI backports the modern GetInstanceInfo() tuple to Vanilla 1.12.
|
||||
-- Keep instance classification behind the API contract instead of teaching
|
||||
-- presentation modules about DLL/global availability details.
|
||||
function A:GetInstanceType()
|
||||
if type(GetInstanceInfo)~="function" then return "none" end
|
||||
local ok,name,instanceType=pcall(GetInstanceInfo)
|
||||
if not ok or type(instanceType)~="string" then return "none" end
|
||||
return instanceType
|
||||
end
|
||||
|
||||
function A:IsInDungeonOrRaid()
|
||||
local instanceType=self:GetInstanceType()
|
||||
return instanceType=="party" or instanceType=="raid"
|
||||
end
|
||||
|
||||
-- pfQuest-classicAPI resolves quest-bound exploration objectives directly from
|
||||
-- ClassicAPI's AreaTrigger.dbc bridge. Keep this behind the API contract so
|
||||
-- generic map code never needs to know whether the DLL/global exists.
|
||||
function A:GetAreaTriggerInfo(areaTriggerID)
|
||||
areaTriggerID=tonumber(areaTriggerID)
|
||||
if not areaTriggerID or not C_Map or type(C_Map.GetAreaTriggerInfo)~="function" then return nil end
|
||||
|
||||
local ok,info=pcall(C_Map.GetAreaTriggerInfo,areaTriggerID)
|
||||
if not ok or type(info)~="table" then return nil end
|
||||
|
||||
local mapID=tonumber(info.areaID)
|
||||
local x=tonumber(info.mapX)
|
||||
local y=tonumber(info.mapY)
|
||||
if not mapID or not x or not y then return nil end
|
||||
|
||||
return {
|
||||
id=areaTriggerID,
|
||||
areaID=mapID,
|
||||
mapX=x,
|
||||
mapY=y
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
function A:GetQuestLogLeaderBoardID(objectiveIndex,questLogIndex)
|
||||
if type(GetQuestLogLeaderBoardID)~="function" then return nil end
|
||||
local ok,id=pcall(GetQuestLogLeaderBoardID,objectiveIndex,questLogIndex)
|
||||
if not ok then return nil end
|
||||
return tonumber(id)
|
||||
end
|
||||
|
||||
function A:GetQuestObjectives(questID,questLogIndex)
|
||||
if C_QuestLog and type(C_QuestLog.GetQuestObjectives)=="function" then
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@ function QuestieOcto:OnFoundationServiceReady()
|
||||
if self.Database.ready and self.DatabaseAPI:IsReady() and self.Completion.ready and self.QuestLog.snapshot then
|
||||
self.ready=true
|
||||
local q=self.DatabaseAPI:GetQuestCount()
|
||||
self:Print("foundation ready - "..tostring(q).." quests, "..tostring(self.Completion:Count())..
|
||||
self:Print("Foundation ready - "..tostring(q).." quests, "..tostring(self.Completion:Count())..
|
||||
" completed, "..tostring(self.QuestLog.stats.resolved).." active quest IDs resolved.")
|
||||
self:SendMessage("FOUNDATION_READY")
|
||||
end
|
||||
|
||||
@@ -1326,6 +1326,15 @@ do -- units/npcs
|
||||
|
||||
-- Barltok Kettleburn (Dun Agrath)
|
||||
pfDB["units"]["data-turtle"][52068]["coords"] = { [1] = { 21, 71.4, 11, 120 } }
|
||||
|
||||
-- Hurl Cinderfist (Restless, 41640): his live world spawn at Rugford's
|
||||
-- Mountain Rest falls inside both Turtle's expanded Dun Morogh map and the
|
||||
-- overlapping Grim Reaches map. Keep both map representations so the quest
|
||||
-- objective is visible from either local map without changing server truth.
|
||||
pfDB["units"]["data-turtle"][62200]["coords"] = {
|
||||
[1] = { 83.2, 70.8, 1, 300 },
|
||||
[2] = { 4.1, 92, 5602, 300 },
|
||||
}
|
||||
end
|
||||
|
||||
do -- quests
|
||||
@@ -1523,6 +1532,13 @@ do -- items
|
||||
end
|
||||
|
||||
do -- quests
|
||||
-- Onu is meditating (960): stale legacy pfDB quest-giver relation.
|
||||
-- The supplied Turtle server data retains the quest template but has no live
|
||||
-- creature/object/item quest-start relation for 960. Quest 961 is the actual
|
||||
-- repeatable Onu helper quest and keeps its active-944 prerequisite. Remove
|
||||
-- 960 from the merged runtime quest database without altering 961.
|
||||
pfDB["quests"]["data-turtle"][960] = "_"
|
||||
|
||||
-- Add "Dark Iron Gunpowder Keg" to objectives of "Vile Dwarven Pigs"
|
||||
-- The extractor scripts did not catch those, as the objetive is filled via a Gossip on use.
|
||||
pfDB["quests"]["data-turtle"][41682]["obj"]["O"] = { 2020173 }
|
||||
@@ -1537,3 +1553,30 @@ end
|
||||
-- cost 166 silently dropped corrections before 1.0.2. patchtable.lua
|
||||
-- reports it.
|
||||
pfDB["octo-overwrites-complete"] = true
|
||||
|
||||
do -- Cloth reputation donations: event 159 is a permanent content-release gate, not quest event membership.
|
||||
-- Verified against the supplied Turtle server data and live server behavior in August 2026.
|
||||
-- Keep every other quest field intact: faction/race restrictions, prerequisites, objectives,
|
||||
-- levels and the repeatable classification of the "Additional Runecloth" follow-ups.
|
||||
--
|
||||
-- Silvermoon Remnants (80370-80374) and Revantusk Tribe (80375-80379) already ship
|
||||
-- without event metadata, so only the legacy capital-city donation chains need correction.
|
||||
local clothDonationQuestIDs = {
|
||||
7791, 7792, 7793, 7794, 7795, 7796,
|
||||
7798, 7799, 7800, 7801,
|
||||
7802, 7803, 7804, 7805, 7806,
|
||||
7807, 7808, 7809, 7811, 7812,
|
||||
7813, 7814, 7817, 7818, 7819,
|
||||
7820, 7821, 7822, 7823, 7824, 7825,
|
||||
7826, 7827, 7831, 7832,
|
||||
7833, 7834, 7835, 7836, 7837,
|
||||
}
|
||||
|
||||
for _, questID in ipairs(clothDonationQuestIDs) do
|
||||
local quest = pfDB["quests"]["data-turtle"][questID]
|
||||
if type(quest) == "table" then
|
||||
quest["event"] = nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -259,6 +259,11 @@ function DB:GetCreatureFaction(id)
|
||||
return raw and (raw["fac"] or raw["faction"]) or nil
|
||||
end
|
||||
|
||||
function DB:GetObjectFaction(id)
|
||||
local raw=self:GetObjectRaw(id)
|
||||
return raw and (raw["fac"] or raw["faction"]) or nil
|
||||
end
|
||||
|
||||
|
||||
function DB:GetMapIDByName(name)
|
||||
if not self.ready or not name then return nil end
|
||||
|
||||
+88
-4
@@ -9,7 +9,19 @@ QM.cache = {}
|
||||
local function CopyArray(src)
|
||||
if not src then return nil end
|
||||
local out={}
|
||||
for _,v in pairs(src) do table.insert(out,v) end
|
||||
local keys={}
|
||||
|
||||
-- These sources are arrays, but pairs() does not guarantee their numeric
|
||||
-- order on Lua 5.0. Preserve the database/compiler order explicitly so
|
||||
-- objective #1/#2 cannot be swapped before BuildObjectiveData consumes it.
|
||||
for k in pairs(src) do
|
||||
if type(k)=="number" then table.insert(keys,k) end
|
||||
end
|
||||
table.sort(keys)
|
||||
|
||||
for i=1,table.getn(keys) do
|
||||
table.insert(out,src[keys[i]])
|
||||
end
|
||||
return out
|
||||
end
|
||||
|
||||
@@ -29,12 +41,37 @@ local ITEM_OBJECTIVE_FIRST={
|
||||
[5088]=true,
|
||||
}
|
||||
|
||||
-- Quests that are not normal static NPC offers. Questie 6 blacklists 7946
|
||||
-- because Morja only exposes it after the Dark Iron Ale/Jubjub interaction.
|
||||
-- Quests that are not normal static NPC offers. Questie 6 blacklists these
|
||||
-- because the NPC only exposes the quest after a scripted world interaction.
|
||||
-- Keeping the real starter relation is useful for quest truth/tooltips, but it
|
||||
-- must not become a permanent map pickup marker.
|
||||
local CONDITIONAL_OFFERS={
|
||||
[3861]="Use /chicken on a Chicken until it temporarily offers CLUCK!.",
|
||||
[7946]="Requires Jubjub to be lured back with Dark Iron Ale.",
|
||||
}
|
||||
|
||||
-- Some situational quests benefit from one deliberately chosen discovery marker
|
||||
-- without pretending every scripted source is a permanent questgiver. CLUCK! can
|
||||
-- be triggered from many Chickens, but the Westfall Chicken at 55.6,30.9 is used
|
||||
-- as the single representative pickup/turn-in marker so new players can discover
|
||||
-- that the quest exists without covering every Chicken spawn with quest icons.
|
||||
-- The underlying starter/finisher relation remains NPC 620 and server truth is
|
||||
-- unchanged; this table is presentation only.
|
||||
local CONDITIONAL_MAP_MARKERS={
|
||||
[3861]={
|
||||
creatureID=620,
|
||||
coords={{55.6,30.9,40,300}},
|
||||
},
|
||||
}
|
||||
|
||||
-- Server repeatability and map presentation are separate. CLUCK! is technically
|
||||
-- repeatable, but Questie-Octo already treats it as one-and-done after the first
|
||||
-- completion. Present any marker that is legitimately shown for it as an
|
||||
-- ordinary yellow quest rather than a blue repeatable quest.
|
||||
local NORMAL_REPEATABLE_PRESENTATION={
|
||||
[3861]=true,
|
||||
}
|
||||
|
||||
local function AddObjectiveData(list,kind,id)
|
||||
if not id then return end
|
||||
|
||||
@@ -51,6 +88,30 @@ local function AddObjectiveData(list,kind,id)
|
||||
})
|
||||
end
|
||||
|
||||
-- 1+4+8+64=77 Alliance races, 2+16+32+128=178 Horde races (pfQuest bitraces).
|
||||
local function NoteFaction(fac,seen)
|
||||
if fac=="A" then seen.a=true
|
||||
elseif fac=="H" then seen.h=true
|
||||
elseif fac=="AH" or fac=="HA" then seen.a=true; seen.h=true end
|
||||
end
|
||||
|
||||
-- Mirror pfQuest's GetRaceMaskByID starter fallback so a faction-specific quest
|
||||
-- with no explicit race field is derived from its quest giver's faction.
|
||||
local function StarterRaceMask(starts)
|
||||
if not starts then return 0 end
|
||||
local seen={}
|
||||
for _,id in pairs(starts.creature or {}) do
|
||||
NoteFaction(QuestieOcto.DatabaseAPI:GetCreatureFaction(id),seen)
|
||||
end
|
||||
for _,id in pairs(starts.gameObject or {}) do
|
||||
NoteFaction(QuestieOcto.DatabaseAPI:GetObjectFaction(id),seen)
|
||||
end
|
||||
if seen.a and seen.h then return 255 end
|
||||
if seen.a then return 77 end
|
||||
if seen.h then return 178 end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function BuildObjectiveData(questID,objectives)
|
||||
local result={}
|
||||
local itemFirst=ITEM_OBJECTIVE_FIRST[questID] and true or false
|
||||
@@ -96,7 +157,10 @@ function QM:MarkObservedRepeatable(questID)
|
||||
local db=ObservedRepeatables()
|
||||
if db[questID] then return false end
|
||||
db[questID]=true
|
||||
if self.cache[questID] then self.cache[questID].repeatable=true end
|
||||
if self.cache[questID] then
|
||||
self.cache[questID].repeatable=true
|
||||
self.cache[questID].presentationRepeatable=not NORMAL_REPEATABLE_PRESENTATION[questID]
|
||||
end
|
||||
if QuestieOcto.AvailableQuests and QuestieOcto.AvailableQuests.Schedule then
|
||||
QuestieOcto.AvailableQuests:Schedule(true,0.02)
|
||||
end
|
||||
@@ -154,6 +218,7 @@ function QM:Get(questID)
|
||||
timed=raw["timed"] and true or false,
|
||||
disabled=raw["disabled"] and true or false,
|
||||
conditionalOffer=CONDITIONAL_OFFERS[tonumber(questID)],
|
||||
conditionalMapMarker=CONDITIONAL_MAP_MARKERS[tonumber(questID)],
|
||||
exclusive=raw["exclusive"] and true or false,
|
||||
nextChain=tonumber(raw["nextChain"]),
|
||||
|
||||
@@ -184,14 +249,33 @@ function QM:Get(questID)
|
||||
gameObject=raw["obj"] and CopyArray(raw["obj"]["O"]) or nil,
|
||||
item=raw["obj"] and CopyArray(raw["obj"]["I"]) or nil,
|
||||
irItems=raw["obj"] and CopyArray(raw["obj"]["IR"]) or nil,
|
||||
-- A = quest-bound AreaTrigger objective. This is deliberately distinct
|
||||
-- from generic map exploration/fog data: only triggers referenced by an
|
||||
-- actual quest are retained here.
|
||||
areaTrigger=raw["obj"] and CopyArray(raw["obj"]["A"]) or nil,
|
||||
},
|
||||
}
|
||||
|
||||
-- Keep server repeatability authoritative for completion/filtering while
|
||||
-- allowing a narrow presentation exception such as CLUCK!.
|
||||
q.presentationRepeatable=q.repeatable and not NORMAL_REPEATABLE_PRESENTATION[tonumber(questID)] and true or false
|
||||
-- CLUCK! was explicitly chosen to stay an ordinary yellow discovery marker.
|
||||
-- Keep that presentation exception independent from the Turtle low-level gray
|
||||
-- marker rule so a high-level player does not turn its representative marker gray.
|
||||
q.presentationAlwaysNormal=NORMAL_REPEATABLE_PRESENTATION[tonumber(questID)] and true or false
|
||||
|
||||
-- IR items are intentionally NOT added to objectiveData. They are a
|
||||
-- pfQuest-special interaction relationship that becomes target guidance
|
||||
-- only while the player actually possesses the required item.
|
||||
q.objectiveData=BuildObjectiveData(questID,q.objectives)
|
||||
|
||||
-- A neutral race field lets faction-only quests leak to the wrong faction;
|
||||
-- fall back to the quest starter's faction just like pfQuest does.
|
||||
if not q.raceMask or tonumber(q.raceMask)==0 then
|
||||
local starterMask=StarterRaceMask(q.starts)
|
||||
if starterMask>0 then q.raceMask=starterMask end
|
||||
end
|
||||
|
||||
self.cache[questID]=q
|
||||
return q
|
||||
end
|
||||
|
||||
@@ -121,6 +121,16 @@ Repeatable flags come from the server-derived enrichment or trustworthy live
|
||||
observation. One-and-done repeatables can opt into `hideAfterFirstCompletion`;
|
||||
`CLUCK!` uses that rule.
|
||||
|
||||
`CLUCK!` (3861) is also a scripted/conditional offer rather than a normal static
|
||||
Chicken pickup: `/chicken` temporarily exposes the quest. To keep the quest
|
||||
discoverable without covering every Chicken spawn, Questie-Octo deliberately uses
|
||||
one representative Westfall Chicken at 55.6,30.9 as the static pickup/turn-in map
|
||||
marker. The tooltip explains that `/chicken` is required. This does not change
|
||||
server truth: other Chickens can still trigger/complete the scripted interaction.
|
||||
The server repeatable flag remains intact for completion logic, but the representative
|
||||
marker uses ordinary yellow quest presentation instead of blue repeatable artwork.
|
||||
After the first completion, `hideAfterFirstCompletion` removes the discovery marker.
|
||||
|
||||
The cloth donation chain is intentionally not flattened into one repeatable
|
||||
category: Wool/Silk/Mageweave/Runecloth donation steps are one-time quests, while
|
||||
the `Additional Runecloth` follow-ups carry the repeatable flag.
|
||||
@@ -354,3 +364,66 @@ once historical completion says it was rewarded.
|
||||
### Darkmoon logical event alias
|
||||
|
||||
Turtle/pfQuest quest rows use both `event = 4` and `event = 5` for quest offers that are shared by the same Darkmoon Faire NPCs in both Elwynn and Mulgore. Questie-Octo preserves the authoritative value as `rawEventID`, but normalizes runtime `eventID` 5 to logical event 4. Availability and presentation therefore see one `DARKMOON_FAIRE` event; the anchored 14-day schedule and NPC coordinates determine the physical location. Construction events 23/24 remain distinct.
|
||||
|
||||
## Restless / custom Dun Morogh map note
|
||||
|
||||
`Restless` (41640) must be interpreted against Turtle/Octo's custom Dun Morogh, not
|
||||
the stock Vanilla artwork/boundary assumptions. The current Dun Morogh map visibly
|
||||
includes custom southeastern terrain such as Rugford's Mountain Rest. Durmir Rugford
|
||||
(62199) has map representations in both Dun Morogh (1) and Grim Reaches (5602).
|
||||
Hurl Cinderfist (62200), the 100% source for Durmir's Belongings (41686), now also
|
||||
has both representations: Dun Morogh 83.2/70.8 and Grim Reaches 4.1/92.0. Both are
|
||||
the same live world spawn transformed through the two overlapping WorldMapArea
|
||||
bounds. Keep both so the active objective is visible on Turtle's expanded Dun
|
||||
Morogh map as well as Grim Reaches; do not collapse these coordinates based on
|
||||
stock Vanilla Dun Morogh assumptions.
|
||||
|
||||
## Turtle low-level gray available marker
|
||||
|
||||
Questie-Octo 1.0.77 adds a gray available `!` presentation for ordinary
|
||||
available quests and ordinary item-start quests using the Turtle-specific
|
||||
accepted boundary:
|
||||
|
||||
- player level <= quest level + 25: normal yellow available marker;
|
||||
- player level >= quest level + 26: gray available marker.
|
||||
|
||||
The boundary is strict: exactly +25 remains yellow and +26 becomes gray.
|
||||
|
||||
The source basis is Turtle's custom quest XP rule in Tortoise
|
||||
`src/game/QuestDef.cpp`, which retains full quest XP through `qLevel + 25`
|
||||
before reducing it at +26 and beyond. That server function computes XP, not
|
||||
client icon color directly, so the project does not claim it is itself the
|
||||
native marker-color implementation. It was adopted because it matches the
|
||||
observed Turtle native quest behavior and replaces the stock Questie
|
||||
`GetQuestGreenRange()` assumption that was contradicted in-game.
|
||||
|
||||
Special presentation remains higher priority than gray: PvP stays red,
|
||||
repeatable stays blue, and verified event/seasonal stays green. CLUCK! (3861)
|
||||
is the deliberate exception already chosen by the project: its single
|
||||
representative discovery marker stays ordinary yellow rather than repeatable
|
||||
blue or low-level gray. Turn-in `?` markers are unchanged.
|
||||
|
||||
This gray classification is presentation-only and remains independent from the
|
||||
`Levels Below` visibility slider. Level-up refreshes rebind visible Map and
|
||||
Minimap marker textures without rebuilding quest data or map geometry.
|
||||
|
||||
|
||||
## 1.0.78 zone-wide rare item-start tooltip compaction
|
||||
|
||||
The representative-marker rule for item-start sources below 0.50% must remain
|
||||
representative in the tooltip as well as on the map. Previously the map correctly
|
||||
collapsed those sources to one marker per quest/item/zone, but hovering that marker
|
||||
still printed every represented creature type and spawn count. For large world-drop
|
||||
items such as Pendant of Myzrael this could create a tooltip taller than the screen
|
||||
and hide the actual quest/item information.
|
||||
|
||||
Zone-wide representative tooltips now show only a compact aggregate source count,
|
||||
the quest, and the actual represented minimum/maximum drop-rate range. The underlying
|
||||
source list remains unchanged. Ordinary non-zone-wide clustered item-start tooltips
|
||||
still list their nearby source creatures normally.
|
||||
|
||||
## 1.0.79 representative-tooltip wording cleanup
|
||||
|
||||
Removed the redundant `One zone marker represents item-start sources below 0.50%.`
|
||||
line from zone-wide rare item-start tooltips. The representative threshold and all
|
||||
source/drop data remain unchanged; this is tooltip presentation only.
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
-- @release $Id: AceConfigDialog-3.0.lua 1139 2016-07-03 07:43:51Z nevcairiel $
|
||||
|
||||
local LibStub = LibStub
|
||||
local MAJOR, MINOR = "AceConfigDialog-3.0", 63
|
||||
local MAJOR, MINOR = "QuestieOcto_AceConfigDialog-3.0", 63
|
||||
local AceConfigDialog, oldminor = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceConfigDialog then return end
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local AceCore = LibStub("QuestieOcto_AceCore-3.0")
|
||||
local wipe, strsplit = AceCore.wipe, AceCore.strsplit
|
||||
local safecall = AceCore.safecall
|
||||
local Dispatchers = AceCore.Dispatchers
|
||||
@@ -23,8 +23,8 @@ AceConfigDialog.frame.apps = AceConfigDialog.frame.apps or {}
|
||||
AceConfigDialog.frame.closing = AceConfigDialog.frame.closing or {}
|
||||
AceConfigDialog.frame.closeAllOverride = AceConfigDialog.frame.closeAllOverride or {}
|
||||
|
||||
local gui = LibStub("AceGUI-3.0")
|
||||
local reg = LibStub("AceConfigRegistry-3.0")
|
||||
local gui = LibStub("QuestieOcto_AceGUI-3.0")
|
||||
local reg = LibStub("QuestieOcto_AceConfigRegistry-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local tconcat, tinsert, tsort, tremove, tgetn, tsetn = table.concat, table.insert, table.sort, table.remove, table.getn, table.setn
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
-- @class file
|
||||
-- @name AceConfigRegistry-3.0
|
||||
-- @release $Id: AceConfigRegistry-3.0.lua 1139 2016-07-03 07:43:51Z nevcairiel $
|
||||
local MAJOR, MINOR = "AceConfigRegistry-3.0", 16
|
||||
local MAJOR, MINOR = "QuestieOcto_AceConfigRegistry-3.0", 16
|
||||
local AceConfigRegistry = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not AceConfigRegistry then return end
|
||||
|
||||
AceConfigRegistry.tables = AceConfigRegistry.tables or {}
|
||||
|
||||
local CallbackHandler = LibStub:GetLibrary("CallbackHandler-1.0")
|
||||
local CallbackHandler = LibStub:GetLibrary("QuestieOcto_CallbackHandler-1.0")
|
||||
|
||||
if not AceConfigRegistry.callbacks then
|
||||
AceConfigRegistry.callbacks = CallbackHandler:New(AceConfigRegistry)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
local ACECORE_MAJOR, ACECORE_MINOR = "AceCore-3.0", 1
|
||||
local ACECORE_MAJOR, ACECORE_MINOR = "QuestieOcto_AceCore-3.0", 1
|
||||
local AceCore, oldminor = LibStub:NewLibrary(ACECORE_MAJOR, ACECORE_MINOR)
|
||||
|
||||
if not AceCore then return end -- No upgrade needed
|
||||
@@ -48,7 +48,7 @@ AceCore.errorhandler = errorhandler
|
||||
|
||||
local function CreateSafeDispatcher(argCount)
|
||||
local code = [[
|
||||
local errorhandler = LibStub("AceCore-3.0").errorhandler
|
||||
local errorhandler = LibStub("QuestieOcto_AceCore-3.0").errorhandler
|
||||
local method, UP_ARGS
|
||||
local function call()
|
||||
local func, ARGS = method, UP_ARGS
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
-- to misbehave. If you think some part of a widget should be modifiable, please open a ticket, and we"ll
|
||||
-- implement a proper API to modify it.
|
||||
-- @usage
|
||||
-- local AceGUI = LibStub("AceGUI-3.0")
|
||||
-- local AceGUI = LibStub("QuestieOcto_AceGUI-3.0")
|
||||
-- -- Create a container frame
|
||||
-- local f = AceGUI:Create("Frame")
|
||||
-- f:SetCallback("OnClose",function(widget) AceGUI:Release(widget) end)
|
||||
@@ -25,12 +25,12 @@
|
||||
-- @class file
|
||||
-- @name AceGUI-3.0
|
||||
-- @release $Id: AceGUI-3.0.lua 1102 2013-10-25 14:15:23Z nevcairiel $
|
||||
local ACEGUI_MAJOR, ACEGUI_MINOR = "AceGUI-3.0", 34
|
||||
local ACEGUI_MAJOR, ACEGUI_MINOR = "QuestieOcto_AceGUI-3.0", 34
|
||||
local AceGUI, oldminor = LibStub:NewLibrary(ACEGUI_MAJOR, ACEGUI_MINOR)
|
||||
|
||||
if not AceGUI then return end -- No upgrade needed
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local AceCore = LibStub("QuestieOcto_AceCore-3.0")
|
||||
local hooksecurefunc = AceCore.hooksecurefunc
|
||||
local safecall = AceCore.safecall
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ BlizOptionsGroup Container
|
||||
Simple container widget for the integration of AceGUI into the Blizzard Interface Options
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "BlizOptionsGroup", 21
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -3,7 +3,7 @@ DropdownGroup Container
|
||||
Container controlled by a dropdown on the top.
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "DropdownGroup", 21
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
Frame Container
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "Frame", 24
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local AceCore = LibStub("QuestieOcto_AceCore-3.0")
|
||||
local wipe = AceCore.wipe
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -3,7 +3,7 @@ InlineGroup Container
|
||||
Simple container widget that creates a visible "box" with an optional title.
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "InlineGroup", 21
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -3,7 +3,7 @@ ScrollFrame Container
|
||||
Plain container that scrolls its content and doesn't grow in height.
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "ScrollFrame", 24
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
local fixlevels = AceGUI.fixlevels
|
||||
|
||||
@@ -3,7 +3,7 @@ SimpleGroup Container
|
||||
Simple container widget that just groups widgets.
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "SimpleGroup", 20
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -3,10 +3,10 @@ TabGroup Container
|
||||
Container that uses tabs on top to switch between groups.
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "TabGroup", 35
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local AceCore = LibStub("QuestieOcto_AceCore-3.0")
|
||||
local wipe = AceCore.wipe
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -3,10 +3,10 @@ TreeGroup Container
|
||||
Container that uses a tree control to switch between groups.
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "TreeGroup", 40
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local AceCore = LibStub("QuestieOcto_AceCore-3.0")
|
||||
local strsplit = AceCore.strsplit
|
||||
local _G = AceCore._G
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
local AceGUI = LibStub("QuestieOcto_AceGUI-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local pairs, assert, type = pairs, assert, type
|
||||
|
||||
@@ -3,10 +3,10 @@ Button Widget
|
||||
Graphical Button.
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "Button", 23
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local AceCore = LibStub("QuestieOcto_AceCore-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local pairs = pairs
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Checkbox Widget
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "CheckBox", 22
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
ColorPicker Widget
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "ColorPicker", 23
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
--[[ $Id: AceGUIWidget-DropDown-Items.lua 1137 2016-05-15 10:57:36Z nevcairiel $ ]]--
|
||||
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
local AceGUI = LibStub("QuestieOcto_AceGUI-3.0")
|
||||
|
||||
local IsLegion = false
|
||||
|
||||
@@ -201,7 +201,7 @@ function ItemBase.Create(type)
|
||||
end
|
||||
|
||||
-- Register a dummy LibStub library to retrieve the ItemBase, so other addons can use it.
|
||||
local IBLib = LibStub:NewLibrary("AceGUI-3.0-DropDown-ItemBase", ItemBase.version)
|
||||
local IBLib = LibStub:NewLibrary("QuestieOcto_AceGUI-3.0-DropDown-ItemBase", ItemBase.version)
|
||||
if IBLib then
|
||||
IBLib.GetItemBase = function() return ItemBase end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
--[[ $Id: AceGUIWidget-DropDown.lua 1116 2014-10-12 08:15:46Z nevcairiel $ ]]--
|
||||
local AceGUI = LibStub("AceGUI-3.0")
|
||||
local AceGUI = LibStub("QuestieOcto_AceGUI-3.0")
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local AceCore = LibStub("QuestieOcto_AceCore-3.0")
|
||||
|
||||
-- Lua APIs
|
||||
local min, max, floor = math.min, math.max, math.floor
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
EditBox Widget
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "EditBox", 26
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local AceCore = LibStub("QuestieOcto_AceCore-3.0")
|
||||
local hooksecurefunc = AceCore.hooksecurefunc
|
||||
local _G = AceCore._G
|
||||
local GetCursorInfo = _G.GetCursorInfo
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Heading Widget
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "Heading", 20
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Icon Widget
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "Icon", 21
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
InteractiveLabel Widget
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "InteractiveLabel", 20
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -3,7 +3,7 @@ Keybinding Widget
|
||||
Set Keybindings in the Config UI.
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "Keybinding", 25
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -3,7 +3,7 @@ Label Widget
|
||||
Displays text and optionally an icon.
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "Label", 23
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
local Type, Version = "MultiLineEditBox", 28
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
local AceCore = LibStub("AceCore-3.0")
|
||||
local AceCore = LibStub("QuestieOcto_AceCore-3.0")
|
||||
local hooksecurefunc = AceCore.hooksecurefunc
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -3,7 +3,7 @@ Slider Widget
|
||||
Graphical Slider, like, for Range values.
|
||||
-------------------------------------------------------------------------------]]
|
||||
local Type, Version = "Slider", 22
|
||||
local AceGUI = LibStub and LibStub("AceGUI-3.0", true)
|
||||
local AceGUI = LibStub and LibStub("QuestieOcto_AceGUI-3.0", true)
|
||||
if not AceGUI or (AceGUI:GetWidgetVersion(Type) or 0) >= Version then return end
|
||||
|
||||
-- Lua APIs
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
-- Original Questie foundation: CallbackHandler-1.0 r1131 / MINOR 6.
|
||||
-- Turtle 1.12 Lua 5.0 supports vararg declarations but consumes them through
|
||||
-- the implicit 'arg' table rather than Lua 5.1's '...' expression.
|
||||
local MAJOR, MINOR = "CallbackHandler-1.0", 6
|
||||
local MAJOR, MINOR = "QuestieOcto_CallbackHandler-1.0", 6
|
||||
local CallbackHandler = LibStub:NewLibrary(MAJOR, MINOR)
|
||||
|
||||
if not CallbackHandler then return end
|
||||
|
||||
+207
@@ -0,0 +1,207 @@
|
||||
-- Quest arrow that points toward the nearest active quest objective in the
|
||||
-- current zone, adapted from pfQuest's route arrow.
|
||||
QuestieOcto.Arrow = QuestieOcto.Arrow or {}
|
||||
local A = QuestieOcto.Arrow
|
||||
|
||||
local ARROW_TEXTURE="Interface\\AddOns\\Questie-Octo\\UI\\Icons\\arrow"
|
||||
local atan2=atan2 or math.atan2
|
||||
|
||||
local function Settings()
|
||||
return QuestieOcto.MinimapSettings
|
||||
end
|
||||
|
||||
local function modulo(value,by)
|
||||
return value-math.floor(value/by)*by
|
||||
end
|
||||
|
||||
-- Red at the player's back, yellow at the sides, green straight ahead.
|
||||
local function ColorGradient(perc)
|
||||
if perc>1 then perc=1 elseif perc<0 then perc=0 end
|
||||
local r1,g1,b1,r2,g2,b2
|
||||
if perc<=0.5 then
|
||||
perc=perc*2
|
||||
r1,g1,b1=1,0,0
|
||||
r2,g2,b2=1,1,0
|
||||
else
|
||||
perc=perc*2-1
|
||||
r1,g1,b1=1,1,0
|
||||
r2,g2,b2=0,1,0
|
||||
end
|
||||
return r1+(r2-r1)*perc, g1+(g2-g1)*perc, b1+(b2-b1)*perc
|
||||
end
|
||||
|
||||
local function PlayerFacing()
|
||||
if GetPlayerFacing then return GetPlayerFacing() or 0 end
|
||||
if MiniMapCompassRing and MiniMapCompassRing.GetFacing then
|
||||
return MiniMapCompassRing:GetFacing()*-1
|
||||
end
|
||||
return 0
|
||||
end
|
||||
|
||||
local function CurrentMapID()
|
||||
if GetRealZoneText and QuestieOcto.DatabaseAPI and QuestieOcto.DatabaseAPI.GetMapIDByName then
|
||||
local id=QuestieOcto.DatabaseAPI:GetMapIDByName(GetRealZoneText())
|
||||
if id then return tonumber(id) end
|
||||
end
|
||||
if QuestieOcto.API and QuestieOcto.API.GetBestMapForPlayer then
|
||||
return tonumber(QuestieOcto.API:GetBestMapForPlayer())
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function IsArrowRole(role)
|
||||
return role=="objectiveCreature" or role=="objectiveObject"
|
||||
or role=="objectiveItemSource" or role=="turnin"
|
||||
end
|
||||
|
||||
-- Pull the current map's prepared plan and keep only objective/turn-in slots.
|
||||
local function CollectTargets(mapID)
|
||||
local targets={}
|
||||
local plan=mapID and QuestieOcto.PreparedMap and QuestieOcto.PreparedMap:Get(mapID) or nil
|
||||
if not plan then return targets end
|
||||
|
||||
for _,desc in pairs(plan) do
|
||||
local x,y,questID
|
||||
if desc.type=="nodeSlot" then
|
||||
for _,entry in pairs(desc.entries or {}) do
|
||||
local node=entry.node
|
||||
if node and IsArrowRole(node.role) then
|
||||
x,y,questID=tonumber(desc.x),tonumber(desc.y),node.questID
|
||||
break
|
||||
end
|
||||
end
|
||||
elseif desc.type=="node" and desc.node and IsArrowRole(desc.node.role) then
|
||||
x,y,questID=tonumber(desc.x),tonumber(desc.y),desc.node.questID
|
||||
end
|
||||
|
||||
if x and y then table.insert(targets,{x,y,questID}) end
|
||||
end
|
||||
|
||||
return targets
|
||||
end
|
||||
|
||||
local function NearestTarget(targets,px,py)
|
||||
local best,bestDist
|
||||
for _,t in pairs(targets or {}) do
|
||||
local dx,dy=(px-t[1])*1.5,(py-t[2])
|
||||
local d=math.sqrt(dx*dx+dy*dy)
|
||||
if not bestDist or d<bestDist then bestDist=d; best=t end
|
||||
end
|
||||
return best,bestDist
|
||||
end
|
||||
|
||||
function A:OnUpdate()
|
||||
local frame=self.frame
|
||||
if not frame then return end
|
||||
|
||||
if not Settings():Get("showQuestArrow") then
|
||||
if frame:IsShown() then frame:Hide() end
|
||||
return
|
||||
end
|
||||
|
||||
local px,py
|
||||
if GetPlayerMapPosition then px,py=GetPlayerMapPosition("player") end
|
||||
px,py=(px or 0)*100,(py or 0)*100
|
||||
if px==0 and py==0 then
|
||||
if frame:IsShown() then frame:Hide() end
|
||||
return
|
||||
end
|
||||
|
||||
local mapID=CurrentMapID()
|
||||
if mapID~=frame.mapID or (frame.refresh or 0)<GetTime() then
|
||||
frame.mapID=mapID
|
||||
frame.targets=CollectTargets(mapID)
|
||||
frame.refresh=GetTime()+1
|
||||
end
|
||||
|
||||
local best,bestDist=NearestTarget(frame.targets,px,py)
|
||||
if not best then
|
||||
if frame:IsShown() then frame:Hide() end
|
||||
return
|
||||
end
|
||||
if not frame:IsShown() then frame:Show() end
|
||||
|
||||
-- Arrow direction and sprite-sheet cell math taken from pfQuest/TomTomVanilla.
|
||||
local xDelta=(best[1]-px)*1.5
|
||||
local yDelta=(best[2]-py)
|
||||
local dir=atan2(xDelta,-(yDelta))
|
||||
dir=dir>0 and (math.pi*2)-dir or -dir
|
||||
if dir<0 then dir=dir+360 end
|
||||
local angle=math.rad(dir)-PlayerFacing()
|
||||
local perc=math.abs((math.pi-math.abs(angle))/math.pi)
|
||||
local r,g,b=ColorGradient(math.floor(perc*100)/100)
|
||||
local cell=modulo(math.floor(angle/(math.pi*2)*108+0.5),108)
|
||||
local column=modulo(cell,9)
|
||||
local row=math.floor(cell/9)
|
||||
frame.model:SetTexCoord((column*56)/512,((column+1)*56)/512,(row*42)/512,((row+1)*42)/512)
|
||||
frame.model:SetVertexColor(r,g,b)
|
||||
|
||||
if best[3]~=frame.lastQuest then
|
||||
frame.lastQuest=best[3]
|
||||
local q=best[3] and QuestieOcto.QuestModel:Get(best[3]) or nil
|
||||
local level=q and tonumber(q.level)
|
||||
local label=q and q.title or "Quest"
|
||||
if level and level>0 then label="["..tostring(level).."] "..label end
|
||||
frame.title:SetText(label)
|
||||
local cr,cg,cb=1,.82,0
|
||||
if q and QuestieOcto.GetNativeQuestDifficultyColor then
|
||||
local nr,ng,nb=QuestieOcto:GetNativeQuestDifficultyColor(q.level,q.id)
|
||||
if nr then cr,cg,cb=nr,ng,nb end
|
||||
end
|
||||
frame.title:SetTextColor(cr,cg,cb)
|
||||
end
|
||||
|
||||
local shown=math.floor(bestDist*10)/10
|
||||
if shown~=frame.shownDistance then
|
||||
frame.shownDistance=shown
|
||||
frame.distance:SetText("Distance: "..string.format("%.1f",bestDist))
|
||||
end
|
||||
end
|
||||
|
||||
function A:Initialize()
|
||||
if self.frame then return end
|
||||
|
||||
local frame=CreateFrame("Frame","QuestieOctoQuestArrow",UIParent)
|
||||
frame:SetPoint("CENTER",UIParent,"CENTER",0,-100)
|
||||
frame:SetWidth(48)
|
||||
frame:SetHeight(36)
|
||||
if frame.SetClampedToScreen then frame:SetClampedToScreen(true) end
|
||||
frame:SetMovable(true)
|
||||
frame:EnableMouse(true)
|
||||
frame:RegisterForDrag("LeftButton")
|
||||
frame:SetScript("OnDragStart",function() if IsShiftKeyDown() then this:StartMoving() end end)
|
||||
frame:SetScript("OnDragStop",function() this:StopMovingOrSizing() end)
|
||||
|
||||
local model=frame:CreateTexture("QuestieOctoQuestArrowModel","ARTWORK")
|
||||
model:SetTexture(ARROW_TEXTURE)
|
||||
model:SetTexCoord(0,56/512,0,42/512)
|
||||
model:SetAllPoints(frame)
|
||||
frame.model=model
|
||||
|
||||
local font=STANDARD_TEXT_FONT or "Fonts\\FRIZQT__.TTF"
|
||||
|
||||
local title=frame:CreateFontString(nil,"OVERLAY","GameFontNormal")
|
||||
title:SetPoint("TOP",model,"BOTTOM",0,-10)
|
||||
title:SetFont(font,13,"OUTLINE")
|
||||
title:SetTextColor(1,.82,0)
|
||||
title:SetJustifyH("CENTER")
|
||||
frame.title=title
|
||||
|
||||
local distance=frame:CreateFontString(nil,"OVERLAY","GameFontNormal")
|
||||
distance:SetPoint("TOP",title,"BOTTOM",0,-2)
|
||||
distance:SetFont(font,11,"OUTLINE")
|
||||
distance:SetTextColor(.8,.8,.8)
|
||||
distance:SetJustifyH("CENTER")
|
||||
frame.distance=distance
|
||||
|
||||
frame:Hide()
|
||||
self.frame=frame
|
||||
|
||||
-- A hidden frame receives no OnUpdate, so drive the logic from an always-shown
|
||||
-- frame that shows/hides the arrow.
|
||||
local driver=CreateFrame("Frame","QuestieOctoQuestArrowDriver",UIParent)
|
||||
driver:SetScript("OnUpdate",function() A:OnUpdate() end)
|
||||
self.driver=driver
|
||||
end
|
||||
|
||||
QuestieOcto:RegisterMessage("FOUNDATION_READY",A,"Initialize")
|
||||
@@ -37,11 +37,19 @@ local function IndexCoords(coords,questID)
|
||||
end
|
||||
end
|
||||
|
||||
local function StarterCreatureCoords(q,creatureID)
|
||||
local marker=q and q.conditionalMapMarker or nil
|
||||
if marker and tonumber(marker.creatureID)==tonumber(creatureID) then
|
||||
return marker.coords
|
||||
end
|
||||
return QuestieOcto.DatabaseAPI:GetCreatureCoords(creatureID)
|
||||
end
|
||||
|
||||
local function IndexQuest(q)
|
||||
if not q then return end
|
||||
|
||||
for _,id in pairs(q.starts.creature or {}) do
|
||||
IndexCoords(QuestieOcto.DatabaseAPI:GetCreatureCoords(id),q.id)
|
||||
IndexCoords(StarterCreatureCoords(q,id),q.id)
|
||||
end
|
||||
|
||||
for _,id in pairs(q.starts.gameObject or {}) do
|
||||
|
||||
@@ -56,6 +56,27 @@ C.zones={
|
||||
[1637]={continent=1,name="Ogrimmar",xMin=1338.460571,yMin=-5083.205566,xMax=2273.877197,yMax=-3680.601074},
|
||||
[1638]={continent=1,name="ThunderBluff",xMin=-1545.833252,yMin=-527.083313,xMax=-849.999939,yMax=516.666626},
|
||||
[1657]={continent=1,name="Darnassis",xMin=9532.586914,yMin=1880.029541,xMax=10238.31641,yMax=2938.362793},
|
||||
|
||||
-- Turtle custom world zones reported missing from continent/world overview.
|
||||
-- Bounds come from the supplied pfQuest WorldMapArea_turtle extractor data,
|
||||
-- using the same coordinate convention as the Vanilla entries above.
|
||||
[408]={continent=0,name="Gillijim",xMin=-14575.719727,yMin=1344.119995,xMax=-12528.709961,yMax=4436.220215},
|
||||
[409]={continent=0,name="Lapidis",xMin=-12959.419922,yMin=2031.880005,xMax=-11043.480469,yMax=4933.330078},
|
||||
[616]={continent=1,name="Hyjal",xMin=3983,yMin=-4286,xMax=6125,yMax=-1080},
|
||||
[2040]={continent=0,name="AlahThalas",xMin=3931,yMin=-3637,xMax=4907,yMax=-2169},
|
||||
[4012]={continent=0,name="ScarletEnclave",xMin=979,yMin=-7209,xMax=3087,yMax=-4050},
|
||||
[5121]={continent=1,name="TelAbim",xMin=-9371,yMin=-8406,xMax=-7184,yMax=-5179},
|
||||
[5179]={continent=0,name="Gilneas",xMin=-2801,yMin=-413,xMax=-359,yMax=3253},
|
||||
[5225]={continent=0,name="ThalassianHighlands",xMin=2891,yMin=-4087,xMax=4952,yMax=-1005},
|
||||
[5536]={continent=1,name="BlackstoneIsland",xMin=-866,yMin=-8746,xMax=799,yMax=-6274},
|
||||
[5561]={continent=0,name="Balor",xMin=-9645,yMin=1630,xMax=-7577,yMax=4728},
|
||||
[5581]={continent=0,name="Northwind",xMin=-8484,yMin=-983,xMax=-6327,yMax=2258},
|
||||
[5602]={continent=0,name="GrimReaches",xMin=-6488,yMin=-7459,xMax=-2904,yMax=-2072},
|
||||
|
||||
-- Moonwhisper Coast post-dates the supplied WorldMapArea_turtle snapshot.
|
||||
-- These bounds are reconstructed from the current Turtle server creature
|
||||
-- world positions plus Questie-Octo's shipped 7856x5241 minimap world size.
|
||||
[5642]={continent=1,name="MoonwhisperCoast",xMin=4963,yMin=-9210,xMax=10204,yMax=-1354},
|
||||
}
|
||||
|
||||
function C:GetClientContinentMapID(clientContinentID)
|
||||
|
||||
@@ -8,7 +8,7 @@ QuestieOcto.ItemStartAreas = QuestieOcto.ItemStartAreas or {}
|
||||
local A = QuestieOcto.ItemStartAreas
|
||||
|
||||
A.radius=14.0
|
||||
A.zoneWideRareThreshold=0.1
|
||||
A.zoneWideRareThreshold=0.5
|
||||
|
||||
function A:IsZoneWideRareChance(chance)
|
||||
chance=tonumber(chance) or 0
|
||||
@@ -184,10 +184,10 @@ function A:BuildForMap(nodes,mapID,includeNode)
|
||||
end
|
||||
|
||||
|
||||
-- World-map-only presentation helper for extremely low-rate starter items.
|
||||
-- Shared zone-wide presentation helper for extremely low-rate starter items.
|
||||
-- These drops are often spread across many unrelated creatures throughout a
|
||||
-- zone. Keep every source in the data/minimap, but let the World Map represent
|
||||
-- all <0.10% sources for the same quest/item with one zone marker.
|
||||
-- zone. Keep every source in the underlying data, but represent all <0.50%
|
||||
-- sources for the same quest/item with one zone marker on Map and Minimap.
|
||||
function A:BuildZoneWideRareForMap(nodes,mapID)
|
||||
local groups={}
|
||||
|
||||
|
||||
+41
-4
@@ -79,6 +79,7 @@ M.stats={active=0,created=0,reused=0,hidden=0,exact=0,objectiveAreas=0,itemStart
|
||||
|
||||
local ICON_ROOT="Interface\\AddOns\\Questie-Octo\\UI\\Icons\\"
|
||||
local TEX_AVAILABLE=ICON_ROOT.."available"
|
||||
local TEX_AVAILABLE_GRAY=ICON_ROOT.."available_gray"
|
||||
local TEX_MOBDROP=ICON_ROOT.."available_mobdrop"
|
||||
local TEX_OBJECTSTART=ICON_ROOT.."available_object"
|
||||
local TEX_COMPLETE=ICON_ROOT.."complete"
|
||||
@@ -106,6 +107,28 @@ local TEX_STABLEMASTER=ICON_ROOT.."stablemaster"
|
||||
local TEX_VENDOR=ICON_ROOT.."vendor"
|
||||
local TEX_RARE=ICON_ROOT.."rares"
|
||||
|
||||
-- Turtle deliberately keeps full quest XP through 25 levels above the quest
|
||||
-- (Tortoise src/game/QuestDef.cpp). The server file is XP logic rather than a
|
||||
-- client icon-color function, but it matches the observed Turtle native quest
|
||||
-- presentation and is the project's accepted low-level gray-marker boundary.
|
||||
-- Exactly +25 stays normal; +26 and beyond becomes gray.
|
||||
local TURTLE_GRAY_QUEST_DELTA=25
|
||||
|
||||
local function IsGrayAvailableQuest(node)
|
||||
if not node or (node.role~="available" and node.role~="itemStart") then return false end
|
||||
local questID=tonumber(node.questID)
|
||||
if not questID then return false end
|
||||
|
||||
local q=QuestieOcto.QuestModel and QuestieOcto.QuestModel:Get(questID) or nil
|
||||
if not q or q.presentationAlwaysNormal then return false end
|
||||
|
||||
local questLevel=tonumber(q.level)
|
||||
local playerLevel=UnitLevel and tonumber(UnitLevel("player")) or nil
|
||||
if not questLevel or questLevel<=0 or not playerLevel or playerLevel<=0 then return false end
|
||||
|
||||
return playerLevel>questLevel+TURTLE_GRAY_QUEST_DELTA
|
||||
end
|
||||
|
||||
local function TextureForNode(node)
|
||||
if node.role=="flightMaster" then return TEX_FLIGHT end
|
||||
if node.role=="auctioneer" then return TEX_AUCTIONEER end
|
||||
@@ -120,16 +143,18 @@ local function TextureForNode(node)
|
||||
if node.role=="vendor" then return TEX_VENDOR end
|
||||
if node.role=="rareMob" then return TEX_RARE end
|
||||
if node.role=="itemStart" then
|
||||
-- Presentation priority: PvP > Repeatable > Event > Normal.
|
||||
-- Presentation priority: PvP > Repeatable > Event > Turtle low-level gray > Normal.
|
||||
if node.pvp then return TEX_PVP_AVAILABLE end
|
||||
if node.repeatable then return TEX_REPEATABLE_AVAILABLE end
|
||||
if node.event then return TEX_EVENT_AVAILABLE end
|
||||
if IsGrayAvailableQuest(node) then return TEX_AVAILABLE_GRAY end
|
||||
return TEX_AVAILABLE
|
||||
end
|
||||
if node.role=="available" then
|
||||
if node.pvp then return TEX_PVP_AVAILABLE end
|
||||
if node.repeatable then return TEX_REPEATABLE_AVAILABLE end
|
||||
if node.event then return TEX_EVENT_AVAILABLE end
|
||||
if IsGrayAvailableQuest(node) then return TEX_AVAILABLE_GRAY end
|
||||
return TEX_AVAILABLE
|
||||
end
|
||||
if node.role=="turnin" then
|
||||
@@ -146,6 +171,7 @@ local function TextureForNode(node)
|
||||
end
|
||||
if node.role=="objectiveObject" then return TEX_OBJECT end
|
||||
if node.role=="objectiveCreature" then return TEX_SLAY end
|
||||
if node.role=="objectiveArea" then return TEX_EVENT end
|
||||
return TEX_INCOMPLETE
|
||||
end
|
||||
local function RolePriority(role)
|
||||
@@ -157,7 +183,7 @@ local function RolePriority(role)
|
||||
-- At a shared Full Nodes coordinate, a direct objective should own the
|
||||
-- displayed quest color over an indirect item-drop source. The merged pin
|
||||
-- still retains every quest/objective entry for its tooltip.
|
||||
if role=="objectiveObject" or role=="objectiveCreature" then return 20 end
|
||||
if role=="objectiveObject" or role=="objectiveCreature" or role=="objectiveArea" then return 20 end
|
||||
if role=="objectiveItemSource" then return 15 end
|
||||
return 10
|
||||
end
|
||||
@@ -219,6 +245,10 @@ function M:GetTextureForNode(node)
|
||||
return TextureForNode(node)
|
||||
end
|
||||
|
||||
function M:IsGrayAvailableQuest(node)
|
||||
return IsGrayAvailableQuest(node)
|
||||
end
|
||||
|
||||
function M:GetRolePriority(role)
|
||||
return RolePriority(role)
|
||||
end
|
||||
@@ -596,7 +626,7 @@ function M:RenderItemStartArea(area,generation,continentZoneMapID)
|
||||
if itemQuest and itemQuest.pvp and not DisplaySettings():Get("showPvPRelatedQuests") then return end
|
||||
local itemEvent=itemQuest and itemQuest.eventID and QuestieOcto.EventAvailability and QuestieOcto.EventAvailability:IsPresentationEvent(itemQuest.eventID) or false
|
||||
local itemPvP=itemQuest and itemQuest.pvp or false
|
||||
local itemRepeatable=itemQuest and itemQuest.repeatable or false
|
||||
local itemRepeatable=itemQuest and itemQuest.presentationRepeatable or false
|
||||
|
||||
local key="itemarea:"..tostring(area.key)
|
||||
local pin=self.frames[key]
|
||||
@@ -636,7 +666,7 @@ function M:RenderItemStartArea(area,generation,continentZoneMapID)
|
||||
pin.sourceKind="area"
|
||||
pin.displayName=area.displayName
|
||||
pin.clusterCount=area.n
|
||||
pin.texture:SetTexture(TextureForNode({role="itemStart",event=pin.event,pvp=pin.pvp,repeatable=pin.repeatable}))
|
||||
pin.texture:SetTexture(TextureForNode({role="itemStart",questID=area.questID,event=pin.event,pvp=pin.pvp,repeatable=pin.repeatable}))
|
||||
pin.texture:SetDrawLayer("OVERLAY",5)
|
||||
if QuestieOcto.Visuals then
|
||||
QuestieOcto.Visuals:ApplyPin(pin,{role="itemStart",questID=area.questID,pvp=pin.pvp,repeatable=pin.repeatable},false,1)
|
||||
@@ -1374,11 +1404,18 @@ QuestieOcto:RegisterMessage("PREPARED_MAP_READY",M,"OnPreparedMapReady")
|
||||
|
||||
local f=CreateFrame("Frame","QuestieOctoWorldMapEvents",UIParent)
|
||||
f:RegisterEvent("WORLD_MAP_UPDATE")
|
||||
f:RegisterEvent("PLAYER_LEVEL_UP")
|
||||
f:SetScript("OnEvent",function()
|
||||
if event=="WORLD_MAP_UPDATE" then
|
||||
if WorldMapFrame and WorldMapFrame:IsVisible() then
|
||||
M:EnsureDisplayedContextCurrent()
|
||||
end
|
||||
elseif event=="PLAYER_LEVEL_UP" then
|
||||
-- Gray classification depends only on the current player/quest levels.
|
||||
-- Rebind the visible map presentation; do not rebuild geometry or quest truth.
|
||||
QuestieOcto.Scheduler:After(0.01,function()
|
||||
if WorldMapFrame and WorldMapFrame:IsVisible() then M:RequestSync(true) end
|
||||
end,"map-gray-level-refresh")
|
||||
end
|
||||
end)
|
||||
|
||||
|
||||
+107
-43
@@ -462,8 +462,32 @@ local function PvPNodeVisible(node)
|
||||
return Settings():Get("showPvPRelatedQuests") and true or false
|
||||
end
|
||||
|
||||
local function ItemAreaVisible(area)
|
||||
if not area or not IsRoleEnabled("itemStart") then return false end
|
||||
-- Map and Minimap share the same item-start presentation plan. The regular
|
||||
-- prepared plan still contains raw item-start descriptors because Full Nodes
|
||||
-- can share physical slots with active objectives, so the minimap explicitly
|
||||
-- ignores item-start entries from that base plan and consumes the dedicated
|
||||
-- World Map item-start plan instead. This gives both displays the same <0.50%
|
||||
-- zone-wide representative marker without changing the underlying source data.
|
||||
-- Inside dungeons/raids, only those ultra-rare representative markers are
|
||||
-- hidden; meaningful >=0.50% item starters remain visible.
|
||||
local function MinimapNodeVisible(node,allowItemStart)
|
||||
if not node or not IsRoleEnabled(node.role) or not PvPNodeVisible(node) then return false end
|
||||
|
||||
if node.role=="itemStart" then
|
||||
if not allowItemStart then return false end
|
||||
if MM.inDungeonOrRaid and QuestieOcto.ItemStartAreas
|
||||
and QuestieOcto.ItemStartAreas:IsZoneWideRareChance(node.chance) then
|
||||
return false
|
||||
end
|
||||
end
|
||||
|
||||
return true
|
||||
end
|
||||
|
||||
local function ItemAreaVisible(area,allowItemStart)
|
||||
if not allowItemStart or not area or not IsRoleEnabled("itemStart") then return false end
|
||||
if MM.inDungeonOrRaid and area.zoneWideRare then return false end
|
||||
|
||||
local q=QuestieOcto.QuestModel:Get(area.questID)
|
||||
if q and q.pvp and not Settings():Get("showPvPRelatedQuests") then return false end
|
||||
return true
|
||||
@@ -479,34 +503,35 @@ local function DescriptorCoordinates(desc)
|
||||
return nil,nil
|
||||
end
|
||||
|
||||
local function DescriptorHasVisibleEntry(desc,revision)
|
||||
local function DescriptorHasVisibleEntry(desc,revision,allowItemStart)
|
||||
if not desc then return false end
|
||||
if desc.minimapVisibilityRevision==revision then return desc.minimapVisible and true or false end
|
||||
local visibilityRevision=(tonumber(revision) or 0)*2+(allowItemStart and 1 or 0)
|
||||
if desc.minimapVisibilityRevision==visibilityRevision then return desc.minimapVisible and true or false end
|
||||
|
||||
local visible=false
|
||||
if desc.type=="itemStartArea" then
|
||||
visible=ItemAreaVisible(desc.area)
|
||||
visible=ItemAreaVisible(desc.area,allowItemStart)
|
||||
elseif desc.type=="nodeSlot" then
|
||||
for _,entry in pairs(desc.entries or {}) do
|
||||
local node=entry.node
|
||||
if node and IsRoleEnabled(node.role) and PvPNodeVisible(node) then visible=true; break end
|
||||
if MinimapNodeVisible(node,allowItemStart) then visible=true; break end
|
||||
end
|
||||
elseif desc.type=="node" and desc.node then
|
||||
visible=IsRoleEnabled(desc.node.role) and PvPNodeVisible(desc.node)
|
||||
visible=MinimapNodeVisible(desc.node,allowItemStart)
|
||||
end
|
||||
|
||||
desc.minimapVisibilityRevision=revision
|
||||
desc.minimapVisibilityRevision=visibilityRevision
|
||||
desc.minimapVisible=visible and true or false
|
||||
return visible
|
||||
end
|
||||
|
||||
local function BindDescriptor(pin,desc,revision)
|
||||
local function BindDescriptor(pin,desc,revision,allowItemStart)
|
||||
ResetPin(pin)
|
||||
pin.boundDescriptor=desc
|
||||
pin.boundRevision=revision
|
||||
|
||||
if desc.type=="itemStartArea" and desc.area then
|
||||
if not ItemAreaVisible(desc.area) then return false end
|
||||
if not ItemAreaVisible(desc.area,allowItemStart) then return false end
|
||||
local area=desc.area
|
||||
pin.itemStartArea=area
|
||||
pin.displayName=area.displayName
|
||||
@@ -519,9 +544,9 @@ local function BindDescriptor(pin,desc,revision)
|
||||
local q=QuestieOcto.QuestModel:Get(area.questID)
|
||||
pin.event=q and q.eventID and QuestieOcto.EventAvailability and QuestieOcto.EventAvailability:IsPresentationEvent(q.eventID) or false
|
||||
pin.pvp=q and q.pvp or false
|
||||
pin.repeatable=q and q.repeatable or false
|
||||
pin.repeatable=q and q.presentationRepeatable or false
|
||||
pin.visualPriority=40
|
||||
pin.texture:SetTexture(QuestieOcto.Map:GetTextureForNode({role="itemStart",event=pin.event,pvp=pin.pvp,repeatable=pin.repeatable}))
|
||||
pin.texture:SetTexture(QuestieOcto.Map:GetTextureForNode({role="itemStart",questID=area.questID,event=pin.event,pvp=pin.pvp,repeatable=pin.repeatable}))
|
||||
pin.texture:SetDrawLayer("OVERLAY",5)
|
||||
if QuestieOcto.Visuals then
|
||||
QuestieOcto.Visuals:ApplyPin(pin,{role="itemStart",questID=area.questID,pvp=pin.pvp,repeatable=pin.repeatable},true,pin.lastAlpha or 1)
|
||||
@@ -544,7 +569,7 @@ local function BindDescriptor(pin,desc,revision)
|
||||
local fullNode=nil
|
||||
for _,entry in pairs(entries or {}) do
|
||||
local node=entry.node
|
||||
if node and IsRoleEnabled(node.role) and PvPNodeVisible(node) then
|
||||
if MinimapNodeVisible(node,allowItemStart) then
|
||||
visible=true
|
||||
pin.clusterCount=math.max(pin.clusterCount or 1,entry.clusterCount or 1)
|
||||
AddEntry(pin,node)
|
||||
@@ -615,6 +640,7 @@ function MM:RefreshPlan()
|
||||
if not mapID then
|
||||
self.mapID=nil
|
||||
self.plan=nil
|
||||
self.itemStartPlan=nil
|
||||
self.planRevision=nil
|
||||
self:HideAll()
|
||||
return
|
||||
@@ -628,9 +654,20 @@ function MM:RefreshPlan()
|
||||
self.lastZoom=nil
|
||||
end
|
||||
|
||||
-- Instance type changes at the same lifecycle boundaries that refresh the
|
||||
-- minimap plan (entering world / zone changes). Evaluate it here rather
|
||||
-- than polling GetInstanceInfo every 0.05 seconds.
|
||||
local inDungeonOrRaid=QuestieOcto.API and QuestieOcto.API:IsInDungeonOrRaid() or false
|
||||
if self.inDungeonOrRaid~=inDungeonOrRaid then
|
||||
self.inDungeonOrRaid=inDungeonOrRaid
|
||||
self.bindRevision=(self.bindRevision or 0)+1
|
||||
end
|
||||
|
||||
local plan=QuestieOcto.PreparedMap:Get(mapID)
|
||||
if not plan then
|
||||
local itemStartPlan=QuestieOcto.PreparedMap:GetWorldItemStarts(mapID)
|
||||
if not plan or not itemStartPlan then
|
||||
self.plan=nil
|
||||
self.itemStartPlan=nil
|
||||
self.planRevision=nil
|
||||
self:HideAll()
|
||||
if QuestieOcto.ZoneBootstrap then QuestieOcto.ZoneBootstrap:Request(mapID,0.01) end
|
||||
@@ -638,6 +675,7 @@ function MM:RefreshPlan()
|
||||
end
|
||||
|
||||
self.plan=plan
|
||||
self.itemStartPlan=itemStartPlan
|
||||
self.planRevision=QuestieOcto.PreparedMap.stateRevision
|
||||
self.stats.refreshes=self.stats.refreshes+1
|
||||
self:UpdatePositions(true)
|
||||
@@ -673,6 +711,8 @@ function MM:UpdatePositions(force)
|
||||
if self.planRevision~=QuestieOcto.PreparedMap.stateRevision then self:RefreshPlan(); return end
|
||||
local published=QuestieOcto.PreparedMap:Get(self.mapID)
|
||||
if published and published~=self.plan then self:RefreshPlan(); return end
|
||||
local publishedItemStarts=QuestieOcto.PreparedMap:GetWorldItemStarts(self.mapID)
|
||||
if publishedItemStarts and publishedItemStarts~=self.itemStartPlan then self:RefreshPlan(); return end
|
||||
|
||||
local px,py=PlayerPosition()
|
||||
if not px or not py then self:HideAll(); return end
|
||||
@@ -717,40 +757,53 @@ function MM:UpdatePositions(force)
|
||||
|
||||
-- pfQuest architecture: scan coordinate data, but allocate/reuse UI Buttons
|
||||
-- only for coordinates that are actually inside the current minimap circle.
|
||||
-- There is intentionally no hard node cap here.
|
||||
for _,desc in ipairs(self.plan or {}) do
|
||||
if DescriptorHasVisibleEntry(desc,revision) then
|
||||
local x,y=DescriptorCoordinates(desc)
|
||||
if x and y then
|
||||
local xPos=(x-px)*xDraw
|
||||
local yPos=(y-py)*yDraw
|
||||
local inside=false
|
||||
if squareMinimap then
|
||||
inside=math.abs(xPos)<(width/2) and math.abs(yPos)<(height/2)
|
||||
else
|
||||
inside=xPos*xPos+yPos*yPos<radiusSquared
|
||||
end
|
||||
if inside then
|
||||
frameIndex=frameIndex+1
|
||||
local pin=self:GetOrCreate(frameIndex)
|
||||
local bound=(pin.boundDescriptor==desc and pin.boundRevision==revision)
|
||||
if not bound then bound=BindDescriptor(pin,desc,revision) end
|
||||
-- There is intentionally no hard node cap here. Item-start entries from the
|
||||
-- base plan are skipped, then the same dedicated item-start plan used by the
|
||||
-- World Map is scanned so ultra-rare sources have identical representation.
|
||||
-- Keep this as a two-pass loop instead of allocating a closure/table in the
|
||||
-- moving-player hot path.
|
||||
local planPass=1
|
||||
while planPass<=2 do
|
||||
local scanPlan=planPass==1 and self.plan or self.itemStartPlan
|
||||
local allowItemStart=planPass==2
|
||||
local bindRevision=(tonumber(revision) or 0)*2+(allowItemStart and 1 or 0)
|
||||
|
||||
if bound then
|
||||
pin.questieOctoBaseX=xPos
|
||||
pin.questieOctoBaseY=-yPos
|
||||
local groupKey=pin.coordKey or tostring(x)..":"..tostring(y)
|
||||
visibleGroups[groupKey]=visibleGroups[groupKey] or {}
|
||||
table.insert(visibleGroups[groupKey],pin)
|
||||
table.insert(activeFrames,pin)
|
||||
if not pin:IsShown() then pin:Show() end
|
||||
CountVisible(pin)
|
||||
for _,desc in ipairs(scanPlan or {}) do
|
||||
if DescriptorHasVisibleEntry(desc,revision,allowItemStart) then
|
||||
local x,y=DescriptorCoordinates(desc)
|
||||
if x and y then
|
||||
local xPos=(x-px)*xDraw
|
||||
local yPos=(y-py)*yDraw
|
||||
local inside=false
|
||||
if squareMinimap then
|
||||
inside=math.abs(xPos)<(width/2) and math.abs(yPos)<(height/2)
|
||||
else
|
||||
frameIndex=frameIndex-1
|
||||
inside=xPos*xPos+yPos*yPos<radiusSquared
|
||||
end
|
||||
if inside then
|
||||
frameIndex=frameIndex+1
|
||||
local pin=self:GetOrCreate(frameIndex)
|
||||
local bound=(pin.boundDescriptor==desc and pin.boundRevision==bindRevision)
|
||||
if not bound then bound=BindDescriptor(pin,desc,bindRevision,allowItemStart) end
|
||||
|
||||
if bound then
|
||||
pin.questieOctoBaseX=xPos
|
||||
pin.questieOctoBaseY=-yPos
|
||||
local groupKey=pin.coordKey or tostring(x)..":"..tostring(y)
|
||||
visibleGroups[groupKey]=visibleGroups[groupKey] or {}
|
||||
table.insert(visibleGroups[groupKey],pin)
|
||||
table.insert(activeFrames,pin)
|
||||
if not pin:IsShown() then pin:Show() end
|
||||
CountVisible(pin)
|
||||
else
|
||||
frameIndex=frameIndex-1
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
planPass=planPass+1
|
||||
end
|
||||
|
||||
local offsets={{0,0},{10,0},{-10,0},{0,10},{0,-10},{8,8},{-8,8},{8,-8},{-8,-8}}
|
||||
@@ -785,7 +838,7 @@ function MM:UpdatePositions(force)
|
||||
self.activeFrames=activeFrames
|
||||
self.stats.active=frameIndex
|
||||
self.stats.positionUpdates=self.stats.positionUpdates+1
|
||||
self.stats.scannedDescriptors=table.getn(self.plan or {})
|
||||
self.stats.scannedDescriptors=table.getn(self.plan or {})+table.getn(self.itemStartPlan or {})
|
||||
self.stats.poolSize=table.getn(self.frames or {})
|
||||
end
|
||||
|
||||
@@ -829,9 +882,20 @@ function MM:Start()
|
||||
f:RegisterEvent("ZONE_CHANGED")
|
||||
f:RegisterEvent("ZONE_CHANGED_NEW_AREA")
|
||||
f:RegisterEvent("MINIMAP_ZONE_CHANGED")
|
||||
f:RegisterEvent("PLAYER_LEVEL_UP")
|
||||
|
||||
f:SetScript("OnEvent",function()
|
||||
local eventName=event
|
||||
if eventName=="PLAYER_LEVEL_UP" then
|
||||
-- Force descriptor rebinding so +25/+26 gray presentation changes as
|
||||
-- soon as the player levels, without touching the prepared map geometry.
|
||||
QuestieOcto.Scheduler:After(0.01,function()
|
||||
MM.bindRevision=(MM.bindRevision or 0)+1
|
||||
MM:RefreshPlan()
|
||||
end,"minimap-gray-level-refresh")
|
||||
return
|
||||
end
|
||||
|
||||
QuestieOcto.Scheduler:After(0.01,function()
|
||||
RestoreCurrentZoneMapContext(eventName)
|
||||
MM:RefreshPlan()
|
||||
|
||||
@@ -90,6 +90,9 @@ S.defaults={
|
||||
questLogShowLevels=true,
|
||||
questLogDifficultyColors=true,
|
||||
|
||||
-- Quest objective arrow is opt-in.
|
||||
showQuestArrow=false,
|
||||
|
||||
-- Options-frame dark theme is off by default so the config window opens with
|
||||
-- native Blizzard colors until the player opts in.
|
||||
useDarkTheme=false,
|
||||
@@ -237,6 +240,7 @@ function S:Set(key,value)
|
||||
key=="enableTooltipsNPCID" or key=="enableTooltipsItemID" or
|
||||
key=="enableTooltipDroprates" or
|
||||
key=="questLogShowLevels" or key=="questLogDifficultyColors" or
|
||||
key=="showQuestArrow" or
|
||||
key=="useDarkTheme" or
|
||||
key=="trackerEnabled" or key=="trackerLocked" or key=="trackerAutoTrack" or
|
||||
key=="trackerShowCompleted" or key=="trackerHideCompletedObjectives" or key=="trackerHideInCombat" or
|
||||
@@ -310,6 +314,11 @@ function S:Set(key,value)
|
||||
return true
|
||||
end
|
||||
|
||||
-- The quest arrow reads its setting live each frame; just persist it.
|
||||
if key=="showQuestArrow" then
|
||||
return true
|
||||
end
|
||||
|
||||
if key=="showLowLevelQuests" or key=="lowLevelQuestRange" or key=="showEventQuests" or key=="showRepeatableQuests" or key=="showPvPRelatedQuests" then
|
||||
if QuestieOcto.AvailableQuests and QuestieOcto.AvailableQuests.FastRefresh then
|
||||
QuestieOcto.AvailableQuests:FastRefresh()
|
||||
|
||||
+32
-3
@@ -17,6 +17,7 @@ N.stats={
|
||||
objectiveCreature=0,
|
||||
objectiveObject=0,
|
||||
objectiveItemSource=0,
|
||||
objectiveArea=0,
|
||||
turnin=0,
|
||||
flightMaster=0,
|
||||
auctioneer=0,
|
||||
@@ -41,6 +42,7 @@ local function NewStats()
|
||||
objectiveCreature=0,
|
||||
objectiveObject=0,
|
||||
objectiveItemSource=0,
|
||||
objectiveArea=0,
|
||||
turnin=0,
|
||||
flightMaster=0,
|
||||
auctioneer=0,
|
||||
@@ -134,15 +136,25 @@ local function ApplyObjectiveState(node,state)
|
||||
return node
|
||||
end
|
||||
|
||||
local function ConditionalCreatureCoords(q,creatureID,role)
|
||||
local marker=q and q.conditionalMapMarker or nil
|
||||
if marker and tonumber(marker.creatureID)==tonumber(creatureID)
|
||||
and (role=="available" or role=="turnin") then
|
||||
return marker.coords
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function AddCreatureNode(questID,role,creatureID,itemID,chance,objectiveState,vendor)
|
||||
local q=QuestieOcto.QuestModel:Get(questID)
|
||||
local node={
|
||||
questID=questID,role=role,event=IsPresentationEvent(q),eventID=q and q.eventID or nil,pvp=q and q.pvp or false,repeatable=q and q.repeatable or false,sourceKind="creature",sourceID=creatureID,
|
||||
questID=questID,role=role,event=IsPresentationEvent(q),eventID=q and q.eventID or nil,pvp=q and q.pvp or false,repeatable=q and q.presentationRepeatable or false,sourceKind="creature",sourceID=creatureID,
|
||||
sourceName=QuestieOcto.DatabaseAPI:GetCreatureName(creatureID),
|
||||
sourceRank=QuestieOcto.DatabaseAPI:GetCreatureRank(creatureID),
|
||||
respawnSeconds=QuestieOcto.DatabaseAPI:GetCreatureRespawnSeconds(creatureID),
|
||||
itemID=itemID,itemName=itemID and QuestieOcto.DatabaseAPI:GetItemName(itemID) or nil,
|
||||
chance=chance,vendor=vendor and true or false,coords=QuestieOcto.DatabaseAPI:GetCreatureCoords(creatureID)
|
||||
chance=chance,vendor=vendor and true or false,coords=ConditionalCreatureCoords(q,creatureID,role) or QuestieOcto.DatabaseAPI:GetCreatureCoords(creatureID),
|
||||
conditionalOffer=q and q.conditionalOffer or nil
|
||||
}
|
||||
AddNode(ApplyObjectiveState(node,objectiveState))
|
||||
end
|
||||
@@ -150,7 +162,7 @@ end
|
||||
local function AddObjectNode(questID,role,objectID,itemID,chance,objectiveState)
|
||||
local q=QuestieOcto.QuestModel:Get(questID)
|
||||
local node={
|
||||
questID=questID,role=role,event=IsPresentationEvent(q),eventID=q and q.eventID or nil,pvp=q and q.pvp or false,repeatable=q and q.repeatable or false,sourceKind="gameObject",sourceID=objectID,
|
||||
questID=questID,role=role,event=IsPresentationEvent(q),eventID=q and q.eventID or nil,pvp=q and q.pvp or false,repeatable=q and q.presentationRepeatable or false,sourceKind="gameObject",sourceID=objectID,
|
||||
sourceName=QuestieOcto.DatabaseAPI:GetObjectName(objectID),
|
||||
itemID=itemID,itemName=itemID and QuestieOcto.DatabaseAPI:GetItemName(itemID) or nil,
|
||||
chance=chance,coords=QuestieOcto.DatabaseAPI:GetObjectCoords(objectID)
|
||||
@@ -158,6 +170,16 @@ local function AddObjectNode(questID,role,objectID,itemID,chance,objectiveState)
|
||||
AddNode(ApplyObjectiveState(node,objectiveState))
|
||||
end
|
||||
|
||||
local function AddAreaTriggerNode(questID,source)
|
||||
if not source or not source.mapID or not source.x or not source.y then return end
|
||||
local q=QuestieOcto.QuestModel:Get(questID)
|
||||
AddNode(ApplyObjectiveState({
|
||||
questID=questID,role="objectiveArea",event=IsPresentationEvent(q),eventID=q and q.eventID or nil,pvp=q and q.pvp or false,repeatable=q and q.presentationRepeatable or false,
|
||||
sourceKind="areaTrigger",sourceID=source.id,sourceName="Exploration Mark",
|
||||
coords={{source.x,source.y,source.mapID}}
|
||||
},source))
|
||||
end
|
||||
|
||||
local function BuildAvailableQuestNodes(questID)
|
||||
local q=QuestieOcto.QuestModel:Get(questID)
|
||||
if not q then return end
|
||||
@@ -479,6 +501,12 @@ local function BuildActiveQuestNodes(questID)
|
||||
end
|
||||
end
|
||||
end
|
||||
for _,src in pairs(resolved.areaTrigger or {}) do
|
||||
if not src.complete then
|
||||
AddAreaTriggerNode(questID,src)
|
||||
CurrentStats().objectiveArea=(CurrentStats().objectiveArea or 0)+1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function BuildActiveNodes()
|
||||
@@ -496,6 +524,7 @@ local function StatKeyForNode(node)
|
||||
if node.role=="objectiveCreature" then return "objectiveCreature" end
|
||||
if node.role=="objectiveObject" then return "objectiveObject" end
|
||||
if node.role=="objectiveItemSource" then return "objectiveItemSource" end
|
||||
if node.role=="objectiveArea" then return "objectiveArea" end
|
||||
if node.role=="turnin" then return "turnin" end
|
||||
if node.role=="flightMaster" then return "flightMaster" end
|
||||
if node.role=="auctioneer" then return "auctioneer" end
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ function P:GetCurrentDensitySignature()
|
||||
end
|
||||
|
||||
local function ExactRole(role)
|
||||
return role=="available" or role=="turnin" or role=="flightMaster"
|
||||
return role=="available" or role=="turnin" or role=="objectiveArea" or role=="flightMaster"
|
||||
or role=="auctioneer" or role=="banker" or role=="mailbox"
|
||||
or role=="battlemaster" or role=="innkeeper" or role=="meetingStone"
|
||||
or role=="repair" or role=="spiritHealer" or role=="stableMaster" or role=="vendor"
|
||||
|
||||
+74
-41
@@ -124,6 +124,28 @@ local function FormatDropRate(rate)
|
||||
return string.format("%.3f",rate)
|
||||
end
|
||||
|
||||
local function ItemStartAreaDropRateText(area)
|
||||
local minimum=nil
|
||||
local maximum=nil
|
||||
|
||||
for _,source in pairs((area and area.sourceList) or {}) do
|
||||
local chance=tonumber(source.chance)
|
||||
if chance and chance>0 then
|
||||
if not minimum or chance<minimum then minimum=chance end
|
||||
if not maximum or chance>maximum then maximum=chance end
|
||||
end
|
||||
end
|
||||
|
||||
if not minimum or not maximum then return nil end
|
||||
|
||||
local minText=FormatDropRate(minimum)
|
||||
local maxText=FormatDropRate(maximum)
|
||||
if not minText or not maxText then return nil end
|
||||
|
||||
if minText==maxText then return minText.."%" end
|
||||
return minText.."%~"..maxText.."%"
|
||||
end
|
||||
|
||||
local function DifficultyColor(level,questID)
|
||||
-- Use the same authority as the native Quest Log. Octo/Turtle modifies the
|
||||
-- low-level/easy band, so reproducing stock Classic thresholds makes map
|
||||
@@ -150,12 +172,22 @@ end
|
||||
local function RespawnText(seconds)
|
||||
seconds=tonumber(seconds)
|
||||
if not seconds or seconds<=0 then return nil end
|
||||
seconds=math.floor(seconds)
|
||||
|
||||
if math.mod(seconds,60)==0 then
|
||||
return "~"..tostring(math.floor(seconds/60)).." min"
|
||||
if seconds<60 then return "~"..tostring(seconds).."s" end
|
||||
|
||||
local minutes=math.floor(seconds/60)
|
||||
-- Roll long respawns up into hours so 18000s reads "~5h", not "~300 min".
|
||||
if minutes>=60 then
|
||||
local hours=math.floor(minutes/60)
|
||||
local mins=math.mod(minutes,60)
|
||||
if mins==0 then return "~"..tostring(hours).."h" end
|
||||
return "~"..tostring(hours).."h "..tostring(mins).."m"
|
||||
end
|
||||
|
||||
return "~"..tostring(math.floor(seconds/60)).."m "..tostring(math.mod(seconds,60)).."s"
|
||||
local secs=math.mod(seconds,60)
|
||||
if secs==0 then return "~"..tostring(minutes).." min" end
|
||||
return "~"..tostring(minutes).."m "..tostring(secs).."s"
|
||||
end
|
||||
|
||||
local function SourceDisplayName(source)
|
||||
@@ -207,6 +239,9 @@ local function LiveObjectiveText(node)
|
||||
end
|
||||
|
||||
local function Extra(node)
|
||||
if node.role=="available" and node.conditionalOffer then
|
||||
return tostring(node.conditionalOffer)
|
||||
end
|
||||
if node.role=="itemStart" and node.itemName then
|
||||
local text
|
||||
if node.vendor then text="Sells ["..tostring(node.itemName).."]"
|
||||
@@ -216,7 +251,8 @@ local function Extra(node)
|
||||
end
|
||||
if node.role=="objectiveCreature"
|
||||
or node.role=="objectiveObject"
|
||||
or node.role=="objectiveItemSource" then
|
||||
or node.role=="objectiveItemSource"
|
||||
or node.role=="objectiveArea" then
|
||||
local liveText=LiveObjectiveText(node)
|
||||
if liveText and liveText~="" then
|
||||
local text=liveText
|
||||
@@ -1101,27 +1137,38 @@ function T:Show(pin)
|
||||
if pin.itemStartArea then
|
||||
local area=pin.itemStartArea
|
||||
|
||||
-- Use AddDoubleLine with an empty right column for every source row.
|
||||
-- This avoids the first source row inheriting the tooltip's title-sized
|
||||
-- presentation while keeping every monster line visually identical.
|
||||
for _,source in pairs(area.sourceList or {}) do
|
||||
local sourceText=SourceDisplayName(source).." ("..tostring(source.count)..
|
||||
(area.zoneWideRare and " zone spawns)" or " nearby spawns)")
|
||||
if area.zoneWideRare and source.chance and Settings():Get("enableTooltipDroprates") then
|
||||
sourceText=sourceText.." - "..(FormatDropRate(source.chance) or tostring(source.chance)).."%"
|
||||
end
|
||||
tooltip:AddDoubleLine(
|
||||
sourceText,
|
||||
"",
|
||||
.2,1,.35,
|
||||
.2,1,.35
|
||||
if area.zoneWideRare then
|
||||
-- A zone-wide representative exists specifically to make enormous
|
||||
-- world-drop source sets readable. Do not undo that simplification by
|
||||
-- dumping every represented creature into the tooltip (Pendant of
|
||||
-- Myzrael can have dozens of source types in a single zone). Keep the
|
||||
-- full source list in the data and present only a compact aggregate.
|
||||
local sourceCount=table.getn(area.sourceList or {})
|
||||
local spawnCount=tonumber(area.n) or 0
|
||||
tooltip:SetText("Rare item-start sources",.2,1,.35)
|
||||
tooltip:AddLine(
|
||||
tostring(sourceCount).." creature types, "..tostring(spawnCount).." zone spawns",
|
||||
.65,.65,.65
|
||||
)
|
||||
else
|
||||
-- Ordinary clustered item-start areas still list their nearby sources.
|
||||
-- Use AddDoubleLine with an empty right column for every source row so
|
||||
-- the first source does not inherit the tooltip's title-sized font.
|
||||
for _,source in pairs(area.sourceList or {}) do
|
||||
local sourceText=SourceDisplayName(source).." ("..tostring(source.count).." nearby spawns)"
|
||||
tooltip:AddDoubleLine(
|
||||
sourceText,
|
||||
"",
|
||||
.2,1,.35,
|
||||
.2,1,.35
|
||||
)
|
||||
|
||||
local rank=RareRankText(source.rank)
|
||||
local respawn=rank and RespawnText(source.respawnSeconds) or nil
|
||||
if respawn then
|
||||
local rareTag=RareRankText(source.rank) or "Rare"
|
||||
tooltip:AddLine("["..rareTag.."] Respawn: "..respawn,.75,.75,.75)
|
||||
local rank=RareRankText(source.rank)
|
||||
local respawn=rank and RespawnText(source.respawnSeconds) or nil
|
||||
if respawn then
|
||||
local rareTag=RareRankText(source.rank) or "Rare"
|
||||
tooltip:AddLine("["..rareTag.."] Respawn: "..respawn,.75,.75,.75)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1136,33 +1183,19 @@ function T:Show(pin)
|
||||
)
|
||||
end
|
||||
|
||||
local chance=nil
|
||||
for _,source in pairs(area.sourceList or {}) do
|
||||
if source.chance then
|
||||
chance=source.chance
|
||||
break
|
||||
end
|
||||
end
|
||||
local dropRateText=ItemStartAreaDropRateText(area)
|
||||
|
||||
local itemLabel=tostring(area.itemName or ("Item "..tostring(area.itemID)))
|
||||
if Settings():Get("enableTooltipsItemID") and area.itemID then
|
||||
itemLabel=itemLabel.." (Item "..tostring(area.itemID)..")"
|
||||
end
|
||||
if area.zoneWideRare then
|
||||
local threshold=tonumber(area.rareThreshold) or 0.1
|
||||
tooltip:AddLine(
|
||||
"One zone marker represents item-start sources below "..string.format("%.2f",threshold).."%.",
|
||||
.65,.65,.65,true
|
||||
)
|
||||
end
|
||||
|
||||
local line="Drops ["..itemLabel.."]"
|
||||
if chance and not area.zoneWideRare and Settings():Get("enableTooltipDroprates") then
|
||||
line=line.." ("..(FormatDropRate(chance) or tostring(chance)).."%)"
|
||||
if dropRateText and Settings():Get("enableTooltipDroprates") then
|
||||
line=line.." ("..dropRateText..")"
|
||||
end
|
||||
tooltip:AddLine(line.." - item starts quest",.82,.82,.82,true)
|
||||
|
||||
NormalizeFirstRowFont(tooltip)
|
||||
if not area.zoneWideRare then NormalizeFirstRowFont(tooltip) end
|
||||
tooltip:Show()
|
||||
return
|
||||
end
|
||||
|
||||
+2
-1
@@ -90,7 +90,8 @@ function V:ApplyPin(pin,node,isMinimap,alpha)
|
||||
|
||||
local r,g,b=1,1,1
|
||||
-- Quest pickup/turn-in presentation priority is PvP > Repeatable > Event >
|
||||
-- Normal. PvP and repeatable use dedicated artwork and keep its native color.
|
||||
-- Turtle low-level gray > Normal. Gray uses dedicated artwork like the other
|
||||
-- quest-start variants; objective tinting never recolors pickup/turn-in icons.
|
||||
if (node.pvp or node.repeatable) and (node.role=="available" or node.role=="itemStart" or node.role=="turnin") then
|
||||
r,g,b=1,1,1
|
||||
elseif objective and colorEnabled then
|
||||
|
||||
+15
-4
@@ -42,23 +42,33 @@ local function ApplyObjectiveState(node,state)
|
||||
return node
|
||||
end
|
||||
|
||||
local function ConditionalCreatureCoords(q,creatureID,role)
|
||||
local marker=q and q.conditionalMapMarker or nil
|
||||
if marker and tonumber(marker.creatureID)==tonumber(creatureID)
|
||||
and (role=="available" or role=="turnin") then
|
||||
return marker.coords
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function CreatureNode(questID,role,id,itemID,chance,objectiveState)
|
||||
local q=QuestieOcto.QuestModel:Get(questID)
|
||||
return ApplyObjectiveState({
|
||||
questID=questID,role=role,event=IsPresentationEvent(q),eventID=q and q.eventID or nil,pvp=q and q.pvp or false,repeatable=q and q.repeatable or false,
|
||||
questID=questID,role=role,event=IsPresentationEvent(q),eventID=q and q.eventID or nil,pvp=q and q.pvp or false,repeatable=q and q.presentationRepeatable or false,
|
||||
sourceKind="creature",sourceID=id,
|
||||
sourceName=QuestieOcto.DatabaseAPI:GetCreatureName(id),
|
||||
sourceRank=QuestieOcto.DatabaseAPI:GetCreatureRank(id),
|
||||
respawnSeconds=QuestieOcto.DatabaseAPI:GetCreatureRespawnSeconds(id),
|
||||
itemID=itemID,itemName=itemID and QuestieOcto.DatabaseAPI:GetItemName(itemID) or nil,
|
||||
chance=chance,coords=QuestieOcto.DatabaseAPI:GetCreatureCoords(id)
|
||||
chance=chance,coords=ConditionalCreatureCoords(q,id,role) or QuestieOcto.DatabaseAPI:GetCreatureCoords(id),
|
||||
conditionalOffer=q and q.conditionalOffer or nil
|
||||
},objectiveState)
|
||||
end
|
||||
|
||||
local function ObjectNode(questID,role,id,itemID,chance,objectiveState)
|
||||
local q=QuestieOcto.QuestModel:Get(questID)
|
||||
return ApplyObjectiveState({
|
||||
questID=questID,role=role,event=IsPresentationEvent(q),eventID=q and q.eventID or nil,pvp=q and q.pvp or false,repeatable=q and q.repeatable or false,
|
||||
questID=questID,role=role,event=IsPresentationEvent(q),eventID=q and q.eventID or nil,pvp=q and q.pvp or false,repeatable=q and q.presentationRepeatable or false,
|
||||
sourceKind="gameObject",sourceID=id,
|
||||
sourceName=QuestieOcto.DatabaseAPI:GetObjectName(id),
|
||||
itemID=itemID,itemName=itemID and QuestieOcto.DatabaseAPI:GetItemName(itemID) or nil,
|
||||
@@ -116,7 +126,8 @@ end
|
||||
|
||||
local function StarterTouchesMap(q,mapID)
|
||||
for _,id in pairs(q.starts.creature or {}) do
|
||||
if CoordsContainMap(QuestieOcto.DatabaseAPI:GetCreatureCoords(id),mapID) then return true end
|
||||
local coords=ConditionalCreatureCoords(q,id,"available") or QuestieOcto.DatabaseAPI:GetCreatureCoords(id)
|
||||
if CoordsContainMap(coords,mapID) then return true end
|
||||
end
|
||||
|
||||
for _,id in pairs(q.starts.gameObject or {}) do
|
||||
|
||||
@@ -300,9 +300,11 @@ function A:EvaluateQuest(questID,trackStats)
|
||||
end
|
||||
|
||||
-- Some quests are conditionally exposed by scripted world interactions rather
|
||||
-- than normal NPC availability. Do not advertise them as static pickups. If
|
||||
-- the player actually accepts one, the Quest Log path still handles it.
|
||||
if q.conditionalOffer then
|
||||
-- than normal NPC availability. Most stay hidden as static pickups. A narrow
|
||||
-- presentation exception may provide one representative discovery marker; in
|
||||
-- that case continue through all ordinary level/race/filter gates and let the
|
||||
-- node builders use only that representative coordinate.
|
||||
if q.conditionalOffer and not q.conditionalMapMarker then
|
||||
return false,"conditional"
|
||||
end
|
||||
|
||||
@@ -389,6 +391,10 @@ function A:EvaluateQuest(questID,trackStats)
|
||||
return false,"noStarter"
|
||||
end
|
||||
|
||||
if q.conditionalOffer and q.conditionalMapMarker then
|
||||
return true,"conditional"
|
||||
end
|
||||
|
||||
return true,"available"
|
||||
end
|
||||
|
||||
|
||||
@@ -13,10 +13,22 @@ end
|
||||
local function RespawnText(seconds)
|
||||
seconds=tonumber(seconds)
|
||||
if not seconds or seconds<=0 then return "unknown" end
|
||||
if math.mod(seconds,60)==0 then
|
||||
return "~"..tostring(math.floor(seconds/60)).." min"
|
||||
seconds=math.floor(seconds)
|
||||
|
||||
if seconds<60 then return "~"..tostring(seconds).."s" end
|
||||
|
||||
local minutes=math.floor(seconds/60)
|
||||
-- Roll long respawns up into hours so 18000s reads "~5h", not "~300 min".
|
||||
if minutes>=60 then
|
||||
local hours=math.floor(minutes/60)
|
||||
local mins=math.mod(minutes,60)
|
||||
if mins==0 then return "~"..tostring(hours).."h" end
|
||||
return "~"..tostring(hours).."h "..tostring(mins).."m"
|
||||
end
|
||||
return "~"..tostring(math.floor(seconds/60)).."m "..tostring(math.mod(seconds,60)).."s"
|
||||
|
||||
local secs=math.mod(seconds,60)
|
||||
if secs==0 then return "~"..tostring(minutes).." min" end
|
||||
return "~"..tostring(minutes).."m "..tostring(secs).."s"
|
||||
end
|
||||
|
||||
local function ArrayContains(t,value)
|
||||
|
||||
+84
-22
@@ -7,7 +7,7 @@ O.running=false
|
||||
O.pending=false
|
||||
O.generation=0
|
||||
O.stats={
|
||||
quests=0, creature=0, object=0, item=0, itemSources=0, irTargets=0,
|
||||
quests=0, creature=0, object=0, item=0, areaTrigger=0, itemSources=0, irTargets=0,
|
||||
mapped=0, unmapped=0, direct=0, fallback=0, fuzzy=0, single=0, typeMismatch=0,
|
||||
rebuilds=0, dependencyWaits=0
|
||||
}
|
||||
@@ -18,7 +18,7 @@ local function ResetStats()
|
||||
local rebuilds=O.stats.rebuilds or 0
|
||||
local waits=O.stats.dependencyWaits or 0
|
||||
O.stats={
|
||||
quests=0, creature=0, object=0, item=0, itemSources=0, irTargets=0,
|
||||
quests=0, creature=0, object=0, item=0, areaTrigger=0, itemSources=0, irTargets=0,
|
||||
mapped=0, unmapped=0, direct=0, fallback=0, fuzzy=0, single=0, typeMismatch=0,
|
||||
rebuilds=rebuilds, dependencyWaits=waits
|
||||
}
|
||||
@@ -213,6 +213,25 @@ local function NormalizeLogType(typ)
|
||||
return typ
|
||||
end
|
||||
|
||||
-- Sparse presentation-only corrections for capture/transform objectives where
|
||||
-- the server awards quest credit against a temporary NPC entry that has no
|
||||
-- natural spawn points. Keep the authoritative live objective ID for progress
|
||||
-- and completion; only map/minimap guidance uses the actionable source NPC.
|
||||
--
|
||||
-- Plagued Lands (2118): Tortoise darkshore.cpp handles spell 9439 by changing
|
||||
-- Rabid Thistle Bear (2164) into Captured Rabid Thistle Bear (11836), then
|
||||
-- awards KilledMonsterCredit for 11836. The player must therefore find 2164,
|
||||
-- while 11836 remains the correct completion target in the quest log/database.
|
||||
local CREATURE_OBJECTIVE_SOURCE_OVERRIDES={
|
||||
[2118]={ [11836]=2164 },
|
||||
}
|
||||
|
||||
local function PresentationCreatureSourceID(questID,objectiveID)
|
||||
local byQuest=CREATURE_OBJECTIVE_SOURCE_OVERRIDES[tonumber(questID)]
|
||||
local sourceID=byQuest and byQuest[tonumber(objectiveID)]
|
||||
return tonumber(sourceID) or tonumber(objectiveID)
|
||||
end
|
||||
|
||||
local function Levenshtein(str1,str2)
|
||||
str1=string.lower(tostring(str1 or ""))
|
||||
str2=string.lower(tostring(str2 or ""))
|
||||
@@ -236,6 +255,7 @@ end
|
||||
|
||||
local function MergeState(entry,row)
|
||||
entry.objectiveIndex=row.index
|
||||
entry.objectiveID=row.objectiveID
|
||||
entry.objectiveText=row.text
|
||||
entry.rawObjectiveText=row.rawText or row.text
|
||||
entry.objectiveType=row.type
|
||||
@@ -253,20 +273,31 @@ local function ObjectiveTypesMatch(logType,dbType)
|
||||
return false
|
||||
end
|
||||
|
||||
local function EntryFromOrderedObjective(q,row)
|
||||
local ordered=q.objectiveData
|
||||
-- row.dataIndex is the objectiveData index bound by name in QuestLog's
|
||||
-- LocalizeObjectiveRows. row.index is only the server leaderboard position,
|
||||
-- which does not necessarily line up with pfQuest's objectiveData ordering,
|
||||
-- so prefer the name-resolved index and fall back to position only when the
|
||||
-- row was never localized (e.g. database not ready at snapshot time).
|
||||
local index=tonumber(row.dataIndex) or tonumber(row.index)
|
||||
if not ordered or not index then return nil end
|
||||
local entry=ordered[index]
|
||||
if not entry then return nil end
|
||||
if ObjectiveTypesMatch(row.type,entry.type) then return entry end
|
||||
O.stats.typeMismatch=O.stats.typeMismatch+1
|
||||
return nil
|
||||
local function EntryFromLiveObjectiveID(q,row)
|
||||
local id=tonumber(row and row.objectiveID)
|
||||
if not q or not id then return nil end
|
||||
local typ=string.lower(tostring(row.type or ""))
|
||||
local creatureMatch=nil
|
||||
local objectMatch=nil
|
||||
local itemMatch=nil
|
||||
|
||||
for i=1,table.getn(q.objectiveData or {}) do
|
||||
local entry=q.objectiveData[i]
|
||||
if entry and tonumber(entry.id)==id then
|
||||
if entry.kind=="creature" then creatureMatch=entry
|
||||
elseif entry.kind=="gameObject" then objectMatch=entry
|
||||
elseif entry.kind=="item" then itemMatch=entry end
|
||||
end
|
||||
end
|
||||
|
||||
if typ=="item" then return itemMatch end
|
||||
if typ=="object" or typ=="gameobject" then return objectMatch end
|
||||
if typ=="monster" or typ=="creature" or typ=="kill" then
|
||||
-- pfQuest notes that Vanilla can return "monster" for some object-style
|
||||
-- objectives; let the live ClassicAPI ID disambiguate the namespace.
|
||||
return creatureMatch or objectMatch
|
||||
end
|
||||
return creatureMatch or objectMatch or itemMatch
|
||||
end
|
||||
|
||||
local function CandidateName(kind,id)
|
||||
@@ -314,7 +345,7 @@ function O:ResolveQuest(questID)
|
||||
if not q then return nil end
|
||||
|
||||
local state=QuestieOcto.QuestLog.active[questID]
|
||||
local result={questID=questID,creature={},gameObject={},item={}}
|
||||
local result={questID=questID,creature={},gameObject={},item={},areaTrigger={}}
|
||||
|
||||
-- Failed quests retain their tracker state but have no active objective map
|
||||
-- guidance until the server permits a retry.
|
||||
@@ -324,15 +355,21 @@ function O:ResolveQuest(questID)
|
||||
local irTargets,skipCreature,skipObject=BuildIRTargets(q)
|
||||
|
||||
for _,row in pairs(state and state.objectives or {}) do
|
||||
local ordered=EntryFromOrderedObjective(q,row)
|
||||
local direct=EntryFromLiveObjectiveID(q,row)
|
||||
local kind=nil
|
||||
local id=nil
|
||||
|
||||
if ordered then
|
||||
kind=ordered.kind
|
||||
id=ordered.id
|
||||
if direct then
|
||||
-- pfQuest-style primary path: use ClassicAPI's live leaderboard entity
|
||||
-- ID so server objective ordering cannot attach one boss's progress to
|
||||
-- another boss's map node.
|
||||
kind=direct.kind
|
||||
id=direct.id
|
||||
O.stats.direct=O.stats.direct+1
|
||||
else
|
||||
-- Questie 5/6-style fallback: when no trustworthy live ID is available,
|
||||
-- match the native objective text against same-type DB candidates rather
|
||||
-- than assuming objective array positions are identical.
|
||||
kind=NormalizeLogType(row.type)
|
||||
id=BestSameTypeCandidate(q,kind,row,used)
|
||||
if id then O.stats.fallback=O.stats.fallback+1 end
|
||||
@@ -345,7 +382,11 @@ function O:ResolveQuest(questID)
|
||||
O.stats.mapped=O.stats.mapped+1
|
||||
|
||||
if kind=="creature" and not skipCreature[id] then
|
||||
table.insert(result.creature,MergeState({kind="creature",id=id},row))
|
||||
local sourceID=PresentationCreatureSourceID(questID,id)
|
||||
table.insert(result.creature,MergeState({
|
||||
kind="creature",id=sourceID,objectiveTargetID=id,
|
||||
sourceOverride=(sourceID~=id) and true or nil
|
||||
},row))
|
||||
O.stats.creature=O.stats.creature+1
|
||||
elseif kind=="gameObject" and not skipObject[id] then
|
||||
table.insert(result.gameObject,MergeState({kind="gameObject",id=id},row))
|
||||
@@ -384,6 +425,27 @@ function O:ResolveQuest(questID)
|
||||
end
|
||||
end
|
||||
|
||||
-- Quest-bound area triggers are not generic exploration/fog helpers. pfQuest
|
||||
-- resolves obj["A"] through ClassicAPI and shows those points while the quest
|
||||
-- is active. Keep them separate from ordinary leaderboard entity matching:
|
||||
-- Vanilla does not provide a reliable creature/object-style namespace for
|
||||
-- these event triggers, and pfQuest likewise keeps them until the quest as a
|
||||
-- whole is complete.
|
||||
for _,areaTriggerID in pairs(q.objectives.areaTrigger or {}) do
|
||||
local info=QuestieOcto.API and QuestieOcto.API.GetAreaTriggerInfo
|
||||
and QuestieOcto.API:GetAreaTriggerInfo(areaTriggerID) or nil
|
||||
if info then
|
||||
table.insert(result.areaTrigger,{
|
||||
kind="areaTrigger",
|
||||
id=tonumber(areaTriggerID),
|
||||
mapID=info.areaID,
|
||||
x=info.mapX,
|
||||
y=info.mapY
|
||||
})
|
||||
O.stats.areaTrigger=(O.stats.areaTrigger or 0)+1
|
||||
end
|
||||
end
|
||||
|
||||
-- IR target guidance appears only while the required quest item is in the
|
||||
-- player's bags/equipment. Its corresponding ordinary U/O objective is
|
||||
-- suppressed even while the item is missing, matching pfQuest SearchQuestID.
|
||||
|
||||
+78
-82
@@ -29,6 +29,16 @@ local function ObjectiveProgressFallback(text)
|
||||
return tonumber(current),tonumber(required)
|
||||
end
|
||||
|
||||
local function SortedNumericKeys(src)
|
||||
local keys={}
|
||||
if type(src)~="table" then return keys end
|
||||
for k in pairs(src) do
|
||||
if type(k)=="number" then table.insert(keys,k) end
|
||||
end
|
||||
table.sort(keys)
|
||||
return keys
|
||||
end
|
||||
|
||||
local function ReadObjectives(index,questID)
|
||||
local objectives={}
|
||||
local snapshot={}
|
||||
@@ -42,7 +52,15 @@ local function ReadObjectives(index,questID)
|
||||
allDone=false
|
||||
local nonLogCount=0
|
||||
|
||||
for i,row in pairs(apiObjectives) do
|
||||
-- ClassicAPI returns objectives indexed by their quest-log objective
|
||||
-- number. Keep that numeric identity/order instead of relying on pairs(),
|
||||
-- whose iteration order can differ between Lua tables/clients. The row's
|
||||
-- text, progress and completion therefore stay attached to the same
|
||||
-- objective while LocalizeObjectiveRows applies the compact DB ordinal.
|
||||
local objectiveKeys=SortedNumericKeys(apiObjectives)
|
||||
for keyIndex=1,table.getn(objectiveKeys) do
|
||||
local i=objectiveKeys[keyIndex]
|
||||
local row=apiObjectives[i]
|
||||
local typ=row.type
|
||||
local text=row.text
|
||||
local current=tonumber(row.numFulfilled)
|
||||
@@ -53,6 +71,8 @@ local function ReadObjectives(index,questID)
|
||||
if required==nil then required=parsedRequired end
|
||||
end
|
||||
local finished=row.finished and true or false
|
||||
local objectiveID=QuestieOcto.API and QuestieOcto.API.GetQuestLogLeaderBoardID
|
||||
and QuestieOcto.API:GetQuestLogLeaderBoardID(i,index) or nil
|
||||
|
||||
-- ClassicAPI/Turtle can publish the numerical counter before its separate
|
||||
-- `finished` boolean catches up. Treat a fulfilled numerical objective as
|
||||
@@ -76,7 +96,8 @@ local function ReadObjectives(index,questID)
|
||||
current=current,
|
||||
required=required,
|
||||
numFulfilled=current,
|
||||
numRequired=required
|
||||
numRequired=required,
|
||||
objectiveID=objectiveID
|
||||
})
|
||||
if not finished then allDone=false end
|
||||
end
|
||||
@@ -86,6 +107,7 @@ local function ReadObjectives(index,questID)
|
||||
table.insert(snapshot,finished and "1" or "0")
|
||||
table.insert(snapshot,tostring(current or -1))
|
||||
table.insert(snapshot,tostring(required or -1))
|
||||
table.insert(snapshot,tostring(objectiveID or 0))
|
||||
|
||||
-- Map nodes only care whether an objective is still active. pfQuest's
|
||||
-- quest-state signature likewise records todo/done, not 3/10 -> 4/10.
|
||||
@@ -95,6 +117,7 @@ local function ReadObjectives(index,questID)
|
||||
table.insert(mapSnapshot,tostring(i))
|
||||
table.insert(mapSnapshot,tostring(typ or ""))
|
||||
table.insert(mapSnapshot,finished and "1" or "0")
|
||||
table.insert(mapSnapshot,tostring(objectiveID or 0))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -111,6 +134,8 @@ local function ReadObjectives(index,questID)
|
||||
local text,typ,done=GetQuestLogLeaderBoard(i,index)
|
||||
local current,required=ObjectiveProgressFallback(text)
|
||||
local finished=done and true or false
|
||||
local objectiveID=QuestieOcto.API and QuestieOcto.API.GetQuestLogLeaderBoardID
|
||||
and QuestieOcto.API:GetQuestLogLeaderBoardID(i,index) or nil
|
||||
|
||||
if current and required and required>0 and current>=required then
|
||||
finished=true
|
||||
@@ -123,7 +148,8 @@ local function ReadObjectives(index,questID)
|
||||
index=i,text=text,rawText=text,type=typ,
|
||||
complete=finished,finished=finished,
|
||||
current=current,required=required,
|
||||
numFulfilled=current,numRequired=required
|
||||
numFulfilled=current,numRequired=required,
|
||||
objectiveID=objectiveID
|
||||
})
|
||||
if not finished then allDone=false end
|
||||
end
|
||||
@@ -133,11 +159,13 @@ local function ReadObjectives(index,questID)
|
||||
table.insert(snapshot,finished and "1" or "0")
|
||||
table.insert(snapshot,tostring(current or -1))
|
||||
table.insert(snapshot,tostring(required or -1))
|
||||
table.insert(snapshot,tostring(objectiveID or 0))
|
||||
|
||||
if typ~="log" then
|
||||
table.insert(mapSnapshot,tostring(i))
|
||||
table.insert(mapSnapshot,tostring(typ or ""))
|
||||
table.insert(mapSnapshot,finished and "1" or "0")
|
||||
table.insert(mapSnapshot,tostring(objectiveID or 0))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -145,48 +173,35 @@ local function ReadObjectives(index,questID)
|
||||
end
|
||||
|
||||
|
||||
local function ObjectiveDefName(def)
|
||||
if not def or not def.id then return nil end
|
||||
if def.kind=="creature" then return QuestieOcto.DatabaseAPI:GetCreatureName(def.id) end
|
||||
if def.kind=="gameObject" then return QuestieOcto.DatabaseAPI:GetObjectName(def.id) end
|
||||
if def.kind=="item" then return QuestieOcto.DatabaseAPI:GetItemName(def.id) end
|
||||
return nil
|
||||
local function ContainsObjectiveID(list,id)
|
||||
id=tonumber(id)
|
||||
if not id then return false end
|
||||
for i=1,table.getn(list or {}) do
|
||||
if tonumber(list[i])==id then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- The live quest-log leaderboard is ordered by the server. pfQuest's compiled
|
||||
-- objectiveData is ordered by DB category (creatures, then objects, then items)
|
||||
-- and, within a category, by raw U/O/I array order. Those two orderings are NOT
|
||||
-- guaranteed to agree. A leaderboard row must therefore be bound to its DB
|
||||
-- objective definition by the entity NAME embedded in the native leaderboard
|
||||
-- text, never by shared array position.
|
||||
--
|
||||
-- Positional binding attaches the wrong entity label to a row -- and because the
|
||||
-- x/y counter comes from the leaderboard row while the name comes from the DB
|
||||
-- definition, two same-type objectives whose orderings differ end up with their
|
||||
-- counts swapped (Shaman 7/10 / Bonesnapper 2/10 instead of 2/10 / 7/10). The
|
||||
-- Tracker escaped this because it renders rawText; tooltips render row.text
|
||||
-- produced here, so they showed the swapped values.
|
||||
local function MatchDefIndexForRow(row,defs,used)
|
||||
local nativeText=string.lower(tostring(row.rawText or row.text or ""))
|
||||
if nativeText=="" then return nil end
|
||||
local function LiveObjectiveKind(q,row)
|
||||
local id=tonumber(row and row.objectiveID)
|
||||
if not q or not id then return nil end
|
||||
local typ=string.lower(tostring(row.type or ""))
|
||||
|
||||
-- Longest matching name wins so "Stonesplinter Shaman Elder" is preferred over
|
||||
-- "Stonesplinter Shaman" when both appear as substrings of the same row.
|
||||
local bestIndex=nil
|
||||
local bestLen=-1
|
||||
for i=1,table.getn(defs) do
|
||||
if not used[i] then
|
||||
local name=ObjectiveDefName(defs[i])
|
||||
if name and name~="" then
|
||||
local lowerName=string.lower(name)
|
||||
if string.find(nativeText,lowerName,1,true) then
|
||||
local len=string.len(lowerName)
|
||||
if len>bestLen then bestLen=len; bestIndex=i end
|
||||
end
|
||||
end
|
||||
end
|
||||
-- pfQuest uses ClassicAPI's GetQuestLogLeaderBoardID and then determines
|
||||
-- whether the returned ID is a unit/object/item from the quest/database.
|
||||
-- Vanilla can report some object interactions as "monster", so prefer the
|
||||
-- native type but allow the live ID to disambiguate creature vs game object.
|
||||
if typ=="item" and ContainsObjectiveID(q.objectives.item,id) then return "item" end
|
||||
if (typ=="object" or typ=="gameobject") and ContainsObjectiveID(q.objectives.gameObject,id) then return "gameObject" end
|
||||
if typ=="monster" or typ=="creature" or typ=="kill" then
|
||||
if ContainsObjectiveID(q.objectives.creature,id) then return "creature" end
|
||||
if ContainsObjectiveID(q.objectives.gameObject,id) then return "gameObject" end
|
||||
end
|
||||
return bestIndex
|
||||
|
||||
if ContainsObjectiveID(q.objectives.creature,id) then return "creature" end
|
||||
if ContainsObjectiveID(q.objectives.gameObject,id) then return "gameObject" end
|
||||
if ContainsObjectiveID(q.objectives.item,id) then return "item" end
|
||||
return nil
|
||||
end
|
||||
|
||||
local function LocalizeObjectiveRows(questID,objectives)
|
||||
@@ -194,52 +209,27 @@ local function LocalizeObjectiveRows(questID,objectives)
|
||||
if not QuestieOcto.DatabaseAPI or not QuestieOcto.DatabaseAPI:IsReady() then return objectives end
|
||||
|
||||
local q=QuestieOcto.QuestModel and QuestieOcto.QuestModel:Get(questID) or nil
|
||||
local defs=q and q.objectiveData or nil
|
||||
if type(defs)~="table" then return objectives end
|
||||
if not q then return objectives end
|
||||
|
||||
local used={}
|
||||
local boundIndex={}
|
||||
|
||||
-- Pass 1: bind each row to the definition whose name appears in its native text.
|
||||
for i=1,table.getn(objectives) do
|
||||
local row=objectives[i]
|
||||
if row then
|
||||
local matched=MatchDefIndexForRow(row,defs,used)
|
||||
if matched then
|
||||
used[matched]=true
|
||||
boundIndex[i]=matched
|
||||
-- Objectives:ResolveQuest reads this to map map/hover nodes to the same
|
||||
-- definition, so node.objectiveIndex stays consistent with row.text.
|
||||
row.dataIndex=matched
|
||||
end
|
||||
end
|
||||
end
|
||||
local id=tonumber(row and row.objectiveID)
|
||||
local kind=LiveObjectiveKind(q,row)
|
||||
local name=nil
|
||||
|
||||
-- Pass 2: positional fallback for rows the name pass could not bind, using only
|
||||
-- definitions no name match has already claimed. Preserves prior behavior on
|
||||
-- data where the two orderings happened to agree.
|
||||
for i=1,table.getn(objectives) do
|
||||
if objectives[i] and not boundIndex[i] and defs[i] and not used[i] then
|
||||
used[i]=true
|
||||
boundIndex[i]=i
|
||||
objectives[i].dataIndex=i
|
||||
end
|
||||
end
|
||||
if id and kind=="creature" then name=QuestieOcto.DatabaseAPI:GetCreatureName(id)
|
||||
elseif id and kind=="gameObject" then name=QuestieOcto.DatabaseAPI:GetObjectName(id)
|
||||
elseif id and kind=="item" then name=QuestieOcto.DatabaseAPI:GetItemName(id) end
|
||||
|
||||
-- Pass 3: apply the localized label from the bound definition. Preserve live
|
||||
-- progress/completion from the quest log; replace only the entity name. Rows
|
||||
-- with no bound definition keep Turtle's native text as the final fallback.
|
||||
for i=1,table.getn(objectives) do
|
||||
local row=objectives[i]
|
||||
local def=row and boundIndex[i] and defs[boundIndex[i]] or nil
|
||||
if row and def and def.id then
|
||||
local name=ObjectiveDefName(def)
|
||||
if name and name~="" then
|
||||
if row.current~=nil and row.required~=nil then
|
||||
row.text=name..": "..tostring(row.current).."/"..tostring(row.required)
|
||||
else
|
||||
row.text=name
|
||||
end
|
||||
-- Never localize by objective array position. Questie 5/6 only assumes
|
||||
-- positional identity when both sides contain one objective; pfQuest uses
|
||||
-- ClassicAPI's live leaderboard ID. If no trustworthy ID is available,
|
||||
-- keep Turtle's native objective text intact for the fallback matcher.
|
||||
if row and name and name~="" then
|
||||
if row.current~=nil and row.required~=nil then
|
||||
row.text=name..": "..tostring(row.current).."/"..tostring(row.required)
|
||||
else
|
||||
row.text=name
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -409,6 +399,7 @@ function QL:Refresh(fastRefresh)
|
||||
local info=QuestieOcto.API:GetQuestLogInfo(index)
|
||||
local title=info and info.title
|
||||
local level=info and info.level
|
||||
local tag=info and info.tag
|
||||
local isHeader=info and info.isHeader
|
||||
local isComplete=info and info.isComplete
|
||||
if title and isHeader then
|
||||
@@ -493,6 +484,7 @@ function QL:Refresh(fastRefresh)
|
||||
table.insert(snapshotParts,tostring(questID or 0))
|
||||
table.insert(snapshotParts,title)
|
||||
table.insert(snapshotParts,tostring(level or 0))
|
||||
table.insert(snapshotParts,tostring(tag or ""))
|
||||
table.insert(snapshotParts,tostring(status))
|
||||
table.insert(snapshotParts,objectiveSnapshot)
|
||||
|
||||
@@ -502,6 +494,10 @@ function QL:Refresh(fastRefresh)
|
||||
logIndex=index,
|
||||
title=title,
|
||||
level=level,
|
||||
-- Vanilla/pfQuest uses GetQuestLogTitle().tag as the live elite
|
||||
-- marker. Preserve it with the quest instead of reclassifying
|
||||
-- quests from database metadata.
|
||||
tag=tag,
|
||||
zoneGroup=currentHeader,
|
||||
status=status,
|
||||
complete=complete,
|
||||
|
||||
+2
-1
@@ -2,7 +2,7 @@
|
||||
## Title: Questie-Octo
|
||||
## Notes: Quest helper.
|
||||
## Author: Questie-Octo
|
||||
## Version: 1.0.58
|
||||
## Version: 1.0.79
|
||||
## SavedVariables: QuestieOctoGlobalDB
|
||||
## SavedVariablesPerCharacter: QuestieOctoDB
|
||||
|
||||
@@ -45,6 +45,7 @@ Map\ContinentProjection.lua
|
||||
Map\Map.lua
|
||||
Map\MinimapSettings.lua
|
||||
Map\Minimap.lua
|
||||
Map\Arrow.lua
|
||||
Libs\LibStub\LibStub.lua
|
||||
Libs\CallbackHandler-1.0\CallbackHandler-1.0.xml
|
||||
Libs\AceCore-3.0\AceCore-3.0.xml
|
||||
|
||||
+4
-23
@@ -2,7 +2,7 @@
|
||||
QuestieOcto = QuestieOcto or {}
|
||||
local QO = QuestieOcto
|
||||
|
||||
QO.version = "1.0.58"
|
||||
QO.version = "1.0.79"
|
||||
QO.enabled = false
|
||||
QO.ready = false
|
||||
QO.messages = {}
|
||||
@@ -24,42 +24,23 @@ local function RGBFromDifficultyFunction(level)
|
||||
return nil,nil,nil
|
||||
end
|
||||
|
||||
local function SameRGB(r,g,b,color)
|
||||
if not color then return false end
|
||||
local cr,cg,cb=tonumber(color.r),tonumber(color.g),tonumber(color.b)
|
||||
if not r or not g or not b or not cr or not cg or not cb then return false end
|
||||
return math.abs(r-cr)<0.01 and math.abs(g-cg)<0.01 and math.abs(b-cb)<0.01
|
||||
end
|
||||
|
||||
function QO:GetNativeQuestDifficultyColor(level,questID)
|
||||
-- First authority: the actual color Turtle's native QuestLog_Update stored on
|
||||
-- this quest's Blizzard title button. QuestLogEnhancements caches that value
|
||||
-- before it changes the displayed text. This remains independent of skins.
|
||||
-- First authority: the actual color Turtle's native QuestLog_Update
|
||||
local qle=self.QuestLogEnhancements
|
||||
if qle and qle.GetCachedQuestColor and questID then
|
||||
local r,g,b=qle:GetCachedQuestColor(questID)
|
||||
if r then return r,g,b end
|
||||
end
|
||||
|
||||
-- Fallback until the native Quest Log color has been observed and cached
|
||||
local r,g,b=RGBFromDifficultyFunction(level)
|
||||
if not r then return nil,nil,nil end
|
||||
|
||||
-- Turtle/Octo keeps quests in the easy/green presentation instead of letting
|
||||
-- the stock trivial band turn gray. ClassicAPI can expose stock 1.12's
|
||||
-- GetDifficultyColor(), so normalize only that one band as a fallback until
|
||||
-- the native Quest Log color for this quest has been observed and cached.
|
||||
if QuestDifficultyColor and SameRGB(r,g,b,QuestDifficultyColor["trivial"]) then
|
||||
local standard=QuestDifficultyColor["standard"]
|
||||
if standard and standard.r then
|
||||
return tonumber(standard.r),tonumber(standard.g),tonumber(standard.b)
|
||||
end
|
||||
end
|
||||
return r,g,b
|
||||
end
|
||||
|
||||
function QO:Print(text)
|
||||
if DEFAULT_CHAT_FRAME then
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cff33ffccQuestie-Octo|r: "..tostring(text))
|
||||
DEFAULT_CHAT_FRAME:AddMessage("|cffffd700Questie-Octo|r: "..tostring(text))
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -136,6 +136,7 @@ function D:Rebuild()
|
||||
logIndex=state.logIndex,
|
||||
title=state.title,
|
||||
level=state.level or 0,
|
||||
tag=state.tag,
|
||||
zoneGroup=state.zoneGroup or "Other",
|
||||
complete=state.complete and true or false,
|
||||
failed=state.failed and true or false,
|
||||
@@ -156,6 +157,7 @@ function D:Rebuild()
|
||||
local row=ordered[i]
|
||||
table.insert(snapshot,tostring(row.id))
|
||||
table.insert(snapshot,row.zoneGroup or "")
|
||||
table.insert(snapshot,tostring(row.tag or ""))
|
||||
table.insert(snapshot,row.complete and "1" or "0")
|
||||
table.insert(snapshot,row.failed and "1" or "0")
|
||||
for j=1,table.getn(row.objectives or {}) do
|
||||
|
||||
@@ -399,6 +399,24 @@ local function WrapTextToWidth(fs,text,maxWidth)
|
||||
return table.concat(lines,"\n"),count
|
||||
end
|
||||
|
||||
local function ZoneSpacerHeight(fontSize)
|
||||
-- Keep zone groups visually separated without consuming a full text row.
|
||||
-- The spacer scales with the configured tracker font and adds a small amount
|
||||
-- for OUTLINE/THICKOUTLINE so heavier glyph edges do not feel crowded.
|
||||
local height=(tonumber(fontSize) or 10)-4
|
||||
if height<4 then height=4 end
|
||||
if height>13 then height=13 end
|
||||
|
||||
if Settings():Get("trackerThickOutlineText") then
|
||||
height=height+2
|
||||
elseif Settings():Get("trackerOutlineText") then
|
||||
height=height+1
|
||||
end
|
||||
|
||||
if height>15 then height=15 end
|
||||
return height
|
||||
end
|
||||
|
||||
local function ApplyRowStyle(row, contentWidth, constrainWidth)
|
||||
local size=tonumber(Settings():Get("trackerFontSize")) or 10
|
||||
local left=0
|
||||
@@ -407,7 +425,7 @@ local function ApplyRowStyle(row, contentWidth, constrainWidth)
|
||||
|
||||
if kind=="zone" then
|
||||
size=size+1
|
||||
r,g,b=1,0.82,0
|
||||
r,g,b=0.88,0.69,0.24
|
||||
left=0
|
||||
elseif kind=="quest" then
|
||||
-- Match the classic Questie/Quest Log presentation: completion is shown
|
||||
@@ -464,7 +482,12 @@ local function ApplyRowStyle(row, contentWidth, constrainWidth)
|
||||
row.textIndent=left
|
||||
|
||||
local baseHeight=size+4
|
||||
local height=baseHeight*math.max(1,lineCount)
|
||||
local height
|
||||
if kind=="spacer" then
|
||||
height=ZoneSpacerHeight(size)
|
||||
else
|
||||
height=baseHeight*math.max(1,lineCount)
|
||||
end
|
||||
row:SetHeight(height)
|
||||
end
|
||||
|
||||
@@ -563,12 +586,20 @@ function T:UpdateTimerRows()
|
||||
end
|
||||
|
||||
local function ObjectiveDisplayText(objective)
|
||||
local text=objective and (objective.rawText or objective.text) or ""
|
||||
-- Turtle can return leaderboard text without its entity name prefer the DB-localized text
|
||||
local raw=objective and objective.rawText or nil
|
||||
local loc=objective and objective.text or nil
|
||||
local text
|
||||
if raw and string.find(raw,"%a") then
|
||||
text=raw
|
||||
elseif loc and string.find(loc,"%a") then
|
||||
text=loc
|
||||
else
|
||||
text=raw or loc or ""
|
||||
end
|
||||
if not text then text="" end
|
||||
|
||||
-- The native Vanilla leaderboard text already contains the desired 5/10 form.
|
||||
-- Only synthesize amounts when the compatibility API supplied numeric progress
|
||||
-- separately and the text itself omitted it. Never display percentages.
|
||||
local current=objective and tonumber(objective.current)
|
||||
local required=objective and tonumber(objective.required)
|
||||
if current and required and required>0 and not string.find(text,"%d+%s*/%s*%d+") then
|
||||
@@ -628,7 +659,9 @@ function T:Render()
|
||||
|
||||
local prefix=""
|
||||
if quest.level and tonumber(quest.level) and tonumber(quest.level)>0 then
|
||||
prefix="["..tostring(quest.level).."] "
|
||||
-- Match pfQuest's Vanilla tracker convention: the native quest-log tag
|
||||
-- adds a '+' inside the level brackets, e.g. [40+].
|
||||
prefix="["..tostring(quest.level)..(quest.tag and "+" or "").."] "
|
||||
end
|
||||
local title=tostring(quest.title or "Quest")
|
||||
if quest.failed then
|
||||
@@ -768,7 +801,7 @@ function T:InstallNativeTimerSuppression()
|
||||
if type(QuestTimerFrame_Update)=="function" then
|
||||
local secureHook=hooksecurefunc
|
||||
if type(secureHook)~="function" and LibStub then
|
||||
local aceCore=LibStub("AceCore-3.0",true)
|
||||
local aceCore=LibStub("QuestieOcto_AceCore-3.0",true)
|
||||
secureHook=aceCore and aceCore.hooksecurefunc
|
||||
end
|
||||
|
||||
|
||||
@@ -72,6 +72,21 @@ local function InstallDragonflightMenu()
|
||||
return true
|
||||
end
|
||||
|
||||
local function SkinPfUIGameMenuButton(button)
|
||||
if not button or not pfUI or not pfUI.api or not pfUI.api.SkinButton then return end
|
||||
if not pfUI.skin or not pfUI.skin["Game Menu"] then return end
|
||||
|
||||
local disabled=pfUI_config and pfUI_config["disabled"]
|
||||
if disabled and disabled["skin_Game Menu"]=="1" then return end
|
||||
|
||||
-- pfUI skins Turtle WoW buttons that are added after its initial Game Menu
|
||||
-- skin pass the same way: normalize the label, then use pfUI's own button
|
||||
-- skin helper instead of imitating pfUI's appearance inside Questie-Octo.
|
||||
local font=button.GetFontString and button:GetFontString()
|
||||
if font and font.SetTextColor then font:SetTextColor(1,1,1,1) end
|
||||
pfUI.api.SkinButton(button)
|
||||
end
|
||||
|
||||
local function InstallBlizzardMenu()
|
||||
if not GameMenuFrame or not GameMenuButtonUIOptions or not GameMenuButtonKeybindings then
|
||||
return false
|
||||
@@ -92,6 +107,7 @@ local function InstallBlizzardMenu()
|
||||
|
||||
button:SetPoint("TOP",anchor,"BOTTOM",0,-1)
|
||||
button:SetText("Questie Options")
|
||||
SkinPfUIGameMenuButton(button)
|
||||
button:SetScript("OnClick",function()
|
||||
if HideUIPanel then
|
||||
HideUIPanel(GameMenuFrame)
|
||||
|
||||
@@ -22,3 +22,11 @@ Files not identified above retain their respective upstream/source licensing.
|
||||
|
||||
`pvp_available.tga` and `pvp_complete.tga` are conversions of the red PvP quest
|
||||
marker references supplied directly for the Questie-Octo project.
|
||||
|
||||
## Questie-Octo gray available artwork
|
||||
|
||||
`available_gray.tga` is a WoW-compatible TGA conversion of the 32x32 gray
|
||||
available-quest marker image supplied directly by the Questie-Octo project
|
||||
maintainer on 2026-08-17. The conversion preserves the supplied pixels and
|
||||
alpha; it is not copied from Questie's `available_gray.blp`.
|
||||
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 4.0 KiB |
+27
-22
@@ -21,9 +21,6 @@ local INNER_R,INNER_G,INNER_B,INNER_A=0.035,0.035,0.035,0.84
|
||||
local INNER_BORDER_R,INNER_BORDER_G,INNER_BORDER_B,INNER_BORDER_A=0.20,0.20,0.20,0.95
|
||||
local TAB_R,TAB_G,TAB_B,TAB_A=0.28,0.28,0.28,1.0
|
||||
|
||||
-- Capture each frame/texture's original Blizzard color exactly once, before the
|
||||
-- dark pass first mutates it. Unchecking Dark Theme then restores the precise
|
||||
-- on-load appearance rather than guessing at Blizzard's default color values.
|
||||
-- 1.12 reports nil for a backdrop color/border that was never explicitly set
|
||||
-- (the AceGUI Frame shell never sets a border color, so its default is white).
|
||||
-- Fill nil with 1 so the value is restorable. `x or 1` is safe here because Lua
|
||||
@@ -259,6 +256,9 @@ end
|
||||
function O:ApplyDarkTheme(apply)
|
||||
if not self.configFrame or not self.configFrame.frame then return end
|
||||
|
||||
-- No-argument callers (the initial open and AceConfig's post-refresh reapply)
|
||||
-- must honor the saved setting, not force the theme on. Only the toggle's own
|
||||
-- setter passes an explicit boolean.
|
||||
if apply == nil then
|
||||
apply=(QuestieOcto.MinimapSettings and QuestieOcto.MinimapSettings:Get("useDarkTheme")) and true or false
|
||||
end
|
||||
@@ -267,7 +267,9 @@ function O:ApplyDarkTheme(apply)
|
||||
if apply then
|
||||
DarkenOuterShell(shell)
|
||||
|
||||
-- Apply the inner treatment only below the outer shell. Keeping the shell seperate
|
||||
-- Apply the inner treatment only below the outer shell. Keeping the shell
|
||||
-- separate is important because its DialogFrame textures need vertex tinting,
|
||||
-- while AceConfig's content containers look better with dark backdrops.
|
||||
if shell.GetChildren then
|
||||
local children={shell:GetChildren()}
|
||||
for _,child in pairs(children) do
|
||||
@@ -275,11 +277,13 @@ function O:ApplyDarkTheme(apply)
|
||||
end
|
||||
end
|
||||
else
|
||||
-- Unchecked: return every frame/texture the dark pass touched to the exact blizzard color
|
||||
-- Unchecked: return every frame/texture the dark pass touched to the exact
|
||||
-- Blizzard color it had on load.
|
||||
RestoreDefaultTheme(shell)
|
||||
end
|
||||
|
||||
-- Run after backdrop darkening so the scrollbar is always the final/top UI layer
|
||||
-- Run after backdrop darkening so the scrollbar is always the final/top UI
|
||||
-- layer, including after AceConfig rebuilds the currently selected tab.
|
||||
RaiseScrollbar(shell)
|
||||
end
|
||||
|
||||
@@ -288,7 +292,7 @@ local function Settings()
|
||||
end
|
||||
|
||||
local function ClearSavedConfigPosition()
|
||||
local Dialog=LibStub and LibStub("AceConfigDialog-3.0",true)
|
||||
local Dialog=LibStub and LibStub("QuestieOcto_AceConfigDialog-3.0",true)
|
||||
if not Dialog or not Dialog.GetStatusTable then return end
|
||||
|
||||
local status=Dialog:GetStatusTable(APP_NAME)
|
||||
@@ -350,32 +354,33 @@ local function CreateGeneralTab()
|
||||
}},
|
||||
|
||||
quest_options={type="group",order=10,name="Quest Options",inline=true,args={
|
||||
questLogShowLevels={type="toggle",order=1,name="Show Quest Levels",desc="Show quest levels in the Quest Log.",width="full",get=GetValue,set=SetValue},
|
||||
showLowLevelQuests={type="toggle",order=2,name="Show Low-Level Quests",desc="Show quests below the normal green quest range.",get=GetValue,set=SetValue},
|
||||
lowLevelQuestRange={type="range",order=2.1,name=function()
|
||||
showQuestArrow={type="toggle",order=1,name="Show Quest Arrow",desc="Shows arrow that points towards nearest quest objective.",width="full",get=GetValue,set=SetValue},
|
||||
questLogShowLevels={type="toggle",order=2,name="Show Quest Levels",desc="Show quest levels in the Quest Log.",width="full",get=GetValue,set=SetValue},
|
||||
showLowLevelQuests={type="toggle",order=3,name="Show Low-Level Quests",desc="Show quests below the normal green quest range.",get=GetValue,set=SetValue},
|
||||
lowLevelQuestRange={type="range",order=4,name=function()
|
||||
local value=tonumber(Settings():Get("lowLevelQuestRange")) or 35
|
||||
if value>=35 then return "Levels Below: All" end
|
||||
return "Levels Below: "..tostring(value)
|
||||
end,desc="Limit how many levels below you are shown. All removes the limit.",width="normal",min=5,max=35,step=5,arg={questieHideEditBox=true,questieMaxLabel="All",questieCommitOnMouseUp=true,questieLiveLabelPrefix="Levels Below: "},disabled=function() return not Settings():Get("showLowLevelQuests") end,get=GetValue,set=SetValue},
|
||||
showRepeatableQuests={type="toggle",order=3,name="Show Repeatable Quests",desc="Show available repeatable quests.",width="full",get=GetValue,set=SetValue},
|
||||
showEventQuests={type="toggle",order=4,name="Show Event Quests",desc="Show available event quests.",width="full",get=GetValue,set=SetValue},
|
||||
showPvPRelatedQuests={type="toggle",order=5,name="Show PVP Related Quests",desc="Show PvP quest icons on the map and minimap.",width="full",get=GetValue,set=SetValue},
|
||||
showRepeatableQuests={type="toggle",order=5,name="Show Repeatable Quests",desc="Show available repeatable quests.",width="full",get=GetValue,set=SetValue},
|
||||
showEventQuests={type="toggle",order=6,name="Show Event Quests",desc="Show available event quests.",width="full",get=GetValue,set=SetValue},
|
||||
showPvPRelatedQuests={type="toggle",order=7,name="Show PVP Related Quests",desc="Show PvP quest icons on the map and minimap.",width="full",get=GetValue,set=SetValue},
|
||||
}},
|
||||
reset_header={type="header",order=90,name="Questie Options"},
|
||||
enableDarkTheme={type="toggle",order=91,name="Enable Dark Theme",desc="Darken the configuration frame backdrops.",width="full",
|
||||
get=function()
|
||||
return Settings():Get("useDarkTheme")
|
||||
get=function()
|
||||
return Settings():Get("useDarkTheme")
|
||||
end,
|
||||
set=function(_, value)
|
||||
set=function(_, value)
|
||||
Settings():Set("useDarkTheme", value)
|
||||
O:ApplyDarkTheme(value) -- Passes true/false directly
|
||||
O:ApplyDarkTheme(value)
|
||||
end
|
||||
},
|
||||
reset_text={type="description",order=92,name="Restore Questie-Octo options to their defaults. Quest data and completed-quest history are not deleted.",fontSize="medium"},
|
||||
resetOptions={type="execute",order=93,name="Reset Options",desc="Restore option defaults.",func=function()
|
||||
Settings():Reset()
|
||||
ClearSavedConfigPosition()
|
||||
local Registry=LibStub and LibStub("AceConfigRegistry-3.0",true)
|
||||
local Registry=LibStub and LibStub("QuestieOcto_AceConfigRegistry-3.0",true)
|
||||
if Registry and Registry.NotifyChange then Registry:NotifyChange(APP_NAME) end
|
||||
end},
|
||||
},
|
||||
@@ -500,9 +505,9 @@ end
|
||||
function O:Initialize()
|
||||
if self.initialized then return true end
|
||||
|
||||
local AceGUI=LibStub and LibStub("AceGUI-3.0",true)
|
||||
local Registry=LibStub and LibStub("AceConfigRegistry-3.0",true)
|
||||
local Dialog=LibStub and LibStub("AceConfigDialog-3.0",true)
|
||||
local AceGUI=LibStub and LibStub("QuestieOcto_AceGUI-3.0",true)
|
||||
local Registry=LibStub and LibStub("QuestieOcto_AceConfigRegistry-3.0",true)
|
||||
local Dialog=LibStub and LibStub("QuestieOcto_AceConfigDialog-3.0",true)
|
||||
|
||||
self.stats.aceGUI=AceGUI and true or false
|
||||
self.stats.aceRegistry=Registry and true or false
|
||||
@@ -609,7 +614,7 @@ end
|
||||
function O:Show()
|
||||
if not self:Initialize() then return end
|
||||
|
||||
local Dialog=LibStub("AceConfigDialog-3.0")
|
||||
local Dialog=LibStub("QuestieOcto_AceConfigDialog-3.0")
|
||||
-- Questie 3.3.5 refreshes the existing standalone frame through Open().
|
||||
Dialog:Open(APP_NAME,self.configFrame)
|
||||
RecenterConfigFrame(self.configFrame)
|
||||
|
||||
Reference in New Issue
Block a user