mirror of
https://github.com/Blaststar/Questie-Octo-Vanilla.git
synced 2026-10-19 11:50:50 +00:00
Added routes and submenu
This commit is contained in:
+262
-13
@@ -1,9 +1,11 @@
|
||||
-- Quest arrow that points toward the nearest active quest objective in the
|
||||
-- current zone, adapted from pfQuest's route arrow.
|
||||
-- Quest guidance overlays adapted from pfQuest's route.lua: a screen arrow that
|
||||
-- points at the nearest active objective, and a dotted route across the map and
|
||||
-- minimap that walks the current zone's objectives in nearest-first order.
|
||||
QuestieOcto.Arrow = QuestieOcto.Arrow or {}
|
||||
local A = QuestieOcto.Arrow
|
||||
|
||||
local ARROW_TEXTURE="Interface\\AddOns\\Questie-Octo\\UI\\Icons\\arrow"
|
||||
local ROUTE_TEXTURE="Interface\\AddOns\\Questie-Octo\\UI\\Icons\\route"
|
||||
local atan2=atan2 or math.atan2
|
||||
|
||||
local function Settings()
|
||||
@@ -54,32 +56,53 @@ local function IsArrowRole(role)
|
||||
or role=="objectiveItemSource" or role=="turnin"
|
||||
end
|
||||
|
||||
-- Pull the current map's prepared plan and keep only objective/turn-in slots.
|
||||
-- Pull the current map's prepared plan and keep only objective/turn-in slots,
|
||||
-- dropping any the quest menu has hidden and recording each node's objective
|
||||
-- index so focus can filter by objective.
|
||||
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
|
||||
|
||||
local menu=QuestieOcto.QuestMenu
|
||||
for _,desc in pairs(plan) do
|
||||
local x,y,questID
|
||||
local node
|
||||
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
|
||||
local n=entry.node
|
||||
if n and IsArrowRole(n.role) then node=n; 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
|
||||
node=desc.node
|
||||
end
|
||||
|
||||
if x and y then table.insert(targets,{x,y,questID}) end
|
||||
if node and (not menu or not menu:IsNodeHidden(node)) then
|
||||
local x,y=tonumber(desc.x),tonumber(desc.y)
|
||||
if x and y then table.insert(targets,{x,y,node.questID,tonumber(node.objectiveIndex)}) end
|
||||
end
|
||||
end
|
||||
|
||||
return targets
|
||||
end
|
||||
|
||||
-- When an objective is focused, restrict to its nodes (falling back to all when
|
||||
-- the focused objective has no node in this zone).
|
||||
local function FocusTargets(targets)
|
||||
if not QuestieOcto.QuestMenu then return targets end
|
||||
local fq,fo=QuestieOcto.QuestMenu:GetFocus()
|
||||
if not fq then return targets end
|
||||
local filtered={}
|
||||
local i=1
|
||||
local total=table.getn(targets)
|
||||
while i<=total do
|
||||
local t=targets[i]
|
||||
if tonumber(t[3])==fq and tonumber(t[4])==fo then table.insert(filtered,t) end
|
||||
i=i+1
|
||||
end
|
||||
if table.getn(filtered)>0 then return filtered end
|
||||
return targets
|
||||
end
|
||||
|
||||
local function NearestTarget(targets,px,py)
|
||||
local best,bestDist
|
||||
for _,t in pairs(targets or {}) do
|
||||
@@ -90,6 +113,232 @@ local function NearestTarget(targets,px,py)
|
||||
return best,bestDist
|
||||
end
|
||||
|
||||
-- Greedy nearest-neighbor ordering starting from the objective closest to the
|
||||
-- player, the same heuristic pfQuest uses to approximate the shortest route.
|
||||
local function BuildRoute(targets,px,py,forceFirst)
|
||||
local n=table.getn(targets)
|
||||
if n==0 then return {} end
|
||||
local used={}
|
||||
local route={}
|
||||
local cx,cy=px,py
|
||||
if forceFirst then
|
||||
local fi=1
|
||||
while fi<=n do
|
||||
if targets[fi]==forceFirst then
|
||||
used[fi]=true
|
||||
route[1]=forceFirst
|
||||
cx,cy=forceFirst[1],forceFirst[2]
|
||||
break
|
||||
end
|
||||
fi=fi+1
|
||||
end
|
||||
end
|
||||
local placed=table.getn(route)
|
||||
while placed<n do
|
||||
local bestIdx,bestDist
|
||||
local j=1
|
||||
while j<=n do
|
||||
if not used[j] then
|
||||
local dx,dy=(cx-targets[j][1])*1.5,(cy-targets[j][2])
|
||||
local d=dx*dx+dy*dy
|
||||
if not bestDist or d<bestDist then bestDist=d; bestIdx=j end
|
||||
end
|
||||
j=j+1
|
||||
end
|
||||
if not bestIdx then break end
|
||||
used[bestIdx]=true
|
||||
route[table.getn(route)+1]=targets[bestIdx]
|
||||
cx,cy=targets[bestIdx][1],targets[bestIdx][2]
|
||||
placed=placed+1
|
||||
end
|
||||
return route
|
||||
end
|
||||
|
||||
local routeWorldDots={}
|
||||
local routeMiniDots={}
|
||||
local routeWorldFrame=nil
|
||||
local routeMiniFrame=nil
|
||||
|
||||
local function EnsureRouteFrames()
|
||||
if not routeWorldFrame and WorldMapButton then
|
||||
routeWorldFrame=CreateFrame("Frame","QuestieOctoRouteWorld",WorldMapButton)
|
||||
routeWorldFrame:SetAllPoints(WorldMapButton)
|
||||
if routeWorldFrame.SetFrameLevel and WorldMapButton.GetFrameLevel then
|
||||
routeWorldFrame:SetFrameLevel(WorldMapButton:GetFrameLevel()+1)
|
||||
end
|
||||
end
|
||||
if not routeMiniFrame and Minimap then
|
||||
routeMiniFrame=CreateFrame("Frame","QuestieOctoRouteMini",Minimap)
|
||||
routeMiniFrame:SetAllPoints(Minimap)
|
||||
end
|
||||
end
|
||||
|
||||
local function AcquireDot(pool,parent,index)
|
||||
local tex=pool[index]
|
||||
if not tex then
|
||||
tex=parent:CreateTexture(nil,"OVERLAY")
|
||||
tex:SetWidth(5)
|
||||
tex:SetHeight(5)
|
||||
tex:SetTexture(ROUTE_TEXTURE)
|
||||
tex:SetVertexColor(1,.8,.2,.9)
|
||||
pool[index]=tex
|
||||
end
|
||||
return tex
|
||||
end
|
||||
|
||||
local function HideDotsFrom(pool,index)
|
||||
local i=index
|
||||
local total=table.getn(pool)
|
||||
while i<=total do
|
||||
if pool[i] then pool[i]:Hide() end
|
||||
i=i+1
|
||||
end
|
||||
end
|
||||
|
||||
local function SegmentSteps(dx,dy)
|
||||
local steps=math.floor(math.sqrt(dx*1.5*dx*1.5+dy*dy))
|
||||
if steps<1 then steps=1 end
|
||||
return steps
|
||||
end
|
||||
|
||||
local function DrawWorldSegment(x1,y1,x2,y2,index)
|
||||
local width=WorldMapButton:GetWidth()
|
||||
local height=WorldMapButton:GetHeight()
|
||||
if not width or width<=0 or not height or height<=0 then return index end
|
||||
local dx,dy=x2-x1,y2-y1
|
||||
local steps=SegmentSteps(dx,dy)
|
||||
local i=1
|
||||
while i<=steps do
|
||||
local x=x1+dx*i/steps
|
||||
local y=y1+dy*i/steps
|
||||
local tex=AcquireDot(routeWorldDots,routeWorldFrame,index)
|
||||
tex:ClearAllPoints()
|
||||
tex:SetPoint("CENTER",WorldMapButton,"TOPLEFT",width*(x/100),-height*(y/100))
|
||||
tex:Show()
|
||||
index=index+1
|
||||
i=i+1
|
||||
end
|
||||
return index
|
||||
end
|
||||
|
||||
local function DrawMiniSegment(proj,x1,y1,x2,y2,index)
|
||||
local dx,dy=x2-x1,y2-y1
|
||||
local steps=SegmentSteps(dx,dy)
|
||||
local i=1
|
||||
while i<=steps do
|
||||
local x=x1+dx*i/steps
|
||||
local y=y1+dy*i/steps
|
||||
local ox=(x-proj.px)*proj.xDraw
|
||||
local oy=-(y-proj.py)*proj.yDraw
|
||||
local inside
|
||||
if proj.square then
|
||||
inside=math.abs(ox)<(proj.width/2) and math.abs(oy)<(proj.height/2)
|
||||
else
|
||||
inside=(ox*ox+oy*oy)<(proj.radius*proj.radius)
|
||||
end
|
||||
if inside then
|
||||
local tex=AcquireDot(routeMiniDots,routeMiniFrame,index)
|
||||
tex:ClearAllPoints()
|
||||
tex:SetPoint("CENTER",Minimap,"CENTER",ox,oy)
|
||||
tex:Show()
|
||||
index=index+1
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
return index
|
||||
end
|
||||
|
||||
function A:ClearRoute()
|
||||
HideDotsFrom(routeWorldDots,1)
|
||||
HideDotsFrom(routeMiniDots,1)
|
||||
end
|
||||
|
||||
function A:UpdateRoute()
|
||||
if (self.routeThrottle or 0)>GetTime() then return end
|
||||
self.routeThrottle=GetTime()+0.1
|
||||
|
||||
EnsureRouteFrames()
|
||||
|
||||
if not Settings():Get("showQuestRoute") then
|
||||
self:ClearRoute()
|
||||
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 self:ClearRoute(); return end
|
||||
|
||||
local mapID=CurrentMapID()
|
||||
if mapID~=self.routeMapID or (self.routeRefresh or 0)<GetTime() then
|
||||
self.routeMapID=mapID
|
||||
self.routeTargets=CollectTargets(mapID)
|
||||
self.routeRefresh=GetTime()+1
|
||||
end
|
||||
|
||||
local targets=self.routeTargets or {}
|
||||
if table.getn(targets)==0 then self:ClearRoute(); return end
|
||||
|
||||
-- A focused objective becomes the route's first stop.
|
||||
local forceFirst,fq,fo
|
||||
if QuestieOcto.QuestMenu then fq,fo=QuestieOcto.QuestMenu:GetFocus() end
|
||||
if fq then
|
||||
local bestD
|
||||
local i=1
|
||||
local total=table.getn(targets)
|
||||
while i<=total do
|
||||
local t=targets[i]
|
||||
if tonumber(t[3])==fq and tonumber(t[4])==fo then
|
||||
local dx,dy=(px-t[1])*1.5,(py-t[2])
|
||||
local d=dx*dx+dy*dy
|
||||
if not bestD or d<bestD then bestD=d; forceFirst=t end
|
||||
end
|
||||
i=i+1
|
||||
end
|
||||
end
|
||||
|
||||
-- Only reorder when the target set, the nearest objective, or the focus changes.
|
||||
local nearest=NearestTarget(targets,px,py)
|
||||
local sig=tostring(table.getn(targets))..":"..(nearest and (tostring(nearest[1])..":"..tostring(nearest[2])) or "")
|
||||
..":"..tostring(fq)..":"..tostring(fo)
|
||||
if sig~=self.routeSig then
|
||||
self.routeSig=sig
|
||||
self.routeOrder=BuildRoute(targets,px,py,forceFirst)
|
||||
end
|
||||
local order=self.routeOrder or {}
|
||||
|
||||
-- World-map dots sit at fixed zone coordinates, so only redraw them when the
|
||||
-- route, the displayed map, or the map size actually changes.
|
||||
local displayed=QuestieOcto.Map and QuestieOcto.Map.GetDisplayedMapID and QuestieOcto.Map:GetDisplayedMapID()
|
||||
local worldVisible=WorldMapButton and WorldMapButton:IsVisible() and tonumber(displayed)==tonumber(mapID)
|
||||
local worldSig=worldVisible and (sig.."@"..tostring(WorldMapButton:GetWidth())) or "hidden"
|
||||
if worldSig~=self.routeWorldSig then
|
||||
self.routeWorldSig=worldSig
|
||||
local worldIndex=1
|
||||
if worldVisible then
|
||||
if order[1] then worldIndex=DrawWorldSegment(px,py,order[1][1],order[1][2],worldIndex) end
|
||||
local i=2
|
||||
while i<=table.getn(order) do
|
||||
worldIndex=DrawWorldSegment(order[i-1][1],order[i-1][2],order[i][1],order[i][2],worldIndex)
|
||||
i=i+1
|
||||
end
|
||||
end
|
||||
HideDotsFrom(routeWorldDots,worldIndex)
|
||||
end
|
||||
|
||||
local miniIndex=1
|
||||
local proj=QuestieOcto.Minimap and QuestieOcto.Minimap.GetProjection and QuestieOcto.Minimap:GetProjection()
|
||||
if proj then
|
||||
if order[1] then miniIndex=DrawMiniSegment(proj,px,py,order[1][1],order[1][2],miniIndex) end
|
||||
local i=2
|
||||
while i<=table.getn(order) do
|
||||
miniIndex=DrawMiniSegment(proj,order[i-1][1],order[i-1][2],order[i][1],order[i][2],miniIndex)
|
||||
i=i+1
|
||||
end
|
||||
end
|
||||
HideDotsFrom(routeMiniDots,miniIndex)
|
||||
end
|
||||
|
||||
function A:OnUpdate()
|
||||
local frame=self.frame
|
||||
if not frame then return end
|
||||
@@ -114,7 +363,7 @@ function A:OnUpdate()
|
||||
frame.refresh=GetTime()+1
|
||||
end
|
||||
|
||||
local best,bestDist=NearestTarget(frame.targets,px,py)
|
||||
local best,bestDist=NearestTarget(FocusTargets(frame.targets),px,py)
|
||||
if not best then
|
||||
if frame:IsShown() then frame:Hide() end
|
||||
return
|
||||
@@ -200,7 +449,7 @@ function A:Initialize()
|
||||
-- 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)
|
||||
driver:SetScript("OnUpdate",function() A:OnUpdate(); A:UpdateRoute() end)
|
||||
self.driver=driver
|
||||
end
|
||||
|
||||
|
||||
@@ -407,6 +407,7 @@ end
|
||||
|
||||
function M:GetOrCreate(key,node,x,y,clusterCount,generation,kind)
|
||||
if not IsRoleEnabled(node.role) or not IsPvPQuestNodeEnabled(node) then return nil end
|
||||
if QuestieOcto.QuestMenu and QuestieOcto.QuestMenu:IsNodeHidden(node) then return nil end
|
||||
|
||||
local pin=self.frames[key]
|
||||
|
||||
@@ -853,6 +854,7 @@ end
|
||||
|
||||
function M:RenderContinentNode(node,mapID,generation)
|
||||
if not node or not IsRoleEnabled(node.role) or not IsPvPQuestNodeEnabled(node) then return 0 end
|
||||
if QuestieOcto.QuestMenu and QuestieOcto.QuestMenu:IsNodeHidden(node) then return 0 end
|
||||
if node.role=="itemStart" and QuestieOcto.ItemStartAreas:IsZoneWideRareChance(node.chance) then return 0 end
|
||||
-- World Map Visibility toggles apply only to continent/world overviews.
|
||||
-- Selected zone and city maps keep normal/special quest markers visible and
|
||||
|
||||
@@ -315,6 +315,32 @@ local function PlayerPosition()
|
||||
return MM.physicalPlayerX,MM.physicalPlayerY
|
||||
end
|
||||
|
||||
-- Expose the live minimap coordinate transform so the route overlay can place
|
||||
-- its own dots using the exact projection the pins use.
|
||||
function MM:GetProjection()
|
||||
if not Minimap or not Minimap.GetZoom then return nil end
|
||||
local mapID=self.mapID or CurrentMapID()
|
||||
if not mapID then return nil end
|
||||
local px,py=PlayerPosition()
|
||||
if not px or not py then return nil end
|
||||
local mapWidth,mapHeight=QuestieOcto.DatabaseAPI:GetMinimapSize(mapID)
|
||||
if not mapWidth or not mapHeight or mapWidth<=0 or mapHeight<=0 then return nil end
|
||||
local zoom=Minimap:GetZoom() or 0
|
||||
local indoor=MinimapIndoor()
|
||||
local mapZoom=MINIMAP_ZOOM[indoor] and MINIMAP_ZOOM[indoor][zoom]
|
||||
if not mapZoom or mapZoom<=0 then return nil end
|
||||
local width,height=Minimap:GetWidth(),Minimap:GetHeight()
|
||||
if not width or width<=0 or not height or height<=0 then return nil end
|
||||
return {
|
||||
px=px,py=py,
|
||||
xDraw=width/(mapZoom/mapWidth)/100,
|
||||
yDraw=height/(mapZoom/mapHeight)/100,
|
||||
radius=math.min(width,height)/2,
|
||||
width=width,height=height,
|
||||
square=UsesSquareMinimap()
|
||||
}
|
||||
end
|
||||
|
||||
local function EntryKey(node)
|
||||
return tostring(node.questID)..":"..tostring(node.role)..":"..
|
||||
tostring(node.sourceKind)..":"..tostring(node.sourceID)..":"..
|
||||
@@ -472,6 +498,7 @@ end
|
||||
-- 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 QuestieOcto.QuestMenu and QuestieOcto.QuestMenu:IsNodeHidden(node) then return false end
|
||||
|
||||
if node.role=="itemStart" then
|
||||
if not allowItemStart then return false end
|
||||
|
||||
@@ -90,8 +90,9 @@ S.defaults={
|
||||
questLogShowLevels=true,
|
||||
questLogDifficultyColors=true,
|
||||
|
||||
-- Quest objective arrow is opt-in.
|
||||
-- Quest objective arrow and route are opt-in.
|
||||
showQuestArrow=false,
|
||||
showQuestRoute=false,
|
||||
|
||||
-- Options-frame dark theme is off by default so the config window opens with
|
||||
-- native Blizzard colors until the player opts in.
|
||||
@@ -240,7 +241,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=="showQuestArrow" or key=="showQuestRoute" or
|
||||
key=="useDarkTheme" or
|
||||
key=="trackerEnabled" or key=="trackerLocked" or key=="trackerAutoTrack" or
|
||||
key=="trackerShowCompleted" or key=="trackerHideCompletedObjectives" or key=="trackerHideInCombat" or
|
||||
@@ -314,8 +315,8 @@ function S:Set(key,value)
|
||||
return true
|
||||
end
|
||||
|
||||
-- The quest arrow reads its setting live each frame; just persist it.
|
||||
if key=="showQuestArrow" then
|
||||
-- The quest arrow and route read their setting live each frame; just persist.
|
||||
if key=="showQuestArrow" or key=="showQuestRoute" then
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
@@ -55,6 +55,7 @@ Libs\AceConfigRegistry-3.0\AceConfigRegistry-3.0.xml
|
||||
Libs\AceConfigDialog-3.0\AceConfigDialog-3.0.xml
|
||||
UI\QuestResearch.lua
|
||||
UI\QuestLinkTooltip.lua
|
||||
UI\QuestMenu.lua
|
||||
UI\Options.lua
|
||||
UI\GameMenu.lua
|
||||
Core\Bootstrap.lua
|
||||
|
||||
@@ -311,6 +311,7 @@ local function EnsureRow(index,parent)
|
||||
row=CreateFrame("Button",nil,parent)
|
||||
row:SetHeight(14)
|
||||
row:EnableMouse(true)
|
||||
if row.RegisterForClicks then row:RegisterForClicks("LeftButtonUp","RightButtonUp") end
|
||||
|
||||
local text=row:CreateFontString(nil,"ARTWORK","GameFontNormal")
|
||||
text:SetPoint("TOPLEFT",row,"TOPLEFT",0,0)
|
||||
@@ -332,6 +333,10 @@ local function EnsureRow(index,parent)
|
||||
end)
|
||||
row:SetScript("OnClick",function()
|
||||
if not this.questID then return end
|
||||
if arg1=="RightButton" then
|
||||
if this.kind=="quest" and QuestieOcto.QuestMenu then QuestieOcto.QuestMenu:Open(this.questID,this) end
|
||||
return
|
||||
end
|
||||
if IsShiftKeyDown and IsShiftKeyDown() then
|
||||
if QuestieOcto.TrackerDriver then QuestieOcto.TrackerDriver:Toggle(this.questID) end
|
||||
return
|
||||
@@ -1031,4 +1036,4 @@ function T:Start()
|
||||
self:Render()
|
||||
end
|
||||
|
||||
QuestieOcto:RegisterMessage("FOUNDATION_READY",T,"Start")
|
||||
QuestieOcto:RegisterMessage("FOUNDATION_READY",T,"Start")
|
||||
Binary file not shown.
+8
-7
@@ -354,17 +354,18 @@ local function CreateGeneralTab()
|
||||
}},
|
||||
|
||||
quest_options={type="group",order=10,name="Quest Options",inline=true,args={
|
||||
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()
|
||||
showQuestRoute={type="toggle",order=1,name="Show Quest Route",desc="Shows fasted route on map to complete objectives.",width="full",get=GetValue,set=SetValue},
|
||||
showQuestArrow={type="toggle",order=2,name="Show Quest Arrow",desc="Shows arrow that points towards nearest quest objective.",width="full",get=GetValue,set=SetValue},
|
||||
questLogShowLevels={type="toggle",order=3,name="Show Quest Levels",desc="Show quest levels in the Quest Log.",width="full",get=GetValue,set=SetValue},
|
||||
showLowLevelQuests={type="toggle",order=4,name="Show Low-Level Quests",desc="Show quests below the normal green quest range.",get=GetValue,set=SetValue},
|
||||
lowLevelQuestRange={type="range",order=5,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=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},
|
||||
showRepeatableQuests={type="toggle",order=6,name="Show Repeatable Quests",desc="Show available repeatable quests.",width="full",get=GetValue,set=SetValue},
|
||||
showEventQuests={type="toggle",order=7,name="Show Event Quests",desc="Show available event quests.",width="full",get=GetValue,set=SetValue},
|
||||
showPvPRelatedQuests={type="toggle",order=8,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",
|
||||
|
||||
@@ -0,0 +1,217 @@
|
||||
-- Right-click quest menu, rendered with the native Vanilla UIDropDownMenu so it
|
||||
-- matches Blizzard/Titan context menus (submenu arrows, checks, highlight).
|
||||
QuestieOcto.QuestMenu = QuestieOcto.QuestMenu or {}
|
||||
local QM = QuestieOcto.QuestMenu
|
||||
|
||||
local function DB()
|
||||
QuestieOctoDB=QuestieOctoDB or {}
|
||||
QuestieOctoDB.questMenu=QuestieOctoDB.questMenu or {}
|
||||
local d=QuestieOctoDB.questMenu
|
||||
d.hiddenObjectives=d.hiddenObjectives or {}
|
||||
d.hiddenQuests=d.hiddenQuests or {}
|
||||
return d
|
||||
end
|
||||
|
||||
local function ObjectiveKey(questID,objIndex)
|
||||
return tostring(questID).." "..tostring(objIndex)
|
||||
end
|
||||
|
||||
local function IsObjectiveRole(role)
|
||||
return role=="objectiveCreature" or role=="objectiveObject" or role=="objectiveItemSource"
|
||||
end
|
||||
|
||||
-- State queries -------------------------------------------------------------
|
||||
|
||||
function QM:IsQuestHidden(questID)
|
||||
return DB().hiddenQuests[tonumber(questID)] and true or false
|
||||
end
|
||||
|
||||
function QM:IsObjectiveHidden(questID,objIndex)
|
||||
return DB().hiddenObjectives[ObjectiveKey(questID,objIndex)] and true or false
|
||||
end
|
||||
|
||||
function QM:GetFocus()
|
||||
local f=DB().focus
|
||||
if type(f)~="string" then return nil,nil end
|
||||
local _,_,qid,oi=string.find(f,"^(%d+) (%d+)$")
|
||||
return tonumber(qid),tonumber(oi)
|
||||
end
|
||||
|
||||
function QM:IsFocused(questID,objIndex)
|
||||
local fq,fo=self:GetFocus()
|
||||
return fq==tonumber(questID) and fo==tonumber(objIndex)
|
||||
end
|
||||
|
||||
-- A node is hidden when its whole quest is hidden (objective nodes only) or when
|
||||
-- its specific objective is hidden.
|
||||
function QM:IsNodeHidden(node)
|
||||
if not node then return false end
|
||||
local qid=tonumber(node.questID)
|
||||
if not qid or qid<=0 then return false end
|
||||
if IsObjectiveRole(node.role) then
|
||||
if self:IsQuestHidden(qid) then return true end
|
||||
local oi=tonumber(node.objectiveIndex)
|
||||
if oi and self:IsObjectiveHidden(qid,oi) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Toggles -------------------------------------------------------------------
|
||||
|
||||
function QM:RefreshMaps()
|
||||
if QuestieOcto.Minimap and QuestieOcto.Minimap.RefreshVisualSettings then
|
||||
QuestieOcto.Minimap:RefreshVisualSettings()
|
||||
end
|
||||
if QuestieOcto.Map and QuestieOcto.Map.RequestSync then
|
||||
QuestieOcto.Map:RequestSync(true)
|
||||
end
|
||||
end
|
||||
|
||||
function QM:ToggleFocus(questID,objIndex)
|
||||
local d=DB()
|
||||
if self:IsFocused(questID,objIndex) then
|
||||
d.focus=nil
|
||||
else
|
||||
d.focus=ObjectiveKey(questID,objIndex)
|
||||
end
|
||||
end
|
||||
|
||||
function QM:ToggleObjectiveHidden(questID,objIndex)
|
||||
local d=DB()
|
||||
local key=ObjectiveKey(questID,objIndex)
|
||||
d.hiddenObjectives[key]=(not d.hiddenObjectives[key]) or nil
|
||||
self:RefreshMaps()
|
||||
end
|
||||
|
||||
function QM:ToggleQuestHidden(questID)
|
||||
local d=DB()
|
||||
questID=tonumber(questID)
|
||||
d.hiddenQuests[questID]=(not d.hiddenQuests[questID]) or nil
|
||||
self:RefreshMaps()
|
||||
end
|
||||
|
||||
-- Menu data -----------------------------------------------------------------
|
||||
|
||||
local function ObjectiveName(q,row)
|
||||
local defs=q and q.objectiveData or nil
|
||||
local def=defs and row.dataIndex and defs[tonumber(row.dataIndex)] or nil
|
||||
if def and def.id then
|
||||
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
|
||||
end
|
||||
local text=row.rawText or row.text or ""
|
||||
local stripped=string.gsub(text,"%s*:?%s*%d+%s*/%s*%d+%s*$","")
|
||||
-- Drop a trailing objective verb (e.g. "... slain") so only the name remains.
|
||||
local nameOnly=string.gsub(stripped,"%s+%l+$","")
|
||||
if nameOnly~="" then return nameOnly end
|
||||
if stripped~="" then return stripped end
|
||||
return text
|
||||
end
|
||||
|
||||
local function ActiveState(questID)
|
||||
return QuestieOcto.QuestLog and QuestieOcto.QuestLog.active and QuestieOcto.QuestLog.active[tonumber(questID)] or nil
|
||||
end
|
||||
|
||||
local function HasIncompleteObjectives(state)
|
||||
local objectives=state and state.objectives or {}
|
||||
local i=1
|
||||
while i<=table.getn(objectives) do
|
||||
if not objectives[i].complete then return true end
|
||||
i=i+1
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Native UIDropDownMenu rendering -------------------------------------------
|
||||
|
||||
local function AddTitle(text)
|
||||
local info={}
|
||||
info.text=text
|
||||
info.isTitle=1
|
||||
info.notClickable=1
|
||||
info.notCheckable=1
|
||||
UIDropDownMenu_AddButton(info,UIDROPDOWNMENU_MENU_LEVEL)
|
||||
end
|
||||
|
||||
local function AddArrow(text,value)
|
||||
local info={}
|
||||
info.text=text
|
||||
info.value=value
|
||||
info.hasArrow=1
|
||||
info.notCheckable=1
|
||||
UIDropDownMenu_AddButton(info,UIDROPDOWNMENU_MENU_LEVEL)
|
||||
end
|
||||
|
||||
local function AddToggle(text,isChecked,onToggle)
|
||||
local info={}
|
||||
info.text=text
|
||||
info.checked=isChecked and 1 or nil
|
||||
info.keepShownOnClick=1
|
||||
info.func=onToggle
|
||||
UIDropDownMenu_AddButton(info,UIDROPDOWNMENU_MENU_LEVEL)
|
||||
end
|
||||
|
||||
local function Initialize()
|
||||
local questID=QM.currentQuestID
|
||||
if not questID then return end
|
||||
local state=ActiveState(questID)
|
||||
local q=QuestieOcto.QuestModel and QuestieOcto.QuestModel:Get(questID) or nil
|
||||
local level=UIDROPDOWNMENU_MENU_LEVEL or 1
|
||||
|
||||
if level==1 then
|
||||
local title=(q and q.title) or (state and state.title) or ("Quest "..tostring(questID))
|
||||
local qlevel=q and tonumber(q.level)
|
||||
if qlevel and qlevel>0 then title="["..tostring(qlevel).."] "..title end
|
||||
AddTitle(title)
|
||||
if HasIncompleteObjectives(state) then
|
||||
AddArrow("Objectives","objectives")
|
||||
end
|
||||
AddToggle("Hide icons",QM:IsQuestHidden(questID),function()
|
||||
QM:ToggleQuestHidden(questID)
|
||||
end)
|
||||
return
|
||||
end
|
||||
|
||||
if level==2 and UIDROPDOWNMENU_MENU_VALUE=="objectives" then
|
||||
local objectives=state and state.objectives or {}
|
||||
local oi=1
|
||||
while oi<=table.getn(objectives) do
|
||||
local row=objectives[oi]
|
||||
local objIndex=tonumber(row.index)
|
||||
if objIndex and not row.complete then
|
||||
AddArrow(ObjectiveName(q,row),"obj:"..objIndex)
|
||||
end
|
||||
oi=oi+1
|
||||
end
|
||||
return
|
||||
end
|
||||
|
||||
if level==3 then
|
||||
local _,_,idx=string.find(tostring(UIDROPDOWNMENU_MENU_VALUE),"^obj:(%d+)$")
|
||||
local objIndex=tonumber(idx)
|
||||
if objIndex then
|
||||
AddToggle("Focus Objective",QM:IsFocused(questID,objIndex),function()
|
||||
QM:ToggleFocus(questID,objIndex)
|
||||
end)
|
||||
AddToggle("Hide icons",QM:IsObjectiveHidden(questID,objIndex),function()
|
||||
QM:ToggleObjectiveHidden(questID,objIndex)
|
||||
end)
|
||||
end
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
function QM:Open(questID,row)
|
||||
questID=tonumber(questID)
|
||||
-- No menu for a quest that is already complete/ready to turn in.
|
||||
local state=ActiveState(questID)
|
||||
if state and state.complete then return end
|
||||
|
||||
if not self.dropdown then
|
||||
self.dropdown=CreateFrame("Frame","QuestieOctoQuestMenuDropDown",UIParent,"UIDropDownMenuTemplate")
|
||||
end
|
||||
self.currentQuestID=questID
|
||||
UIDropDownMenu_Initialize(self.dropdown,Initialize,"MENU")
|
||||
ToggleDropDownMenu(1,nil,self.dropdown,"cursor",0,0)
|
||||
end
|
||||
Reference in New Issue
Block a user