vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
---------------------------------------------------------------------------------------------------
--Name: QuestieQuest
--Description: Handles all the quest related functions
---------------------------------------------------------------------------------------------------
--///////////////////////////////////////////////////////////////////////////////////////////////--
---------------------------------------------------------------------------------------------------
--Local Vars
---------------------------------------------------------------------------------------------------
local QuestieHashCache = {};
local LastNrOfEntries = 0;
local CachedIds = {};
local QuestieQuestHashCache = {};
local QGet_TitleText = GetTitleText;
local QGet_QuestLogTitle = GetQuestLogTitle;
local QGet_NumQuestLeaderBoards = GetNumQuestLeaderBoards;
local QGet_QuestLogLeaderBoard = GetQuestLogLeaderBoard;
local QGet_QuestLogQuestText = GetQuestLogQuestText;
local QGet_NumQuestLogEntries = GetNumQuestLogEntries;
local QGet_QuestLogSelection = GetQuestLogSelection;
local QSelect_QuestLogEntry = SelectQuestLogEntry;
---------------------------------------------------------------------------------------------------
--Global Vars
---------------------------------------------------------------------------------------------------
LastQuestLogHashes = nil;
LastQuestLogCount = 0;
lastObjectives = nil;
QuestAbandonOnAccept = nil;
QuestAbandonWithItemsOnAccept = nil;
QuestRewardCompleteButton = nil;
QuestProgressCompleteButton = nil;
QuestDetailAcceptButton = nil;
Questie.lastCollapsedCount = 0;
Questie.collapsedThisRun = false;
QUESTIE_LAST_UPDATECACHE = GetTime();
---------------------------------------------------------------------------------------------------
--Blizzard Hook: Quest Abandon On Accept
---------------------------------------------------------------------------------------------------
QuestAbandonOnAccept = StaticPopupDialogs["ABANDON_QUEST"].OnAccept;
StaticPopupDialogs["ABANDON_QUEST"].OnAccept = function()
    local qName = GetAbandonQuestName();
    for hash,v in pairs(QuestieCachedQuests) do
        if v["questName"] == qName then
            QuestieSeenQuests[hash] = -1;
            QuestieCachedQuests[hash] = nil;
            QuestieHandledQuests[hash] = nil;
            --Questie:debug_Print("Quest:QuestAbandonOnAccept: [questTitle: "..qName.."] | [Hash: "..hash.."]");
            RemoveCrazyArrow(hash);
        end
    end
    QuestAbandonOnAccept();
end
---------------------------------------------------------------------------------------------------
--Blizzard Hook: Quest Abandon With Items On Accept
---------------------------------------------------------------------------------------------------
QuestAbandonWithItemsOnAccept = StaticPopupDialogs["ABANDON_QUEST_WITH_ITEMS"].OnAccept;
StaticPopupDialogs["ABANDON_QUEST_WITH_ITEMS"].OnAccept = function()
    local qName = GetAbandonQuestName();
    for hash,v in pairs(QuestieCachedQuests) do
        if v["questName"] == qName then
            QuestieSeenQuests[hash] = -1;
            QuestieCachedQuests[hash] = nil;
            QuestieHandledQuests[hash] = nil;
            --Questie:debug_Print("Quest:QuestAbandonWithItemsOnAccept: [questTitle: "..qName.."] | [Hash: "..hash.."]");
            RemoveCrazyArrow(hash);
        end
    end
    QuestAbandonWithItemsOnAccept();
end
---------------------------------------------------------------------------------------------------
--This function saves the number of slots that get freed when turning in a quest
--to the QuestieCachedQuests table, so it can be read in the next function.
--It is called upon the QUEST_PROGRESS event.
---------------------------------------------------------------------------------------------------
function Questie:OnQuestProgress()
    local questTitle = QGet_TitleText();
    local _, _, qlevel, qName = string.find(questTitle, "%[(.+)%] (.+)");
    if qName == nil then
        qName = QGet_TitleText();
    end
    for hash,v in pairs(QuestieCachedQuests) do
        if v["questName"] == qName then
            v["numQuestItems"] = GetNumQuestItems();
        end
    end
end
---------------------------------------------------------------------------------------------------
--This function is used by the next two hooks.
--It checks if the conditions for completing a quest are met:
--    1. If there is a choice available, the player must have chosen.
--    2. There must be (numRewards-numItems) free slots in the invetory.
--If both conditions are met, the quest is marked as finished, otherwise
--false is returned (return value is currently unused).
---------------------------------------------------------------------------------------------------
function Questie:MarkQuestAsFinished()
    local rewards = GetNumQuestRewards();
    local choices = GetNumQuestChoices();
    -- Filter condition: choices available but no choice made.
    if ( QuestFrameRewardPanel.itemChoice == 0 and choices > 0 ) then
        return false;
    end
    -- If there are rewards and choices, we need 1 more space.
    if choices > 0 then
        rewards = rewards + 1;
    end
    -- Get quest name and compare it against values in cache
    local questTitle = QGet_TitleText();
    local _, _, qlevel, qName = string.find(questTitle, "%[(.+)%] (.+)");
    if qName == nil then
        qName = QGet_TitleText();
    end
    for hash,v in pairs(QuestieCachedQuests) do
        if v["questName"] == qName then
            if (not v["numQuestItems"]) then
                Questie:debug_Print("ERROR: QuestRewardCompleteButton: [questTitle: "..qName.."] | [Hash: "..hash.."]:\n    Failed to read numQuestItems from SavedVariables")
            end
            -- Filter condition: not enough space in inventory.
            if (rewards > 0) and (Questie:CheckPlayerInventory() < (rewards - (v["numQuestItems"] or 0))) then
                return false;
            end
            -- All checks passed, mark quest as finished and remove it from cache
            QuestieSeenQuests[hash] = 1;
            QuestieCompletedQuestMessages[qName] = 1;
            QuestieCachedQuests[hash] = nil;
            QuestieHandledQuests[hash] = nil;
            Questie:debug_Print("Quest:QuestRewardCompleteButton: [questTitle: "..qName.."] | [Hash: "..hash.."]");
            RemoveCrazyArrow(hash);
            return true;
        end
    end
end
---------------------------------------------------------------------------------------------------
--Blizzard Hook: GetQuestReward function
--This hook marks a quest as finished in Questies DB if it can be finished.
--The call to the hooked function then finishes the quest.
--It is needed in addition to the next hook, because it is used by EQL3
--when its "Auto Complete Quests"-option is enabled.
---------------------------------------------------------------------------------------------------
QGetQuestReward = GetQuestReward;
GetQuestReward = function(choice)
    Questie:MarkQuestAsFinished();
    QGetQuestReward(choice);
