QuestieOcto.ZoneBootstrap = QuestieOcto.ZoneBootstrap or {} local Z = QuestieOcto.ZoneBootstrap Z.running=false Z.ready=false Z.mapID=nil Z.requestedMapID=nil Z.generation=0 Z.stats={scanned=0,candidates=0,available=0,nodes=0,itemSources=0,requests=0,cancelled=0,msHint="priority"} local function CoordsContainMap(coords,mapID) if not coords then return false end for _,coord in pairs(coords) do if type(coord)=="table" and tonumber(coord[3])==tonumber(mapID) then return true end end return false end local function AddNode(nodes,node) table.insert(nodes,node) end local function IsPresentationEvent(q) if not q or not q.eventID or not QuestieOcto.EventAvailability then return false end if QuestieOcto.EventAvailability.IsPresentationEventForQuest then return QuestieOcto.EventAvailability:IsPresentationEventForQuest(q) and true or false end return QuestieOcto.EventAvailability:IsPresentationEvent(q.eventID) and true or false end local function ApplyObjectiveState(node,state) if state then node.objectiveIndex=state.objectiveIndex node.objectiveText=state.objectiveText node.objectiveType=state.objectiveType node.current=state.current node.required=state.required node.objectiveComplete=state.complete and true or false end 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.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=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.presentationRepeatable or false, sourceKind="gameObject",sourceID=id, sourceName=QuestieOcto.DatabaseAPI:GetObjectName(id), itemID=itemID,itemName=itemID and QuestieOcto.DatabaseAPI:GetItemName(itemID) or nil, chance=chance,coords=QuestieOcto.DatabaseAPI:GetObjectCoords(id) },objectiveState) end local function AddActiveNodes(nodes,mapID) if not QuestieOcto.Objectives.ready then return end for questID,state in pairs(QuestieOcto.QuestLog.active or {}) do local q=QuestieOcto.QuestModel:Get(questID) if q then if state.complete then for _,id in pairs(q.finishes.creature or {}) do local node=CreatureNode(questID,"turnin",id,nil,nil) if CoordsContainMap(node.coords,mapID) then AddNode(nodes,node) end end for _,id in pairs(q.finishes.gameObject or {}) do local node=ObjectNode(questID,"turnin",id,nil,nil) if CoordsContainMap(node.coords,mapID) then AddNode(nodes,node) end end else local resolved=QuestieOcto.Objectives.byQuest[questID] if resolved then for _,src in pairs(resolved.creature or {}) do if not src.complete then local node=CreatureNode(questID,"objectiveCreature",src.id,nil,nil,src) if CoordsContainMap(node.coords,mapID) then AddNode(nodes,node) end end end for _,src in pairs(resolved.gameObject or {}) do if not src.complete then local node=ObjectNode(questID,"objectiveObject",src.id,nil,nil,src) if CoordsContainMap(node.coords,mapID) then AddNode(nodes,node) end end end for _,item in pairs(resolved.item or {}) do if not item.complete then for _,src in pairs(item.sources or {}) do local node if src.kind=="creature" then node=CreatureNode(questID,"objectiveItemSource",src.id,item.itemID,src.chance,item) else node=ObjectNode(questID,"objectiveItemSource",src.id,item.itemID,src.chance,item) end if node and CoordsContainMap(node.coords,mapID) then AddNode(nodes,node) end end end end end end end end end local function StarterTouchesMap(q,mapID) for _,id in pairs(q.starts.creature or {}) do 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 if CoordsContainMap(QuestieOcto.DatabaseAPI:GetObjectCoords(id),mapID) then return true end end for _,itemID in pairs(q.starts.item or {}) do local sources=QuestieOcto.DatabaseAPI:GetItemSources(itemID) if sources then for id,chance in pairs(sources.Creature or {}) do if QuestieOcto.ItemStarts:IsPositiveDropChance(chance) and CoordsContainMap(QuestieOcto.DatabaseAPI:GetCreatureCoords(id),mapID) then return true end end for id,chance in pairs(sources.GameObject or {}) do if QuestieOcto.ItemStarts:IsPositiveDropChance(chance) and CoordsContainMap(QuestieOcto.DatabaseAPI:GetObjectCoords(id),mapID) then return true end end end end return false end local function AddAvailableQuestNodes(nodes,q,mapID) for _,id in pairs(q.starts.creature or {}) do local node=CreatureNode(q.id,"available",id,nil,nil) if CoordsContainMap(node.coords,mapID) then AddNode(nodes,node) end end for _,id in pairs(q.starts.gameObject or {}) do local node=ObjectNode(q.id,"available",id,nil,nil) if CoordsContainMap(node.coords,mapID) then AddNode(nodes,node) end end for _,itemID in pairs(q.starts.item or {}) do local sources=QuestieOcto.DatabaseAPI:GetItemSources(itemID) if sources then for id,chance in pairs(sources.Creature or {}) do if QuestieOcto.ItemStarts:IsPositiveDropChance(chance) then local node=CreatureNode(q.id,"itemStart",id,itemID,chance) if CoordsContainMap(node.coords,mapID) then AddNode(nodes,node) Z.stats.itemSources=Z.stats.itemSources+1 end end end for id,chance in pairs(sources.GameObject or {}) do if QuestieOcto.ItemStarts:IsPositiveDropChance(chance) then local node=ObjectNode(q.id,"itemStart",id,itemID,chance) if CoordsContainMap(node.coords,mapID) then AddNode(nodes,node) Z.stats.itemSources=Z.stats.itemSources+1 end end end end end end local function DependenciesReady() return QuestieOcto.DatabaseAPI:IsReady() and QuestieOcto.Completion.ready and QuestieOcto.QuestLog.snapshot and QuestieOcto.Objectives.ready end function Z:Start(mapID) mapID=tonumber(mapID or QuestieOcto.API:GetBestMapForPlayer()) if not mapID or not DependenciesReady() then return end self.requestedMapID=mapID self.stats.requests=(self.stats.requests or 0)+1 self.generation=self.generation+1 local generation=self.generation self.running=true self.ready=false self.mapID=mapID local requests=self.stats.requests or 0 local cancelled=self.stats.cancelled or 0 self.stats={scanned=0,candidates=0,available=0,nodes=0,itemSources=0,requests=requests,cancelled=cancelled,indexed=false,msHint="priority"} local ids=nil local indexed=false if QuestieOcto.MapCandidateIndex and QuestieOcto.MapCandidateIndex:HasMap(mapID) then ids=QuestieOcto.MapCandidateIndex:Get(mapID) indexed=true else ids=QuestieOcto.DatabaseAPI:GetQuestIDs() end self.stats.indexed=indexed local nodes={} AddActiveNodes(nodes,mapID) local pos=1 local function step() if generation~=Z.generation then Z.stats.cancelled=(Z.stats.cancelled or 0)+1 return end -- This pass is deliberately bounded so a first map visit cannot monopolize a Vanilla frame. -- Most quests fail the cheap starter-map test and never run eligibility. local count=0 local batch=indexed and 400 or 160 while pos<=table.getn(ids) and count