mirror of
https://github.com/Blaststar/Questie-Octo-Vanilla.git
synced 2026-10-20 04:10:49 +00:00
Force pushing all my changes
This commit is contained in:
+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
|
||||
|
||||
Reference in New Issue
Block a user