end
---------------------------------------------------------------------------------------------------
--Blizzard Hook: Quest Reward Complete Button
--This hook marks a quest as finished in Questies DB if it can be finished.
--The call to the hooked function then finishes the quest.
---------------------------------------------------------------------------------------------------
QuestRewardCompleteButton = QuestRewardCompleteButton_OnClick;
QuestRewardCompleteButton_OnClick = function()
    Questie:MarkQuestAsFinished();
    QuestRewardCompleteButton();
end
---------------------------------------------------------------------------------------------------
--Blizzard Hook: Quest Progress Accept Button
---------------------------------------------------------------------------------------------------
QuestDetailAcceptButton = QuestDetailAcceptButton_OnClick;
function QuestDetailAcceptButton_OnClick()
    Questie:CheckQuestLogStatus();
    QuestDetailAcceptButton();
end
---------------------------------------------------------------------------------------------------
--Matches a looted item to quest items that are contained in the QuestieCachedQuests table
---------------------------------------------------------------------------------------------------
function Questie:DetectQuestItem(itemName)
    for k, v in pairs(QuestieCachedQuests) do
        local num = v["leaderboards"]
        for i=1,num do
            desc = v["objective"..i]["desc"]
            if (desc) then
                local  _, _, questItem, itemHave, itemNeed = string.find(desc, "(.+)%: (%d+)/(%d+)");
                if itemName == questItem and itemHave ~= itemNeed then
                    --Questie:debug_Print("Quest:DetectQuestItem: TRUE");
                    --Questie:debug_Print("Quest:DetectQuestItem: [itemName: "..itemName.."] | [questItem: "..questItem.."] | [itemHave: "..itemHave.."] | [itemNeed: "..itemNeed.."]");
                    return true
                else
                    --Questie:debug_Print("Quest:DetectQuestItem: FALSE");
                    return false
                end
            end
        end
    end
end
---------------------------------------------------------------------------------------------------
--Parses loot messages then passes item to DetectQuestItem for verification
---------------------------------------------------------------------------------------------------
function Questie:ParseQuestLoot(arg1)
    local msg, item, loot
    if string.find(arg1, "(You receive loot%:) (.+)") then
        _, _, msg, item = string.find(arg1, "(You receive loot%:) (.+)");
    elseif string.find(arg1, "(Received item%:) (.+)") then
        _, _, msg, item = string.find(arg1, "(Received item%:) (.+)");
    elseif string.find(arg1, "(You receive item%:) (.+)") then
        _, _, msg, item = string.find(arg1, "(You receive item%:) (.+)");
    end
    if item then
        _, _, loot = string.find(item, "%[(.+)%].+");
        if Questie:DetectQuestItem(loot) then
            --Questie:debug_Print("Quest:ParseQuestLoot --> [POST] Quest Loot: [ "..loot.." ] was found.");
            Questie:CheckQuestLogStatus();
        else
            --Questie:debug_Print("Quest:ParseQuestLoot --> [POST] Quest Loot: [ "..loot.." ] is not a quest item.");
        end
    end
end
---------------------------------------------------------------------------------------------------
--Used to make sure the players inventory isn't full before auto-completing quest.
---------------------------------------------------------------------------------------------------
function Questie:CheckPlayerInventory()
    local totalSlots, usedSlosts, availableSlots;
    local totalSlots = 0;
    local usedSlots = 0;
    for bag = 0, 4 do
        local size = GetContainerNumSlots(bag);
        if (size and size > 0) then
                    totalSlots = totalSlots + size;
            for slot = 1, size do
                if (GetContainerItemInfo(bag, slot)) then
                    usedSlots = usedSlots + 1;
                end
            end
        end
    end
    availableSlots = totalSlots - usedSlots;
    return availableSlots
end
---------------------------------------------------------------------------------------------------
--Finishes a quest and performs a recrusive check to make sure all the required quests that come
--before it are also finsihed and recorded in the players QuestieSeenQuests. It will also clear
--any redundant quest tracking data and make sure a quest that is in a players log isn't
--accidently marked finished. When ever this function is run it will also remove invalid tracker
--data when it doesn't find a matching hash in the QuestieSeenQuests table. This sometimes
--happens when a player starts a quest chain.
---------------------------------------------------------------------------------------------------
function Questie:finishAndRecurse(questhash)
    local QSQ = QuestieSeenQuests;
    local QCQ = QuestieCachedQuests;
    local QHM = QuestieHashMap;
    --If it finds a completed quest with left over cached data, then the cached
    --data gets cleared.
    if (QSQ[questhash] == 1) then
        if (QCQ[questhash]) then
            QCQ[questhash] = nil;
        end
    end
    --This loop checks to make sure a quest is finished before marking it complete. It then
    --recursively checks all required quests before it and marks those as complete as well. It
    --also checks each one to make sure we aren't marking a seen quest finished.
    if (QSQ[questhash] == 0) and (QCQ[questhash]) then
        if ((QCQ[questhash]["leaderboards"] == 0 or QCQ[questhash]["leaderboards"] == 1) or (QCQ[questhash]["isComplete"] == 1)) then
            QSQ[questhash] = 1;
            QCQ[questhash] = nil;
            RemoveCrazyArrow(questhash);
        else
            local req = nil;
            if QHM[questhash] then
                req = QHM[questhash]['rq'];
            end
            if req and QSQ[req] ~= 1 then
                Questie:finishAndRecurse(req);
            end
            return;
        end
    --This loop allows a player to recursively finish a quest and all required quests that comes
    --before it by shift+clicking an icon from one of the maps. It also checks each one to make
    --sure we aren't marking a seen quest finished.
    elseif ((QSQ[questhash] == nil) and (QCQ[questhash] == nil)) then
        QSQ[questhash] = 1;
        local req = nil;
        if QHM[questhash] then
            req = QHM[questhash]['rq'];
        end
        if req and QSQ[req] ~= 1 then
            Questie:finishAndRecurse(req);
        else
            return;
        end
    end
    --This trolls through all cached data to make sure it stays cleaned up.
    local index = 0;
    for i,v in pairs(QCQ) do
        if QSQ[i] == 1 then
            QCQ[i] = nil;
            index = index + 1;
        end
    end
end
---------------------------------------------------------------------------------------------------
--Checks the players quest log upon login or ReloadUI to make sure QuestieMapNotes and
--QuestieCachedQuests get pre-populated with cache data before normal CheckLog functions are run.
--This is especially important if this data isn't already in the WoW game clients local cache.
---------------------------------------------------------------------------------------------------
function Questie:UpdateGameClientCache(force)
    if (IsQuestieActive == false) then return; end
    Questie:debug_Print();
    Questie:debug_Print("****************| Running Quest:UpdateGameClientCache |****************");
    local prevQuestLogSelection = QGet_QuestLogSelection();
    local id = 1;
    local qc = 0;
    local nEntry, nQuests = QGet_NumQuestLogEntries();
    while qc < nQuests do
        local questName, level, _, isHeader, isCollapsed, _ = QGet_QuestLogTitle(id);
        if not isHeader and not isCollapsed then
            QSelect_QuestLogEntry(id);
            local questText, objectiveText = QGet_QuestLogQuestText();
            local hash = Questie:getQuestHash(questName, level, objectiveText);
            if (force) then
                Questie:AddQuestToMap(hash, true);
                Questie:debug_Print("Quest:UpdateGameClientCache --> Questie:AddQuestToMap(forced): [Name: "..questName.."]");
                QuestieTracker:addQuestToTrackerCache(hash, id, level);
                Questie:debug_Print("Quest:UpdateGameClientCache --> Questie:addQuestToTrackerCache(forced): [Hash: "..hash.."]");
            end
            for index=1, QGet_NumQuestLeaderBoards(id) do
                local desc = QGet_QuestLogLeaderBoard(index, id);
                local objectiveName = desc;
                local splitIndex = findLast(objectiveName, ":");
                if splitIndex ~= nil then
                    objectiveName = string.sub(objectiveName, 1, splitIndex-1);
                    if (string.find(objectiveName, " slain")) then
                        objectiveName = string.sub(objectiveName, 1, string.len(objectiveName)-6);
                    end
                end
                if (not LastQuestLogHashes and not force) or (QuestieHandledQuests[hash] and QuestieHandledQuests[hash]["objectives"] and QuestieHandledQuests[hash]["objectives"][index]["name"] ~= objectiveName) then
                    Questie:AddQuestToMap(hash);
                    Questie:debug_Print("Quest:UpdateGameClientCache --> Questie:AddQuestToMap(): [Name: "..QuestieHandledQuests[hash]["objectives"][index]["name"].."]");
                    QuestieTracker:addQuestToTrackerCache(hash, id, level);
                    Questie:debug_Print("Quest:UpdateGameClientCache --> Questie:addQuestToTrackerCache(): [Hash: "..hash.."]");
                end
            end
        end
        if not isHeader then
            qc = qc + 1;
        end
        id = id + 1;
    end
    QSelect_QuestLogEntry(prevQuestLogSelection);
end
---------------------------------------------------------------------------------------------------
--Checks the players quest log
---------------------------------------------------------------------------------------------------
function Questie:CheckQuestLog()
    --LastQuestLogHashes should always be nil upon Login or a ReloadUI - do these checks
    if (not LastQuestLogHashes) then
        Questie:debug_Print();
        Questie:debug_Print("****************| Running [PRE] Quest:CheckQuestLog |****************");
        --Clears abandoned quests
        for k, v in pairs(QuestieSeenQuests) do
            if (QuestieSeenQuests[k] == -1) then
                Questie:RemoveQuestFromMap(k);
                QuestieCachedQuests[k] = nil;
                QuestieSeenQuests[k] = nil;
                QUEST_WATCH_LIST[k] = nil;
                Questie:debug_Print("Quest:CheckQuestLog: Found abandoned quest in QuestDB - Removed: [Hash: "..k.."]");
            end
        end
        --Clears cached data
        for k, v in pairs(QuestieCachedQuests) do
            if QuestieSeenQuests[k] == 1 then
                Questie:RemoveQuestFromMap(k);
                QuestieCachedQuests[k] = nil;
                Questie:debug_Print("Quest:CheckQuestLog: Found cached data for a finished quest - Removed: [Hash: "..k.."]");
            end
        end
        LastQuestLogHashes, LastQuestLogCount = Questie:AstroGetAllCurrentQuestHashesAsMeta();
        for k, v in pairs(LastQuestLogHashes) do
            --If a quest is found in the log and for some reason it's set as finished (1), or
            --missing all together (nil), reset its status back to active (0).
            if QuestieSeenQuests[k] == 1 or QuestieSeenQuests[k] == nil then
                QuestieSeenQuests[k] = 0;
                Questie:debug_Print("Quest:CheckQuestLog: --> Quest found in QuestLog marked complete - Fixed: [Hash: "..v["hash"].."]");
            end
            --This "double-tap" ensures quest data is inserted into the cache
            if (QuestieCachedQuests[v["hash"]] == nil) or (QuestieHandledQuests[v["hash"]] == nil) then
                QuestieTracker:addQuestToTrackerCache(v["hash"], v["logId"], v["level"]);
                Questie:AddQuestToMap(v["hash"]);
                Questie:debug_Print("Quest:CheckQuestLog: --> Add quest to Tracker and MapNotes caches: [Hash: "..v["hash"].."]");
            end
        end
        --Removes active quests from QuestDB if it's not active in the QuestLog
        for k, v in pairs(QuestieSeenQuests) do
            if QuestieSeenQuests[k] == 0 and LastQuestLogHashes[k] == nil then
                QuestieCachedQuests[k] = nil;
                QuestieSeenQuests[k] = nil;
                QUEST_WATCH_LIST[k] = nil;
                Questie:debug_Print("Quest:CheckQuestLog: --> Quest found in QuestDB not in QuestLog - Removed: [Hash: "..k.."]");
            end
        end
        QUESTIE_LAST_UPDATE_FINISHED = GetTime();
        return;
    end
    local CheckLogTime = GetTime();
    local Quests, QuestsCount = Questie:AstroGetAllCurrentQuestHashesAsMeta();
    MapChanged = false;
    delta = {};
    if (QuestsCount > LastQuestLogCount) then
        for k, v in pairs(Quests) do
            if (Quests[k] and LastQuestLogHashes[k]) then
            else
                if (Quests[k]) then
                    v["deltaType"] = 1;
                    table.insert(delta, v);
                else
                    v["deltaType"] = 0;
                    table.insert(delta, v);
                end
            end
        end
    else
        for k, v in pairs(LastQuestLogHashes) do
            if (Quests[k] and LastQuestLogHashes[k]) then
            else
                if (Quests[k]) then
                    v["deltaType"] = 1;
                    table.insert(delta, v);
                else
                    v["deltaType"] = 0;
                    table.insert(delta, v);
                end
            end
        end
    end
    for k, v in pairs(delta) do
        Questie:debug_Print();
        Questie:debug_Print("****************| Running [POST] Quest:CheckQuestLog |**************** ");
        Questie:debug_Print("Quest:CheckQuestLog: UPON ENTER: [QuestsCount: "..QuestsCount.."] | [LastCount: "..LastQuestLogCount.."]");
        if (v["deltaType"] == 1) then
            Questie:AddQuestToMap(v["hash"]);
            --This adds a quest to the cache
            if (QuestieSeenQuests[v["hash"]] == nil) then
                QuestieSeenQuests[v["hash"]] = 0;
                QuestieTracker:addQuestToTrackerCache(v["hash"], v["logId"], v["level"]);
                Questie:debug_Print("Quest:CheckQuestLog: --> Add quest to Tracker and MapNotes caches: [Hash: "..v["hash"].."]");
                RemoveCrazyArrow(v["hash"]);
                if (AUTO_QUEST_WATCH == "1") then
                    AddQuestWatch(v["logId"]);
                end
            end
            MapChanged = true;
        elseif not Questie.collapsedThisRun then
            Questie:RemoveQuestFromMap(v["hash"]);
            --This clears cache of finished quests
            if (QuestieSeenQuests[v["hash"]] == 1) then
                QuestieTracker:removeQuestFromTracker(v["hash"]);
                QUEST_WATCH_LIST[v["hash"]] = nil;
                Questie:finishAndRecurse(v["hash"]);
                Questie:debug_Print("Quest:CheckQuestLog: --> Quest:finishAndRecurse() [Hash: "..v["hash"].."]");
                if (not QuestieCompletedQuestMessages[v["name"]]) then
                    QuestieCompletedQuestMessages[v["name"]] = 0;
                end
            --This clears cache of abandoned quests
            elseif (QuestieSeenQuests[v["hash"]] == -1) then
                QuestieTracker:removeQuestFromTracker(v["hash"]);
                QuestieCachedQuests[v["hash"]] = nil;
                QuestieSeenQuests[v["hash"]] = nil;
                QUEST_WATCH_LIST[v["hash"]] = nil;
                Questie:debug_Print("Quest:CheckQuestLog: clear abandoned quest: [Hash: "..v["hash"].."]");
            end
            --Cleans cached data
            for k, v in pairs(QuestieCachedQuests) do
                if QuestieSeenQuests[k] == 1 then
                    QuestieCachedQuests[k] = nil;
                    Questie:debug_Print("Quest:CheckQuestLog: Cleaned Quest Cache: [Hash: "..k.."]");
                end
            end
            if lastObjectives and lastObjectives[v["hash"]] then
                Questie:debug_Print("Quest:CheckQuestLog: lastObjectives update [Hash: "..v["hash"].."]");
                lastObjectives = {};
            end
            MapChanged = true;
        end
    end
    delta = nil;
    LastQuestLogHashes = Quests;
    LastQuestLogCount = QuestsCount;
    if (MapChanged == true) then
        Questie:debug_Print("Quest:CheckQuestLog: QuestLog Changed --> Questie:RefreshQuestStatus()");
        Questie:RefreshQuestStatus();
        QUESTIE_LAST_UPDATE_FINISHED = GetTime();
        Questie:debug_Print("Quest:CheckQuestLog: UPON EXIT: [QuestsCount: "..QuestsCount.."] | [LastCount: "..LastQuestLogCount.."]");
        return true;
    else
        Questie:debug_Print("Quest:CheckQuestLog: NO CHANGE --> Refresh Notes and Tracker");
        Questie:AddEvent("SYNCLOG", 0.2);
        Questie:AddEvent("DRAWNOTES", 0.4);
        Questie:AddEvent("TRACKER", 0.6);
        QUESTIE_LAST_UPDATE_FINISHED = GetTime();
        return nil;
    end
end
---------------------------------------------------------------------------------------------------
--Adds or updates all active objectives in the questlog to the lastObjectives table
---------------------------------------------------------------------------------------------------
function Questie:UpdateQuests(force)
    if (not lastObjectives) then
        lastObjectives = {};
        Questie:UpdateQuestsInit();
        return;
    end
    local UpdateQuestsTime = GetTime();
    local ZonesChecked = 0;
    local CurrentZone = GetZoneText();
    local numEntries, numQuests = QGet_NumQuestLogEntries();
    local change = Questie:UpdateQuestInZone(CurrentZone);
    local i = 1;
    local qc = 0;
    ZonesChecked = ZonesChecked + 1;
    if (not change) then
        change = Questie:UpdateQuestInZone(GetMinimapZoneText());
        ZonesChecked = ZonesChecked + 1;
    end
    if (not change or force) then
        while qc < numQuests do
            local q, level, questTag, isHeader, isCollapsed, isComplete = QGet_QuestLogTitle(i);
            if (isHeader and q ~= CurrentZone) then
                local c = Questie:UpdateQuestInZone(q, force);
                ZonesChecked = ZonesChecked + 1;
                change = c;
                if (c and not force)then
                    break;
                end
            end
            if not isHeader then
                qc = qc + 1;
            end
            i = i + 1;
        end
    else
    end
    return change;
end
---------------------------------------------------------------------------------------------------
--Updates all active objectives in a zone then updates the lastObjectives table
---------------------------------------------------------------------------------------------------
function Questie:UpdateQuestInZone(Zone, force)
    local numEntries, numQuests = QGet_NumQuestLogEntries();
    local foundChange = nil;
    local ZoneFound = nil;
    local QuestsChecked = 0;
    local i = 1;
    local qc = 0;
    local prevQuestLogSelection = QGet_QuestLogSelection();
    while qc < numQuests do
        local q, level, questTag, isHeader, isCollapsed, isComplete = QGet_QuestLogTitle(i);
        if (ZoneFound and isHeader) then
            break;
        end
        if (isHeader and q == Zone) then
            ZoneFound = true;
        end
        if not isHeader and ZoneFound then
            QuestsChecked = QuestsChecked + 1;
            QSelect_QuestLogEntry(i);
            local count =  QGet_NumQuestLeaderBoards();
            local questText, objectiveText = QGet_QuestLogQuestText();
            local hash = Questie:getQuestHash(q, level, objectiveText);
            if QuestieHashCache[q] == nil then QuestieHashCache[q] = {}; end
            QuestieHashCache[q][hash] = GetTime();
            if not lastObjectives[hash] then
                lastObjectives[hash] = {};
            end
            local Refresh = nil;
            for obj = 1, count do
                if (not lastObjectives[hash][obj]) then
                    lastObjectives[hash][obj] = {};
                end
                local desc, typ, done = QGet_QuestLogLeaderBoard(obj);
                if(lastObjectives[hash][obj].desc == desc and lastObjectives[hash][obj].typ == typ and lastObjectives[hash][obj].done == done) then
                elseif(lastObjectives[hash][obj].done ~= done) then
                    Refresh = true;
                    foundChange = true;
                else
                    foundChange = true;
                end
                lastObjectives[hash][obj].desc = desc;
                lastObjectives[hash][obj].typ = typ;
                lastObjectives[hash][obj].done = done;
            end
            if (Refresh) then
                Questie:AddQuestToMap(hash, true);
            end
            if (foundChange and QuestieConfig.trackerEnabled == true) then
                if (QuestieCachedQuests[hash]) then
                    QuestieTracker:updateTrackerCache(hash, i, level);
                end
            end
        end
        if (foundChange and not force) then
            break;
        end
        if not isHeader then
            qc = qc + 1;
        end
        i = i + 1;
    end
    QSelect_QuestLogEntry(prevQuestLogSelection);
    return foundChange;
end
---------------------------------------------------------------------------------------------------
--Adds all active objectives from all quests in the questlog to the lastObjectives table
---------------------------------------------------------------------------------------------------
function Questie:UpdateQuestsInit()
    local numEntries, numQuests = QGet_NumQuestLogEntries();
    local i = 1;
    local qc = 0;
    local prevQuestLogSelection = QGet_QuestLogSelection();
    while qc < numQuests do
        local q, level, questTag, isHeader, isCollapsed, isComplete = QGet_QuestLogTitle(i);
        if not isHeader then
            QSelect_QuestLogEntry(i);
            local count =  QGet_NumQuestLeaderBoards();
            local questText, objectiveText = QGet_QuestLogQuestText();
            local hash = Questie:getQuestHash(q, level, objectiveText);
            if not lastObjectives[hash] then
                lastObjectives[hash] = {};
            end
            for obj = 1, count do
                if (not lastObjectives[hash][obj]) then
                    lastObjectives[hash][obj] = {};
                end
                lastObjectives[hash][obj].desc = desc;
                lastObjectives[hash][obj].typ = typ;
                lastObjectives[hash][obj].done = done;
            end
            qc = qc + 1;
        end
        i = i + 1;
    end
    QSelect_QuestLogEntry(prevQuestLogSelection);
end
---------------------------------------------------------------------------------------------------
--Astrolabe functions
---------------------------------------------------------------------------------------------------
function Questie:AstroGetAllCurrentQuestHashes(print)
    local hashes = {};
    local numEntries, numQuests = QGet_NumQuestLogEntries();
    local i = 1;
    local qc = 0;
    if (print) then
        --Questie:debug_Print("Quest:AstroGetAllCurrentQuestHashes: Listing all current quests");
    end
    local prevQuestLogSelection = QGet_QuestLogSelection();
    while qc < numQuests do
        local q, level, questTag, isHeader, isCollapsed, isComplete = QGet_QuestLogTitle(i);
        if not isHeader then
            QSelect_QuestLogEntry(i);
            local count =  QGet_NumQuestLeaderBoards();
            local questText, objectiveText = QGet_QuestLogQuestText();
            local quest = {};
            quest["name"] = q;
            quest["level"] = level;
            local hash = Questie:getQuestHash(q, level, objectiveText);
            quest["hash"] = hash;
            if(IsAddOnLoaded("URLCopy") and print) then
                Questie:debug_Print("        "..q,URLCopy_Link(quest["hash"]));
            elseif(print) then
                Questie:debug_Print("        "..q,quest["hash"]);
            end
            table.insert(hashes, quest);
            qc = qc + 1;
        else
            if (print) then
                Questie:debug_Print("    Zone:", q);
            end
        end
        i = i + 1;
    end
    QSelect_QuestLogEntry(prevQuestLogSelection);
    if (print) then
        --Questie:debug_Print("Quest:AstroGetAllCurrentQuestHashes: End of all current quests");
    end
    return hashes;
end
---------------------------------------------------------------------------------------------------
function Questie:AstroGetAllCurrentQuestHashesAsMeta(print)
    local agacqhamtime = GetTime();
    local hashes = {};
    local Count = 0;
    local numEntries, numQuests = QGet_NumQuestLogEntries();
    local collapsedCount = 0;
    local i = 1;
    local qc = 0;
    Questie.collapsedThisRun = false;
    local prevQuestLogSelection = QGet_QuestLogSelection();
    while qc < numQuests do
        local q, level, questTag, isHeader, isCollapsed, isComplete = QGet_QuestLogTitle(i);
        if isCollapsed then collapsedCount = collapsedCount + 1; end
        if not isHeader then
            QSelect_QuestLogEntry(i);
            local count =  QGet_NumQuestLeaderBoards();
            local questText, objectiveText = QGet_QuestLogQuestText();
            local hash = Questie:getQuestHash(q, level, objectiveText);
            if hash >= 0 then
                hashes[hash] = {};
                hashes[hash]["hash"] = hash;
                hashes[hash]["name"] = q;
                hashes[hash]["level"] = level;
                hashes[hash]["logId"] = i;
                if(IsAddOnLoaded("URLCopy") and print)then
                    Questie:debug_Print("        "..q,URLCopy_Link(quest["hash"]));
                elseif(print) then
                    Questie:debug_Print("        "..q,quest["hash"]);
                end
            end
            qc = qc + 1;
        else
            if (print) then
                Questie:debug_Print("    Zone:", q);
            end
        end
        i=i+1
    end
    QSelect_QuestLogEntry(prevQuestLogSelection);
    if (print) then
        --Questie:debug_Print("Quest:AstroGetAllCurrentQuestHashesAsMeta: End of all current quests");
    end
    if not (collapsedCount == Questie.lastCollapsedCount) then
        Questie.lastCollapsedCount = collapsedCount;
        Questie.collapsedThisRun = true;
    end
    --Questie:debug_Print("Quest:AstroGetAllCurrentQuestHashesAsMeta --> Getting all hashes took: ["..tostring((GetTime()- agacqhamtime)*1000).."ms]");
    return hashes, numQuests;
end
---------------------------------------------------------------------------------------------------
function Questie:AstroGetFinishedQuests()
    numEntries, numQuests = QGet_NumQuestLogEntries();
    local FinishedQuests = {};
    local i = 1;
    local qc = 0;
    local prevQuestLogSelection = QGet_QuestLogSelection();
    while qc < numQuests do
        local q, level, questTag, isHeader, isCollapsed, isComplete = QGet_QuestLogTitle(i);
        if not isHeader then
            QSelect_QuestLogEntry(i);
            local count =  QGet_NumQuestLeaderBoards();
            local questText, objectiveText = QGet_QuestLogQuestText();
            Done = true;
            for obj = 1, count do
                local desc, typ, done = QGet_QuestLogLeaderBoard(obj);
                if not done then
                    Done = nil;
                end
            end
            if(Done) then
                local hash = Questie:getQuestHash(q, level, objectiveText);
                --Questie:debug_Print("AstroGetFinishedQuests: [Hash: "..hash.."] | [Quest: "..q.."] | [Level: "..level.."]");
                table.insert(FinishedQuests, hash);
            end
            qc = qc + 1;
        end
        i = i + 1;
    end
    QSelect_QuestLogEntry(prevQuestLogSelection);
    return FinishedQuests;
end
---------------------------------------------------------------------------------------------------
function Questie:GetQuestObjectivePaths(questHash)
    local prevQuestLogSelection = QGet_QuestLogSelection();
    local questLogID = Questie:GetQuestIdFromHash(questHash);
    QSelect_QuestLogEntry(questLogID);
    local count = QGet_NumQuestLeaderBoards();
    local objectivePaths = {};
    for i = 1, count do
        local desc, type, done = QGet_QuestLogLeaderBoard(i);
        local typeFunctions = {
            ['item'] = GetItemLocations,
            ['event'] = GetEventLocations,
            ['monster'] = GetMonsterLocations,
            ['object'] = GetObjectLocations,
            ['reputation'] = GetReputationLocations
        };
        local typeFunction = typeFunctions[type];
        if typeFunction ~= nil then
            local objectiveName = desc;
            local splitIndex = findLast(objectiveName, ":");
            if splitIndex ~= nil then
                objectiveName = string.sub(objectiveName, 1, splitIndex-1);
                if (string.find(objectiveName, " slain")) then
                    objectiveName = string.sub(objectiveName, 1, string.len(objectiveName)-6);
                end
            end
            locations = typeFunction(objectiveName);
            objectivePaths[i] = {};
            objectivePaths[i]['path'] = locations;
            objectivePaths[i]['done'] = done;
            objectivePaths[i]['type'] = type;
            objectivePaths[i]['name'] = objectiveName;
            objectivePaths[i]['desc'] = desc
        end
    end
    QSelect_QuestLogEntry(prevQuestLogSelection);
    return objectivePaths;
end
---------------------------------------------------------------------------------------------------
--Perhaps we should consider removing this function from Questie
---------------------------------------------------------------------------------------------------
function Questie:AstroGetQuestObjectives(questHash)
    local prevQuestLogSelection = QGet_QuestLogSelection();
    local QuestLogID = Questie:GetQuestIdFromHash(questHash);
    local mapid = GetCurrentMapID();
    local q, level, questTag, isHeader, isCollapsed, isComplete = QGet_QuestLogTitle(QuestLogID);
    QSelect_QuestLogEntry(QuestLogID);
    local count =  QGet_NumQuestLeaderBoards();
    local questText, objectiveText = QGet_QuestLogQuestText();
    local AllObjectives = {};
    AllObjectives["QuestName"] = q;
    AllObjectives["objectives"] = {};
    for i = 1, count do
        local desc, typ, done = QGet_QuestLogLeaderBoard(i);
        local typeFunction = AstroobjectiveProcessors[typ];
        if typ == "item" or typ == "monster" or not (typeFunction == nil) then
            local indx = findLast(desc, ":");
            local countless = indx == nil;
            local countstr = "";
            local namestr = desc;
            if not countless then
                countstr = string.sub(desc, indx + 2);
                namestr = string.sub(desc, 1, indx - 1);
            end
            local objectives = typeFunction(q, namestr, countstr, selected, mapid);
            Objective = {};
            local hash = Questie:getQuestHash(q, level, objectiveText);
            for k, v in pairs(objectives) do
                if (AllObjectives["objectives"][v["name"]] == nil) then
                    AllObjectives["objectives"][v["name"]] = {};
                end
                if (not QuestieCachedMonstersAndObjects[hash]) then
                    QuestieCachedMonstersAndObjects[hash] = {};
                end
                if (not QuestieCachedMonstersAndObjects[hash][v["name"]]) then
                    QuestieCachedMonstersAndObjects[hash][v["name"]] = {};
                end
                QuestieCachedMonstersAndObjects[hash][v["name"]].name = v["name"];
                for monster, info in pairs(v['locations']) do
                    local obj = {};
                    obj["mapid"] = info[1];
                    obj["x"] = info[2];
                    obj["y"] = info[3];
                    obj["lootname"] = v["lootname"];
                    obj["type"] = v["type"];
                    obj["done"] = done;
                    obj['objectiveid'] = i;
                    table.insert(AllObjectives["objectives"][v["name"]], obj);
                end
            end
        else
        end
    end
    QSelect_QuestLogEntry(prevQuestLogSelection);
    return AllObjectives;
end
---------------------------------------------------------------------------------------------------
AstroobjectiveProcessors = {
    ['item'] = function(quest, name, amount, selected, mapid)
        local list = {};
        local itemdata = QuestieItems[name];
        --Questie:debug_Print(name);
        if itemdata == nil then
            Questie:debug_Print("Quest:AstroobjectiveProcessors --> ERROR1 PROCESSING: [Quest: "..quest.."] | [Objective: "..name.."] | No [itemdata] found | ID:0");
            itemdata = QuestieItems[name];
        end
        if itemdata then
            for k,v in pairs(itemdata) do
                if k == "locationCount" then
                    local monster = {};
                    monster["name"] = name;
                    monster["locations"] = {};
                    monster["type"] = "loot";
                    for b=1,itemdata['locationCount'] do
                        local loc = itemdata['locations'][b];
                        table.insert(monster["locations"], loc);
                    end
                    table.insert(list, monster);
                elseif k == "drop" then
                    for e,r in pairs(v) do
                        local monster = {};
                        monster["name"] = name;
                        monster["lootname"] = e;
                        monster["locations"] = {};
                        monster["type"] = "loot";
                        for k, pos in pairs(QuestieMonsters[e]['locations']) do
                            table.insert(monster["locations"], pos);
                        end
                        table.insert(list, monster);
                    end
                elseif k == "contained" then
                    for objectName, someNumber in pairs(v) do
                        local monster = {};
                        monster["name"] = name;
                        monster["lootname"] = objectName;
                        monster["locations"] = {};
                        monster["type"] = "object";
                        if QuestieObjects[objectName] then
                            --TODO: handle objects that appear when a mob is killed
                            for k, pos in pairs(QuestieObjects[objectName]['locations']) do
                                table.insert(monster["locations"], pos);
                            end
                            table.insert(list, monster);
                        end
                    end
                elseif k =="locations" then
                else
                    Questie:debug_Print("Quest:AstroobjectiveProcessors --> ERROR2: [Quest: "..quest.."] | [Objective: "..name.."] | ID:1");
                    for s, r in pairs(itemdata) do
                        Questie:debug_Print(s,tostring(r));
                    end
                end
            end
        end
        return list;
    end,
    ['event'] = function(quest, name, amount, selected, mapid)
        local evtdata = QuestieEvents[name];
        local list = {};
        if evtdata == nil then
            Questie:debug_Print("Quest:AstroobjectiveProcessors --> ERROR3 UNKNOWN EVENT: [Quest: "..quest.."] | [Objective: "..name.."] | ID:2");
        else
            for b=1,evtdata['locationCount'] do
                local monster = {};
                monster["name"] = name;
                monster["locations"] = {};
                monster["type"] = "event";
                for b=1,evtdata['locationCount'] do
                    local loc = evtdata['locations'][b];
                    table.insert(monster["locations"], loc);
                end
                table.insert(list, monster);
            end
        end
        return list;
    end,
    ['monster'] = function(quest, name, amount, selected, mapid)
        local list = {};
        local monster = {};
        if (string.find(name, " slain")) then
            name = string.sub(name, 1, string.len(name)-6);
        end
        monster["name"] = name;
        monster["type"] = "slay";
        monster["locations"] = {};
        if (QuestieMonsters[name] and QuestieMonsters[name]['locations']) then
            for k, pos in pairs(QuestieMonsters[name]['locations']) do
                table.insert(monster["locations"], pos);
            end
        end
        table.insert(list, monster);
        return list;
    end,
    ['object'] = function(quest, name, amount, selected, mapid)
        local list = {};
        local objdata = QuestieObjects[name];
        if objdata == nil then
            Questie:debug_Print("Quest:AstroobjectiveProcessors: ERROR4 UNKNOWN OBJECT: [Quest: "..quest.."] | [Objective: "..name.."]");
        else
            for b=1,objdata['locationCount'] do
                local monster = {};
                monster["name"] = name;
                monster["locations"] = {};
                monster["type"] = "object";
                for b=1,objdata['locationCount'] do
                    local loc = objdata['locations'][b];
                    table.insert(monster["locations"], loc);
                end
                table.insert(list, monster);
            end
        end
        return list;
    end
}
---------------------------------------------------------------------------------------------------
--End of Astrolabe functions
---------------------------------------------------------------------------------------------------
--///////////////////////////////////////////////////////////////////////////////////////////////--
---------------------------------------------------------------------------------------------------
--Get quest ID from quest hash
---------------------------------------------------------------------------------------------------
function Questie:GetQuestIdFromHash(questHash)
    local numEntries, numQuests = QGet_NumQuestLogEntries();
    if (QUESTIE_UPDATE_EVENT or numEntries ~= LastNrOfEntries or not CachedIds[questHash]) then
        CachedIds[questHash] = {};
        QUESTIE_UPDATE_EVENT = 0;
        LastNrOfEntries = numEntries;
        Questie:UpdateQuestIds();
        if CachedIds[questHash] then
            return CachedIds[questHash];
        end
    else
        local prevQuestLogSelection = QGet_QuestLogSelection();
        local q, level, questTag, isHeader, isCollapsed, isComplete = QGet_QuestLogTitle(CachedIds[questHash]);
        QSelect_QuestLogEntry(CachedIds[questHash]);
        local questText, objectiveText = QGet_QuestLogQuestText();
        if (q and level and objectiveText) then
            if(Questie:getQuestHash(q, level, objectiveText) == questHash) then
                QSelect_QuestLogEntry(prevQuestLogSelection)
                return CachedIds[questHash];
            else
                Questie:debug_Print("Quest:GetQuestIdFromHash --> Error: [Hash: "..tostring(CachedIds[questHash]).."]1");
            end
        else
            Questie:debug_Print("Quest:GetQuestIdFromHash --> Error2: [Hash: "..tostring(CachedIds[questHash]).."] | [Quest: "..tostring(q).."] | [Level: "..tostring(level).."]");
        end
        QSelect_QuestLogEntry(prevQuestLogSelection);
    end
end
---------------------------------------------------------------------------------------------------
--Update quest ID's
---------------------------------------------------------------------------------------------------
function Questie:UpdateQuestIds()
    local uqidtime = GetTime()
    local numEntries, numQuests = QGet_NumQuestLogEntries();
    local i = 1;
    local qc = 0;
    local prevQuestLogSelection = QGet_QuestLogSelection()
    while qc < numQuests do
        local q, level, questTag, isHeader, isCollapsed, isComplete = QGet_QuestLogTitle(i);
        if not isHeader then
            QSelect_QuestLogEntry(i);
            local questText, objectiveText = QGet_QuestLogQuestText();
            local hash = Questie:getQuestHash(q, level, objectiveText);
            if (not q or not level or not objective) then
                --commented out the error because it was really annoying. -ZoeyZolotova
                --Questie:debug_Print("Quest:UpdateQuestIds --> Error1: [Name: "..tostring(name).."] | [Level: "..tostring(level).."] | [Id: "..tostring(i).."] | [Hash: "..tostring(hash).."]")
            end
            CachedIds[hash] = i;
            qc = qc + 1;
        end
        i = i + 1;
    end
    QSelect_QuestLogEntry(prevQuestLogSelection);
    --Questie:debug_Print("Quest:UpdateQuestID: --> Updating QuestIds took: ["..tostring((GetTime()- uqidtime)*1000).."ms]")
end
---------------------------------------------------------------------------------------------------
--Some outdated server databases still use names like "Tower of Althalaxx part x".
--which were used to turn quest names into a unique key. This function removes
--those suffixes, so that they don't harm the quest lookup.
---------------------------------------------------------------------------------------------------
function Questie:SanitisedQuestLookup(name)
    local realName, matched = string.gsub(name, " [(]?[Pp]art %d+[)]?", "");
    return QuestieLevLookup[realName] or false;
end
---------------------------------------------------------------------------------------------------
--Remove unique suffix from text.
---------------------------------------------------------------------------------------------------
function Questie:RemoveUniqueSuffix(text)
    if string.sub(text, -1) == "]" then
        local strlen = string.len(text)
        text = string.sub(text, 1, strlen-4)
    end
    return text
end
---------------------------------------------------------------------------------------------------
--Lookup quest hash from name, level or objective text
---------------------------------------------------------------------------------------------------
function Questie:getQuestHash(name, level, objectiveText)
    local hashLevel = level or "hashLevel";
    local hashText = objectiveText or "hashText";
    if QuestieQuestHashCache[name..hashLevel..hashText] then
        return QuestieQuestHashCache[name..hashLevel..hashText];
    end
    local questLookup = Questie:SanitisedQuestLookup(name);
    local hasOthers = false;
    if questLookup then
        local count = 0;
        local retval = 0;
        local bestDistance = 4294967295; --some high number (0xFFFFFFFF)
        local race = UnitRace("Player");
        for k,v in pairs(questLookup) do
            if QuestieHashMap[v[2]] ~= nil then
                local rr = v[1];
                local adjustedDescription = Questie:RemoveUniqueSuffix(k)
                if count == 1 then
                    hasOthers = true;
                end
                local requiredQuest = QuestieHashMap[v[2]]['rq']
                if adjustedDescription == objectiveText and tonumber(QuestieHashMap[v[2]]['questLevel']) == hashLevel and checkRequirements(null, race, null, rr) and (not requiredQuest or QuestieSeenQuests[requiredQuest]) and not QuestieSeenQuests[v[2]] then
                    QuestieQuestHashCache[name..hashLevel..hashText] = v[2];
                    return v[2],hasOthers; --exact match
                end
                local dist = 4294967294;
                if not (objectiveText == nil) then
                    dist = Questie:Levenshtein(objectiveText, adjustedDescription);
                end
                if dist < bestDistance then
                    bestDistance = dist;
                    retval = v[2];
                end
                count = count + 1;
            else
                Questie:debug_Print("ERROR: Quest '"..name.."' was found but data is missing for hash "..v[2].." Please report this on Github!")
            end
        end
        if not (retval == 0) then
            QuestieQuestHashCache[name..hashLevel..hashText] = retval;
            return retval, hasOthers; --nearest match
        end
    end
    if name == nil then
        return -1;
    end
    local hash = Questie:MixString(0, name);
    if not (level == nil) then
        hash = Questie:MixInt(hash, level);
        QuestieQuestHashCache[name..hashLevel..hashText] = hash;
    end
    if not (objectiveText == nil) then
        hash = Questie:MixString(hash, objectiveText);
        QuestieQuestHashCache[name..hashLevel..hashText] = hash;
    end
    QuestieQuestHashCache[name..hashLevel..hashText] = hash;
    return hash, false;
end
---------------------------------------------------------------------------------------------------
--Checks to see if a quest is finished by quest hash
---------------------------------------------------------------------------------------------------
function Questie:IsQuestFinished(questHash)
    local id = Questie:GetQuestIdFromHash(questHash);
    if (not id) then
        return false;
    end
    local prevQuestLogSelection = QGet_QuestLogSelection()
    local FinishedQuests = {};
    local q, level, questTag, isHeader, isCollapsed, isComplete = QGet_QuestLogTitle(id);
    QSelect_QuestLogEntry(id);
    local count =  QGet_NumQuestLeaderBoards();
    local questText, objectiveText = QGet_QuestLogQuestText();
    local Done = true;
    for obj = 1, count do
        local desc, typ, done = QGet_QuestLogLeaderBoard(obj);
        if not done then
            Done = nil;
        end
    end
    QSelect_QuestLogEntry(prevQuestLogSelection);
    if (Done and Questie:getQuestHash(q, level, objectiveText) == questHash) then
        local ret = {};
        ret["questHash"] = questHash;
        ret["name"] = q;
        ret["level"] = level;
        return ret;
    end
    return nil;
end
---------------------------------------------------------------------------------------------------
--Race, Class and Profession filter functions
---------------------------------------------------------------------------------------------------
RaceBitIndexTable = {
    ['human'] = 1,
    ['orc'] = 2,
    ['dwarf'] = 3,
    ['nightelf'] = 4,
    ['night elf'] = 4,
    ['scourge'] = 5,
    ['undead'] = 5,
    ['tauren'] = 6,
    ['gnome'] = 7,
    ['troll'] = 8,
    ['goblin'] = 9
};
ClassBitIndexTable = {
    ['warrior'] = 1,
    ['paladin'] = 2,
    ['hunter'] = 3,
    ['rogue'] = 4,
    ['priest'] = 5,
    ['shaman'] = 7,
    ['mage'] = 8,
    ['warlock'] = 9,
    ['druid'] = 11
};
---------------------------------------------------------------------------------------------------
function unpackBinary(val)
    ret = {};
    for q=0,16 do
        if bit.band(bit.rshift(val,q), 1) == 1 then
            table.insert(ret, true);
        else
            table.insert(ret, false);
        end
    end
    return ret;
end
---------------------------------------------------------------------------------------------------
function checkRequirements(class, race, dbClass, dbRace)
    local valid = true;
    if race and dbRace and not (dbRace == 0) then
        local racemap = unpackBinary(dbRace);
        valid = racemap[RaceBitIndexTable[strlower(race)]];
    end
    if class and dbClass and valid and not (dbRace == 0)then
        local classmap = unpackBinary(dbClass);
        valid = classmap[ClassBitIndexTable[strlower(class)]];
    end
    return valid;
end
---------------------------------------------------------------------------------------------------
function Questie:GetAvailableQuestHashes(mapFileName, levelFrom, levelTo)
    local mapid =  -1;
    if(QuestieZones[mapFileName]) then
        c = QuestieZones[mapFileName][4];
        z = QuestieZones[mapFileName][5];
    end
    local class = UnitClass("Player");
    local race = UnitRace("Player");
    local hashes = {};
    for l = 0,100 do
        if QuestieZoneLevelMap[c] and QuestieZoneLevelMap[c][z] then
            local content = QuestieZoneLevelMap[c][z][l];
            if content then
                for v, locationMeta in pairs(content) do
                    local qdata = QuestieHashMap[v];
                    if (qdata) then
                        local stop = false;
                        local questLevel = qdata.questLevel;
                        for x in string.gfind(questLevel, "%d+") do questLevel = x; end
                        questLevel = tonumber(questLevel);
                        if QuestieConfig.minLevelFilter and questLevel < levelFrom then
                            stop = true;
                        end
                        if QuestieConfig.maxLevelFilter and qdata.level > levelTo then
                            stop = true;
                        end
                        if (not stop) then
                            local requiredQuest = qdata['rq'];
                            local requiredRaces = qdata['rr'];
                            local requiredClasses = qdata['rc'];
                            local requiredSkill = qdata['rs'];
                            local valid = not QuestieSeenQuests[requiredQuest];
                            if(requiredQuest) then valid = QuestieSeenQuests[requiredQuest]; end
                            valid = valid and (requiredSkill == nil or QuestieConfig.showProfessionQuests);
                            if valid then valid = valid and checkRequirements(class, race, requiredClasses,requiredRaces); end
                            if valid and not QuestieHandledQuests[requiredQuest] and not QuestieSeenQuests[v] then
                                hashes[v] = locationMeta;
                            end
                        end
                    end
                end
            end
        end
    end
    return hashes;
end
---------------------------------------------------------------------------------------------------
--End of filter functions
---------------------------------------------------------------------------------------------------