Quantcast
---------------------------------------------------------------------------------------
-- NxQuest - Quest stuff
-- Copyright 2007-2012 Carbon Based Creations, LLC
---------------------------------------------------------------------------------------
---------------------------------------------------------------------------------------
-- Carbonite - Addon for World of Warcraft(tm)
-- Copyright 2007-2012 Carbon Based Creations, LLC
--
-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.
--
-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.
--
-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.
---------------------------------------------------------------------------------------

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Quest general

function Nx.Quest:OptsReset()

	local qopts = Nx:GetQuestOpts()

	qopts.NXShowHeaders = true
	qopts.NXSortWatchMode = 1

	qopts.NXWAutoMax = nil
	qopts.NXWVisMax = 8
	qopts.NXWShowOnMap = true
	qopts.NXWWatchParty = true

	qopts.NXWHideUnfinished = false
	qopts.NXWHideGroup = false
	qopts.NXWHideNotInZone = false
	qopts.NXWHideNotInCont = false
	qopts.NXWHideDist = 20000

	qopts.NXWPriDist = 1
	qopts.NXWPriComplete = 50
	qopts.NXWPriLevel = 20

	qopts.NXWPriGroup = -100			-- Not used yet
end

--------
-- DEBUG

--function Nx.Quest.SelectQuestLogEntry (qn)
--	Nx.prt ("QSel %s", qn)
--	Nx.Quest.OldSelectQuestLogEntry (qn)
--end

--------
-- Init quest and watch data and windows

function Nx.Quest:Init()

--	hooksecurefunc ("WatchFrame_Update", self.WatchFrame_Update)


	local opts = Nx:GetGlobalOpts()

	self.Enabled = opts["QEnable"]
	if not self.Enabled then

--		Nx.Quest = nil
		Nx.Quests = nil	-- Data
		return
	end

	self.GOpts = opts

	if opts["QWBlizzModify"] then
--		if not GetCVarBool ("advancedWatchFrame") then

--			SetCVar ("questFadingDisable", 1)	--V4 gone
			SetCVar ("autoQuestProgress", 0)
			SetCVar ("autoQuestWatch", 0)
--			SetCVar ("advancedWatchFrame", 1)
--			SetCVar ("watchFrameIgnoreCursor", 0)
--		end
	end

	-- DEBUG
--	self.OldSelectQuestLogEntry = SelectQuestLogEntry
--	SelectQuestLogEntry = Nx.Quest.SelectQuestLogEntry

	-- Force it to create/enable and then we disable
	GetUIPanelWidth (QuestLogFrame)
	QuestLogFrame:SetAttribute ("UIPanelLayout-enabled", false)

	if QuestLogDetailFrame then	-- Patch 3.2
		GetUIPanelWidth (QuestLogDetailFrame)
		QuestLogDetailFrame:SetAttribute ("UIPanelLayout-enabled", false)
	end

	local Map = Nx.Map

	self.QuestStartEnd = Nx.QuestStartEnd

	self.IdToQuest = {}

	self.QIds = {}					-- Our quests by id
	self.QIdsNew = {}				-- Time stamp of getting a new quest. [Id] = time()

	self.Tracking = {}
	self.Sorted = {}

	self.CurQ = {}					-- Current quests (including gotos)
	self.RealQ = {}				-- Real Blizzard quests
	self.RealQEntries = 0
	self.PartyQ = {}				-- Party quests

	self.IdToCurQ = {}

	self.HeaderExpanded = {}	-- Blizzard quest headers we expanded
	self.HeaderHide = {}			-- Names of quest headers to hide

	self.RcvPlyrLast = "None"
	self.RcvCnt = 0
	self.RcvTotal = 0
	self.FriendQuests = {}

	self.IconTracking = {}

	self:CalcWatchColors()

	self.TagNames = {
		["Group"] = "+",
		["Gruppe"] = "+",			-- German
		["Dungeon"] = "D",
		["Heroic"] = "H",
		["Heroisch"] = "H",		-- German
		["Raid"] = "R",
	}

	self.PerColors = {
		"|cffc00000", "|cffc03000", "|cffc06000", "|cffc09000", "|cffc0c000", "|cff90c000", "|cff60c000", "|cff30c000", "|cff00c000",
	}

	local qopts = Nx:GetQuestOpts()
	if qopts.NXBroadcastQChanges ~= nil then

		opts["QBroadcastQChanges"] = qopts.NXBroadcastQChanges	-- Convert

		qopts.NXBroadcastQChanges = nil
	end

	-- Capture

	self.CapturePlyrData = {}

	self.CapFactionAbr = {
		["Argent Crusade"] = 1,
		["Argent Dawn"] = 2,
		["Ashtongue Deathsworn"] = 3,
		["Bloodsail Buccaneers"] = 4,
		["Booty Bay"] = 5,
		["Brood of Nozdormu"] = 6,
		["Cenarion Circle"] = 7,
		["Cenarion Expedition"] = 8,
		["Darkmoon Faire"] = 9,
		["Darkspear Trolls"] = 10,
		["Darnassus"] = 11,
		["Everlook"] = 12,
		["Exodar"] = 13,
		["Explorers' League"] = 14,
		["Frenzyheart Tribe"] = 15,
		["Frostwolf Clan"] = 16,
		["Gadgetzan"] = 17,
		["Gelkis Clan Centaur"] = 18,
		["Gnomeregan Exiles"] = 19,
		["Honor Hold"] = 20,
		["Hydraxian Waterlords"] = 21,
		["Ironforge"] = 22,
		["Keepers of Time"] = 23,
		["Kirin Tor"] = 24,
		["Knights of the Ebon Blade"] = 25,
		["Kurenai"] = 26,
		["Lower City"] = 27,
		["Magram Clan Centaur"] = 28,
		["Netherwing"] = 29,
		["Ogri'la"] = 30,
		["Orgrimmar"] = 31,
		["Ratchet"] = 32,
		["Ravenholdt"] = 33,
		["Sha'tari Skyguard"] = 34,
		["Shattered Sun Offensive"] = 35,
		["Shen'dralar"] = 36,
		["Silvermoon City"] = 37,
		["Silverwing Sentinels"] = 38,
		["Sporeggar"] = 39,
		["Stormpike Guard"] = 40,
		["Stormwind"] = 41,
		["Syndicate"] = 42,
		["The Aldor"] = 43,
		["The Consortium"] = 44,
		["The Defilers"] = 45,
		["The Frostborn"] = 46,
		["The Hand of Vengeance"] = 47,
		["The Kalu'ak"] = 48,
		["The League of Arathor"] = 49,
		["The Mag'har"] = 50,
		["The Oracles"] = 51,
		["The Scale of the Sands"] = 52,
		["The Scryers"] = 53,
		["The Sha'tar"] = 54,
		["The Silver Covenant"] = 55,
		["The Sons of Hodir"] = 56,
		["The Taunka"] = 57,
		["The Violet Eye"] = 58,
		["The Wyrmrest Accord"] = 59,
		["Thorium Brotherhood"] = 60,
		["Thrallmar"] = 61,
		["Thunder Bluff"] = 62,
		["Timbermaw Hold"] = 63,
		["Tranquillien"] = 64,
		["Undercity"] = 65,
		["Valiance Expedition"] = 66,
		["Warsong Offensive"] = 67,
		["Warsong Outriders"] = 68,
		["Wildhammer Clan"] = 69,
		["Wintersaber Trainers"] = 70,
		["Zandalar Tribe"] = 71,
	}

	-- Patch quest data
--[[
	local qdata = {
		[3444] = "4^97^626^386",
		[10447] = "5^96^738^357",
--		[11514] = "4^19",
--		[11521] = "4^96",
--		[ 11541] = "2^119^538^341",
--		[111541] = "3",
--		[11546] = "4^119",
--		[11877] = "4^66",
	}

	for id, s in pairs (qdata) do

		id = id <= 100000 and id or id - 100000

		local quest = Nx.Quests[(id + 7) * 2 - 3]

		local objI, zone, x, y = strsplit ("^", s)
		objI = tonumber (objI)

		if not zone then
			quest[objI] = nil

		else
--			Nx.prt ("%s z%s", id, objI)

			local obj = quest[objI]
			if obj then

				zone = tonumber (zone)

				if x then
--					Nx.prt ("%s %s %s", id, x, y)
					x = tonumber (x) * 10
					y = tonumber (y) * 10
					local oDesc = self:UnpackObjective (obj)
					quest[objI] = format ("%c%s%c %c%c%c%c", #oDesc + 35, oDesc, zone + 35,
							floor (x / 221) + 35, x % 221 + 35, floor (y / 221) + 35, y % 221 + 35)
				else
					local oDesc, oZone, oLoc = self:UnpackObjective (obj)
					quest[objI] = format ("%c%s%c%s", #oDesc + 35, oDesc, zone + 35, strsub (obj, oLoc))
				end
			end
		end
	end
--]]

	-- Patch Molten Front
--[[
	for mungeId, q in pairs (Nx.Quests) do

		local id = (mungeId + 3) / 2 - 7		-- Decode

		if q[2] then
			local sName, zone = self:UnpackSE (q[2])
			if zone == 199 then
				Nx.prt ("qpatch %s", sName)

				local i = 1
				while q[3 + i] do
				end
			end
		end
	end
--]]

	self.DailyTypes = {
		["1"] = "Daily",
		["2"] = "Daily Dungeon",
		["3"] = "Daily Heroic",
	}
	self.Reputations = {
		["A"] = "Aldor",
		["S"] = "Scryer",
		["c"] = "Consortium",
		["e"] = "Cenarion Expedition",
		["g"] = "Sha'tari Skyguard",
		["k"] = "Keepers of Time",
		["l"] = "Lower City",
		["n"] = "Netherwing",
		["o"] = "Ogri'la",
		["s"] = "Shattered Sun Offensive",
		["t"] = "Sha'tar",
		["z"] = "Honor Hold/Thrallmar",
		-- WotLK
		["C"] = "Argent Crusade",
		["E"] = "Explorers' League",
		["F"] = "Frenzyheart Tribe",
		["f"] = "The Frostborn",
		["H"] = "Horde Expedition",
		["K"] = "The Kalu'ak",
		["i"] = "Kirin Tor",
		["N"] = "Knights of the Ebon Blade",
		["O"] = "The Oracles",
		["h"] = "The Sons of Hodir",
		["a"] = "Alliance Vanguard",
		["V"] = "Valiance Expedition",
		["W"] = "Warsong Offensive",
		["w"] = "The Wyrmrest Accord",
		["I"] = "The Silver Covenant",		-- Patch 3.1
		["R"] = "The Sunreavers",				-- Patch 3.1
	}
	self.Requirements = {
--		["1"] = "Alliance",		-- Already stripped out by quest side removal code
--		["2"] = "Horde",
		["oH"] = "Ogri'la Honored",
		["H350"] = "Herbalism 350",
		["M350"] = "Mining 350",
		["S350"] = "Skining 350",
		["G"] = "Gathering Skill",
		["nF"] = "Netherwing Friendly",
		["nH"] = "Netherwing Honored",
		["nRA"] = "Netherwing Revered (Aldor)",
		["nRS"] = "Netherwing Revered (Scryer)",
		-- WotLK
		["hH"] = "The Sons of Hodir Honored",
		["hR"] = "The Sons of Hodir Revered",
		["J375"] = "Jewelcrafting 375",
		["C"] = "Cooking",
		["F"] = "Fishing",
	}

	self.DailyIds = {
		-- Type ^ Reward (Gold*100+Silver) ^ Rep letter/Rep amount (000) ^ Requirement
		-- Req - H herb, M mine, S skin, G any gather, F friendly, H honored, R revered

		-- Honor Hold/Thrallmar
		[10106] = "1^70^z150",			-- Hellfire Fortifications
		[10110] = "1^70^z150",			-- Hellfire Fortifications
		-- Ogri'la
		[11023] = "1^1199^o500g500",	-- Bomb Them Again!
		[11066] = "1^1199^o350g350",	-- Wrangle More Aether Rays!
		[11080] = "1^910^o350",			-- The Relic's Emanation
		[11051] = "1^1199^o350^oH",	-- Banish More Demons
		-- Netherwing
		[11020] = "1^1199^n250",		-- A Slow Death
		[11035] = "1^1199^n250",		-- The Not-So-Friendly Skies...
		[11049] = "1^1828^n350",		-- The Great Netherwing Egg Hunt
		[11015] = "1^1199^n250",		-- Netherwing Crystals
		[11017] = "1^1199^n250^H350",	-- Netherdust Pollen (Herbalist)
		[11018] = "1^1199^n250^M350",	-- Nethercite Ore (Miner)
		[11016] = "1^1199^n250^S350",	-- Nethermine Flayer Hide (Skinner)
		[11055] = "1^1199^n350^nF",	-- The Booterang: A Cure For The Common Worthless Peon
		[11076] = "1^1828^n350^nF",	-- Picking Up The Pieces...
		[11086] = "1^1199^n500^nH",	-- Disrupting the Twilight Portal
		[11101] = "1^1828^n500^nRA",	-- The Deadliest Trap Ever Laid (Aldor)
		[11097] = "1^1828^n500^nRS",	-- The Deadliest Trap Ever Laid (Scryer)
		-- Shattered Sun
		[11514] = "1^1010^s250",		-- Maintaining the Sunwell Portal
		[11515] = "1^1199^s250",		-- Blood for Blood
		[11516] = "1^1010^s250",		-- Blast the Gateway
		[11521] = "1^1388^s350",		-- Rediscovering Your Roots
		[11523] = "1^910^s150",			-- Arm the Wards!
		[11525] = "1^910^s150",			-- Further Conversions
		[11533] = "1^910^s150",			-- The Air Strikes Must Continue
		[11536] = "1^1199^s250",		-- Don't Stop Now....
		[11537] = "1^1010^s250",		-- The Battle Must Go On
		[11540] = "1^1199^s250",		-- Crush the Dawnblade
		[11541] = "1^1199^s250",		-- Disrupt the Greengill Coast
		[11543] = "1^759^s250",			-- Keeping the Enemy at Bay
		[11544] = "1^1828^s350",		-- Ata'mal Armaments
		[11546] = "1^1199^s250",		-- Open for Business
		[11547] = "1^1199^s250",		-- Know Your Ley Lines
		[11548] = "1^-1000^s150",		-- Your Continued Support
		[11877] = "1^1010^s250",		-- Sunfury Attack Plans
		[11880] = "1^910^s250",			-- The Multiphase Survey
		[11875] = "1^1639^s250^G",		-- Gaining the Advantage
		-- Skettis
		[11008] = "1^1199^g350",		-- Fires Over Skettis
		[11085] = "1^910^g150",			-- Escape from Skettis

		-- WotLK Borean Tundra
		[11940] = "1^470^w250",			-- Drake Hunt
		[11945] = "1^500^K500",			-- Preparing for the Worst
		[13414] = "1^740^w250",			-- Aces High!
		-- WotLK Howling Fjord
		[11153] = "1^470^a 38V250^1",	-- Break the Blockade
		[11391] = "1^470^E250^1",		-- Steel Gate Patrol
		[11472] = "1^470^K500",			-- The Way to His Heart...
		-- WotLK Dragonblight
		[11960] = "1^500^K500",			-- Planning for the Future
		[12372] = "1^560^w250",			-- Defending Wyrmrest Temple
		-- WotLK Grizzly Hills
		[12437] = "1^560^^1",			-- Riding the Red Rocket
		[12444] = "1^560^a 38V250^1",	-- Blackriver Skirmish
		[12316] = "1^560^^1",			-- Keep Them at Bay!
		[12289] = "1^560^a 38V250^1",	-- Kick 'Em While They're Down
		[12296] = "1^560^a 38V250^1",	-- Life or Death
		[12268] = "1^560^^1",			-- Pieces Parts
		[12244] = "1^560^^1",			-- Shredder Repair
		[12323] = "1^560^^1",			-- Smoke 'Em Out
		[12314] = "1^560^^1",			-- Down With Captain Zorna!
		[12038] = "1^986",				-- Seared Scourge
		[12433] = "1^560",				-- Seeking Solvent
		[12170] = "1^560^H250^2",		-- Blackriver Brawl
		[12284] = "1^560^W250^2",		-- Keep 'Em on Their Heels
		[12280] = "1^560^W250^2",		-- Making Repairs
		[12288] = "1^560^W250^2",		-- Overwhelmed!
		[12270] = "1^560^W250^2",		-- Shred the Alliance
		[12315] = "1^560^^2",			-- Crush Captain Brightwater!
		[12324] = "1^560^^2",			-- Smoke 'Em Out
		[12317] = "1^560^^2",			-- Keep Them at Bay
		[12432] = "1^560^^2",			-- Riding the Red Rocket
		-- WotLK Zul'Drak
		[12501] = "1^620^C250",			-- Troll Patrol
		[12541] = "1^158^C 75",			-- Troll Patrol: The Alchemist's Apprentice
		[12502] = "1^158^C 75",			-- Troll Patrol: High Standards
		[12564] = "1^158^C 75",			-- Troll Patrol: Something for the Pain
		[12588] = "1^158^C 75",			-- Troll Patrol: Can You Dig It?
		[12568] = "1^158^C 75",			-- Troll Patrol: Done to Death
		[12509] = "1^158^C250",			-- Troll Patrol: Intestinal Fortitude
		[12591] = "1^158^C 75",			-- Troll Patrol: Throwing Down
		[12585] = "1^158^C 75",			-- Troll Patrol: Creature Comforts
		[12519] = "1^158^C 25",			-- Troll Patrol: Whatdya Want, a Medal?
		[12594] = "1^158^C 75",			-- Troll Patrol: Couldn't Care Less
		[12604] = "1^1860^C350",		-- Congratulations!
		-- WotLK Sholazar Basin
		[12704] = "1^650^O250",			-- Appeasing the Great Rain Stone
		[12761] = "1^1360^O350",		-- Mastery of the Crystals
		[12762] = "1^1360^O350",		-- Power of the Great Ones
		[12705] = "1^1360^O350",		-- Will of the Titans
		[12735] = "1^740^O500",			-- A Cleansing Song
		[12737] = "1^740^O250",			-- Song of Fecundity
		[12736] = "1^740^O250",			-- Song of Reflection
		[12726] = "1^740^O500",			-- Song of Wind and Water
		[12689] = "1^330^O***",			-- Hand of the Oracles (one time rep bonus)
		[12582] = "1^330^F***",			-- Frenzyheart Champion (one time rep bonus)
		[12702] = "1^650^F500",			-- Chicken Party!
		[12703] = "1^1360^F350",		-- Kartak's Rampage
		[12760] = "1^1360^F350",		-- Secret Strength of the Frenzyheart
		[12759] = "1^1360^F350",		-- Tools of War
		[12734] = "1^740^F500",			-- Rejek: First Blood
		[12758] = "1^740^F500",			-- A Hero's Headgear
		[12741] = "1^740^F500",			-- Strength of the Tempest (check rep??)
		[12732] = "1^740^F500",			-- The Heartblood's Strength
		-- WotLK Icecrown
		[13309] = "1^740^V250^1",		-- Assault by Air
		[13284] = "1^740^V250^1",		-- Assault by Ground
		[13336] = "1^740^V250^1",		-- Blood of the Chosen
		[13323] = "1^740^^1",			-- Drag and Drop
		[13344] = "1^740^^1",			-- Not a Bug
		[13322] = "1^740^^1",			-- Retest Now
		[13404] = "1^740^^1",			-- Static Shock Troops: the Bombardment
		[13300] = "1^740^C250^1",		-- Slaves to Saronite
		[13289] = "1^740^^1",			-- That's Abominable!
		[13292] = "1^740^^1",			-- The Solution Solution
		[13333] = "1^740^^1",			-- Capture More Dispatches
		[13297] = "1^2220^^1",			-- Neutralizing the Plague
		[13350] = "1^2220^^1",			-- No Rest For The Wicked
		[13280] = "1^740^V250^1",		-- King of the Mountain
		[13233] = "1^740^^1",			-- No Mercy!
		[13310] = "1^740^W250^2",		-- Assault by Air
		[13301] = "1^740^W250^2",		-- Assault by Ground
		[13330] = "1^740^W250^2",		-- Blood of the Chosen
		[13353] = "1^740^^2",			-- Drag and Drop
		[13365] = "1^740^^2",			-- Not a Bug
		[13357] = "1^740^^2",			-- Retest Now
		[13406] = "1^740^^2",			-- Riding the Wavelength: The Bombardment
		[13302] = "1^740^C250^2",		-- Slaves to Saronite
		[13376] = "1^740^^2",			-- Total Ohmage: The Valley of Lost Hope!
		[13276] = "1^740^^2",			-- That's Abominable!
		[13331] = "1^740^W250^2",		-- Keeping the Alliance Blind
		[13261] = "1^740^^2",			-- Volatility
		[13281] = "1^2220^^2",			-- Neutralizing the Plague
		[13368] = "1^2220^^2",			-- No Rest For The Wicked
		[13283] = "1^740^W250^2",		-- King of the Mountain
		[13234] = "1^740^^2",			-- Make Them Pay!
		[12813] = "1^740^N250",			-- From Their Corpses, Rise!
		[12838] = "1^740^N250",			-- Intelligence Gathering
		[12995] = "1^740^N250",			-- Leave Our Mark
		[12815] = "1^740^N250",			-- No Fly Zone
		[13069] = "1^740^N250",			-- Shoot 'Em Up
		[13071] = "1^370^N250",			-- Vile Like Fire!
		-- WotLK Icecrown Argent Tournament
--		[13681] = "1^740",				-- A Chip Off the Ulduar Block (OLD)
--		[13627] = "1^740",				-- Jack Me Some Lumber (OLD)
		[13625] = "1^580^I250",			-- Learning The Reins (A)
		[13677] = "1^580^R250",			-- Learning The Reins (H)
		[13671] = "1^580^I250",			-- Training In The Field (A)
		[13676] = "1^580^R250",			-- Training In The Field (H)
		[13666] = "1^580^I250",			-- A Blade Fit For A Champion (A)
		[13603] = "1^740^I250",			-- A Blade Fit For A Champion
		[13741] = "1^740^I250",			-- A Blade Fit For A Champion
		[13746] = "1^740^I250",			-- A Blade Fit For A Champion
		[13752] = "1^740^I250",			-- A Blade Fit For A Champion
		[13757] = "1^740^I250",			-- A Blade Fit For A Champion
		[13673] = "1^580^R250",			-- A Blade Fit For A Champion (H)
		[13762] = "1^740^R250",			-- A Blade Fit For A Champion
		[13768] = "1^740^R250",			-- A Blade Fit For A Champion
		[13783] = "1^740^R250",			-- A Blade Fit For A Champion
		[13773] = "1^740^R250",			-- A Blade Fit For A Champion
		[13778] = "1^740^R250",			-- A Blade Fit For A Champion
		-- WotLK The Storm Peaks
		[12994] = "1^740^h350^hH",		-- Spy Hunter
		[12833] = "1^680",				-- Overstock
		[13424] = "1^740",				-- Back to the Pit (Hyldnir Spoils)
		[12977] = "1^740^h250",			-- Blowing Hodir's Horn
		[13423] = "1^740",				-- Defending Your Title (Hyldnir Spoils)
		[13046] = "1^740^h250^hR",		-- Feeding Arngrim
		[12981] = "1^740^h250",			-- Hot and Cold
		[13422] = "1^550",				-- Maintaining Discipline (Hyldnir Spoils)
		[13006] = "1^740^h250",			-- Polishing the Helm
		[12869] = "1^680^f250",			-- Pushed Too Far
		[13425] = "1^740",				-- The Aberrations Must Die (Hyldnir Spoils)
		[13003] = "1^1480^h500^hH",	-- Thrusting Hodir's Spear
		-- WotLK Wintergrasp
		[13156] = "1^740",				-- A Rare Herb
		[13195] = "1^740",				-- A Rare Herb
		[13154] = "1^740",				-- Bones and Arrows
		[13193] = "1^740",				-- Bones and Arrows
		[13196] = "1^740",				-- Bones and Arrows
		[13199] = "1^740",				-- Bones and Arrows
		[13222] = "1^740",				-- Defend the Siege
		[13223] = "1^740",				-- Defend the Siege
		[13191] = "1^740",				-- Fueling the Demolishers
		[13197] = "1^740",				-- Fueling the Demolishers
		[13200] = "1^740",				-- Fueling the Demolishers
		[13194] = "1^740",				-- Healing with Roses
		[13201] = "1^740",				-- Healing with Roses
		[13202] = "1^740",				-- Jinxing the Walls
		[13177] = "1^740",				-- No Mercy for the Merciless
		[13179] = "1^740",				-- No Mercy for the Merciless
		[13178] = "1^740",				-- Slay them all!
		[13180] = "1^740",				-- Slay them all!
		[13538] = "1^740",				-- Southern Sabotage
		[13185] = "1^740",				-- Stop the Siege
		[13186] = "1^740",				-- Stop the Siege
		[13539] = "1^740",				-- Toppling the Towers
		[13181] = "1^740",				-- Victory in Wintergrasp
		[13183] = "1^740",				-- Victory in Wintergrasp
		[13192] = "1^740",				-- Warding the Walls
		[13153] = "1^740",				-- Warding the Warriors
		[13198] = "1^740",				-- Warding the Warriors
		-- WotLK Cooking
		[13101] = "1^580^i150^C",		-- Convention at the Legerdemain
		[13113] = "1^580^i150^C",		-- Convention at the Legerdemain
		[13100] = "1^580^i150^C",		-- Infused Mushroom Meatloaf
		[13112] = "1^580^i150^C",		-- Infused Mushroom Meatloaf
		[13107] = "1^580^i150^C",		-- Mustard Dogs!
		[13116] = "1^580^i150^C",		-- Mustard Dogs!
		[13102] = "1^580^i150^C",		-- Sewer Stew
		[13114] = "1^580^i150^C",		-- Sewer Stew
		-- WotLK Jewelcrafting
		[12958] = "1^740^i 25^J375",	-- Shipment: Blood Jade Amulet
		[12962] = "1^740^i 25^J375",	-- Shipment: Bright Armor Relic
		[12959] = "1^740^i 25^J375",	-- Shipment: Glowing Ivory Figurine
		[12961] = "1^740^i 25^J375",	-- Shipment: Intricate Bone Figurine
		[12963] = "1^740^i 25^J375",	-- Shipment: Shifting Sun Curio
		[12960] = "1^740^i 25^J375",	-- Shipment: Wicked Sun Brooch
		-- WotLK Fishing
		[13833] = "1^0^i250^F",			-- Blood Is Thicker
		[13834] = "1^0^i250^F",			-- Dangerously Delicious
		[13832] = "1^0^i250^F",			-- Jewel Of The Sewers
		[13836] = "1^0^i250^F",			-- Monsterbelly Appetite
		[13830] = "1^0^i250^F",			-- The Ghostfish
	}
	self.DailyDungeonIds = {
		-- Dungeon
		[11389] = "2^1639^c250t250",	-- Wanted: Arcatraz Sentinels
		[11371] = "2^1639^c250e250",	-- Wanted: Coilfang Myrmidons
		[11376] = "2^1639^c250l250",	-- Wanted: Malicious Instructors
		[11383] = "2^1639^c250k250",	-- Wanted: Rift Lords
		[11364] = "2^1639^c250z250",	-- Wanted: Shattered Hand Centurions
		[11500] = "2^1639^c250s250",	-- Wanted: Sisters of Torment
		[11385] = "2^1639^c250t250",	-- Wanted: Sunseeker Channelers
		[11387] = "2^1639^c250t250",	-- Wanted: Tempest-Forge Destroyers
		-- Dungeon Heroic
		[11369] = "3^2460^c250e250",	-- Wanted: A Black Stalker Egg
		[11384] = "3^2460^c350t350",	-- Wanted: A Warp Splinter Clipping
		[11382] = "3^2460^c350k350",	-- Wanted: Aeonus's Hourglass
		[11363] = "3^2460^c350z350",	-- Wanted: Bladefist's Seal
		[11362] = "3^2460^c350z350",	-- Wanted: Keli'dan's Feathered Stave
		[11375] = "3^2460^c350l350",	-- Wanted: Murmur's Whisper
		[11354] = "3^2460^c350z350",	-- Wanted: Nazan's Riding Crop
		[11386] = "3^2460^c350t350",	-- Wanted: Pathaleon's Projector
		[11373] = "3^2460^c500",		-- Wanted: Shaffar's Wondrous Pendant
		[11378] = "3^2460^c350k350",	-- Wanted: The Epoch Hunter's Head
		[11374] = "3^2460^c350l350",	-- Wanted: The Exarch's Soul Gem
		[11372] = "3^2460^c350l350",	-- Wanted: The Headfeathers of Ikiss
		[11368] = "3^2460^c350e350",	-- Wanted: The Heart of Quagmirran
		[11388] = "3^2460^c350t350",	-- Wanted: The Scroll of Skyriss
		[11499] = "3^2460^c350s350",	-- Wanted: The Signet Ring of Prince Kael'thas
		[11370] = "3^2460^c350e350",	-- Wanted: The Warlord's Treatise
		-- WotLK Dungeon
		[13240] = "2^3466^i 75",		-- Timear Foresees Centrifuge Constructs in your Future!
		[13243] = "2^3466^i 75",		-- Timear Foresees Infinite Agents in your Future!
		[13244] = "2^3466^i 75",		-- Timear Foresees Titanium Vanguards in your Future!
		[13241] = "2^3466^i 75",		-- Timear Foresees Ymirjar Berserkers in your Future!
		-- WotLK Dungeon Heroic
		[13190] = "2^4200",				-- All Things in Good Time
		[13254] = "2^4866^i 75",		-- Proof of Demise: Anub'arak
		[13256] = "2^4866^i 75",		-- Proof of Demise: Cyanigosa
		[13250] = "2^4866^i 75",		-- Proof of Demise: Gal'darah
		[13255] = "2^4866^i 75",		-- Proof of Demise: Herald Volazj
		[13245] = "2^4866^i 75",		-- Proof of Demise: Ingvar the Plunderer
		[13246] = "2^4866^i 75",		-- Proof of Demise: Keristrasza
		[13248] = "2^4866^i 75",		-- Proof of Demise: King Ymiron
		[13247] = "2^4866^i 75",		-- Proof of Demise: Ley-Guardian Eregos
		[13253] = "2^4866^i 75",		-- Proof of Demise: Loken
		[13251] = "2^4866^i 75",		-- Proof of Demise: Mal'Ganis
		[13252] = "2^4866^i 75",		-- Proof of Demise: Sjonnir The Ironshaper
		[14199] = "2^4866^i 75",		-- Proof of Demise: The Black Knight
		[13249] = "2^4866^i 75",		-- Proof of Demise: The Prophet Tharon'ja
	}
	self.DailyPVPIds = {	-- For not auto watching
		[11335] = "1",	-- AV, AB, EOS, WG both sides
		[11336] = "1",
		[11337] = "1",
		[11338] = "1",
		[11339] = "1",
		[11340] = "1",
		[11341] = "1",
		[11342] = "1",
		[13405] = "1",	-- SoA
		[13407] = "1",
		[14163] = "1",	-- IoC
		[14164] = "1",
	}

	--	DEBUG for Jamie

	Nx.Quests = Nx["Quests"] or Nx.Quests								-- Copy unmunged data to munged data
	Nx.QuestStartEnd = Nx["QuestStartEnd"] or Nx.QuestStartEnd	-- Copy unmunged data to munged data

	--	DEBUG kill all quests

--	Nx.Quests = {}

	--

	self.Map = Map:GetMap (1)

	local enFact = Nx.PlFactionNum == 1 and 1 or 2		-- Remap 0 to 2, 1 to 1
--	enFact = 2
	local qLoadLevel = UnitLevel ("player") - opts["QLevelsToLoad3"]
	local qMaxLevel = 999

	local qCnt = 0
	local maxid = 0
	local sameCnt = 0
--[[
	--DB
	local startMissingCnt = 0
	local start0Cnt = 0
	local startNoZoneCnt = 0
	local end0Cnt = 0
	local endNoZoneCnt = 0
	local obj0Cnt = 0
	local objNoZoneCnt = 0
--]]

	for mungeId, q in pairs (Nx.Quests) do

		local id = (mungeId + 3) / 2 - 7		-- Decode

--		Nx.Quests[mungeId][2] = nil
--		Nx.Quests[mungeId][3] = nil
--		Nx.Quests[mungeId][4] = nil

		qCnt = qCnt + 1
		maxid = max (id, maxid)

		local name, side, level = self:Unpack (q[1])
		if side == enFact or level > 0 and level < qLoadLevel or level > qMaxLevel then
--[[
			if side == enFact then
--				Nx.prt ("Del q %s side", id)
			else
				Nx.prt ("Del q %s level", id)
			end
--]]
			Nx.Quests[mungeId] = nil

		else

			self.IdToQuest[id] = q

			if q[3] and q[3] == q[2] then
--				q[3] = nil							-- Release mem !!!!! FIX for non enders !!!!!
				sameCnt = sameCnt + 1
			end

--			if not q[4] and q[5] then
--				Nx.prt ("q %s obj hole", id)
--			end
--[[
			--DB

			if q[2] then

				local sName = self:UnpackSE (q[2])
				local _, zone, x, y = self:GetSEPos (q[2])
				local mapId = Map.NxzoneToMapId[zone]

				if not zone then
					startNoZoneCnt = startNoZoneCnt + 1
				end

				if (x == 0 or y == 0) and mapId and not Map:IsInstanceMap (mapId) then
--					Nx.prt ("%s (%s) %s 0 start", name, id, sName)
					start0Cnt = start0Cnt + 1
				end
			else
				startMissingCnt = startMissingCnt + 1
			end
			if q[3] then

				local eName = self:UnpackSE (q[3])
				local _, zone, x, y = self:GetSEPos (q[3])
				local mapId = Map.NxzoneToMapId[zone]

				if not zone then
					endNoZoneCnt = endNoZoneCnt + 1
				end

				if (x == 0 or y == 0) and mapId and not Map:IsInstanceMap (mapId) then
--					Nx.prt ("%s (%s) %s 0 end", name, id, eName)
					end0Cnt = end0Cnt + 1
				end
			end
			for n = 4, 99 do
				if not q[n] then
					break
				end

				local oName = self:UnpackObjective (q[n])
				local _, zone, x, y = self:GetObjectivePos (q[n])
				local mapId = Map.NxzoneToMapId[zone]

				if not zone or zone == 0 then
--					Nx.prt ("%s (%s) #%s %s: no zone", name, id, n - 3, oName or "nil")
					objNoZoneCnt = objNoZoneCnt + 1
				elseif zone > 0 and zone < 220 and not mapId then
--					Nx.prt ("%s (%s) #%s %s z%s: no mapId", name, id, n - 3, oName, zone)
				end

				if (x == 0 and y ~= 0) or (x ~= 0 and y == 0) then
					Nx.prt ("? %s %s", name, id)
				end

				if (x == 0 or y == 0) and mapId and not Map:IsInstanceMap (mapId) then
--					Nx.prt ("%s (%s) #%s %s z%s: no xy", name, id, n - 3, oName, zone)
					obj0Cnt = obj0Cnt + 1
				end
			end
--]]

			self:CheckQuestSE (q, 3)

			for n = 4, 99 do
				if not q[n] then
					break
				end

				self:CheckQuestObj (q, n)
			end

		end
	end
--[[
	--DB
	Nx.prt ("------- %s quests", qCnt)
	Nx.prt ("maxid %s", maxid)
	Nx.prt ("end==start %s, no start %s", sameCnt, startMissingCnt)
	Nx.prt ("0 xy, start %s, end %s, obj %s", start0Cnt, end0Cnt, obj0Cnt)
	Nx.prt ("no zone, start %s, end %s, obj %s", startNoZoneCnt, endNoZoneCnt, objNoZoneCnt)
--]]

	--

	for mungeId, q in pairs (Nx.Quests) do

		local name, side, lvl, minlvl, next = self:Unpack (q[1])

--		if next > 0 then
--			Nx.prt ("%s %s %s", name, mungeId, next)
--		end

--		if strfind (name, "Safety First") then
--			Nx.prt ("%s %s %s", name, mungeId, next)
--		end

		if not q.CNum and next > 0 then

			local clvlmax = lvl

			local qc = q
			local cnum = 0
			while qc do
				cnum = cnum + 1
				qc.CNum = cnum

--				if strfind (name, "Vile Famil") then
--					Nx.prt ("%s %d %d %d", name, mungeId, next, cnum)
--				end

				name, side, lvl, minlvl, next = self:Unpack (qc[1])

				clvlmax = max (clvlmax, lvl)

--				next = self:UnpackNext (qc[1])
				if next == 0 then
					break
				end

				qc = self.IdToQuest[next]
			end

			q.CLvlMax = clvlmax		-- Max level in chain
		end
	end


	for lvl = 0, 90 do

		local grp = {}

		for id, q in pairs (Nx.Quests) do

			id = (id + 3) / 2 - 7

			local name, side, level = self:Unpack (q[1])

			if level == lvl then
				if side ~= enFact then

					if not q.CNum then
						tinsert (grp, format ("%s^%d", name, id))

					elseif q.CNum == 1 then

						local qc = q
						while qc do

							local pname, side, _, _, next = self:Unpack (qc[1])

--							if strfind (name, "Load Lightening") then
--								Nx.prt ("%s %d %d (%d %d)", pname, id, side, next, qc.CNum)
--							end

							tinsert (grp, format ("%s%2d^%d", name, qc.CNum, id))
							qc = self.IdToQuest[next]
							id = next
						end
					end

--					Nx.prt ("Quest "..id.." "..level)
				end
			end
		end

--		table.sort (grp)

		for _, v in ipairs (grp) do
			local name, id = strsplit ("^", v)
			tinsert (self.Sorted, tonumber (id))
		end
	end

	-- Create quest givers

	local usedIds = {}

	local starters = {}
	self.QGivers = starters

	for qsIndex, qId in ipairs (self.Sorted) do

		if not usedIds[qId] then

			local quest = self.IdToQuest[qId]
			if quest then
				local sName, zone, x, y = self:GetSEPos (quest[2])

				if zone and x ~= 0 and y ~= 0 then

					usedIds[qId] = true

					sName = format ("%s=%d%d", sName, x, y)

					local stmap = starters[zone] or {}
					starters[zone] = stmap
					local s = stmap[sName] or ""
					stmap[sName] = s .. format ("%4x", qId)
				end
			end
--		else
--			Nx.prt ("skipped %s", qId)
		end
	end

	--

	self.List:Open()
	self.Watch:Open()

	-- Menu

	local menu = Nx.Menu:Create (self.Map.Frm)
	self.IconMenu = menu

	menu:AddItem (0, "Track", self.Menu_OnTrack, self)
	menu:AddItem (0, "Show Quest Log", self.Menu_OnShowQuest, self)
	self.IconMenuIWatch = menu:AddItem (0, "Watch", self.Menu_OnWatch, self)

	menu:AddItem (0, "Add Note", self.Map.Menu_OnAddNote, self.Map)

	-- Quest area blob test
--[[
	local f = CreateFrame ("QuestPOIFrame", "NxQuestBlob", self.Map.Frm)
	self.BlobFrm = f

	f:SetFillTexture ("Interface\\WorldMap\\UI-QuestBlob-Inside")
	f:SetBorderTexture ("Interface\\WorldMap\\UI-QuestBlob-Outside")
	f:SetFillAlpha (128)
	f:SetBorderAlpha (192)
	f:SetBorderScalar (1)
--]]
	-- Hook quests

	self.BlizzAcceptQuest = AcceptQuest
	AcceptQuest = self.AcceptQuest

--	self.BlizzCompleteQuest = CompleteQuest
--	CompleteQuest = self.CompleteQuest

	self.BlizzGetQuestReward = GetQuestReward
	GetQuestReward = self.GetQuestReward

	local function func()
--		Nx.prt ("QAccept")
		if QuestGetAutoAccept() then
--			Nx.prt ("auto")
			Nx.Quest:RecordQuestAcceptOrFinish()
		end

		QuestFrameDetailPanel_OnShow()

		local auto = Nx:GetGlobalOpts()["QAutoAccept"]
		if IsShiftKeyDown() and IsControlKeyDown() then
			auto = not auto
		end
		if auto and not QuestGetAutoAccept() then
			AcceptQuest()
		end
	end

	QuestFrameDetailPanel:SetScript ("OnShow", func);

	-- Hook tooltip

	local ttHooks = {
		"SetAction", "SetAuctionItem", "SetBagItem", "SetCraftItem", "SetCraftSpell",
		"SetGuildBankItem", "SetHyperlink", "SetInboxItem", "SetInventoryItem", "SetLootItem",
		"SetLootRollItem", "SetMerchantItem", "SetQuestItem", "SetQuestLogItem", "SetTradeSkillItem",
		"SetTradeTargetItem",
	}

	for k, name in ipairs (ttHooks) do
		if not Nx.V30 or name ~= "SetCraftItem" and name ~= "SetCraftSpell" then
			hooksecurefunc (GameTooltip, name, Nx.Quest.TooltipHook)
		end
	end

	local unitNames = {	-- 5 letter and shorter words are already blocked
		"Hunter", "Paladin", "Priest",
		"Shaman", "Warlock", "Warrior", "Deathknight"
	}

	self.TTIgnore = {
		["Attack"] = true,
		["Lumber Mill"] = true,
		["Stables"] = true,
		["Blacksmith"] = true,
		["Gold Mine"] = true,
	}

	self.TTIgnore[UnitName ("player")] = true

	for _, v in pairs (unitNames) do
		self.TTIgnore[v] = true
	end

	self.TTChange = {
		["Bloodberry Bush"] = "Bloodberries",
		["Erratic Sentry"] = "Erratic Sentries",
	}

	-- ID test

--[[
	for hash, q in pairs (Nx.Quests) do

		if NxQID2BQID[q.ID] ~= q.BlizID then
			Nx.prt ("Id mismatch %d %d %d", hash, q.ID, q.BlizID)
		end
	end
--]]

	-- Hash test

--[[
	for hash, q in pairs (Nx.Quests) do

		if NxQID2BQID[q.ID] ~= q.BlizID then
			Nx.prt ("Id mismatch %d %d %d", hash, q.ID, q.BlizID)
		end
	end

	local time = GetTime()

	local failcnt = 0
	local xcnt = 0

	local loopCnt = 10

	for n = 1, loopCnt do

		for k, q in pairs (Nx.Quests) do

			if k < 0x1000000 then
				local h = self:Hash (q.title, q.level)
				if k ~= h then
					Nx.prt (format ("Hash '%s %s %s'", q.title, q.level, q.side))
					Nx.prt (format ("Hash %x %x", k, h))
					failcnt = failcnt + 1
				end
			else
				xcnt = xcnt + 1

			end
		end
	end

	time = GetTime() - time

	Nx.prt (format ("Hash %d failed, %d extended. %f secs", failcnt / loopCnt, xcnt / loopCnt, time / loopCnt))
--]]

end

function Nx.Quest:CheckQuestSE (q, n)

	local _, zone, x, y = self:GetSEPos (q[n])
	local mapId = Nx.Map.NxzoneToMapId[zone]

	if (x == 0 or y == 0) and mapId and not Nx.Map:IsInstanceMap (mapId) then
		q[n] = format ("%s# ####", strsub (q[n], 1, 2))	-- Zero it to get a red button
--		local oName = self:UnpackSE (q[n])
--		Nx.prt ("zeroed %s, %s", self:UnpackName (q[1]), oName)
	end
end

function Nx.Quest:CheckQuestObj (q, n)

	local oName, zone, x, y = self:GetObjectivePos (q[n])
	local mapId = Nx.Map.NxzoneToMapId[zone]

	if (x == 0 or y == 0) and mapId and not Nx.Map:IsInstanceMap (mapId) then
		q[n] = format ("%c%s# ####", #oName + 35, oName)	-- Zero it to get a red button
--		Nx.prt ("zeroed %s, %s", self:UnpackName (q[1]), oName)
	end
end

--------
-- Calculate the watch colors

function Nx.Quest:CalcWatchColors()

--	Nx.QLocColors = { 1,0,0, "QuestWatchR", 0,1,0, "QuestWatchG", .2,.2,1, "QuestWatchB" }

	local opts = self.GOpts

	local colors = {}
	self.QLocColors = colors

	local a = Nx.Util_num2a (opts["QMapWatchAreaAlpha"])

	local colMax = opts["QMapWatchColorCnt"]
	local colI = 1

	for n = 1, 15 do

		local color = {}
		colors[n] = color

		local r, g, b = Nx.Util_num2rgba (opts["QMapWatchC" .. colI])
		color[1] = r
		color[2] = g
		color[3] = b
		color[4] = a
		color[5] = "QuestListWatch"

		colI = colI + 1
		colI = colI > colMax and 1 or colI
	end
end

--------
-- Menu

function Nx.Quest:Menu_OnTrack()

	local cur = self.IconMenuCur
	local v = cur.QId * 0x10000 + self.IconMenuObjI * 0x100 + cur.QI

--	Nx.prt ("Track %x (%d)", v, self.IconMenuObjI)

	self.Watch:Set (v, true, true)

--	self.IconMenuCur
--	self.IconMenuObjI
end

function Nx.Quest:Menu_OnShowQuest()

	ShowUIPanel (QuestLogFrame)

	self.List.Bar:Select (1)

	local cur = self.IconMenuCur
	self.List:Select (cur.QId, cur.QI)
end

function Nx.Quest:Menu_OnWatch (item)

	local cur = self.IconMenuCur
	self.List:ToggleWatch (cur.QId, cur.QI, 0)
end

--------
-- Track quest acception

function Nx.Quest.AcceptQuest (...)

	Nx.Quest:RecordQuestAcceptOrFinish()
	Nx.Quest.BlizzAcceptQuest (...)
end

--------
--
--[[
function Nx.Quest.CompleteQuest (...)

--	Nx.prt ("CompleteQuest ")
--	Nx.prt ("Title '%s'", GetTitleText())

	Nx.Quest.BlizzCompleteQuest (...)
end
--]]

function Nx.Quest.GetQuestReward (choice, ...)

--	Nx.prt ("GetQuestReward %s", choice or "nil")

	local q = Nx.Quest
	q:FinishQuest()
    q.BlizzGetQuestReward (choice, ...)
end

function Nx.Quest:FinishQuest()

--	Nx.prt ("FinishQuest")

	local finTitle = GetTitleText()
	finTitle = self:ExtractTitle (finTitle)

	local i, cur = self:FindCur (finTitle)

	if not i then

--		Nx:ShowMessage (Nx.TXTBLUE.."Carb:\n|rCan't find quest in list!\nAn addon may have modified the title\n'" .. finTitle .. "'", "Continue")
--		assert (nil)
		return
	end

	cur.QI = 0		-- 0 so we dont get a final party message

	local qId = cur.QId

	assert (type (qId) ~= "string")

	local id = qId > 0 and qId or cur.Title
	Nx:SetQuest (id, "C", time())

	self:RecordQuestAcceptOrFinish()
	self:Capture (i, -1)

--	Nx.prt ("FinishQuest #%s (%s) %s", i, id, cur.Title)

	if cur.Q then

		self.Tracking[qId] = 0
		self:TrackOnMap (qId, 0)
	end

--	self.List:Update()
	self.Watch:Update()
end

--------
-- Do Blizzard select quest

function Nx.Quest:SelectBlizz (qi)

	if qi > 0 then

		SelectQuestLogEntry (qi)

--		QuestLog_SetSelection (qi)
--		QuestLog_Update()

--[[
		local lh = getglobal ("LightHeaded")
		if lh then

			if lh["SelectQuestLogEntry"] then
				lh["SelectQuestLogEntry"](lh)
			elseif lh["QuestLogTitleButton_OnClick"] then
				lh["QuestLogTitleButton_OnClick"](lh)
			end
		end
--]]
	end
end

--------
-- Expand any collapsed quests

function Nx.Quest:ExpandQuests()

--	if next (self.HeaderExpanded) then	-- Currently expanded?
--		Nx.prt ("ExpandQuests skip")
--		return
--	end

--	Nx.prt ("ExpandQuests")

	repeat
		local found = false
		local cnt = GetNumQuestLogEntries()

		for qn = 1, cnt do

			local title, level, tag, groupCnt, isHeader, isCollapsed = GetQuestLogTitle (qn)
			if isHeader and isCollapsed then

				local he = self.HeaderExpanded
				he[title] = true

				ExpandQuestHeader (qn)
--				Nx.prt ("Expand #%s %s %s", qn, title, isCollapsed or "nil")
				found = true
				break
			end
		end
	until not found
end

--------
-- Expand any collapsed quests

function Nx.Quest:RestoreExpandQuests()

--[[

	if self.List.Win:IsShown() then		-- Don't restore if our window is shown
--		Nx.prt ("RestoreExpandQuests skip")
		return
	end

--	Nx.prt ("RestoreExpandQuests")

	for hName in pairs (self.HeaderExpanded) do

--		Nx.prt ("Collapse %s", hName)

		local cnt = GetNumQuestLogEntries()
		for qn = 1, cnt do

			local title, level, tag, groupCnt, isHeader, isCollapsed = GetQuestLogTitle (qn)
			if isHeader and title == hName then
				CollapseQuestHeader (qn)
--				Nx.prt ("Collapse #%s %s %s", qn, title, isCollapsed or "nil")
				break
			end
		end

		self.HeaderExpanded[hName] = nil
	end

--]]

end

--------
-- Access all quests. Forces game to fetch data, so we do not get ": x/x" objectives

function Nx.Quest:AccessAllQuests()

--	Nx.prt ("AccessAllQuests")

	self:ExpandQuests()

	local qcnt = GetNumQuestLogEntries()

	for qi = 1, qcnt do

		local title, level = GetQuestLogTitle (qi)

		local lbCnt = GetNumQuestLeaderBoards (qi)
		for n = 1, lbCnt do
			GetQuestLogLeaderBoard (n, qi)
		end
	end

	self:RestoreExpandQuests()
end

--------
-- Record quests
-- Example:
--  1 Get Attack << Fake quest (no blizz Num)
--  2 Bring
--  3 Capture

function Nx.Quest:RecordQuests()

--	Nx.prt ("Record Quests")

	local qcnt = GetNumQuestLogEntries()

	for qn = 1, qcnt do	-- Test all quests

		local title, level = GetQuestLogTitle (qn)
		if level < 0 then		-- If a -1 then data not updated. QuestGuru causes this to happen when zoning
			return
		end
	end

--	local tm = GetTime()

	self:ScanBlizzQuestDataZone()			-- Capture current zone
	self:ScanBlizzQuestData()				-- Triggers RecordQuestsLog() after done

	self:RecordQuestsLog()

--	Nx.prt ("%f secs", GetTime() - tm)
end

--------

function Nx.Quest:RecordQuestsLog()

	local qcnt = GetNumQuestLogEntries()

	local opts = self.GOpts
	local curq = self.CurQ
	local oldSel = GetQuestLogSelection()

--	Nx.prt ("RecordQuestsLog %s, %s", qcnt, #curq)

	local lastChanged

	local qIds = {}
	self.QIds = qIds

	--

	local partySend

	if self.RealQEntries == qcnt then	-- No quests added or removed?

		for curi, cur in ipairs (curq) do

			local qi = cur.QI
			if qi > 0 then

				local title, level, tag, groupCnt, isHeader, isCollapsed, isComplete = GetQuestLogTitle (qi)
				title = self:ExtractTitle (title)

--				Nx.prt ("QD %s %s %s %s", title, qi, isHeader and "H1" or "H0", isComplete and "C1" or "C0")

				if cur.Title == title then		-- Still matches?

					local change

					if isComplete == 1 and not cur.Complete then
						Nx.prt ("Quest Complete '%s'", title)

						if opts["QSndPlayCompleted"] then
							self:PlaySound()
						end

						if opts["QAutoTurnInAC"] and cur.IsAutoComplete then
							ShowQuestComplete (qi)
						end

						if opts["QWRemoveComplete"] and not cur.IsAutoComplete then
							self.Watch:RemoveWatch (cur.QId, cur.QI)
							self.Watch:Update()
							change = false
						else
							change = true
						end

					end

					local lbCnt = GetNumQuestLeaderBoards (qi)
					for n = 1, lbCnt do

						local desc, _typ, done = GetQuestLogLeaderBoard (n, qi)

						--V4

						if desc and (desc ~= cur[n] or done ~= cur[n + 100]) then

--							Nx.prt ("Q Change %s->%s", desc, cur[n] or "nil")

							if opts["QWAddChanged"] then
								if change == nil then
									change = true
								end
							end

							local s1, _, oldCnt = strfind (cur[n] or "", ": (%d+)/")
							if s1 then
								oldCnt = tonumber (oldCnt)
							end

							local s1, _, newCnt = strfind (desc, ": (%d+)/")
							if s1 then
--								Nx.prt ("%s %s", i, total)
								newCnt = tonumber (newCnt)
							end

							if done or (oldCnt and newCnt and newCnt > oldCnt) then
								self:Capture (curi, n)
							end

							lastChanged = cur

							partySend = true
						end
					end

					if change and opts["QWAddChanged"] then
						self.Watch:Add (curi)
					end
				end
			end
		end

	else

		partySend = true
	end

	-- Remove real blizz quests

	local fakeq = {}

	local n = 1
	while curq[n] do

		local cur = curq[n]
		if not cur.Goto or cur.Party then
--			Nx.prt ("RecordQuests RemoveQ %s - %s", cur.Title, cur.QI)
			table.remove (curq, n)
		else
			fakeq[cur.Q] = cur
			n = n + 1
		end
	end

	-- Add blizz quests

	self.RealQ = {}

	local header = "?"

	self.RealQEntries = qcnt

	local index = #curq + 1

	for qn = 1, qcnt do

		local title, level, tag, groupCnt, isHeader, isCollapsed, isComplete, isDaily = GetQuestLogTitle (qn)

--		Nx.prt ("Q %d %s %s %d %s %s %s %s", qn, isHeader and "H" or " ", title, level, tag or "nil", groupCnt or "nil", isDaily or "not daily", isComplete and "C1" or "C0")

		if isHeader then
			header = title or "?"
--			if isCollapsed then
--				Nx.prt ("Q %s collapsed!", title)
--			end

		else
			title = self:ExtractTitle (title)

			SelectQuestLogEntry (qn)

			local qDesc, qObj = GetQuestLogQuestText()

			local qId, qLevel = self:GetLogIdLevel (qn)
			assert (qId)
			local quest = self.IdToQuest[qId]

--			local quest = self:Find (title, level, qDesc, qObj)
			local lbCnt = GetNumQuestLeaderBoards (qn)

			local cur = quest and fakeq[quest]
--			local DBqId = quest and self:UnpackId (quest[1])
--			assert (qId == DBqId)

			if not cur then
				cur = {}
				curq[index] = cur
				cur.Index = index
				index = index + 1

			else
				cur.Goto = nil						-- Might have been a goto quest
				cur.Index = index

				if quest then
					self.Tracking[qId] = 0
					self:TrackOnMap (qId, 0, true)
				end
			end

			qIds[qId] = cur

			cur.Q = quest
			cur.QI = qn							-- Blizzard index
			cur.QId = qId
			cur.Header = header
			cur.Title = title
			cur.ObjText = qObj
			cur.DescText = qDesc
			cur.Level = level
			cur.RealLevel = qLevel
			cur.NewTime = self.QIdsNew[qId]	-- Copy new time

			cur.Tag = tag
			cur.GCnt = groupCnt or 0

			cur.PartySize = groupCnt or 1
--			if cur.Tag then Nx.prt ("%s %s", cur.Tag, cur.GCnt) end
			if tag == "Dungeon" or tag == "Heroic" then
				cur.PartySize = 5
			elseif tag == "Raid" then
				cur.PartySize = 10
			end

			cur.TagShort = self.TagNames[tag] or ""

			cur.Daily = isDaily
			if isDaily then
				cur.TagShort = "$" .. cur.TagShort
			end

			cur.CanShare = GetQuestLogPushable()
			cur.Complete = isComplete		-- 1 is Done, nil not. Otherwise failed
			cur.IsAutoComplete = GetQuestLogIsAutoComplete (qn)

			local left = GetQuestLogTimeLeft()
			if left then
				cur.TimeExpire = time() + left
				cur.HighPri = true
			end

			cur.ItemLink, cur.ItemImg, cur.ItemCharges = GetQuestLogSpecialItemInfo (qn)

			cur.Priority = 1
			cur.Distance = 999999999
			cur.LBCnt = lbCnt

			for n = 1, lbCnt do
				local desc, typ, done = GetQuestLogLeaderBoard (n, qn)
				cur[n] = desc or "?"		--V4
				cur[n + 100] = done
			end

			local mask = 0
			local ender = quest and (quest[3] or quest[2])

			if (isComplete and ender) or lbCnt == 0 or (cur.Goto and quest[2]) then
				mask = 1

			else
				for n = 1, 99 do

					local done
					if n <= lbCnt then
						done = cur[n + 100]
					end

					local obj = quest and quest[3 + n]

					if not obj then
						break
					end

					if obj and not done then
						mask = mask + bit.lshift (1, n)
					end
				end
			end

			cur.TrackMask = mask

--			Nx.prt ("%s %x", title, mask)

			self.RealQ[title] = cur			-- For diff

			-- Calc total number in quest chain

			if quest then
				self:CalcCNumMax (cur, quest)
			end
		end
	end

	--

	if self.GOpts["QPartyShare"] and self.Watch.ButShowParty:GetPressed() then

--		Nx.prt ("-PQuest-")

		local pq = self.PartyQ

		for plName, pdata in pairs (pq) do

--			Nx.prt ("PQuest %s", plName)

			for qId, qT in pairs (pdata) do

				local quest = self.IdToQuest[qId]
				local cur = qIds[qId]

				if cur then		-- We have the quest?

					local s = format ("\n|cff8080f0%s|r", plName)

					if not cur.PartyDesc then

						cur.PartyDesc = ""
						cur.PartyNames = "\n|cfff080f0Me"
						cur.PartyCnt = 0
						cur.PartyComplete = cur.Complete

						for n, cnt in ipairs (qT) do
							cur[n + 200] = cur[n + 100]
							cur[n + 400] = "\n|cfff080f0Me" .. s
						end
					end

					cur.PartyDesc = cur.PartyDesc .. s
					cur.PartyNames = cur.PartyNames .. s
					cur.PartyCnt = cur.PartyCnt + 1
					cur.PartyComplete = cur.PartyComplete and qT.Complete

					local mask = (cur.PartyComplete or #qT == 0) and 1 or 0

					for n, cnt in ipairs (qT) do

						local total = qT[n + 100]

						local desc, done = self:CalcDesc (quest, n, cnt, total)

--						cur[n] = desc

						done = cur[n + 200] and done
						cur[n + 200] = done

						cur.PartyDesc = cur.PartyDesc .. "\n " .. desc
						cur[n + 400] = cur[n + 400] .. " " .. desc

						if not done then
							mask = mask + bit.lshift (1, n)
						end
					end

					cur.TrackMask = mask

				elseif quest then

					local name, side, lvl = self:Unpack (quest[1])

--					Nx.prt ("PartyQ %s", name)

					local cur = {}
					cur.Goto = true
					cur.Party = plName
					cur.PartyDesc = format ("\n|cff8080f0%s|r", plName)
					cur.PartyNames = cur.PartyDesc
					cur.Q = quest
					cur.QI = 0
					cur.QId = qId
					cur.Header = "Party, " .. plName
					cur.Title = name
					cur.ObjText = ""
					cur.Level = lvl
					cur.PartySize = 1
					cur.TagShort = ""
					cur.Complete = qT.Complete
					cur.Priority = 1
					cur.Distance = 999999999

					self:CalcCNumMax (cur, quest)

					tinsert (curq, cur)
					cur.Index = #curq

					cur.LBCnt = #qT

					local mask = (qT.Complete or #qT == 0) and 1 or 0

					for n, cnt in ipairs (qT) do

						local total = qT[n + 100]

						cur[n], cur[n + 100] = self:CalcDesc (quest, n, cnt, total)

						cur[n + 400] = cur.PartyNames

						if not cur[n + 100] then
							mask = mask + bit.lshift (1, n)
						end
					end

					cur.TrackMask = mask
				end
			end
		end
	end

	for curi, cur in ipairs (curq) do
		if cur.PartyCnt then
			cur.CompleteMerge = cur.PartyComplete

			for n, desc in ipairs (cur) do
				cur[n + 300] = cur[n + 200]
			end

		else
			cur.CompleteMerge = cur.Complete

			for n, desc in ipairs (cur) do
				cur[n + 300] = cur[n + 100]
			end
		end
	end

	--

	if lastChanged then
		self.QLastChanged = self:FindCurFromOld (lastChanged)
	end

	SelectQuestLogEntry (oldSel)

--	Nx.prt ("CurQ %d", #curq)

	self:SortQuests()

	if partySend then
		self:PartyStartSend()
	end

--	local map = Nx.Map:GetMap (1)
	self.Map.Guide:UpdateMapIcons()
end

--------
-- Scan
-- <QuestPOIFrame name="WorldMapBlobFrame">
--  DrawQuestBlob (id, bool)
--  UpdateMouseOverTooltip
--  GetNumTooltips()
--  GetTooltipIndex (i)

function Nx.Quest:ScanBlizzQuestData()

--	if Nx.Timer:IsActive ("QScanBlizz") then
--		Nx.prt ("ScanQ skip")
--		return
--	end

--	Nx.prt ("ScanQ")

	SetCVar ("questPOI", 1)		-- Enable or no POI data returned

--	self.ScanBlizzChanged = false

	self.ScanBlizzMapId = 1001

	-- Use delay or some quests won't be ready
	Nx.Timer:Start ("QScanBlizz", 1.0, self, self.ScanBlizzQuestDataTimer)
end

function Nx.Quest:ScanBlizzQuestDataTimer()

	WatchFrame:UnregisterEvent ("WORLD_MAP_UPDATE")		-- Map::ScanContinents can enable this again

--	local tm = GetTime()

	local Map = Nx.Map
	local curMapId = Map:GetCurrentMapId()

	local mapId = self.ScanBlizzMapId
	local scanCnt = 0

	while scanCnt < 10 do

		if mapId ~= curMapId then

			Map:SetCurrentMap (mapId)		-- Triggers WORLD_MAP_UPDATE, which calls MapChanged

			scanCnt = scanCnt + 1
		end

		local cont = floor (mapId / 1000)
--		local cont = Map:IdToContZone (mapId)
		local info = Map.MapInfo[cont]

--		Nx.prt ("ScanQ %s %s", cont, mapId)

		mapId = mapId + 1
--[[
		if not info.Max then
			Nx.prtVar ("err", info)
			return
		end
--]]
		if mapId > info.Max then

--			Nx.prt ("ScanQ next %s %s", cont, mapId)

			if cont == 5 then			-- Done?

				WatchFrame:RegisterEvent ("WORLD_MAP_UPDATE")	-- Back on when done

				Map:SetCurrentMap (curMapId)

--				Nx.prt ("ScanQ changed")

				self:RecordQuestsLog()

				return
			end

			mapId = (cont + 1) * 1000 + 1
		end

		self.ScanBlizzMapId = mapId
	end

	Map:SetCurrentMap (curMapId)

--	Nx.prt ("%f secs", GetTime() - tm)

	return 0
end

--------
-- Called by map WORLD_MAP_UPDATE

function Nx.Quest:MapChanged()

--	Nx.prt ("MapChanged %s", Nx.Map:GetCurrentMapId())

	if self.IdToQuest then	-- Quests inited?

		self:ScanBlizzQuestDataZone()
	end
end

function Nx.Quest:ScanBlizzQuestDataZone()

	local num = QuestMapUpdateAllQuests()		-- Blizz calls these in this order

	if num > 0 then

		QuestPOIUpdateIcons()

		local Map = Nx.Map
		local mapId = Map:GetCurrentMapId()
		local zone = Nx.MapIdToNxzone[mapId]

		if not zone then
--			Nx.prt ("ScanQuestZone %s, %s", mapId or "nil", num)
			return
		end

--		Nx.prt ("Id %s, %s", mapId, num)

		for n = 1, num do
			local id, qi = QuestPOIGetQuestIDByVisibleIndex (n)
			if qi and qi > 0 then
				local title, level, tag, groupCnt, isHeader, isCollapsed, isComplete = GetQuestLogTitle (qi)
				local lbCnt = GetNumQuestLeaderBoards (qi)

				local quest = self.IdToQuest[id] or {}
				local patch = self.IdToQuest[-id] or 0
				local needEnd = isComplete and not quest[3]
				if patch > 0 or needEnd or (not isComplete and not quest[4]) then
					local _, x, y, objective = QuestPOIGetIconInfo (id)

					if x then	-- Miner's Fortune was found in org, but x, y, obj were nil

--						Nx.prt ("%s #%s %s %s %s %s", mapId, n, id, x or "nil", y or "nil", objective or "nil")

						if not quest[1] then

--							self.ScanBlizzChanged = true

							quest[1] = format ("%c%s######", #title + 35, title)

							self.IdToQuest[id] = quest

							Nx.Quests[(id + 7) * 2 - 3] = quest
						end

						x = x * 10000
						y = y * 10000

						if needEnd or bit.band (patch, 1) then

							patch = bit.bor (patch, 1)		-- Flag as a patched quest

							quest[3] = format ("##%c %c%c%c%c", zone + 35,
									floor (x / 221) + 35, x % 221 + 35, floor (y / 221) + 35, y % 221 + 35)
						end

						if not isComplete then

							patch = bit.bor (patch, 2)

							local s = title
							local obj = format ("%c%s%c %c%c%c%c", #s + 35, s, zone + 35,
									floor (x / 221) + 35, x % 221 + 35, floor (y / 221) + 35, y % 221 + 35)

							for i = 1, lbCnt do
								quest[3 + i] = obj
							end
						end

						self.IdToQuest[-id] = patch

--[[
						if not self.ScanBlizzChanged and
								(q2 ~= quest[2] or q4 ~= quest[4]) then

							self.ScanBlizzChanged = true
						end
--]]
					end
				end
			end
		end
	end
end


--------

function Nx.Quest:CalcCNumMax (cur, quest)

	if quest.CNum then

		cur.CNumMax = quest.CNum - 1

		local qc = quest
		while qc do
			cur.CNumMax = cur.CNumMax + 1
			qc = self.IdToQuest[self:UnpackNext (qc[1])]
		end
	end
end

--------
-- Set quests done

function Nx.Quest:CurQSetPreviousDone()

--	local sTime = GetTime()

	local cnt = 0

	for curi, cur in ipairs (self.CurQ) do
		if cur.QI > 0 then
			cnt = cnt + self:CalcPreviousDone (cur.QId)
		end
	end

	if cnt > 0 then
		Nx.prt ("Set %d chain quests as done", cnt)
	end

--	Nx.prt ("Calc %f secs", GetTime() - sTime)
end

function Nx.Quest:CalcPreviousDone (qId)

	local cnt = 0

	for mungeId, q in pairs (Nx.Quests) do

		if q.CNum == 1 then		-- Only look at chain starters

			local id = (mungeId + 3) / 2 - 7
			local qc = q
			while qc do

				if id == qId then		-- Found me in chain? Mark before me complete

					local id = (mungeId + 3) / 2 - 7
					local qc = q
					while id ~= qId do

						local qStatus = Nx:GetQuest (id)
						if qStatus ~= "C" then

							cnt = cnt + 1

--							Nx.prt ("%s %s", id, qId)
							Nx:SetQuest (id, "C", time())
						end

						id = self:UnpackNext (qc[1])
						qc = self.IdToQuest[id]
					end

					break
				end

				id = self:UnpackNext (qc[1])
				qc = self.IdToQuest[id]
			end
		end
	end

	return cnt
end

--------
-- Fired on login

function Nx.Quest:GetHistoryTimer()

--	local down = GetNetStats()		-- .08 to 4. Seems to be an average since it creeps down

--	Nx.prt ("GetNetStats %f", down)

--	if down > 2.5 then	-- Wait?
--		return 2
--	end
	if not Nx.CurCharacter["QHAskedGet"] then
		Nx.CurCharacter["QHAskedGet"] = true
		local function func()
			Nx.Timer:Start ("QHistQuery", .1, Nx.Quest, Nx.Quest.QuestQueryTimer)
		end

		Nx:ShowMessage ("Get character's quest completion data from the server?", "Get", func, "Cancel")
	end
end

function Nx.Quest:QuestQueryTimer()

	local qc = GetQuestsCompleted()
	if not qc then
		Nx.prt ("QuestQueryTimer wait")
		return 1
	end

--	Nx.prtVar ("OnQuest_query_complete", qc)

	local cnt = 0

	for id in pairs (qc) do

		local qStatus = Nx:GetQuest (id)
		if qStatus ~= "C" then

			cnt = cnt + 1
			Nx:SetQuest (id, "C", time())
		end
	end

	if cnt > 0 then
		Nx.prt ("Set %d quests as done", cnt)
		Nx.Quest.List:Update()
	end
end


function Nx.Quest:CalcDesc (quest, objI, cnt, total)

	local desc = ""
	local obj = quest and quest[objI + 3]
	if obj then
		desc = self:UnpackObjective (obj)
	end

	if total == 0 then
		return desc, cnt == 1
	else
		return format ("%s : %d/%d", desc, cnt, total), cnt >= total
	end
end


function Nx.Quest:GetLogIdLevel (index)

	if index > 0 then
		local qlink = GetQuestLink (index)
		if qlink then
			local s1, _, id, level = strfind (qlink, "Hquest:(%d+):(.%d*)")
			if s1 then

--				Nx.prt ("qlink %s", gsub (qlink, "|", "^"))

				return tonumber (id), tonumber (level)
			end
		end
	end
end

function Nx.Quest:CreateLink (qId, realLevel, title)

	if realLevel <= 0 then	-- Could be a 0
		realLevel = -1
	end
	return format ("|cffffff00|Hquest:%s:%s|h[%s]|h|r", qId, realLevel, title)
end

function Nx.Quest:ExtractTitle (title)

--	Nx.prt ("Orig '%s'", title)

	local _, e = strfind (title, "^%[%S+%] ")
	if e then
		title = strsub (title, e + 1)

	else
		local _, e = strfind (title, "^%d+%S* ")
		if e then
			title = strsub (title, e + 1)
		end
	end

--	Nx.prt ("'%s'", title)

	return title
end

--------
-- Sort quests

function Nx.Quest:SortQuests()

	local curq = self.CurQ

	-- Sort by level

	repeat
		local done = true

		for n = 1, #curq - 1 do

			if curq[n].Level > curq[n + 1].Level then
				curq[n], curq[n + 1] = curq[n + 1], curq[n]
				done = false
			end
		end

	until done

	-- Sort by header

	if self.List.QOpts.NXShowHeaders then

		local hdrNames = {}

		for n = 1, #curq do
			hdrNames[curq[n].Header] = 1
		end

		local hdrs = {}

		for name in pairs (hdrNames) do
			tinsert (hdrs, name)
		end

		sort (hdrs)

--		Nx.prtVar ("HDR", hdrs)

		local curq2 = curq
		curq = {}

		for _, name in ipairs (hdrs) do

			for n = 1, #curq2 do

				if curq2[n].Header == name then
					tinsert (curq, curq2[n])
				end
			end
		end

		self.CurQ = curq

--		Nx.prtVar ("curq", curq)
	end

	-- Build id mapping

	local t = {}
	self.IdToCurQ = t

	for k, cur in ipairs (curq) do

		if cur.Q then
			local id = cur.QId
			t[id] = cur
		end
	end
end

--------
-- Detect a new quest

function Nx.Quest:FindNewQuest()

	-- Id
--[[
	if self.AcceptQId then	-- Auto accept quest triggered?

		local qi = GetQuestLogIndexByID (self.AcceptQId)
		self.AcceptQId = nil

		local title = self:ExtractTitle (GetQuestLogTitle (qi))

		if not self.RealQ[title] then
			return qi
		end
	end
--]]

	-- Scan by name

	local aQName = self.AcceptQName

	if not aQName then
		return
	end

	local cnt = GetNumQuestLogEntries()

--	Nx.prt ("FindNewQuest %d", cnt)

	for qn = 1, cnt do

		local title, level, tag, groupCnt, isHeader, isCollapsed, isComplete = GetQuestLogTitle (qn)

		if not isHeader then

			title = self:ExtractTitle (title)

			if title == aQName then

				if not self.RealQ[title] then
--					Nx.prtVar ("RealQ", self.RealQ)

					self.AcceptQName = nil
					return qn
				end
			end
		end
	end
end

--------

function Nx.Quest:RecordQuestAcceptOrFinish()

	local giver = UnitName ("npc") or "?"

	local guid = UnitGUID ("npc")
	if guid then
		local typ = tonumber (strsub (guid, 3, 5), 16)
		if typ == 0 then	-- Player
			giver = "p"

		elseif bit.band (typ, 0xf) == 1 then
			local id = tonumber (strsub (guid, 6, 12), 16)
			giver = format ("%s#o%x", giver, id)

		elseif bit.band (typ, 0xf) == 3 then		-- NPC?
			local id = tonumber (strsub (guid, 7, 10), 16)
			giver = format ("%s#%x", giver, id)
		end
	end

	self.AcceptGiver = giver

	local qname = GetTitleText()		-- Also works for auto accept
	self.AcceptQName = qname

	local id = Nx.Map:GetRealMapId()
--	self.AcceptNxzone = Nx.MapIdToNxzone[id] or 0
	self.AcceptAId = Nx.IdToAId[id] or 0

	self.AcceptDLvl = 0

	if Nx.Map:GetCurrentMapId() == id then
		self.AcceptDLvl = GetCurrentMapDungeonLevel()
	end

	local map = Nx.Map:GetMap (1)
	self.AcceptX = map.PlyrRZX
	self.AcceptY = map.PlyrRZY

--	Nx.prt ("AcceptQuest (%s) (%s) %s,%s", giver, qname, self.AcceptAId, self.AcceptDLvl)

end

--------

function Nx.Quest.OnChat_msg_combat_faction_change (event, arg1)

	local self = Nx.Quest

--	Nx.prt ("OnChat_msg_combat_faction_change %s", arg1)

	local form = FACTION_STANDING_INCREASED
	form = gsub (form, "%%s", "(.+)")
	form = gsub (form, "%%d", "(%%d+)")
	local facName, rep = strmatch (arg1, form)
	rep = tonumber (rep)

	if facName and rep and self.CaptureQEndTime and GetTime() - self.CaptureQEndTime < 2 then

		local facNum = self.CapFactionAbr[facName]
		if facNum then

			local _, race = UnitRace ("player")
			if race == "Human" then
				rep = rep / 1.1 + .5
			end

--			Nx.prt ("Fac %s %s", facName, rep)

			local cap = Nx:GetCap()
			local quests = Nx:CaptureFind (cap, "Q")
			local qdata = { strsplit ("~", quests[self.CaptureQEndId]) }
			local ender, reps = strsplit ("@", qdata[2])

			local repdata = reps and { strsplit ("^", reps) } or {}
			tinsert (repdata, format ("%d %x", rep, facNum))
			reps = table.concat (repdata, "^")

			qdata[2] = format ("%s@%s", ender, reps)
			quests[self.CaptureQEndId] = table.concat (qdata, "~")	-- concat is not global!!!
		end
	end

	self.CaptureQEndTime = nil
end

--------
-- Capture a quest
-- (current index, objective # (nil for start, -1 end)

function Nx.Quest:Capture (curi, objNum)

	local Nx = Nx
	local opts = self.GOpts

	if not opts["CaptureEnable"] then
		return
	end

	local cur = self.CurQ[curi]
	local id = cur.QId

	if NxData.DebugMap and (not objNum or objNum < 0) then	-- Start or end
		Nx.prt ("Quest Capture %s", id or "nil")
	end

	if not id then
		return
	end

	local cap = Nx:GetCap()

	local facI = UnitFactionGroup ("player") == "Horde" and 1 or 0
	local quests = Nx:CaptureFind (cap, "Q")
	local saveId = id * 2 + facI

	local len = 0

	for id, str in pairs (quests) do
		len = len + 4 + #str + 1
	end

	if len > 110 * 1024 then
		return
	end

--	Nx.prt ("Cap len %s", len)

--[[
	if not objNum or objNum < 0 then
		Nx.prt ("Capture %s %s %s %.2f,%.2f", self.AcceptGiver, self.AcceptAId or 0, self.AcceptDLvl, self.AcceptX, self.AcceptY)
	else
		local map = self.Map
		Nx.prt ("Capture #%s %s %.2f,%.2f", objNum, map.RMapId, map.PlyrRZX, map.PlyrRZY)
	end
--]]

--	local ids = self:CaptureGet (quests, id)
--	ids["I"] = format ("%d^%s^%s", cur.RealLevel, cur.Title, cur.Header)

	local q = quests[saveId]

	if not q then
		q = strrep ("~", cur.LBCnt + 1)
	end

	local qdata = { strsplit ("~", q) }

	if not objNum then	-- Starter

--		local flags = bit.bor (tonumber (strsub (qdata[1], 1, 1), 16) or 0, facMask)
		local plLvl = UnitLevel ("player")

		-- 0 is reserved
		local s = Nx:PackXY (self.AcceptX, self.AcceptY)
--		qdata[1] = format ("0%s^%02x%02x%s", self.AcceptGiver, plLvl, self.AcceptAId, s)
		qdata[1] = format ("0%s^%03x%x%s", self.AcceptGiver, self.AcceptAId, self.AcceptDLvl, s)

--		Nx.prt ("Capture start %s", qdata[1])

	elseif objNum < 0 then	-- Ender

		local s = Nx:PackXY (self.AcceptX, self.AcceptY)
		qdata[2] = format ("%s^%03x%x%s", self.AcceptGiver, self.AcceptAId, self.AcceptDLvl, s)

		self.CaptureQEndTime = GetTime()
		self.CaptureQEndId = saveId

--		Nx.prt ("Capture end %s", qdata[2])

	else

		local map = self.Map
--		local nxzone = Nx.MapIdToNxzone[map.RMapId]
		local nxzone = Nx.IdToAId[map.RMapId]
		if nxzone then

			local index = objNum + 2
			local obj = qdata[index]

			if not obj then
				Nx.prt ("Capture err %s, %s", cur.Title, objNum)
				return
			end

			if #obj >= 3 then
				local z = tonumber (strsub (obj, 1, 3), 16)
				if nxzone ~= z then
					return
				end
			else
				obj = format ("%03x", nxzone)
			end

			local cnt = (#obj - 3) / 6
			if cnt >= 15 then
				return
			end

			qdata[index] = obj .. Nx:PackXY (map.PlyrRZX, map.PlyrRZY)

--			Nx.prt ("Capture%d #%d %s", objNum, cnt, qdata[index])
		end
	end

	quests[saveId] = table.concat (qdata, "~")	-- concat is not global!!!

--	Nx.prt ("CapStr %s", quests[saveId])
end

function Nx.Quest:CaptureGetCount()

	local cap = Nx:GetCap()
	local quests = Nx:CaptureFind (cap, "Q")

	local cnt = 0

	for id, str in pairs (quests) do
		cnt = cnt + 1
	end

	return cnt
end

--------
-- Check for newly completed quests

--[[
function Nx.Quest:CheckForNewCompleted()

	local opts = Nx:GetGlobalOpts()

	if self.RealQEntries ~= GetNumQuestLogEntries() then	-- Quests added or removed?
		return
	end

	local curq = self.CurQ

	for _, cur in ipairs (curq) do

		if cur.QI > 0 then

			local title, level, tag, groupCnt, isHeader, isCollapsed, isComplete = GetQuestLogTitle (cur.QI)

			if isComplete and not cur.Complete then
				Nx.prt ("Quest Complete '%s'", title)
--PAIDS!
				if opts["QSndPlayCompleted"] then
					self:PlaySound()
				end
--PAIDE!
				if opts["QWRemoveComplete"] then
					self.Watch:RemoveWatch (cur.QId, cur.QI)
					self.Watch:Update()
					self.List:Update()
				end

				break
			end
		end
	end
end
--]]

--------
-- Play a completed sound
-- (snd index or nil for random)

function Nx.Quest:PlaySound (sndI)

	if not sndI then

		local opts = self.GOpts
		local cnt = 0

		for n = 1, 10 do
			if opts["QSnd" .. n] then
				cnt = cnt + 1
			end
		end

		if cnt > 0 then

			local i = random (1, cnt)
			cnt = 0

			for n = 1, 10 do
				if opts["QSnd" .. n] then
					cnt = cnt + 1
					if cnt == i then
						sndI = n
						break
					end
				end
			end
		end
	end

	if sndI then
		local snd = Nx.OptsDataSounds[sndI]
		Nx:PlaySoundFile (snd)
	end
end

--------
-- Tell party of quest changes

function Nx.Quest:TellPartyOfChanges()

--PAIDS!

	if self.RealQEntries ~= GetNumQuestLogEntries() then	-- Quests added or removed?
		return
	end

	local opts = self.GOpts
	if not opts["QBroadcastQChanges"] then
		return
	end

	local curq = self.CurQ

	for _, cur in ipairs (curq) do

		if cur.QI > 0 then

			for n = 1, cur.LBCnt do

				local skip
				local desc, _, done = GetQuestLogLeaderBoard (n, cur.QI)

				if desc then
					if not done then

						local num = opts["QBroadcastQChangesNum"]
						local oldCnt = tonumber (strmatch (cur[n] or "", ": (%d+)/"))
						local newCnt = tonumber (strmatch (desc, ": (%d+)/"))
						if oldCnt and newCnt then
							if floor (oldCnt / num) == floor (newCnt / num) then
								skip = true
							end
						end
					end

					if not skip and (desc ~= cur[n] or done ~= cur[n + 100]) then

						Nx.Com:Send ("P", desc)
--						Nx.prt ("%s", desc)
					end
				end
			end
		end
	end

--PAIDE!
end

--------
-- unused???

function Nx.Quest:GetLongTitle (cur)

	local title = format ("[%d] %s", cur.Level, cur.Title)

	local quest = cur.Q
	if quest and quest.CNum then
		title = title .. format (" (Part %d of %d)", quest.CNum, cur.CNumMax)
	end

	return title
end

function Nx.Quest:GetPartTitle (quest, cur)

	local s = ""

	if quest and quest.CNum then
		if cur then
			s = s .. format ("(Part %d of %d)", quest.CNum, cur.CNumMax)
		else
			s = s .. format ("(Part %d)", quest.CNum)
		end
	end

	return s
end

function Nx.Quest:FindCur (qId, qIndex)

	if type (qId) == "string" then	-- Quest title?

		for n, v in ipairs (self.CurQ) do
			if v.Title == qId then
				return n, v, qId
			end
		end

		return
	end

	if qIndex and qId == 0 then
		local i, cur = self:FindCurByIndex (qIndex)
		return i, cur, cur.Title	-- Also return string type id
	end

	assert (qId > 0)

	for n, v in ipairs (self.CurQ) do
		if v.QId == qId then
			return n, v, qId
		end
	end
end

function Nx.Quest:FindCurByIndex (qi)

	assert (qi > 0)

	local curq = self.CurQ

	for n, v in ipairs (curq) do
		if v.QI == qi then
			return n, v
		end
	end
end

function Nx.Quest:FindCurFromOld (oldCur)

	for n, cur in ipairs (self.CurQ) do
		if cur.Title == oldCur.Title and cur.ObjText == oldCur.ObjText then
			return cur
		end
	end
end

--------
-- Check if any part of quest in the map

function Nx.Quest:CheckShow (mapId, qId)

	local nxid = Nx.MapIdToNxzone[mapId]
	local quest = self.IdToQuest[qId]

	if not quest then
		return
	end

	local qname, side, lvl, minlvl, next = self:Unpack (quest[1])

	--	Check start, end and objectives
--[[
	if not quest[2] then
		Nx.prt ("quest error: %s %s", qname, qId)
		assert (quest[2])
	end
--]]
	local _, startMapId = self:UnpackSE (quest[2])
	if startMapId then
		if startMapId == nxid then
			return true
		end
	end

	if quest[3] then
		local _, endMapId = self:UnpackSE (quest[3])
		if endMapId then
			if endMapId == nxid then
				return true
			end
		end
	end

	for n = 1, 15 do

		local obj = quest[n + 3]
		if not obj then
			break
		end

		local _, objMapId = self:UnpackObjective (obj)

		if objMapId then

			if objMapId == nxid then
				return true
			end
		end
	end
end

--------
--

function Nx.Quest:WatchAtLogin()

	for n, cur in ipairs (self.CurQ) do

		local qStatus = Nx:GetQuest (cur.QId)
		if not qStatus then

--			Nx.prt ("Add watch %s", cur.Title)
			self.Watch:Add (n)

--		elseif qStatus == "W" then
--			Nx.prt ("Watched %s", cur.Title)

--		elseif qStatus == "C" then
--			Nx.prt ("Completed %s", cur.Title)

		end
	end
end

function Nx.Quest:WatchAll()

	local curq = self.CurQ
	if curq then
		for i, cur in ipairs (curq) do
			self.Watch:Add (i)
		end
	end
end

function Nx.Quest:Goto (qId)

	if qId == 0 then
		return
	end

--	Nx.prt ("Goto %s", qId)

	local i = self:FindCur (qId)

	if i then
		Nx.prt ("Already going to quest")
		return
	end

	local curq = self.CurQ
	local quest = self.IdToQuest[qId]

	if not quest[2] then
		Nx.prt ("No quest starter")
		return
	end

	local name, side, lvl = self:Unpack (quest[1])

	local cur = {}
	cur.Goto = true
	cur.Q = quest
	cur.QI = 0
	cur.QId = qId
	cur.Header = "Goto"
	cur.Title = "Goto: " .. name
	cur.ObjText = ""
	cur.Level = lvl
	cur.PartySize = 1
	cur.LBCnt = 0
	cur.TrackMask = 1
	cur.TagShort = ""

	cur.Priority = 1
	cur.Distance = 999999999

	cur.HighPri = true

	self:CalcCNumMax (cur, quest)

	tinsert (curq, cur)
	cur.Index = #curq

	self.Watch:Add (#curq)

	self:RecordQuests()
	self.List:Update()
end

function Nx.Quest:Abandon (qIndex, qId)

	if qIndex > 0 then

		self:ExpandQuests()

		local title, level, tag, groupCnt, isHeader = GetQuestLogTitle (qIndex)

		if not isHeader then

--			Nx.prt ("Abandon %s %s", qIndex, title)

			SelectQuestLogEntry (qIndex)
--			QuestLog_SetSelection (qIndex)

			SetAbandonQuest()
			local items = GetAbandonQuestItems()
			if items then
				StaticPopup_Hide ("ABANDON_QUEST")
				StaticPopup_Show ("ABANDON_QUEST_WITH_ITEMS", GetAbandonQuestName(), items)
			else
				StaticPopup_Hide ("ABANDON_QUEST_WITH_ITEMS")
				StaticPopup_Show ("ABANDON_QUEST", GetAbandonQuestName())
			end
		end

		self:RestoreExpandQuests()

		if qId > 0 then
			Nx:SetQuest (qId, "c")
		end

	else
		if qId > 0 then

			self.Watch:RemoveWatch (qId, qIndex)

			local i = self:FindCur (qId)
			if i then
				local curq = self.CurQ
				tremove (curq, i)
			end
		end
	end
end

--------
-- Link a quest to chat edit frame

function Nx.Quest:LinkChat (qId)

	local box = ChatEdit_ChooseBoxForSend()
	ChatEdit_ActivateChat (box)

	if box then
		local s = self.List:MakeDescLink (nil, qId, IsControlKeyDown())
		if s then
			box:Insert (s)
		end
	else

		Nx.prt ("|cffff4040No edit box open!")
	end
end

--------
-- Get quests from a player

function Nx.Quest:GetFromPlyr (plName)

	Nx.ShowMessageTrial()

--	Nx.prt ("GetFromPlyr %s", plName)

	self.List.Bar:Select (4)

	self.FriendQuests = {}

	self.RcvPlyr = plName
	self.RcvPlyrLast = plName

	Nx.Com:Send ("W", "Q*", plName)
end

--------
-- Clear captured quests

function Nx.Quest:ClearCaptured()
	Nx:GetCap()["Q"] = {}
end

--------
-- Quest com message from a player

function Nx.Quest:OnMsgQuest (plName, msg)

--	Nx.prt ("OnMsgQuest (%s) %s", plName, msg)

	local id = strsub (msg, 2, 2)

	if id == "*" then		-- Request for all quests

--		if nil then
		if not self.SendPlyr or self.SendPlyr == plName then

			Nx.prt ("Sending quests to %s", plName)

			self.SendPlyr = plName
			self:BuildQSendData()
			Nx.Timer:Start ("QSendAll", 0, self, self.QSendAllTimer)
		else

			Nx.Com:Send ("W", "QB", plName)
		end

	elseif id == "B" then	-- Busy

		if plName == self.RcvPlyr then

			local mode = strsub (msg, 3, 3)

			if mode == "s" then
				Nx.prt (" %s -share", self.RcvPlyr)
			elseif mode == "C" then
				Nx.prt (" %s busy", self.RcvPlyr)
			else
				tinsert (self.FriendQuests, " ^Player is busy")
			end

			self.RcvPlyr = nil

			local pd = self.CapturePlyrData[plName]
			if pd then
				pd.RcvPlyrCapName = nil
			end
		end

	elseif id == "D" then	-- Incoming quest data

		if plName == self.RcvPlyr then

			if #msg >= 4 then

				local data = strsub (msg, 3)
				local mode = strsub (msg, 3, 3)

				if mode == "0" then

					self.RcvCnt = 0
					self.RcvTotal = tonumber (strsub (data, 3)) or 0

				elseif mode == "H" then

					tinsert (self.FriendQuests, data)
					self.List:Update()

				elseif mode == "T" then

					self.RcvCnt = self.RcvCnt + 1
					tinsert (self.FriendQuests, data)

--					Nx.prt ("Quest Data %s", data)

					self.List:Update()

				elseif mode == "O" then

					tinsert (self.FriendQuests, data)
					self.List:Update()
				end
			else
				self.RcvPlyr = nil
			end
		end

	elseif id == "p" then	-- Incoming party data

		self:OnPartyMsg (plName, msg)

	end
end

function Nx.Quest:BuildQSendData()

	local data = {}

	self.QSendData = data
	self.QSendDataI = 1

	local header
	local cnt = 0

	for n, cur in ipairs (self.CurQ) do

		if not cur.Goto then

			if cur.Header ~= header then
				header = cur.Header

				local str = format ("QDH^%s", header)
				tinsert (data, str)
			end

			local qStatus = Nx:GetQuest (cur.QId)
			local watched = qStatus == "W" and 1 or 0

			local str = format ("QDT^%s^%s^%s^%s^%s", cur.QId, watched, cur.Complete or 0, cur.Level, cur.Title)
			tinsert (data, str)

			for n = 1, cur.LBCnt do
				local str = format ("QDO^%s^%s", -n, cur[n])
				tinsert (data, str)
			end

			cnt = cnt + 1
		end
	end

	tinsert (data, "QD")

	local str = format ("QD0^%d", cnt)
	tinsert (data, 1, str)
end

function Nx.Quest:QSendAllTimer()

	local qi = self.QSendDataI
	local data = self.QSendData[qi]

	if data then

		Nx.Com:Send ("W", data, self.SendPlyr)

--		Nx.prt ("QSendAllTimer: %s", data)
	end

	self.QSendDataI = qi + 1

	if self.QSendData[self.QSendDataI] then
		return .2
	end

	self.SendPlyr = nil
end

--------
-- Show quest is not in DB

function Nx.Quest:MsgNotInDB (typ)

	if typ == "O" then
		UIErrorsFrame:AddMessage ("This objective is not in the database", 1, 0, 0, 1)
	elseif typ == "Z" then
		UIErrorsFrame:AddMessage ("This objective zone is not in the database", 1, 0, 0, 1)
	else
		UIErrorsFrame:AddMessage ("This quest is not in the database", 1, 0, 0, 1)
	end
end

-------------------------------------------------------------------------------

-- Troll Patrol: The Alchemist's Apprentice quest

Nx.Quest.AlchemistsApprenticeData = {

	["Abomination Guts"] = "3~4~3492~5283",
	["Amberseed"] = "3~3~3496~5157",
	["Ancient Ectoplasm"] = "3~2~3498~5157",
	["Blight Crystal"] = "3~2~3488~5347",
	["Chilled Serpent Mucus"] = "3~3~3509~5342",
	["Crushed Basilisk Crystals"] = "4~2~3487~5339",
	["Crystallized Hogsnot"] = "3~4~3494~5157",
	["Frozen Spider Ichor"] = "3~2~3472~5309",
	["Ghoul Drool"] = "4~     4~3490~5100",
	["Hairy Herring Head"] = "Floor~Crate~3511~5127",
	["Icecrown Bottled Water"] = "2~1~3499~5157",
	["Knotroot"] = "4~1~3499~5152",
	["Muddy Mire Maggots"] = "Floor~Sack~3485~5155",
	["Pickled Eagle Egg"] = "2~2~3497~5157",
	["Prismatic Mojo"] = "4~3~3491~5289",
	["Pulverized Gargoyle Teeth"] = "2~4~3494~5157",
	["Putrid Pirate Perspiration"] = "2~3~3496~5157",
	["Raptor Claw"] = "3~2~3489~5283",
	["Seasoned Slider Cider"] = "Floor~Barrel~3508~5317",
	["Shrunken Dragon's Claw"] = "3~3~3489~5093",
	["Speckled Guano"] = "2~3~3490~5093",
	["Spiky Spider Egg"] = "3~4~3510~5095",
	["Trollbane"] = "3~1~     3505~5095",
	["Wasp's Wings"] = "3~1~3499~5157",
	["Withered Batwing"] = "4~3~3496~5153",
}

function	Nx.Quest:OnChat_msg_raid_boss_whisper (event, arg1)

	if arg1 then

		if GetMinimapZoneText() == "Heb'Valok" then

			local self = Nx.Quest	-- Need?
--			Nx.prt ("%s, %s, %s", arg1, arg2 or "nil", arg3 or "nil")

			local name = gsub (arg1, "!", "")

			local data = self.AlchemistsApprenticeData[name]
			if data then
				local shelf, item, x, y = strsplit ("~", data)
				x = tonumber (x) * .01
				y = tonumber (y) * .01

				local s = format ("%s on %s in %s", name, shelf, item)

				if tonumber (shelf) then
					s = format ("%s, shelf %s, item %s", name, shelf, item)
				end

				self.Map:SetTargetXY (4011, x, y, s)
			end
		end
	end
end

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Quest tooltips

function	Nx.Quest.TooltipHook()

--	Nx.prt ("TooltipHook")

	Nx.Quest:TooltipProcess()
end

function	Nx.Quest:TooltipProcess (stripColor)

	local tipStr = GameTooltipTextLeft1:GetText()
	if not tipStr then		-- Happens in WotLK on empty slots
		return
	end

--	Nx.prt ("TooltipProcess %s", tipStr)

	Nx.TooltipLastDiffText = tipStr

--	local sTime = GetTime()

	local show = Nx.Quest:TooltipProcess2 (stripColor, tipStr)
--	show = show or Nx.Warehouse:TooltipProcess()
	show = Nx.Warehouse:TooltipProcess() or show

	if show then
		GameTooltip:Show()	-- Adjusts size
	end

--	Nx.prt ("TTProcess %f secs", GetTime() - sTime)

	Nx.TooltipLastDiffNumLines = GameTooltip:NumLines()	-- Stop multiple checks
end

function	Nx.Quest:TooltipProcess2 (stripColor, tipStr)

	if not self.GOpts["QAddTooltip"] then
		return
	end

	local tip = GameTooltip

	-- Check if already added

	local textName = "GameTooltipTextLeft"
	local questStr = format ("|cffffffffQ%suest:", Nx.TXTBLUE)

	for n = 2, tip:NumLines() do
		local s = _G[textName .. n]:GetText()
		if s then

			local s1 = strfind (s, questStr)
			if s1 then
--				Nx.prt ("TTM #%s", GameTooltip:NumLines())
				return
			end
			if strsub (s, 1, 3) == " - " then	-- Blizz added quest info?

				local fstr = _G[textName .. (n - 1)]
				local qTitle = fstr:GetText()

				local i, cur = self:FindCur (qTitle)
				if cur then
					local color = self:GetDifficultyColor (cur.Level)
					color = format ("|cff%02x%02x%02x", color.r * 255, color.g * 255, color.b * 255)
					fstr:SetText (format ("%s %s%d %s", questStr, color, cur.Level, cur.Title))
				end

				tip:AddLine (" ")		-- Add blank or same tip will not add info again

				return true;
			end
		end
	end

	-- Scan tooltip

	if stripColor then
		tipStr = gsub (tipStr, "|c%x%x%x%x%x%x%x%x", "")
	end

	if tipStr and #tipStr > 5 and #tipStr < 50 and not self.TTIgnore[tipStr] then

		tipStr = self.TTChange[tipStr] or tipStr
		local tipStrLower = strlower (tipStr)

		local curq = self.CurQ

		for curi, cur in ipairs (curq) do

			if not cur.Goto then		-- Skip Goto and Party quests

				local s1 = strfind (cur.ObjText, tipStr, 1, true)
				if not s1 then
					s1 = strfind (cur.DescText, tipStr, 1, true)
				end
				if not s1 then
					s1 = strfind (cur.ObjText, tipStrLower, 1, true)
				end
				if not s1 then
					s1 = strfind (cur.DescText, tipStrLower, 1, true)
				end
				if not s1 then
					for n = 1, cur.LBCnt do
						if cur[n] then	-- V4
							s1 = strfind (cur[n], tipStr)
							if s1 then
								break
							end
						end
					end
				end

				if s1 then

					local color = self:GetDifficultyColor (cur.Level)
					color = format ("|cff%02x%02x%02x", color.r * 255, color.g * 255, color.b * 255)

					tip:AddLine (format ("%s %s%d %s", questStr, color, cur.Level, cur.Title))

					for n = 1, cur.LBCnt do
						if strfind (cur[n], tipStr) then
							local color, s1 = self:CalcPercentColor (cur[n], cur[n + 100])
							if s1 then
								local oName = strsub (cur[n], 1, s1 - 1)
								tip:AddLine (format ("    |cffb0b0b0%s%s%s", oName, color, strsub (cur[n], s1)))
							else
								tip:AddLine (format ("    %s%s", color, cur[n]))
							end
						end
					end

--					Nx.prt ("TTProcess %s #%s", tipStr, tip:NumLines())

					return true;
				end
			end
		end
	end
end

--------

function Nx.Quest:GetDifficultyColor (level)

	return GetQuestDifficultyColor (level)
end

--------

function Nx.Quest:CalcPercentColor (desc, done)

	local s1, _, i, total = strfind (desc, ": (%d+)/(%d+)")

	if done then
		return self.PerColors[9], s1
	else
		i = s1 and floor (tonumber (i) / tonumber (total) * 8.99) + 1 or 1
		return self.PerColors[i], s1
	end
end

--------

function Nx.Quest:GetZoneAchievement (always)

	local mId = Nx.Map:GetCurrentMapId()
--	local mId = Map:GetRealMapId()

	local a = Nx.Map.MapWorldInfo[mId].QAchievementId
	if a then

		local id, name, _, done = GetAchievementInfo (a)
		if always or not done then
			if GetAchievementNumCriteria(a) > 0 then
				local _, _, done, cnt, need = GetAchievementCriteriaInfo (a, 1)
				local col = done and "|cff808080" or "|cff8080ff"
				return format ("%s%s %d/%d", col, name, cnt, need)
			end
		end
	end
end

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Quest list

----------------------------------

--------
-- Open and init or toggle Quest frame

function Nx.Quest.List:Open()

	local gopts = Nx:GetGlobalOpts()

	local qopts = Nx:GetQuestOpts()
	self.QOpts = qopts

	local TabBar = Nx.TabBar

	self.ShowAllZones = false
	self.Opened = true

	-- Create window

	local win = Nx.Window:Create ("NxQuestList")
	self.Win = win

	win:CreateButtons (true, true)
	win:InitLayoutData (nil, -.24, -.15, -.52, -.65)

	tinsert (UISpecialFrames, "QuestLogFrame")
	tinsert (UISpecialFrames, win.Frm:GetName())

	win.Frm:SetToplevel (true)
	win.Frm:SetMinResize (250, 120)

	win:SetUser (self, self.OnWin)
	win:RegisterEvent ("PLAYER_LOGIN", self.OnQuestUpdate)
--	win:RegisterEvent ("PLAYER_LOGOUT", self.OnQuestUpdate)
--	win:RegisterEvent ("PLAYER_LEAVING_WORLD", self.OnQuestUpdate)
	win:RegisterEvent ("QUEST_LOG_UPDATE", self.OnQuestUpdate)
	win:RegisterEvent ("QUEST_WATCH_UPDATE", self.OnQuestUpdate)
	win:RegisterEvent ("UPDATE_FACTION", self.OnQuestUpdate)
	win:RegisterEvent ("UNIT_QUEST_LOG_CHANGED", self.OnQuestUpdate)
	win:RegisterEvent ("QUEST_PROGRESS", self.OnQuestUpdate)
	win:RegisterEvent ("QUEST_COMPLETE", self.OnQuestUpdate)
	win:RegisterEvent ("QUEST_DETAIL", self.OnQuestUpdate)
    win:RegisterEvent ("SCENARIO_UPDATE", self.OnQuestUpdate)
    win:RegisterEvent ("SCENARIO_CRITERIA_UPDATE", self.OnQuestUpdate)
    win:RegisterEvent ("WORLD_STATE_TIMER_START", self.OnQuestUpdate)
    win:RegisterEvent ("WORLD_STATE_TIMER_STOP", self.OnQuestUpdate)

	-- Filter Edit Box

	local f = CreateFrame ("EditBox", "NxQuestFilter", win.Frm)
	self.FilterFrm = f

	f.NxInst = self

	f:SetScript ("OnEditFocusGained", self.FilterOnEditFocusGained)
	f:SetScript ("OnEditFocusLost", self.FilterOnEditFocusLost)
	f:SetScript ("OnTextChanged", self.FilterOnTextChanged)
	f:SetScript ("OnEnterPressed", self.FilterOnEnterPressed)
	f:SetScript ("OnEscapePressed", self.FilterOnEscapePressed)

	f:SetFontObject ("NxFontS")

	local t = f:CreateTexture()
	t:SetTexture (.1, .2, .3, 1)
	t:SetAllPoints (f)
	f.texture = t

	f:SetAutoFocus (false)
	f:ClearFocus()

	win:Attach (f, 0, 1, 0, 18)

	self.FilterDesc = "Search: [click]"
	self.FilterDescEsc = "Search: %[click%]"

--	if Nx.Free then
--		self.FilterDesc = "Search: " .. Nx.FreeMsg
--	end

	self.Filters = { "", "", "", ""}

	f:SetText (self.FilterDesc)
	f:SetMaxLetters (30)

	-- List

	Nx.List:SetCreateFont ("FontQuest", 12)

	local list = Nx.List:Create ("Quest", 0, 0, 1, 1, win.Frm)
	self.List = list

	list:SetUser (self, self.OnListEvent)

	list:SetLineHeight (0, 6)

	list:ColumnAdd ("", 1, 20)
	list:ColumnAdd ("", 2, 300)
--	list:ColumnAdd ("Lvl", 3, 20, "CENTER")
	list:ColumnAdd ("", 3, 0)
	list:ColumnAdd ("", 4, 600)
	list:ColumnAdd ("", 5, 200)
	list:ColumnAdd ("", 6, 500)

	-- Create menu

	local menu = Nx.Menu:Create (list.Frm, 240)
	self.Menu = menu

	local menui1 = {}
	self.MenuItems1 = menui1

	local menui2 = {}
	self.MenuItems2 = menui2

	local menui3 = {}
	self.MenuItems3 = menui3

	local menui4 = {}
	self.MenuItems4 = menui4

	local item = menu:AddItem (0, "Toggle High Watch Priority", self.Menu_OnHighPri, self)
	tinsert (menui1, item)

	local item = menu:AddItem (0, "Show Category Headers", self.Menu_OnShowHeaders, self)
	item:SetChecked (qopts.NXShowHeaders)
	tinsert (menui1, item)

	local item = menu:AddItem (0, "Show Objectives", self.Menu_OnShowObjectives, self)
	item:SetChecked (qopts.NXShowObj)
	tinsert (menui1, item)

	local item = menu:AddItem (0, "Show Only Party Quests", self.Menu_OnShowParty, self)
	item:SetChecked (false)
	tinsert (menui1, item)

	local item = menu:AddItem (0, "")
	tinsert (menui1, item)

	local item = menu:AddItem (0, "Watch All Quests", self.Menu_OnWatchAll, self)
	tinsert (menui1, item)

	local item = menu:AddItem (0, "Watch All Completed Quests", self.Menu_OnWatchCompleted, self)
	tinsert (menui1, item)

	local item = menu:AddItem (0, "")
	tinsert (menui1, item)

	local item = menu:AddItem (0, "Broadcast Quest Changes To Party", nil, self)
	item:SetChecked (gopts, "QBroadcastQChanges")
	tinsert (menui1, item)
	local item = menu:AddItem (0, "Send Quest Status To Party", self.Menu_OnSendQInfo, self)
	tinsert (menui1, item)
	local item = menu:AddItem (0, "Share", self.Menu_OnShare, self)
	self.MenuIShare = item
	tinsert (menui1, item)

	local item = menu:AddItem (0, "")
	tinsert (menui1, item)
	local item = menu:AddItem (0, "Abandon", self.Menu_OnAbandon, self)
	tinsert (menui1, item)

	local item = menu:AddItem (0, "Remove", self.Menu_OnCompleted, self)
	tinsert (menui2, item)

	local item = menu:AddItem (0, "Remove All", self.Menu_OnHistoryRemoveAll, self)
	tinsert (menui2, item)

	local function func()
		Nx.CurCharacter["QHAskedGet"] = true
		Nx.Timer:Start ("QHistLogin", .1, Nx.Quest, Nx.Quest.QuestQueryTimer)
--		QueryQuestsCompleted()
	end
	local item = menu:AddItem (0, "Get Completed From Server", func, self)
	tinsert (menui2, item)

	local item = menu:AddItem (0, "Mark As Previously Completed", self.Menu_OnCompleted, self)
	tinsert (menui3, item)

	tinsert (menui3, menu:AddItem (0, "Goto Quest Giver", self.Menu_OnGoto, self))

	local item = menu:AddItem (0, "")
	tinsert (menui2, item)
	tinsert (menui3, item)

	local item = menu:AddItem (0, "Show All Quests", self.Menu_OnShowAllQuests, self)
	item:SetChecked (false)
	tinsert (menui2, item)
	tinsert (menui3, item)

	local item = menu:AddItem (0, "Show Low Level Quests", self.Menu_OnShowLowLevel, self)
	item:SetChecked (false)
--	tinsert (menui2, item)
	tinsert (menui3, item)

	local item = menu:AddItem (0, "Show High Level Quests", self.Menu_OnShowHighLevel, self)
	item:SetChecked (false)
--	tinsert (menui2, item)
	tinsert (menui3, item)

	local item = menu:AddItem (0, "Show Quests From All Zones", self.Menu_OnShowAllZones, self)
	item:SetChecked (false)
	tinsert (menui2, item)
	tinsert (menui3, item)

	local item = menu:AddItem (0, "Show Finished Quests", self.Menu_OnShowFinished, self)
	item:SetChecked (false)
	tinsert (menui3, item)

	local item = menu:AddItem (0, "Show Only Non Dungeon Dailies", self.Menu_OnShowOnlyDailies, self)
	item:SetChecked (false)
	tinsert (menui3, item)

	local item = menu:AddItem (0, "")
	tinsert (menui3, item)
	local item = menu:AddItem (0, "Track None", self.Menu_OnTrackNone, self)
	tinsert (menui3, item)

	local item = menu:AddItem (0, "")
	tinsert (menui1, item)
	tinsert (menui2, item)
	tinsert (menui3, item)

	local function func()
		Nx.Opts:Open ("Quest")
	end
	local item = menu:AddItem (0, "Options...", func)
	tinsert (menui1, item)
	tinsert (menui2, item)
	tinsert (menui3, item)

	-- Quest details

	local f = CreateFrame ("ScrollFrame", "NxQuestD", win.Frm, "NxQuestDetails")

	self.DetailsFrm = f
	f.NxSetSize = self.OnDetailsSetSize

	f:SetMovable (true)
	f:EnableMouse (true)

	f:SetFrameStrata ("MEDIUM")

	local t = f:CreateTexture()
	t:SetTexture (.7, .7, .5, 1)
	t:SetAllPoints (f)
	f.texture = t

	f:Show()

	-- Create Tab Bar

	local bar = TabBar:Create (nil, win.Frm, 1, 1)
	self.Bar = bar

	local tbH = TabBar:GetHeight()

	win:Attach (bar.Frm, 0, 1, -tbH, 1)

	bar:SetUser (self, self.OnTabBar)

	self.TabSelected = 1

	bar:AddTab ("Current", 1, nil, true)
	bar:AddTab ("History", 2)
	bar:AddTab ("Database", 3)
	bar:AddTab ("Player", 4)

	-- Old attach

--	local qdf = getglobal ("QuestLogDetailScrollFrame")
--	win:Attach (qdf, 0, 1, .6, 1)
--[[
	local t = qdf:CreateTexture()
	t:SetTexture (.7, .7, .5, .7)
	t:SetAllPoints (qdf)
	qdf.texture = t
--]]
	-- Quest log

--	local qlogf = getglobal ("QuestLogFrame")
--	win:Attach (qlogf, .8, 1, 0, 1, true)

	--

	self:AttachFrames()
end

--------
-- Attach our frames

function Nx.Quest.List:AttachFrames()

	local gopts = Nx:GetGlobalOpts()
	local win = self.Win
	local list = self.List
	local tbH = Nx.TabBar:GetHeight()

	if gopts["QSideBySide"] then

		local r = .55
		if self.TabSelected ~= 1 then
			r = 1
		end
		win:Attach (list.Frm, 0, r, 18, -tbH)
		win:Attach (self.DetailsFrm, .55, 1, 18, -tbH)

	else
		local bot = .6
		if self.TabSelected ~= 1 then
			bot = -tbH
		end
		win:Attach (list.Frm, 0, 1, 18, bot)
		win:Attach (self.DetailsFrm, 0, 1, .6, -tbH)
	end
end

function Nx.Quest.List:UpdateMenu()

	local showi = self.MenuItems1
	local hidei1 = self.MenuItems2
	local hidei2 = self.MenuItems3

	if self.TabSelected == 2 then

		showi = self.MenuItems2
		hidei1 = self.MenuItems1

	elseif self.TabSelected == 3 then

		showi = self.MenuItems3
		hidei2 = self.MenuItems1
	end

	for k, v in pairs (hidei1) do
		v:Show (false)
	end

	for k, v in pairs (hidei2) do
		v:Show (false)
	end

	for k, v in pairs (showi) do		-- Do last so items in multiple lists work
		v:Show()
	end

	if self.TabSelected == 1 then

		local show = -1
		local i = self.List:ItemGetData()
		if i then

			local qi = bit.band (i, 0xff)
			if qi > 0 then
				local i, cur = Nx.Quest:FindCurByIndex (qi)
				if cur then
					if cur.CanShare then
						show = true
					end
				end
			end
		end

		self.MenuIShare:Show (show)
	end
end

--------

function Nx.Quest:ShowUIPanel (frame)

	if self.InShowUIPanel then
		return
	end

	self.InShowUIPanel = true

	frame:Hide()

	local detailFrm = QuestLogDetailFrame
	if detailFrm then
		detailFrm:Hide()
	end

	local orig = IsAltKeyDown() and not self.IgnoreAlt
	local opts = self.GOpts
	if opts["QUseAltLKey"] then
		orig = not orig
	end

	if orig then	-- Show original quest log?

		frame:SetScale (1)

		QuestLogFrame:SetAttribute ("UIPanelLayout-enabled", true)
		ShowUIPanel (frame)

		if detailFrm then
			detailFrm:SetScale (1)
		end

		self:LightHeadedAttach (frame)
	else

		local win = self.List.Win

		if win and not GameMenuFrame:IsShown() then

			self:ExpandQuests()

			local wf	= win.Frm

--			local ff = frame
--			Nx.prt ("LevS1 "..wf:GetFrameLevel().." "..ff:GetFrameLevel())

			win:Show()
			self.List:Update()
			wf:Raise()

--			Nx.prt ("LevS2 "..wf:GetFrameLevel().." "..ff:GetFrameLevel())

			frame:Show()
			frame:SetScale (.1)
			frame:SetPoint ("TOPLEFT", -999, 999)

			if detailFrm then
				detailFrm:SetScale (.1)
				detailFrm:SetPoint ("TOPLEFT", -999, 999)
			end

--			Nx.prt ("LevS3 "..wf:GetFrameLevel().." "..ff:GetFrameLevel())

			self:LightHeadedAttach (wf, true)
		end
	end

	self.InShowUIPanel = false
end

--------

function Nx.Quest:HideUIPanel (frame)

	QuestLogFrame:SetAttribute ("UIPanelLayout-enabled", false)

	local detailFrm = QuestLogDetailFrame
	if detailFrm then
		detailFrm:Hide()
	end

	self.List:DetailsSetWidth (285)

	self.List.Win:Show (false)

	if self.List.List:ItemGetNum() > 0 then
		self.List.List:Empty()
		collectgarbage ("collect")
	end

	self:RestoreExpandQuests()		-- Hide window first, then restore

	self.LHAttached = nil
end

function Nx.Quest:LightHeadedAttach (frm, attach, onlyLevels)

	local lh = getglobal ("LightHeaded")
	local lhf = getglobal ("LightHeadedFrame")
	if not (lh and lhf) then
		return
	end

	local db = lh["db"]
	if not db then
		return
	end

	local profile = db["profile"]
	if not profile then
		return
	end

--	Nx.prtFrame ("LightHeaded", lhf)
--	Nx.prtFrameChildren ("LightHeaded", lhf)

	lhf:SetParent (frm)

	local lvl = frm:GetFrameLevel()
	local open = profile["open"]

	if not attach then

		lvl = lvl - 1
		local x = open and -15 or -328
		lhf:ClearAllPoints()
		lhf:SetPoint ("LEFT", frm, "RIGHT", x, 0)		--  OLD -50, 19

	else

		self.LHAttached = profile
		self.LHOpen = open

		lvl = open and lvl or 1
		local x = open and -4 or -326
		lhf:ClearAllPoints()
		lhf:SetPoint ("TOPLEFT", frm, "TOPRIGHT", x, -19)
	end

	lhf:SetFrameLevel (lvl)
	Nx.Util_SetChildLevels (lhf, lvl + 1)

	if not onlyLevels then

		lhf:Show()

		if not profile["attached"] then
			lh["LockUnlockFrame"](lh)
		end
	end
end

--------
-- Frame update. Called by main addon frame

function Nx.Quest:OnUpdate (elapsed)

	if not self.List.Win:IsShown() then
--		Nx.prt ("skip")
		return
	end

	if self.LHAttached then

		local profile = self.LHAttached
		if self.LHOpen ~= profile["open"] then
			self:LightHeadedAttach (self.List.Win.Frm, true)
		end

		if Nx.Tick % 20 == 0 then
			self:LightHeadedAttach (self.List.Win.Frm, true, true)
		end
	end
end

--------
-- Select quest in list

function Nx.Quest.List:Select (qId, qI)

	local list = self.List

	for n = 1, list:ItemGetNum() do

		local i = list:ItemGetData (n)
		if i then

			local qi = bit.band (i, 0xff)
			local qid = bit.rshift (i, 16)

			if qi == qI and qid == qId then

				Nx.Quest:SelectBlizz (qi)
				list:Select (n)
				self:Update()

				break
			end
		end
	end
end

--------

function Nx.Quest.List:GetCurSelected()

	local i = self.List:ItemGetData()
	if i then

		local qi = bit.band (i, 0xff)
		local qid = bit.rshift (i, 16)
		if qid > 0 or qi > 0 then
			local _, cur = Nx.Quest:FindCur (qid, qi)
			return cur
		end
--[[
		local qi = bit.band (i, 0xff)
		if qi > 0 then

			local i, cur = Nx.Quest:FindCurByIndex (qi)
			return cur
		else

			local qid = bit.rshift (i, 16)
			local i, cur = Nx.Quest:FindCur (qid)
			return cur
		end
--]]
	end
end

--------

function Nx.Quest.List:OnWin (typ)

	if typ == "Close" then
		HideUIPanel (QuestLogFrame)
--		QuestLogFrame:Hide()
	end
end

--------

function Nx.Quest.List:FilterOnEditFocusGained()

	Nx.ShowMessageTrial()

	local this = self			--V4
	local self = this.NxInst

	local s = self.Filters[self.TabSelected]
	if s ~= "" then
		this:SetText (s)
	else
		this:SetText ("")
	end
end

function Nx.Quest.List:FilterOnEditFocusLost()

	local this = self			--V4
	local self = this.NxInst

	if self.Filters[self.TabSelected] == "" then
		this:SetText (self.FilterDesc)
	end
end

function Nx.Quest.List:FilterOnTextChanged()

	local this = self			--V4
	local self = this.NxInst
	self.Filters[self.TabSelected] = gsub (this:GetText(), self.FilterDescEsc, "")
--	Nx.prt ("Filter #%s = %s", self.TabSelected, self.Filters[self.TabSelected])
	self:Update()
end

function Nx.Quest.List:FilterOnEnterPressed()
	local this = self			--V4
	this:ClearFocus()
end

function Nx.Quest.List:FilterOnEscapePressed()

	local this = self			--V4
	local self = this.NxInst
	self.Filters[self.TabSelected] = ""

	this:ClearFocus()
end

--------

function Nx.Quest.List:OnTabBar (index, click)

	self.FilterFrm:ClearFocus()

	self.TabSelected = index

	if index == 1 then
		self.DetailsFrm:Show()
		self:AttachFrames()
	else
		self.DetailsFrm:Hide()
		self:AttachFrames()
	end

	local s = self.Filters[self.TabSelected]
	s = s ~= "" and s or self.FilterDesc
	self.FilterFrm:SetText (s)

	self:Update()
end

--------
-- Menu handlers

function Nx.Quest.List:Menu_OnGoto (item)

	local i = self.List:ItemGetData()
	if i then

		local qIndex = bit.band (i, 0xff)

		if qIndex > 0 then
			Nx.prt ("Already have the quest!")

		else
			local qId = bit.rshift (i, 16)
			Nx.Quest:Goto (qId)

			self:Update()
		end
	end
end

function Nx.Quest.List:Menu_OnHighPri (item)

	local cur = self:GetCurSelected()
	if cur then
		cur.HighPri = not cur.HighPri
		self:Update()
	end
end

function Nx.Quest.List:Menu_OnShowHeaders (item)

	self.QOpts.NXShowHeaders = item:GetChecked()
	Nx.Quest:SortQuests()
	self:Update()
end

function Nx.Quest.List:Menu_OnShowObjectives (item)

	self.QOpts.NXShowObj = item:GetChecked()
--	Nx.Quest:SortQuests()
	self:Update()
end

function Nx.Quest.List:Menu_OnShowAllQuests (item)
	self.ShowAllQuests = item:GetChecked()
	self:Update()
end

function Nx.Quest.List:Menu_OnShowLowLevel (item)
	self.ShowLowLevel = item:GetChecked()
	self:Update()
end

function Nx.Quest.List:Menu_OnShowHighLevel (item)
	self.ShowHighLevel = item:GetChecked()
	self:Update()
end

function Nx.Quest.List:Menu_OnShowAllZones (item)
	self.ShowAllZones = item:GetChecked()
	self:Update()
end

function Nx.Quest.List:Menu_OnShowFinished (item)
	self.ShowFinished = item:GetChecked()
	self:Update()
end

function Nx.Quest.List:Menu_OnShowOnlyDailies (item)
	self.ShowOnlyDailies = item:GetChecked()
	self:Update()
end

function Nx.Quest.List:Menu_OnShowParty (item)
	self.ShowParty = item:GetChecked()
	self:Update()
end

function Nx.Quest.List:Menu_OnCompleted (item)

	local i = self.List:ItemGetData()
	if i then

		local qId = bit.rshift (i, 16)
		local qStatus, qTime = Nx:GetQuest (qId)

		if qStatus == "C" then
			qStatus = "c"
		else
			qStatus = "C"
			qTime = time()
		end

--		Nx.prt ("ToggleQuestComplete %d %s %s", qId, qStatus, qTime)

		Nx:SetQuest (qId, qStatus, qTime)

		self:Update()
	end
end

function Nx.Quest.List:Menu_OnHistoryRemoveAll()

	local idT = Nx.Quest.IdToCurQ
	local questT = Nx.CurCharacter.Q

	for id in pairs (questT) do
		if not idT[id] then
			questT[id] = nil
		end
	end

	Nx.prt ("History cleared")
	self:Update()
end


function Nx.Quest.List:Menu_OnSortWatched (item)
	local on = item:GetChecked()
	Nx.Quest:SetWatchSortMode (on and 1 or 0)
end

function Nx.Quest.List:Menu_OnWatchAll()
	Nx.Quest:WatchAll()
	self:Update()
end

function Nx.Quest.List:Menu_OnWatchCompleted (item)

	local curq = Nx.Quest.CurQ

	if curq then

		for i, cur in ipairs (curq) do

--			Nx.prt ("Q #%d %s %s", i, cur.Title, cur.Complete or "nil")

			if cur.Complete and cur.Complete == 1 then
				Nx.Quest.Watch:Add (i)
			end
		end

		self:Update()
	end
end

function Nx.Quest.List:Menu_OnSendQInfo (item)

	local i = self.List:ItemGetData()
	if i then

		local qi = bit.band (i, 0xff)
		self:SendQuestInfo (qi)
	end
end

function Nx.Quest.List:SendQuestInfo (qi)

	if qi > 0 then

		self.SendQInfoQI = qi
		self.SendQInfoMode = -1
		self.SendQTarget = nil

		local box = Nx.FindActiveChatFrameEditBox()
		if box then
			local typ = box:GetAttribute ("chatType")
--			Nx.prt ("chattype %s", typ)
			if typ == "WHISPER" then
				self.SendQTarget = box:GetAttribute ("tellTarget")
				self.SendQLanguage = box["language"]
				ChatEdit_OnEscapePressed (box)
			end
		end

		Nx.Timer:Start ("QSendInfo", 0, self, self.OnSendQuestInfoTimer)
	end
end

function Nx.Quest.List:OnSendQuestInfoTimer()

	local qi = self.SendQInfoQI
	local i, cur = Nx.Quest:FindCurByIndex (qi)

	if not i then
		return
	end

	local sendStr
	local mode = self.SendQInfoMode

	if mode == -1 then

		sendStr = self:MakeDescLink (cur)
		mode = 0
--[[
	elseif mode == 0 then
		local str = strsub (cur.ObjText, 1, 180)
		str = format (" %s", str)
		str = gsub (str, "[\n\r\t]", "")
		if #cur.ObjText > 180 then
			str = str .. "..."
		end
		Nx.Com:Send ("P", str)
--]]
	else

		local desc = cur[mode]
		if not desc then
			return
		end

		sendStr = format ("  %s", desc)
	end

	if self.SendQTarget then
--		Nx.Com:Send ("W", sendStr, self.SendQTarget)
		SendChatMessage (sendStr, "WHISPER", self.SendQLanguage, self.SendQTarget);
	else
		Nx.Com:Send ("P", sendStr)
	end

	self.SendQInfoMode = mode + 1

	return .33
end

function Nx.Quest.List:Menu_OnShare (item)

	local i = self.List:ItemGetData()
	if i then

		local qi = bit.band (i, 0xff)
		if qi > 0 then
			if GetNumSubgroupMembers() > 0 then
				QuestLogPushQuest()
			else
				Nx.prt ("Must be in party to share")
			end
		end
	end
end

function Nx.Quest.List:Menu_OnAbandon (item)

	local i = self.List:ItemGetData()
	if i then

		local qIndex = bit.band (i, 0xff)
		local qId = bit.rshift (i, 16)
		Nx.Quest:Abandon (qIndex, qId)

--		self:Update()	-- Dialog gets closed!
	end
end

--[[
function Nx.Quest.List:Menu_OnTrackAll (item)

	local curq = Nx.Quest.CurQ

	if curq then

		for _, cur in ipairs (curq) do

			local quest = cur.Q
			if quest then
				Nx.Quest.Tracking[cur.QId] = 0xffffffff		-- Track all
			end
		end

		self:Update()
	end
end
--]]

function Nx.Quest.List:Menu_OnTrackNone (item)
	Nx.Quest.Watch:ClearAutoTarget()
	self:Update()
end

--------
-- On list control updates

function Nx.Quest.List:OnListEvent (eventName, sel, val2, click)

	local Quest = Nx.Quest
	local Map = Nx.Map

	local itemData = self.List:ItemGetData (sel) or 0
	local hdrCur = self.List:ItemGetDataEx (sel, 1)

	local qIndex = bit.band (itemData, 0xff)
	local qId = bit.rshift (itemData, 16)

	local shift = IsShiftKeyDown() or eventName == "mid"

--	Nx.prt (format ("Data #%d, Id%d", qIndex, qId))

	if eventName == "select" or eventName == "mid" or eventName == "back" then

		local columnId = val2

		if shift then

			if hdrCur then		-- Header?

				local setStr

				for n = sel + 1, sel + 99 do		-- Toggle watch of all

					local itemData = self.List:ItemGetData (n)
					if not itemData or itemData == 0 then
						break
					end

					local qIndex = bit.band (itemData, 0xff)
					local qId = bit.rshift (itemData, 16)

					local i, cur, id = Quest:FindCur (qId, qIndex)

					if not setStr then

						local qStatus = Nx:GetQuest (id)
						setStr = qStatus == "W" and "c" or "W"
					end

					Nx:SetQuest (id, setStr)
				end

				Quest:PartyStartSend()

			else

				-- Track or paste to chat

				local i, cur, id = Quest:FindCur (qId, qIndex)

				local box = Nx:FindActiveChatFrameEditBox()
				if box then

					local s = self:MakeDescLink (cur, id or qId, IsControlKeyDown())
					if s then
						box:Insert (s)
					end

				else

					if cur then

						-- Shift click toggles quest-watch

						local qStatus = Nx:GetQuest (id)
						if qStatus == "W" then
							Nx:SetQuest (id, "c")
						else
							Nx:SetQuest (id, "W")
						end

						Quest:PartyStartSend()
					end
				end
			end
		end

		Nx.Quest:SelectBlizz (qIndex)

		self:Update()

		if qId > 0 then

			-- 0 is quest name line
			local qObj = bit.band (bit.rshift (itemData, 8), 0xff)

			local mapId = Map:GetCurrentMapId()
			Quest:TrackOnMap (qId, qObj, qIndex > 0, shift)
			Map:SetCurrentMap (mapId)

			-- LightHeaded select

			if self.TabSelected == 3 then
				local lh = getglobal ("LightHeaded")
				if lh then
					lh["UpdateFrame"] (lh, qId)
--					Nx.prt ("LH qid %s", qId)
				end
			end
		end

	elseif eventName == "button" then

		if hdrCur then		-- Header?

			local v
			if not Quest.HeaderHide[hdrCur.Header] then
				v = true
			end
			Quest.HeaderHide[hdrCur.Header] = v

			self:Update()

		else
			-- 0 is quest name line
			local qObj = bit.band (bit.rshift (itemData, 8), 0xff)

			if self.TabSelected == 1 then

				self:ToggleWatch (qId, qIndex, qObj, shift)

			elseif self.TabSelected == 3 then

				local tbits = Quest.Tracking[qId] or 0

				if qObj == 0 then
					Quest.Tracking[qId] = bit.bxor (tbits, 1)
				else
					Quest.Tracking[qId] = bit.bxor (tbits, bit.lshift (1, qObj))
				end

				self:Update()
			end
		end

	elseif eventName == "menu" then

		if qIndex > 0 then

			Quest:SelectBlizz (qIndex)
			self:Update()
		end

		if self.TabSelected ~= 4 then

			self:UpdateMenu()
			self.Menu:Open()
		end
	end
end

function Nx.Quest.List:ToggleWatch (qId, qIndex, qObj, shift)

	local Quest = Nx.Quest
	local Map = Nx.Map

	if qObj == 0 and not shift then

		local i, cur, id = Quest:FindCur (qId, qIndex)
		if cur then

			local qStatus = Nx:GetQuest (id)
			if qStatus == "W" then

				Nx.Quest.Watch:RemoveWatch (qId, qIndex)
			else
				Nx:SetQuest (id, "W")
			end

			Quest:PartyStartSend()
		end
	else

		if qId > 0 and qObj > 0 then	-- FIX: Diabled qObj == 0 case

			if shift and qObj == 0 or qObj > 0 then

				local tbits = Quest.Tracking[qId] or 0

				if qObj == 0 then
					if bit.band (tbits, 1) > 0 then
						Quest.Tracking[qId] = nil
					else
						Quest.Tracking[qId] = 0xffffffff		-- Track all
					end
				else
					Quest.Tracking[qId] = bit.bxor (tbits, bit.lshift (1, qObj))
				end

				self:Update()
			end

			local mapId = Map:GetCurrentMapId()
			Quest:TrackOnMap (qId, qObj, qIndex > 0, true)
			Map:SetCurrentMap (mapId)
		end
	end

	self:Update()
end

--------
-- Make a quest link
-- (cur or id can be nil)

function Nx.Quest.List:MakeDescLink (cur, id, debug)

	local qId = cur and cur.QId or id	-- Database list will have nil cur

	local Quest = Nx.Quest
	local quest = cur and cur.Q or Quest.IdToQuest[qId]

	local title = cur and cur.Title
	local realLevel = cur and cur.RealLevel

	if quest then
		local s
		s, _, realLevel = Quest:Unpack (quest[1])
		title = title or s
	end

	local level = realLevel

	if realLevel <= 0 then
		level = UnitLevel ("player")
	end

	local s = Quest:CreateLink (qId, realLevel, title)

	-- Needs a leading space according to Blizzard. White color breaks link

	local opts = Nx:GetGlobalOpts()
	if quest and opts["QShowLinkExtra"] then
		local part = Quest:GetPartTitle (quest, cur)
		s = format (" [%s] %s%s", level, s, part)
	else
		s = format (" %s", s)
	end

	if debug then
		local fac = strsub (UnitFactionGroup ("player"), 1, 1)
		s = format ("%s[%s %d]", s, fac, qId)
	end

--	Nx.prt ("quest %s", gsub (s, "|", "^"))

	return s
end

--------
-- On quest updates

function Nx.Quest.List:OnQuestUpdate (event)

--	Nx.prt ("OnQuestUpdate %s", event)

	local Quest = Nx.Quest
	local opts = Nx:GetGlobalOpts()

	if event == "PLAYER_LOGIN" then

		self.LoggingIn = true

	elseif event == "QUEST_PROGRESS" then

		local auto = opts["QAutoTurnIn"]

		if IsShiftKeyDown() and IsControlKeyDown() then
			auto = not auto
		end

		if auto then
			CompleteQuest()
--			Nx.prt ("Auto turn in")
		end

		return

	elseif event == "QUEST_COMPLETE" then

		local auto = opts["QAutoTurnIn"]

		if IsShiftKeyDown() and IsControlKeyDown() then
			auto = not auto
		end

		if auto then
			if GetNumQuestChoices() == 0 then
				QuestRewardCompleteButton_OnClick()
--				Nx.prt ("Auto turn in choice")
			end
		end

		return

	elseif event == "QUEST_DETAIL" then		-- Happens when auto accept quest is given

		if QuestGetAutoAccept() and QuestIsFromAreaTrigger() then

			Quest:RecordQuestAcceptOrFinish()

--			Quest.AcceptQId = GetQuestID()
			Nx.prt ("QUEST_DETAIL %s", GetQuestID())
		end

	elseif event == "QUEST_LOG_UPDATE" then

--		Nx.prtStack ("QUpdate")
--		Nx.prt ("#%d", GetNumQuestLogEntries())

		if self.LoggingIn then

			Quest:AccessAllQuests()
			Nx.Timer:Start ("QLogUpdate", .5, self, self.LogUpdate)	-- Small delay, so access works (0 does work)

		else

			self:LogUpdate()
		end
	end

--	Nx.prt ("OnQuestUpdate %s Done", event)
end

--------
-- Quest Log update

function Nx.Quest.List:LogUpdate()

--	Nx.prtStack ("QUpdate")
--	Nx.prt ("#%d", GetNumQuestLogEntries())

	local Quest = Nx.Quest
	local opts = Nx:GetGlobalOpts()

	local qn

	Quest:ExpandQuests()

	if not self.LoggingIn then

		qn = Quest:FindNewQuest()
		if not qn then
--			Quest:CheckForNewCompleted()
			Quest:TellPartyOfChanges()
		end
	end

	Quest:RecordQuests()

	if self.LoggingIn then
		Nx.Timer:Start ("QWatchLogin", .7, Quest, Quest.WatchAtLogin)
		Nx.Timer:Start ("QSetPDLogin", 2, Quest, Quest.CurQSetPreviousDone)
		if opts["QHCheckCompleted"]  then
			Nx.Timer:Start ("QHistLogin", 60, Quest, Quest.GetHistoryTimer)
		end
	end

	if qn then

		local curi, cur = Quest:FindCurByIndex (qn)

		Quest.QIdsNew[cur.QId] = time()

		if opts["QWAddNew"] and not Quest.DailyPVPIds[cur.QId] then
			Quest.Watch:Add (curi)
		end

		Quest:Capture (curi)

--		Nx.prt ("OnQuestUpdate Watch %d %d", qn, i)
	end

	Quest:RestoreExpandQuests()

	self.LoggingIn = nil

	Quest.Watch:ClearCompleted()

	self:Update()
end

--------
-- Update list security stub

function Nx.Quest.List:Update()
--	Nx.Quest.List:Update_()
end

--------
-- Update list

function Nx.Quest.List:Update_()

	if not self.Win:IsShown() then
		return
	end

--	Nx.prt ("QuestListUpdate")

	local Nx = Nx
	local Quest = Nx.Quest
	local Map = Nx.Map
	local qLocColors = Quest.QLocColors

	local opts = Nx:GetGlobalOpts()
	local showQId = opts["QShowId"]

	-- Title

	local _, i = GetNumQuestLogEntries()

	local dailyStr = ""
	local dailysDone = GetDailyQuestsCompleted()
    if opts["QShowDailyCount"] then
	  if dailysDone > 0 then
		dailyStr = "Daily Quests Completed: |cffffffff" .. dailysDone
	  end
    end
	if opts["QShowDailyReset"] then
		dailyStr = dailyStr .. "|r  Daily reset: |cffffffff" .. Nx.Util_GetTimeElapsedStr (GetQuestResetTime())
	end

	self.Win:SetTitle (format ("Quests: |cffffffff%d/%d|r  %s", i, MAX_QUESTS, dailyStr))

	-- List

	local list = self.List
	list:Empty()

	if self.TabSelected == 1 then

		local oldSel = GetQuestLogSelection()

		local header
		local curq = Quest.CurQ

		for n = 1, curq and #curq or 0 do

			local cur = curq[n]
			local quest = cur.Q
			local qId = cur.QId

			local title, level, tag, isComplete = cur.Title, cur.Level, cur.Tag, cur.Complete
			local qn = cur.QI

			if qn > 0 then
				SelectQuestLogEntry (qn)
			end

			local onQ = 0
			local onQStr = ""

			if qn > 0 then
				for n = 1, 4 do
					if IsUnitOnQuest (qn, "party"..n) then
						if onQ > 0 then
							onQStr = onQStr .. "," .. UnitName ("party" .. n)
						else
							onQStr = onQStr .. UnitName ("party" .. n)
						end
						onQ = onQ + 1
					end
				end
			end

			if not self.ShowParty or onQ > 0 then

				local lvlStr = "  "
				if level > 0 then
					lvlStr = format ("|cffd0d0d0%2d", level)
				end

				local color = Quest:GetDifficultyColor (level)
				color = format ("|cff%02x%02x%02x", color.r * 255, color.g * 255, color.b * 255)

				local nameStr = format ("%s %s%s", lvlStr, color, title)

				if quest and quest.CNum then
					nameStr = nameStr .. format (" (Part %d of %d)", quest.CNum, cur.CNumMax)
				end

				if onQ > 0 then
					nameStr = format ("(%d) %s (%s)", onQ, nameStr, onQStr)
				end

				if isComplete then
					nameStr = nameStr .. (isComplete == 1 and "|cff80ff80 - Complete" or "|cfff04040 - "..FAILED)
				end

				if tag and cur.GCnt > 0 then
					tag = tag .. " " .. cur.GCnt
				end

				if cur.Daily then

					if tag then
						tag = format (DAILY_QUEST_TAG_TEMPLATE, tag)
					else
						tag = DAILY
					end
				end

				local show = true

				if self.Filters[self.TabSelected] ~= "" then

					local str = strlower (format ("%s %s", nameStr, tag or ""))
					local filtStr = strlower (self.Filters[self.TabSelected])

					show = strfind (str, filtStr, 1, true)
				end

				if self.QOpts.NXShowHeaders and cur.Header ~= header then
					header = cur.Header
					if show then
						list:ItemAdd (0)
						list:ItemSet (2, format ("|cff8f8fff---- %s ----", header))
						list:ItemSetDataEx (list:ItemGetNum(), cur, 1)
						list:ItemSetButton ("QuestHdr", Quest.HeaderHide[cur.Header])
					end
				end

				if show and not Quest.HeaderHide[cur.Header] then

					local id = qId > 0 and qId or cur.Title
					local qStatus = Nx:GetQuest (id)
					local qWatched = qStatus == "W"

					list:ItemAdd (qId * 0x10000 + qn)

					local trackMode = Quest.Tracking[qId] or 0

					local butType = "QuestWatch"
					local butOn

					local trkStr = " "
					if bit.band (trackMode, 1) > 0 then
						trkStr = "*"
						butOn = true
					end

					if qWatched then
						butType = "QuestWatching"
						butOn = true
					end

					list:ItemSetButton (butType, butOn)

					if quest and showQId then
						nameStr = nameStr .. format (" [%s]", qId)
					end

					if cur.HighPri then
						nameStr = "> " .. nameStr
					end

					list:ItemSet (2, nameStr)
					list:ItemSet (4, tag)

					if self.QOpts.NXShowObj then

						local num = GetNumQuestLeaderBoards (qn)

						local str = ""
						local desc, typ, done
						local zone, loc

						for ln = 1, 15 do

							zone = nil

							local obj = quest and quest[ln + 3]

							if obj then
								desc, zone, loc = Quest:UnpackObjective (obj)
							end

							if ln <= num then
								desc, typ, done = GetQuestLogLeaderBoard (ln, qn)
								desc = desc or "?"	--V4

							else
								if not obj then
									break
								end

								done = false
							end

							color = done and "|cff5f5f6f" or "|cff9f9faf"
							str = format ("     %s%s", color, desc)

							list:ItemAdd (qId * 0x10000 + ln * 0x100 + qn)

							local trkStr = ""

							if zone then
--								trkStr = "|cff505050o"
								list:ItemSetButton ("QuestWatch", false)
							end

							if bit.band (trackMode, bit.lshift (1, ln)) > 0 then
								list:ItemSetButton (qLocColors[ln][5], true)
							end
							list:ItemSet (1, trkStr)

							list:ItemSet (2, str)
						end
					end
				end
			end
		end

		SelectQuestLogEntry (oldSel)

	end

	-- Add history quests

	if Nx.Quests and self.TabSelected == 2 then

		local qIds = Quest.QIds

		local sortT = {}

		local showAllZones = self.ShowAllZones or self.ShowAllQuests
		local showLowLevel = self.ShowLowLevel or self.ShowAllQuests
		local showHighLevel = self.ShowHighLevel or self.ShowAllQuests
		local showFinished = self.ShowFinished or self.ShowAllQuests
		local showOnlyDailies = self.ShowOnlyDailies and not self.ShowAllQuests

		local mapId = Map:GetCurrentMapId()

		local minLevel = UnitLevel ("player") - GetQuestGreenRange()
		local maxLevel = showHighLevel and MAX_PLAYER_LEVEL or UnitLevel ("player") + 6

		-- Divider

		list:ItemAdd (0)
		list:ItemAdd (0)
		local dbTitleIndex = list:ItemGetNum()
		local dbTitleNum = 0
		list:ItemAdd (0)

		for qId in pairs (Nx.CurCharacter.Q) do			-- Loop over quests with history

--			if not Quest.IdToQuest[qId] then
--				Nx.prt ("QID %s?", qId)
--			end

			local quest = Quest.IdToQuest[qId]

			local status, qTime = Nx:GetQuest (qId)
			local qCompleted = status == "C"

			local show = qCompleted

			if show and not showAllZones then
				show = Quest:CheckShow (mapId, qId)
			end

			if show then

				local qname, side_, lvl

				if quest then
					qname, side_, lvl = Quest:Unpack (quest[1])
				else
					qname = format ("%s?", qId)
					lvl = 0
				end

--				Nx.prt ("%s [%s] %s", qname, qId, quest.CNum or "")

				local lvlStr = format ("|cffd0d0d0%2d", lvl)
				local title = qname

				if quest and quest.CNum then
					title = title .. format (" (Part %d)", quest.CNum)
				end

				if showQId then
					title = title .. format (" [%s]", qId)
				end

				local dailyName = ""

				local dailyStr = Quest.DailyIds[qId] or Quest.DailyDungeonIds[qId] or Quest.DailyPVPIds[qId]
				if dailyStr then

					local typ = strsplit ("^", dailyStr)
					dailyName = format (" |cffd060d0(%s)", Quest.DailyTypes[typ])

					local age = time() - qTime
					local dayChange = 86400 - GetQuestResetTime()

					if age < dayChange then
						dailyName = dailyName .. " |cffff8080today"
					end
				end

				local show = true

				if self.Filters[self.TabSelected] ~= "" then

					local str = strlower (format ("%2d %s %s%s", lvl, title, date ("%m/%d %H:%M:%S", qTime), dailyName))
					local filtStr = strlower (self.Filters[self.TabSelected])

					show = strfind (str, filtStr, 1, true)
				end

				if show then

					local t = {}
					tinsert (sortT, t)

					t.T = qTime
					t.QId = qId

					dbTitleNum = dbTitleNum + 1

					local haveStr = ""

					if qIds[qId] then
						haveStr = "|cffe0e0e0+ "
					end

					local color = Quest:GetDifficultyColor (lvl)
					color = format ("|cff%02x%02x%02x", color.r * 255, color.g * 255, color.b * 255)

					t.Desc = format ("%s %s%s%s", lvlStr, haveStr, color, title)
					t.Col4 = format ("%s %s", date ("|cff9f9fcf%m/%d %H:%M:%S", qTime), dailyName)
				end
			end
		end

		sort (sortT, function (a, b) return a.T > b.T end)

		for _, qEntry in ipairs (sortT) do

			list:ItemAdd (qEntry.QId * 0x10000)
			list:ItemSet (2, qEntry.Desc)
			list:ItemSet (4, qEntry.Col4)
		end

		local str = (showAllZones and "All" or Map:IdToName (mapId)) .. " Completed"

		list:ItemSet (2, format ("|cffc0c0c0--- %s (%d) ---", str, dbTitleNum), dbTitleIndex)
	end

	-- Add database quests

	if Nx.Quests and self.TabSelected == 3 then

		local qIds = Quest.QIds

		local sortT = {}

		local showAllZones = self.ShowAllZones or self.ShowAllQuests
		local showLowLevel = self.ShowLowLevel or self.ShowAllQuests
		local showHighLevel = self.ShowHighLevel or self.ShowAllQuests
		local showFinished = self.ShowFinished or self.ShowAllQuests
		local showOnlyDailies = self.ShowOnlyDailies and not self.ShowAllQuests

		local mapId = Map:GetCurrentMapId()

		local minLevel = UnitLevel ("player") - GetQuestGreenRange()
		local maxLevel = showHighLevel and 90 or UnitLevel ("player") + 6

		-- Divider

		list:ItemAdd (0)
		list:ItemAdd (0)
		local dbTitleIndex = list:ItemGetNum()
		local dbTitleNum = 0
		list:ItemAdd (0)

		local addBlank
		local inchain
		local showchain

--		local qsIndex = 1
--		local qsLast = #Quest.Sorted
--		while qsIndex <= qsLast do

		for qsIndex, qId in ipairs (Quest.Sorted) do

--			local qId = Quest.Sorted[qsIndex]

			local quest = Quest.IdToQuest[qId]
			if not quest then
				Nx.prt ("nil quest %s", qId)
			end
			local qname, side, lvl, minlvl, next = Quest:Unpack (quest[1])

			local status, qTime = Nx:GetQuest (qId)
			local qCompleted = status == "C"

			if not quest.CNum or quest.CNum == 1 then
				addBlank = true
			end

			local show = showchain

			if not inchain then

				show = true

				if quest.CLvlMax then
					inchain = true
				end

				if not showLowLevel then
					if quest.CLvlMax then
						show = show and quest.CLvlMax >= minLevel
					else
						show = show and ((lvl == 0) or (lvl >= minLevel))
					end
				end
				show = show and lvl <= maxLevel

				if show and not showAllZones then
					show = self:CheckShow (mapId, qsIndex)
				end

				showchain = show
			end

			if not Quest.DailyIds[qId] then
				if (not showFinished and qCompleted) or showOnlyDailies then
					show = false
				end
			end

			if show then

				local lvlStr = format ("|cffd0d0d0%2d", lvl)
				local title = qname

				local cati = Quest:UnpackCategory (quest[1])
				if cati > 0 then
					title = title .. " <" .. Nx.QuestCategory[cati] .. ">"
				end

				if quest.CNum then

--					if quest.CNum > 1 then
--						lvlStr = "    " .. lvlStr
--					end
					title = title .. format (" (Part %d)", quest.CNum)
				end

				local tag = qCompleted and "(History) " or ""

				local dailyStr = Quest.DailyIds[qId] or Quest.DailyDungeonIds[qId]
				if dailyStr then
					local typ, money, rep, req = strsplit ("^", dailyStr)
					tag = format ("|cffd060d0(%s %.2fg", Quest.DailyTypes[typ], money / 100)
					for n = 0, 1 do	-- Only support 2 reps
						local i = n * 4 + 1
						local repChar = strsub (rep or "", i, i)
						if repChar == "" then
							break
						end
						tag = format ("%s, %s %s", tag, strsub (rep, i + 1, i + 3), Quest.Reputations[repChar])
					end
					if req and Quest.Requirements[req] then	-- 1 and 2 (Ally, Horde) not in table
						tag = tag .. ", |cffe0c020Need " .. Quest.Requirements[req]
					end
					tag = tag .. ")"
				end

				local filterName = ""

				local sMapName
				local sName, sMapId = Quest:UnpackSE (quest[2])
				if sMapId then
					sMapName = Map:IdToName (Map.NxzoneToMapId[sMapId])
					filterName = format ("%s(%s)", sName, sMapName)
				end

				local eMapName
				local eName, eMapId = Quest:UnpackSE (quest[3])
				if eMapId then
					eMapName = Map:IdToName (Map.NxzoneToMapId[eMapId])
					if sName ~= eName then
						filterName = format ("%s%s(%s)", filterName, eName, eMapName)
					end
				end

				local show = true

				if self.Filters[self.TabSelected] ~= "" then

					for n = 1, 15 do

						local obj = quest[n + 3]
						if not obj then
							break
						end

						local name, zone = Quest:UnpackObjective (obj)
						if zone then
							filterName = filterName .. Map:IdToName (Map.NxzoneToMapId[zone])
						end
					end

					local str = strlower (format ("%2d %s %s %s", lvl, title, filterName, tag))
					local filtStr = strlower (self.Filters[self.TabSelected])

					show = strfind (str, filtStr, 1, true)
				end

				if show then

					if addBlank then
						addBlank = false
						list:ItemAdd (0)
					end

					dbTitleNum = dbTitleNum + 1

					local trackMode = Quest.Tracking[qId] or 0

					list:ItemAdd (qId * 0x10000)

					local haveStr = ""

					if qIds[qId] then
						haveStr = "|cffe0e0e0+ "
					end

					local color = Quest:GetDifficultyColor (lvl)
					color = format ("|cff%02x%02x%02x", color.r * 255, color.g * 255, color.b * 255)

					local str = format ("%s %s%s%s", lvlStr, haveStr, color, title)

					if showQId then
						str = str .. format (" [%s]", qId)
					end

					local questTip = "@" .. qId

					list:ItemSet (2, str)
					list:ItemSet (4, tag)

					if sName then
						list:ItemAdd (qId * 0x10000)

						if not eName then
							list:ItemSet (2, "     |cff6060ffStart/End: " .. sName)
						else
							list:ItemSet (2, "     |cff6060ffStart: " .. sName)
						end
						list:ItemSet (4, sMapName)

						list:ItemSetButton ("QuestWatch", false)
						if bit.band (trackMode, 1) > 0 then
							list:ItemSetButton ("QuestWatch", true)
						end
						list:ItemSetButtonTip (questTip)
					end
					if eName then
						list:ItemAdd (qId * 0x10000 + 16 * 0x100)
						list:ItemSet (2, "     |cff6060ffEnd: " .. eName)
						list:ItemSet (4, eMapName)

						list:ItemSetButton ("QuestWatch", false)
						if bit.band (trackMode, 0x10000) > 0 then
							list:ItemSetButton ("QuestWatch", true)
						end
						list:ItemSetButtonTip (questTip)
					end

					-- Objectives (max of 15)

					for n = 1, 15 do

						local obj = quest[n + 3]
						if not obj then
							break
						end

						list:ItemAdd (qId * 0x10000 + n * 0x100)

						local name, zone, loc = Quest:UnpackObjective (obj)

--						str = zone and "|cff505050o" or ""

						if zone then
							list:ItemSetButton ("QuestWatch", false)
							list:ItemSetButtonTip (questTip)
							list:ItemSet (4, Map:IdToName (Map.NxzoneToMapId[zone]))
						end

						if bit.band (trackMode, bit.lshift (1, n)) > 0 then
							list:ItemSetButton (qLocColors[n][5], true)
						end

--						list:ItemSet (1, str)
						list:ItemSet (2, format ("     |cff9f9faf%s", name))
					end
				end
			end

			if next == 0 then
				inchain = false
			end

--			qsindex = qsindex + 1
		end

		local str = (showAllZones and "Full" or Map:IdToName (mapId)) .. " Database"

		list:ItemSet (2, format ("|cffc0c0c0--- %s (%d) ---", str, dbTitleNum), dbTitleIndex)

		local low = max (1, showLowLevel and 1 or minLevel)
		local high = min (MAX_PLAYER_LEVEL, maxLevel)
		list:ItemSet (2, format ("|cffc0c0c0--- Levels %d to %d ---", low, high), dbTitleIndex + 1)
	end

	-- Add other player quests

	if self.TabSelected == 4 then

		local qIds = Quest.QIds

		list:ItemAdd (0)
		list:ItemSet (2, format ("|cffc0c0c0--- %s %s/%s ---", Quest.RcvPlyrLast, Quest.RcvCnt, Quest.RcvTotal))

		for n = 1, #Quest.FriendQuests do

			local data = Quest.FriendQuests[n]
			local mode = strsub (data, 1, 1)

			list:ItemAdd (0)

			if mode == " " then		-- Simple text

				list:ItemSet (2, strsub (data, 3))

			elseif mode == "H" then

				list:ItemSet (2, format ("|cff8f8fff---- %s ----", strsub (data, 3)))

			elseif mode == "T" then

				local _, qId, watched, done, lvl, name = strsplit ("^", data)

				if qId and name then

					qId = tonumber (qId)

					if qId >= 0 then

--						watched = watched == "0" and "" or "*"

						if watched ~= "0" then
							list:ItemSet (1, "|cffcfcfcfw")
						end

						local haveStr = ""
						if qIds[qId] then
							haveStr = "|cffe0e0e0+ "
						end

						done = done == "0" and "" or "|cff80ff80 - Complete"

						list:ItemSet (2, format ("%s %s%s%s", lvl, haveStr, name, done))
					end
				end

			elseif mode == "O" then

				local _, qId, name = strsplit ("^", data)

				if name then

					local color = done and "|cff5f5f6f" or "|cff9f9faf"
					local str = format ("     %s%s", color, name)

					list:ItemSet (2, str)
				end
			end
		end
	end

	--

	list:Update()

	Quest.Watch:Update()

	if self.TabSelected == 1 then

		local i = list:GetSelected()
		local data = list:ItemGetData (i) or 0

--		Nx.prt ("%s %s", i, data)

		if data > 0 then
			Nx.Quest:SelectBlizz (bit.band (data, 0xff))
			NxQuestD:Show()
			Quest:UpdateQuestDetails()
		else
			NxQuestD:Hide()
		end
	end

end

function Nx.Quest.List:CheckShow (mapId, index)

	local NxzoneToMapId = Nx.Map.NxzoneToMapId
	local Quest = Nx.Quest

	while true do

		local qId = Quest.Sorted[index]

		if Quest:CheckShow (mapId, qId) then
			return true
		end

		local quest = Quest.IdToQuest[qId]
		local next = Quest:UnpackNext (quest[1])

		if next == 0 then		-- End?
			return
		end

		index = index + 1
	end
end

--------
-- Update map icons (called by map)

function Nx.Quest:UpdateIcons (map)

	Nx.Timer:ProfilerStart ("Quest UpdateIcons")

	local Nx = Nx
	local Quest = Nx.Quest
	local Map = Nx.Map
	local qLocColors = Quest.QLocColors
	local ptSz = 4 * map.ScaleDraw

	local navscale = Quest.Map.IconNavScale * 16
	local showOnMap = Quest.Watch.ButShowOnMap:GetPressed()

	local opts = self.GOpts
	local showWatchAreas = opts["QMapShowWatchAreas"]
	local trkR, trkG, trkB, trkA = Nx.Util_num2rgba (opts["QMapWatchAreaTrackColor"])
	local hovR, hovG, hovB, hovA = Nx.Util_num2rgba (opts["QMapWatchAreaHoverColor"])

	-- Update target

	local typ, tid = Map:GetTargetInfo()
	if typ == "Q" then

--		Nx.prt ("QTar %s", tid)

		local qid = floor (tid / 100)
		local i, cur = Quest:FindCur (qid)

		if cur then
			Quest:CalcDistances (cur.Index, cur.Index)
			Quest:TrackOnMap (cur.QId, tid % 100, cur.QI > 0 or cur.Party, true, true)

--			Nx.prt ("UpIcons target %s %s", typ or "nil", tid or "nil")
		end
	end

	-- Blob

--	local f = self.BlobFrm


	-- Draw completed quests

	for k, cur in ipairs (Quest.CurQ) do

		if cur.Q and cur.CompleteMerge then

			local q = cur.Q
			local obj = q[3] or q[2]

			local endName, zone, x, y = Quest:GetSEPos (obj)
			local mapId = Map.NxzoneToMapId[zone]

			if mapId then

				local wx, wy = map:GetWorldPos (mapId, x, y)
				local f = map:GetIconStatic (4)

				if map:ClipFrameW (f, wx, wy, navscale, navscale, 0) then

					f.NXType = 9000
					f.NXData = cur
					local qname = Nx.TXTBLUE .. "Quest: " .. cur.Title
					f.NxTip = format ("%s\nEnd: %s (%.1f %.1f)", qname, endName, x, y)
					if cur.PartyNames then
						f.NxTip = f.NxTip .. "\n" .. cur.PartyNames
					end
					f.texture:SetTexture ("Interface\\AddOns\\Carbonite\\Gfx\\Map\\IconQuestion")
				end
			end
		end
	end

	-- Update tracking data

	local tracking = self.IconTracking

	if Nx.Tick % 10 == 0 then

--		tracking = {}		-- garbage creator
		wipe (tracking)

		for trackId, trackMode in pairs (Quest.Tracking) do
			tracking[trackId] = trackMode
		end

		if showOnMap then
			for k, cur in ipairs (Quest.CurQ) do
				if cur.Q and (Nx:GetQuest (cur.QId) == "W" or cur.PartyDesc) then
					tracking[cur.QId] = (tracking[cur.QId] or 0) + 0x10000		-- cur.TrackMask + i
				end
			end
		end

		self.IconTracking = tracking
	end

	-- Draw

	local areaTex = Nx.Opts.ChoicesQAreaTex[opts["QMapWatchAreaGfx"]]

	local colorPerQ = opts["QMapWatchColorPerQ"]
	local colMax = opts["QMapWatchColorCnt"]

	for trackId, trackMode in pairs (tracking) do

		local cur = Quest.IdToCurQ[trackId]
		local quest = cur and cur.Q or Quest.IdToQuest[trackId]
		local qname = Nx.TXTBLUE .. "Quest: " .. (cur and cur.Title or Quest:UnpackName (quest[1]))

		local mask = showOnMap and cur and cur.TrackMask or trackMode
		local showEnd

		if bit.band (mask, 1) > 0 then

			if not (cur and (cur.QI > 0 or cur.Party)) then

				local startName, zone, x, y = Quest:GetSEPos (quest[2])
				local mapId = Map.NxzoneToMapId[zone]

				if mapId then

					local wx, wy = map:GetWorldPos (mapId, x, y)
					local f = map:GetIconStatic (4)

					if map:ClipFrameW (f, wx, wy, navscale, navscale, 0) then
						f.NxTip = format ("%s\nStart: %s (%.1f %.1f)", qname, startName, x, y)
						f.texture:SetTexture ("Interface\\AddOns\\Carbonite\\Gfx\\Map\\IconExclaim")
					end
				end
			else

				showEnd = true
			end
		end

		if showEnd or bit.band (mask, 0x10000) > 0 then

			local obj = quest[3] or quest[2]

			local endName, zone, x, y = Quest:GetSEPos (obj)
			local mapId = Map.NxzoneToMapId[zone]

			if mapId and (not cur or not cur.CompleteMerge) then

				local wx, wy = map:GetWorldPos (mapId, x, y)
				local f = map:GetIconStatic (4)

				if map:ClipFrameW (f, wx, wy, navscale, navscale, 0) then

					f.NXType = 9000
					f.NXData = cur
					f.NxTip = format ("%s\nEnd: %s (%.1f %.1f)", qname, endName, x, y)
					if cur and cur.PartyNames then
						f.NxTip = f.NxTip .. "\n" .. cur.PartyNames
					end
					f.texture:SetVertexColor (.6, 1, .6, 1)
					f.texture:SetTexture ("Interface\\AddOns\\Carbonite\\Gfx\\Map\\IconQuestion")
--					f.texture:SetTexture ("Interface\\AddOns\\Carbonite\\Gfx\\Map\\IconQTarget")
				end
			end
		end

		-- Objectives (max of 15)

		if not cur or cur.QI > 0 or cur.Party then

			local drawArea

			if cur then
				local qStatus = Nx:GetQuest (cur.QId)
				drawArea = showWatchAreas and qStatus == "W"
			end
--			local drawArea = bit.band (trackMode, 0x10000) == 0

			for n = 1, 15 do

				local obj = quest[n + 3]
				if not obj then
					break
				end

				local objName, objZone, loc = Quest:UnpackObjective (obj)

				if objZone then

					local mapId = Map.NxzoneToMapId[objZone]

					if not mapId then
--						Nx.prt ("Nxzone error %s %s", objName, objZone)
						break
					end

--					Nx.prt ("%s zone %d %s", objName, mapId, loc)

					if loc and bit.band (mask, bit.lshift (1, n)) > 0 then

						local colI = n

						if colorPerQ then
							colI = ((cur and cur.Index or 1) - 1) % colMax + 1
						end

						local col = qLocColors[colI]
						local r = col[1]
						local g = col[2]
						local b = col[3]

						local oname = cur and cur[n] or objName

						if strbyte (obj, loc) == 32 then  -- Points

--							Nx.prt ("%s, pt %s", objName, strsub (obj, loc + 1))

							loc = loc + 1

							local cnt = floor ((#obj - loc + 1) / 4)
							local sz = navscale

							if cnt > 1 then
								sz = map:GetWorldZoneScale (mapId) / 10.02 * ptSz
							end

							for locN = loc, loc + cnt * 4 - 1, 4 do

								local x, y = Quest:UnpackLocPtOff (obj, locN)
								local wx, wy = map:GetWorldPos (mapId, x, y)

								local f = map:GetIconStatic (4)

								if map:ClipFrameW (f, wx, wy, sz, sz, 0) then

									f.NXType = 9000 + n
									f.NXData = cur

									f.NxTip = format ("%s\nObj: %s (%.1f %.1f)", qname, oname, x, y)
									if cur and cur[n + 400] then
										f.NxTip = f.NxTip .. "\n" .. cur[n + 400]
									end

									if cnt == 1 then
										f.texture:SetTexture ("Interface\\AddOns\\Carbonite\\Gfx\\Map\\IconQTarget")
										f.texture:SetVertexColor (r, g, b, .9)
									else
										f.texture:SetTexture ("Interface\\AddOns\\Carbonite\\Gfx\\Map\\IconCirclePlus")
										f.texture:SetVertexColor (r, g, b, .5)
									end
								end
							end

						else -- Spans (areas)

--							Nx.prt ("%s, spans %s", objName, strsub (obj, loc))

							local hover = Quest.IconHoverCur == cur and Quest.IconHoverObjI == n
							local tracking = bit.band (trackMode, bit.lshift (1, n)) > 0

							local tip = format ("%s\nObj: %s", qname, oname)
							if cur and cur[n + 400] then
								tip = tip .. "\n" .. cur[n + 400]
							end

							local x

							if cur then
								local d = cur["OD"..n]
								if d and d > 0 then
									x = cur["OX"..n]
								end
							end

							if x then
								local y = cur["OY"..n]
								local f = map:GetIcon (4)
								local sz = navscale

								if not hover then
									sz = sz * .8
								end

								if map:ClipFrameW (f, x, y, sz, sz, 0) then

									f.NXType = 9000 + n
									f.NXData = cur
									f.NxTip = tip

									f.texture:SetTexture ("Interface\\AddOns\\Carbonite\\Gfx\\Map\\IconAreaArrows")

									if tracking then
										f.texture:SetVertexColor (.8, .8, .8, 1)
									else
										f.texture:SetVertexColor (r, g, b, .7)
									end
								end
							end

							if not cur or drawArea or hover
									or (bit.band (trackMode, bit.lshift (1, n)) > 0 and trkA > .05) then

								local scale = map:GetWorldZoneScale (mapId) / 10.02
								local cnt = floor ((#obj - loc + 1) / 4)
								local ssub = strsub

								for locN = loc, loc + cnt * 4 - 1, 4 do

									local loc1 = ssub (obj, locN, locN + 3)
									if loc1 == "" then
										break
									end

									local x, y, w, h = Quest:UnpackLocRect (loc1)
									local wx, wy = map:GetWorldPos (mapId, x, y)

									local f = map:GetIconStatic (hover and 1)

									if areaTex then

										if map:ClipFrameTL (f, wx, wy, w * scale, h * scale) then

											f.NXType = 9000 + n
											f.NXData = cur
											f.NxTip = tip

											f.texture:SetTexture (areaTex)

											if hover then
												f.texture:SetVertexColor (hovR, hovG, hovB, hovA)
											elseif tracking then
												f.texture:SetVertexColor (trkR, trkG, trkB, trkA)
											else
												f.texture:SetVertexColor (r, g, b, col[4])
											end
										end

									else

										if map:ClipFrameTLSolid (f, wx, wy, w * scale, h * scale) then

											f.NXType = 9000 + n
											f.NXData = cur
											f.NxTip = tip

											if hover then
												f.texture:SetTexture (hovR, hovG, hovB, hovA)
											elseif tracking then
												f.texture:SetTexture (trkR, trkG, trkB, trkA)
											else
												f.texture:SetTexture (r, g, b, col[4])
											end
										end

									end
								end
							end
						end
					end
				end
			end
		end
	end

	Nx.Timer:ProfilerEnd ("Quest UpdateIcons")
end

function Nx.Quest:IconOnEnter (frm)

	local i = frm.NXType - 9000
	local cur = frm.NXData

	self.IconHoverCur = cur
	self.IconHoverObjI = i
end

function Nx.Quest:IconOnLeave (frm)

	self.IconHoverCur = nil
end

function Nx.Quest:IconOnMouseDown (frm)

	local cur = self.IconHoverCur
	if cur then

		self.IconMenuCur = cur
		self.IconMenuObjI = self.IconHoverObjI

		local qStatus = Nx:GetQuest (cur.QId)
		self.IconMenuIWatch:SetChecked (qStatus == "W")

		self.IconMenu:Open()
	end
end

--------
-- Called when details frame size changes

function Nx.Quest.List:OnDetailsSetSize (w, h)

--	Nx.prt ("QDetails %d %d", w, h)

	local scale = Nx:GetGlobalOpts()["QDetailScale"]

	NXQuestLogDetailScrollChildFrame:SetScale (scale)

	local upH = NxQuestDScrollBarScrollUpButton:GetHeight()

	local bar = NxQuestDScrollBar
	local barW = bar:GetWidth()

	local details = NxQuestD
	bar:SetPoint ("TOPLEFT", details, "TOPRIGHT", 1, -upH)
	details:SetWidth (w - barW - 1)

	local dw = (w - barW - 8) / scale

	Nx.Quest.List:DetailsSetWidth (dw)
end

function Nx.Quest.List:DetailsSetWidth (w)

--	NXQuestLogDetailScrollChildFrame:SetWidth (w)
--	QuestInfoFrame:SetWidth (w)
	QuestInfoObjectivesText:SetWidth (w)
	QuestInfoDescriptionText:SetWidth (w)
	QuestInfoItemChooseText:SetWidth (w)
--	QuestInfoRewardText:SetWidth (w)
end

--------
-- Details

function Nx.Quest:UpdateQuestDetails()

	-- 1 tick delay, since Blizz is hiding/resetting on log open
	Nx.Timer:Start ("QDetail", 0, self, self.UpdateQuestDetailsTimer)
end

function Nx.Quest:UpdateQuestDetailsTimer()

--	Nx.prt ("UpdateQuestDetails")

	QuestInfo_Display (QUEST_TEMPLATE_LOG, NXQuestLogDetailScrollChildFrame, nil, nil, "Carb")

	local r, g, b, a = Nx.Util_num2rgba (self.GOpts["QDetailBC"])
	self.List.DetailsFrm.texture:SetTexture (r, g, b, a)

	-- 0.18, 0.12, 0.06 parchment
	local r, g, b = Nx.Util_num2rgba (self.GOpts["QDetailTC"])

	local t = {
			"QuestInfoTitleHeader", "QuestInfoDescriptionHeader", "QuestInfoObjectivesHeader", "QuestInfoRewardsHeader",
			"QuestInfoDescriptionText", "QuestInfoObjectivesText", "QuestInfoGroupSize", "QuestInfoRewardText",
			"QuestInfoItemChooseText", "QuestInfoItemReceiveText", "QuestInfoSpellLearnText",
--V4 fix!!!!!!!!!!!! replace???
--			"QuestInfoHonorFrameReceiveText",
--			"QuestInfoArenaPointsFrameReceiveText",
--			"QuestInfoTalentFrameReceiveText",
			"QuestInfoXPFrameReceiveText",
	}

	for k, name in ipairs (t) do
		if not _G[name] then
			Nx.prt ("QDetails missing %s", name)
		end
		_G[name]:SetTextColor (r, g, b)
	end

	for n = 1, 10 do
		_G["QuestInfoObjective" .. n]:SetTextColor (r, g, b)
	end

--[[
	-- 3.2

	QuestFrame_SetAsLastShown (NxQuestDSC, NxQuestDSCSpacerFrame)

	Nx.Quest:FrameItems_Update()

	local questID = GetQuestLogSelection()
	local questTitle = GetQuestLogTitle (questID) or ""

	if IsCurrentQuestFailed() then
		questTitle = questTitle.." - ("..FAILED..")"
	end

--	Nx.prt ("UpdateQuestDetails %s %s", questID or "nil", questTitle or "nil")

	local title = NxQuestDSCQuestTitle
	title:SetText (questTitle)

	local _, relTo = NxQuestDSCSpacerFrame:GetPoint()
	local corner = relTo == NxQuestDSC and "TOP" or "BOTTOM"
	title:ClearAllPoints()
	title:SetPoint ("TOP", relTo, corner, 0, -10)
	title:SetPoint ("LEFT", NxQuestDSC, "LEFT", 0, 0)

	local questDescription, questObjectives = GetQuestLogQuestText()
	NxQuestDSCObjectivesText:SetText (questObjectives)

	local questTimer = GetQuestLogTimeLeft()
	if questTimer then
--		QuestLogFrame.hasTimer = 1
--		QuestLogFrame.timePassed = 0
		NxQuestDSCTimerText:Show()
		NxQuestDSCTimerText:SetText (TIME_REMAINING.." "..SecondsToTime (questTimer))
		NxQuestDSCObjective1:SetPoint ("TOPLEFT", "NxQuestDSCTimerText", "BOTTOMLEFT", 0, -10)
	else
--		QuestLogFrame.hasTimer = nil
		NxQuestDSCTimerText:Hide()
		NxQuestDSCObjective1:SetPoint ("TOPLEFT", "NxQuestDSCObjectivesText", "BOTTOMLEFT", 0, -10)
	end

	-- Show Quest Watch if track quest is checked
	local numObjectives = GetNumQuestLeaderBoards()

	for i = 1, numObjectives do
		local string = getglobal ("NxQuestDSCObjective"..i)
		local text, typ, finished = GetQuestLogLeaderBoard (i)
		if not text or strlen (text) == 0 then
			text = typ
		end
		if finished then
			string:SetTextColor (.2, .2, .2)
			text = text.." ("..COMPLETE..")"
		else
			string:SetTextColor (0, 0, 0)
		end
		string:SetText(text)
		string:Show()
		QuestFrame_SetAsLastShown (string, NxQuestDSCSpacerFrame)
	end

	for i = numObjectives + 1, MAX_OBJECTIVES, 1 do
		getglobal ("NxQuestDSCObjective"..i):Hide()
	end

	-- If there's money required then anchor and display it

	if GetQuestLogRequiredMoney() > 0 then

		if numObjectives > 0 then
			NxQuestDSCRequiredMoneyText:SetPoint("TOPLEFT", "NxQuestDSCObjective"..numObjectives, "BOTTOMLEFT", 0, -4)
		else
			NxQuestDSCRequiredMoneyText:SetPoint("TOPLEFT", "NxQuestDSCObjectivesText", "BOTTOMLEFT", 0, -10)
		end

		MoneyFrame_Update("NxQuestDSCRequiredMoneyFrame", GetQuestLogRequiredMoney())

		if GetQuestLogRequiredMoney() > GetMoney() then
			-- Not enough cash
			NxQuestDSCRequiredMoneyText:SetTextColor (0, 0, 0)
			SetMoneyFrameColor ("NxQuestDSCRequiredMoneyFrame", 1, .1, .1)
		else
			NxQuestDSCRequiredMoneyText:SetTextColor (.2, .2, .2)
			SetMoneyFrameColor ("NxQuestDSCRequiredMoneyFrame", 1, 1, 1)
		end
		NxQuestDSCRequiredMoneyText:Show()
		NxQuestDSCRequiredMoneyFrame:Show()
	else
		NxQuestDSCRequiredMoneyText:Hide()
		NxQuestDSCRequiredMoneyFrame:Hide()
	end

	if GetQuestLogGroupNum() > 0 then

		local suggestedGroupString = format (QUEST_SUGGESTED_GROUP_NUM, GetQuestLogGroupNum())
		NxQuestDSCSuggestedGroupNum:SetText (suggestedGroupString)
		NxQuestDSCSuggestedGroupNum:Show()
		NxQuestDSCSuggestedGroupNum:ClearAllPoints()

		if GetQuestLogRequiredMoney() > 0 then
			NxQuestDSCSuggestedGroupNum:SetPoint ("TOPLEFT", "NxQuestDSCRequiredMoneyText", "BOTTOMLEFT", 0, -4)
		elseif numObjectives > 0 then
			NxQuestDSCSuggestedGroupNum:SetPoint ("TOPLEFT", "NxQuestDSCObjective"..numObjectives, "BOTTOMLEFT", 0, -4)
		elseif questTimer then
			NxQuestDSCSuggestedGroupNum:SetPoint ("TOPLEFT", "NxQuestDSCTimerText", "BOTTOMLEFT", 0, -10)
		else
			NxQuestDSCSuggestedGroupNum:SetPoint ("TOPLEFT", "NxQuestDSCObjectivesText", "BOTTOMLEFT", 0, -10)
		end
	else
		NxQuestDSCSuggestedGroupNum:Hide()
	end

	if GetQuestLogGroupNum() > 0 then
		NxQuestDSCDescriptionTitle:SetPoint("TOPLEFT", "NxQuestDSCSuggestedGroupNum", "BOTTOMLEFT", 0, -10)

	elseif GetQuestLogRequiredMoney() > 0 then
		NxQuestDSCDescriptionTitle:SetPoint("TOPLEFT", "NxQuestDSCRequiredMoneyText", "BOTTOMLEFT", 0, -10)

	elseif numObjectives > 0 then
		NxQuestDSCDescriptionTitle:SetPoint("TOPLEFT", "NxQuestDSCObjective"..numObjectives, "BOTTOMLEFT", 0, -10)

	else
		if questTimer then
			NxQuestDSCDescriptionTitle:SetPoint ("TOPLEFT", "NxQuestDSCTimerText", "BOTTOMLEFT", 0, -10)
		else
			NxQuestDSCDescriptionTitle:SetPoint ("TOPLEFT", "NxQuestDSCObjectivesText", "BOTTOMLEFT", 0, -10)
		end
	end

	if questDescription then
		NxQuestDSCQuestDescription:SetText (questDescription)
		QuestFrame_SetAsLastShown (NxQuestDSCQuestDescription, NxQuestDSCSpacerFrame)
	end

	local numRewards = GetNumQuestLogRewards()
	local numChoices = GetNumQuestLogChoices()
	local money = GetQuestLogRewardMoney()

	if numRewards + numChoices + money > 0 then
		NxQuestDSCRewardTitleText:Show()
--		QuestFrame_SetAsLastShown (NxQuestDSCRewardTitleText, NxQuestDSCSpacerFrame)

	else
		NxQuestDSCRewardTitleText:Hide()
	end

	NxQuestDScrollBar:SetValue (0)
	NxQuestD:UpdateScrollChildRect()

--]]

end

--------
--

function Nx.Quest:FrameItems_Update (questState)

	NxQuestDSCRewardTitleText:SetPoint ("TOPLEFT", "NxQuestDSC", "TOPLEFT", 0, -10)

	local questState = "NxQuestDSC"
	local questItemName = "NxQuestDSCItem"

	local numQuestRewards
	local numQuestChoices
	local money = GetQuestLogRewardMoney()
	local spacerFrame = NxQuestDSCSpacerFrame

	numQuestRewards = GetNumQuestLogRewards()
	numQuestChoices = GetNumQuestLogChoices()

	local numQuestSpellRewards = 0
	if GetQuestLogRewardSpell() then
		numQuestSpellRewards = 1
	end

	local totalRewards = numQuestRewards + numQuestChoices + numQuestSpellRewards
	local material = QuestFrame_GetMaterial()
	local questItemReceiveText = getglobal (questState.."ItemReceiveText")

	if totalRewards == 0 and money == 0 then
		getglobal (questState.."RewardTitleText"):Hide()
	else
		getglobal (questState.."RewardTitleText"):Show()
		QuestFrame_SetTitleTextColor (getglobal (questState.."RewardTitleText"), material)
		QuestFrame_SetAsLastShown (getglobal (questState.."RewardTitleText"), spacerFrame)
	end

	if money == 0 then
		getglobal (questState.."MoneyFrame"):Hide()
	else
		getglobal (questState.."MoneyFrame"):Show()
		QuestFrame_SetAsLastShown (getglobal(questState.."MoneyFrame"), spacerFrame)
		MoneyFrame_Update (questState.."MoneyFrame", money)
	end

	-- Hide unused rewards

	for n = totalRewards + 1, MAX_NUM_ITEMS do
		getglobal (questItemName..n):Hide()
	end

	local questItem, name, texture, isTradeskillSpell, isSpellLearned, quality, isUsable, numItems = 1
	local rewardsCount = 0

	-- Setup choosable rewards

	if numQuestChoices > 0 then

		local itemChooseText = getglobal (questState.."ItemChooseText")
		itemChooseText:Show()
		QuestFrame_SetTextColor (itemChooseText, material)
		QuestFrame_SetAsLastShown (itemChooseText, spacerFrame)

		local index
		local baseIndex = rewardsCount

		for i = 1, numQuestChoices do

			index = i + baseIndex
			questItem = getglobal (questItemName..index)
			questItem.type = "choice"
			numItems = 1

			name, texture, numItems, quality, isUsable = GetQuestLogChoiceInfo (i)

			questItem:SetID (i)
			questItem:Show()

			-- For the tooltip
			questItem.rewardType = "item"

			getglobal (questItemName..index.."Name"):SetText(name)
			SetItemButtonCount (questItem, numItems)
			SetItemButtonTexture (questItem, texture)

			if isUsable then
				SetItemButtonTextureVertexColor (questItem, 1.0, 1.0, 1.0)
				SetItemButtonNameFrameVertexColor (questItem, 1.0, 1.0, 1.0)
			else
				SetItemButtonTextureVertexColor (questItem, 0.9, 0, 0)
				SetItemButtonNameFrameVertexColor (questItem, 0.9, 0, 0)
			end

			if i > 1 then

				if mod (i, 2) == 1 then
					questItem:SetPoint ("TOPLEFT", questItemName..(index - 2), "BOTTOMLEFT", 0, -2)
					QuestFrame_SetAsLastShown (questItem, spacerFrame)

				else
					questItem:SetPoint ("TOPLEFT", questItemName..(index - 1), "TOPRIGHT", 1, 0)
				end

			else
				questItem:SetPoint ("TOPLEFT", itemChooseText, "BOTTOMLEFT", 0, -5)
				QuestFrame_SetAsLastShown (questItem, spacerFrame)
			end

			rewardsCount = rewardsCount + 1
		end
	else
		getglobal (questState.."ItemChooseText"):Hide()
	end

	-- Setup spell rewards

	local learnSpellText = getglobal (questState.."SpellLearnText")

	if numQuestSpellRewards > 0 then

		learnSpellText:Show()
		QuestFrame_SetTextColor (learnSpellText, material)
		QuestFrame_SetAsLastShown (learnSpellText, spacerFrame)

		--Anchor learnSpellText if there were choosable rewards
		if rewardsCount > 0 then
			learnSpellText:SetPoint("TOPLEFT", questItemName..rewardsCount, "BOTTOMLEFT", 3, -5)
		else
			learnSpellText:SetPoint("TOPLEFT", questState.."RewardTitleText", "BOTTOMLEFT", 0, -5)
		end

		texture, name, isTradeskillSpell, isSpellLearned = GetQuestLogRewardSpell()

		if isTradeskillSpell then
			learnSpellText:SetText (REWARD_TRADESKILL_SPELL)
		elseif not isSpellLearned then
			learnSpellText:SetText (REWARD_AURA)
		else
			learnSpellText:SetText (REWARD_SPELL)
		end

		rewardsCount = rewardsCount + 1

		questItem = getglobal (questItemName..rewardsCount)
		questItem:Show()
		-- For the tooltip
		questItem.rewardType = "spell"
		SetItemButtonCount (questItem, 0)
		SetItemButtonTexture (questItem, texture)
		getglobal (questItemName..rewardsCount.."Name"):SetText(name)

		QuestFrame_SetAsLastShown (questItem, spacerFrame)

		questItem:SetPoint ("TOPLEFT", learnSpellText, "BOTTOMLEFT", 0, -5)
	else
		learnSpellText:Hide()
	end

	-- Setup mandatory rewards
	if numQuestRewards > 0 or money > 0 then

		QuestFrame_SetTextColor (questItemReceiveText, material)

		-- Anchor the reward text differently if there are choosable rewards
		if numQuestSpellRewards > 0 then
			questItemReceiveText:SetText (REWARD_ITEMS)
			questItemReceiveText:SetPoint ("TOPLEFT", questItemName..rewardsCount, "BOTTOMLEFT", 3, -5)

		elseif numQuestChoices > 0 then
			questItemReceiveText:SetText (REWARD_ITEMS)
			local index = numQuestChoices
			if mod (index, 2) == 0 then
				index = index - 1
			end
			questItemReceiveText:SetPoint ("TOPLEFT", questItemName..index, "BOTTOMLEFT", 3, -5)

		else
			questItemReceiveText:SetText (REWARD_ITEMS_ONLY)
			questItemReceiveText:SetPoint ("TOPLEFT", questState.."RewardTitleText", "BOTTOMLEFT", 3, -5)
		end

		questItemReceiveText:Show()
		QuestFrame_SetAsLastShown (questItemReceiveText, spacerFrame)

		-- Setup mandatory rewards
		local index
		local baseIndex = rewardsCount

		for i = 1, numQuestRewards do

			index = i + baseIndex
			questItem = getglobal (questItemName..index)
			questItem.type = "reward"
			numItems = 1

			name, texture, numItems, quality, isUsable = GetQuestLogRewardInfo (i)

			questItem:SetID (i)
			questItem:Show()
			-- For the tooltip
			questItem.rewardType = "item"
			getglobal (questItemName..index.."Name"):SetText(name)
			SetItemButtonCount (questItem, numItems)
			SetItemButtonTexture (questItem, texture)

			if isUsable then
				SetItemButtonTextureVertexColor (questItem, 1.0, 1.0, 1.0)
				SetItemButtonNameFrameVertexColor (questItem, 1.0, 1.0, 1.0)
			else
				SetItemButtonTextureVertexColor (questItem, 0.5, 0, 0)
				SetItemButtonNameFrameVertexColor (questItem, 1.0, 0, 0)
			end

			if i > 1 then

				if mod (i, 2) == 1 then
					questItem:SetPoint ("TOPLEFT", questItemName..(index - 2), "BOTTOMLEFT", 0, -2)
					QuestFrame_SetAsLastShown (questItem, spacerFrame)

				else
					questItem:SetPoint ("TOPLEFT", questItemName..(index - 1), "TOPRIGHT", 1, 0)
				end

			else
				questItem:SetPoint ("TOPLEFT", questState.."ItemReceiveText", "BOTTOMLEFT", 0, -5)
				QuestFrame_SetAsLastShown (questItem, spacerFrame)

			end

			rewardsCount = rewardsCount + 1
		end
	else
		questItemReceiveText:Hide()
	end
end

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Quest watch

--------
-- Init and open

function Nx.Quest.Watch:Open()

	local opts = Nx:GetGlobalOpts()
	self.GOpts = opts
	local qopts = Nx:GetQuestOpts()

	self.Watched = {}

	self.Opened = true

	local fixedSize = opts["QWFixedSize"]

	-- Create window

--	Nx.Window:ClrSaveData ("NxQuestWatch")

	Nx.Window:SetCreateFade (1, .15)

	local border = fixedSize and true or 1

	local win = Nx.Window:Create ("NxQuestWatch", nil, nil, nil, 1, border)
	self.Win = win

	win:InitLayoutData (nil, -.80, -.35, -.2, -.1)

	win:CreateButtons (opts["QWShowClose"], nil, true)

	win:SetUser (self, self.OnWin)
	win:SetBGAlpha (0, 1)
	win.Frm:SetClampedToScreen (true)

	local xo = 0
	local yo = 0

	if not fixedSize then
		xo = 7
		yo = 3
		win:SetBorderSize (0, 7)
	end

	win:SetTitleXOff (84 + xo, -1 - yo)

--	win:SetTitle ("[  ]")
	win.UserUpdateFade = self.WinUpdateFade

	-- Update helper. Can't call directly due to validation changing function

	local function update (self)
		self:Update()
	end

	-- Buttons

	local function func (self)
		self.Menu:Open()
	end
	self.ButMenu = Nx.Button:Create (win.Frm, "QuestWatchMenu", nil, nil, 4, -5 + yo, "TOPLEFT", 1, 1, func, self)

	local function func (self)
		self.MenuPri:Open()
	end
	self.ButPri = Nx.Button:Create (win.Frm, "QuestWatchPri", nil, nil, 19, -5 + yo, "TOPLEFT", 1, 1, func, self)

	local function func (self, but)
		local qopts = Nx:GetQuestOpts()
		qopts.NXWShowOnMap = but:GetPressed()
	end
	self.ButShowOnMap = Nx.Button:Create (self.ButMenu.Frm, "QuestWatchShowOnMap", nil, nil, 29, 0, "CENTER", 1, 1, func, self)
	self.ButShowOnMap:SetPressed (qopts.NXWShowOnMap)

	local function func (self, but)
		if not but:GetPressed() and not IsShiftKeyDown() then
			Nx.Quest.Tracking = {}	-- Kill all
		end
		self:Update()
	end
	self.ButATarget = Nx.Button:Create (self.ButMenu.Frm, "QuestWatchATrack", nil, nil, 43, 0, "CENTER", 1, 1, func, self)

	local function func (self, but)
		Nx.CharOpts["QMapShowQuestGivers3"] = but:GetState()
		local map = Nx.Map:GetMap (1)
		map.Guide:UpdateGatherFolders()
	end
	self.ButQGivers = Nx.Button:Create (self.ButMenu.Frm, "QuestWatchGivers", nil, nil, 57, 0, "CENTER", 1, 1, func, self)
	self.ButQGivers:SetState (Nx.CharOpts["QMapShowQuestGivers3"])

	local function func (self, but)
		qopts.NXWWatchParty = but:GetPressed()
		Nx.Quest:PartyUpdateTimer()
	end
	self.ButShowParty = Nx.Button:Create (self.ButMenu.Frm, "QuestWatchParty", nil, nil, 71, 0, "CENTER", 1, 1, func, self)
	self.ButShowParty:SetPressed (qopts.NXWWatchParty == nil or qopts.NXWWatchParty)

	-- List

	Nx.List:SetCreateFont ("FontWatch", 12)

	local list = Nx.List:Create (false, 2, -2, 100, 12 * 3, win.Frm, not fixedSize, true)
	self.List = list

	list:SetUser (self, self.OnListEvent)
--	self:SetFont()

	if not fixedSize then
		list:SetMinSize (124, 1)		-- Sets the window minimum
		list.Frm:EnableMouse (false)
	end

	list:ColumnAdd ("", 1, 14)
	list:ColumnAdd ("Name", 2, fixedSize and 900 or 20)
--	list:ColumnAdd ("", 3, 0)
--	list:ColumnAdd ("Type", 4, 60)
--	list:ColumnAdd ("Status", 5, 500)

	win:Attach (list.Frm, 0, 1, 0, 1)

	-- Create menu button menu

	local qlist = Nx.Quest.List

	local menu = Nx.Menu:Create (list.Frm)
	self.Menu = menu

	menu:AddItem (0, "Watch All Quests", qlist.Menu_OnWatchAll, qlist)
	menu:AddItem (0, "Remove All Watches", self.Menu_OnRemoveAllWatches, self)

	menu:AddItem (0, "Track None", qlist.Menu_OnTrackNone, qlist)

--	local item = menu:AddItem (0, "Max Auto Track", update, self)
--	item:SetSlider (qopts, 1, 25, 1, "NXWAutoMax")

	local i = 25

	local item = menu:AddItem (0, "Max Visible In List", update, self)
	item:SetSlider (qopts, 1, i, 1, "NXWVisMax")

--	menu:AddItem (0, "")

	local function func()
		Nx.Opts:Open ("Quest Watch")
	end

	menu:AddItem (0, "Options...", func)

	-- Create priority button menu

	local menu = Nx.Menu:Create (list.Frm, 260)
	self.MenuPri = menu

	local item = menu:AddItem (0, "Hide Unfinished Quests", update, self)
	item:SetChecked (qopts, "NXWHideUnfinished")

	local item = menu:AddItem (0, "Hide 5+ Group Quests", update, self)
	item:SetChecked (qopts, "NXWHideGroup")

	local item = menu:AddItem (0, "Hide Quests Not In Zone", update, self)
	item:SetChecked (qopts, "NXWHideNotInZone")

--	local item = menu:AddItem (0, "Hide Quests Not On Continent", update, self)
--	item:SetChecked (qopts, "NXWHideNotInCont")

	local item = menu:AddItem (0, "Hide Quests Farther Than", update, self)
	item:SetSlider (qopts, 200, 20000, 1, "NXWHideDist")

	local item = menu:AddItem (0, "Sort, Distance", update, self)
	item:SetSlider (qopts, 0, 1, nil, "NXWPriDist")

	local item = menu:AddItem (0, "Sort, Complete", update, self)
	item:SetSlider (qopts, -200, 200, 1, "NXWPriComplete")

	local item = menu:AddItem (0, "Sort, Low Level", update, self)
	item:SetSlider (qopts, -200, 200, 1, "NXWPriLevel")

	local function func()
		Nx.Map:GetMap (1).Guide:UpdateGatherFolders()
	end

	local item = menu:AddItem (0, "Quest Giver Lower Levels To Show", func, self)
	item:SetSlider (opts, 0, 90, 1, "QMapQuestGiversLowLevel")

	local item = menu:AddItem (0, "Quest Giver Higher Levels To Show", func, self)
	item:SetSlider (opts, 0, 90, 1, "QMapQuestGiversHighLevel")

--	local item = menu:AddItem (0, "Group", update, self)
--	item:SetSlider (qopts, -200, 200, 1, "NXWPriGroup")

	-- Create watch button menu

	local menu = Nx.Menu:Create (list.Frm)
	self.WatchMenu = menu

	menu:AddItem (0, "Remove Watch", self.Menu_OnRemoveWatch, self)
	menu:AddItem (0, "Link Quest (shift right click)", self.Menu_OnLinkQuest, self)
	menu:AddItem (0, "Show Quest Log (alt right click)", self.Menu_OnShowQuest, self)
	menu:AddItem (0, "Show On Map (shift left click)", self.Menu_OnShowMap, self)
	menu:AddItem (0, "Share", self.Menu_OnShare, self)

	menu:AddItem (0, "")
	menu:AddItem (0, "Abandon", self.Menu_OnAbandon, self)

	--

	self.FirstUpdate = true
	self.FlashColor = 0

	--

	self:SetSortMode (1)
end

--------
-- Setup list font

function Nx.Quest.Watch:FixedChange()

	Nx.Window:ClrSaveData ("NxQuestWatch")
end

--------
-- Setup list font

--[[
function Nx.Quest.Watch:SetFont()

	local opts = Nx:GetGlobalOpts()

	local sz = opts["QWLargeFont"] and 15 or 12
	self.List:SetFont (sz)

	local sz = opts["QWLargeFont"] and 15 or 13
	self.List:SetLineHeight (sz, 0)
end
--]]

--------

function Nx.Quest.Watch:OnWin (typ)
	self:Update()
end

--------

function Nx.Quest.Watch:Menu_OnRemoveWatch (item)

	self:RemoveWatch (self.MenuQId, self.MenuQIndex)
	self:Update()
	Nx.Quest.List:Update()
end

function Nx.Quest.Watch:Menu_OnShowQuest()

	ShowUIPanel (QuestLogFrame)

	Nx.Quest.List.Bar:Select (1)
	Nx.Quest.List:Select (self.MenuQId, self.MenuQIndex)
end

function Nx.Quest.Watch:Menu_OnShowMap (item)

	self:Set (self.MenuItemData, true)
end

function Nx.Quest.Watch:Menu_OnLinkQuest()

	Nx.Quest:LinkChat (self.MenuQId)
end

function Nx.Quest.Watch:Menu_OnShare (item)

	local qi = self.MenuQIndex
	if qi > 0 then

		if GetNumSubgroupMembers() > 0 then
			Nx.Quest:ExpandQuests()
--			Nx.Quest.List:Select (self.MenuQId, self.MenuQIndex)
			QuestLogPushQuest (qi)
			Nx.Quest:RestoreExpandQuests()
		else
			Nx.prt ("Must be in party to share")
		end
	end
end

function Nx.Quest.Watch:Menu_OnAbandon (item)
	Nx.Quest.List:Select (self.MenuQId, self.MenuQIndex)
	Nx.Quest:Abandon (self.MenuQIndex, self.MenuQId)
end

function Nx.Quest.Watch:Menu_OnRemoveAllWatches (item)

	local curq = Nx.Quest.CurQ

	for n = 1, curq and #curq or 0 do

		local cur = curq[n]
		self:RemoveWatch (cur.QId, cur.QI)
	end

	self:Update()
	Nx.Quest.List:Update()
end

function Nx.Quest.Watch:RemoveWatch (qId, qI)

	local i, cur, id = Nx.Quest:FindCur (qId, qI)

	if i then

		local qStatus, qTime = Nx:GetQuest (id)
		if qStatus == "W" then

			Nx:SetQuest (id, "c", qTime)
			Nx.Quest:PartyStartSend()

			if qId > 0 then
				Nx.Quest.Tracking[qId] = nil

				if Nx.Quest:IsTargeted (qId) then
					Nx.Quest.Map:ClearTargets()
				end
			end
		end

		if IsQuestWatched (qI) then	-- Blizz crap? Remove
			RemoveQuestWatch (qI)
		end
	end
end

--------
-- Show or hide

function Nx.NXWatchKeyToggleMini()

	local self = Nx.Quest.Watch
--	self.ButMini:SetPressed (not self.ButMini:GetPressed())

	self.Win:ToggleMinimize()
	self:Update()
end

function Nx.NXWatchKeyUseItem()

	if NxListFrms1 then
		NxListFrms1:Click()
	end
end

function Nx.Quest.Watch:ClearAutoTarget (keepTracking)

	if Nx.Quest.Enabled then

		if not keepTracking then
			Nx.Quest.Tracking = {}	-- Kill all
		end
		self.ButATarget:SetPressed (false)
		self:Update()
	end
end

--------
-- Set sort mode

function Nx.Quest.Watch:SetSortMode (mode)

	Nx.Timer:Start ("QuestWatchUpdate", .01, self, self.OnUpdateTimer)

--[[
	local qopts = Nx:GetQuestOpts()
	qopts.NXSortWatchMode = mode

	if mode == 1 then
		Nx.Timer:Start ("QuestWatchUpdate", .01, self, self.OnUpdateTimer)

	else
		Nx.Timer:Stop ("QuestWatchUpdate")

		local curq = self.CurQ

		for n = 1, curq and #curq or 0 do
			local cur = curq[n]
			cur.Distance = 999999999
			cur.CloseObjI = -1
		end

		self.Watch:Update()
	end
--]]
end

function Nx.Quest.Watch:OnUpdateTimer (item)

	if not Nx.Timer:IsActive ("QuestWatchDist") then
		self:Update()
		self.CalcDistCnt = 3
	end
	return 1.5
end

--------
-- Update list security stub

function Nx.Quest.Watch:Update()
--	Nx.Quest.Watch:Update_()
end

function Nx.Quest.Watch:Update_()

--	Nx.prt ("Watch update")
--[[
	if Nx.Free then
		local ver = GetBuildInfo()
		if ver ~= "2.3.3" then

			local function func()
				Nx.prt ("data error")
			end

			Nx.Timer:Start ("WatchDataError", .1, self, func)
			return
		end
	end
--]]

	self.CalcDistI = 1
	self.CalcDistCnt = 20
	Nx.Timer:Start ("QuestWatchDist", 0, self, self.OnTimer)
end

function Nx.Quest.Watch:OnTimer (item)

	local curq = Nx.Quest.CurQ
	if not curq then	-- Bad stuff?
		return
	end

	local i = self.CalcDistI
	local cnt = self.CalcDistCnt

	Nx.Quest:CalcDistances (i, i + cnt - 1)

	i = i + cnt

	if i <= #curq then
		self.CalcDistI = i
		return .02
	end

	local watched = self:UpdateList()

--	Nx.Quest:Route (watched)
end

--------
-- Update watch list

function Nx.Quest.Watch:UpdateList()

--	Nx.prt ("QWatchUpdate")

	local Nx = Nx
	local Quest = Nx.Quest
	local Map = Nx.Map

	Nx.Timer:ProfilerStart ("Watch UpList")

	local qopts = Nx:GetQuestOpts()
	local hideUnfinished = qopts["NXWHideUnfinished"]
	local hideGroup = qopts["NXWHideGroup"]
	local hideNotInZone = qopts["NXWHideNotInZone"]
	local hideNotInCont = qopts["NXWHideNotInCont"]
	local hideDist = qopts["NXWHideDist"] >= 19900 and 99999 or qopts["NXWHideDist"]
	local hideDist = hideDist / 4.575		-- Convert to world units
	local priDist = qopts.NXWPriDist

	local gopts = self.GOpts

	local fixedSize = gopts["QWFixedSize"]
	local showDist = gopts["QWShowDist"]
	local showPerColor = gopts["QWShowPerColor"]
	local hideDoneObj = gopts["QWHideDoneObj"]

	local compColor = Nx.Util_num2colstr (gopts["QWCompleteColor"])
	local incompColor = Nx.Util_num2colstr (gopts["QWIncompleteColor"])
	local oCompColor = Nx.Util_num2colstr (gopts["QWOCompleteColor"])
	local oIncompColor = Nx.Util_num2colstr (gopts["QWOIncompleteColor"])

	-- List

	local list = self.List

	local oldw, oldh = list:GetSize()

	list:SetBGColor (Nx.Util_num2rgba (gopts["QWBGColor"]))

	list:Empty()


	local watched = wipe (self.Watched)

	local curq = Quest.CurQ
	if curq then

		for n, cur in ipairs (curq) do

			local qId = cur.QId
			local id = qId > 0 and qId or cur.Title
			local qStatus = Nx:GetQuest (id)
			local qWatched = qStatus == "W" or cur.PartyDesc

--			Nx.prt ("qid %s %s dist %s", qId, qStatus, cur.Distance)

			if qWatched and (cur.Distance < hideDist or cur.Distance > 999999) then

				if (not hideUnfinished or cur.CompleteMerge) and
					(not hideGroup or cur.PartySize < 5) and
					(not hideNotInZone or cur.InZone) and
					(not hideNotInCont or cur.InCont) then

					local d = max (cur.Distance * priDist * cur.Priority * 10 + cur.Priority * 100, 0)
					d = cur.HighPri and 0 or d
					d = floor (d) * 256 + n
					tinsert (watched, d)
				end
			end
		end

		sort (watched)
		local disti = watched[1]

		-- Auto target objective of closest quest

		if self.ButATarget:GetPressed() then

			if disti then
				local cur = curq[bit.band (disti, 0xff)]
				Quest:CalcAutoTrack (cur)
			end
		end

		-- Remember closest quest for com

		self.ClosestCur = disti and curq[bit.band (disti, 0xff)]

		--

		if not self.Win:IsSizeMin() and self.Win:IsVisible() then
			self.FlashColor = (self.FlashColor + 1) % 2
			list:SetItemFrameScaleAlpha (gopts["QWItemScale"], Nx.Util_num2a (gopts["QWItemAlpha"]))
			if gopts["QWHideBlizz"] then
				WatchFrame:Hide()		-- Hide Blizzard's
			end
			if gopts["QWChalTrack"] then
			  local cTimer ={GetWorldElapsedTimers()}
				for _,id in ipairs(cTimer) do
 		          local description, elapsedTime, isChallengeModeTimer = GetWorldElapsedTime(id)
 		          if (isChallengeModeTimer) then
 		            list:ItemAdd(0)
 			        list:ItemSet(2,format("|cffff8888%s",description))
 			        list:ItemSetButton("QuestWatchTip",false)
 			        local s = "  |cffffffff" .. SecondsToTime(elapsedTime)
 			        list:ItemAdd(0)
 			        list:ItemSet(2,s)
 		          end
 		        end
			end
			if gopts["QWScenTrack"] then
				local name, currentStage, numStages = C_Scenario.GetInfo()
				if (currentStage > 0) then
					local stageName, stageDescription, numCriteria = C_Scenario.GetStepInfo()
					list:ItemAdd(0)
					list:ItemSet(2,format("|cffff8888Scenario: %s",name))
					list:ItemSetButtonTip(stageDescription)
					list:ItemSetButton("QuestWatchTip",false)
					if (currentStage <= numStages) then
						s = format("  |cffff0000Stage [|cffffffff%d|cffff0000/|cffffffff%d|cffff0000]:|cff00ff00%s", currentStage, numStages,stageName)
					else
						s = "  |cffff0000[|cffffffffComplete|cffff0000]"
					end
					list:ItemAdd(0)
					list:ItemSet(2,s)
				end
			end
			if gopts["QWAchTrack"] then
				local ach = { GetTrackedAchievements() }
				for _, id in ipairs (ach) do
					local aId, aName, aPoints, aComplete, aMonth, aDay, aYear, aDesc = GetAchievementInfo (id)
					if aName then		-- Person had nil name happen
						list:ItemAdd (0)
						list:ItemSet (2, format ("|cffdf9fffAchievement: %s", aName))
						local numC = GetAchievementNumCriteria (id)
						local progressCnt = 0
						local tip = aDesc
						for n = 1, numC do
							local cName, cType, cComplete, cQuantity, cReqQuantity = GetAchievementCriteriaInfo (id, n)
							local color = cComplete and "|cff80ff80" or "|cffa0a0a0"
							if not cComplete and cReqQuantity > 1 and cQuantity > 0 then
								progressCnt = progressCnt + 1
								tip = tip .. format ("\n%s%s: %s / %s", color, cName, cQuantity, cReqQuantity)
							else
								tip = tip .. format ("\n%s%s", color, cName)
							end
						end
						list:ItemSetButton ("QuestWatchTip", false)
						list:ItemSetButtonTip (tip)
						local showCnt = 0
						for n = 1, numC do
							local cName, cType, cComplete, cQuantity, cReqQuantity = GetAchievementCriteriaInfo (id, n)
							if not cComplete and (progressCnt <= 3 or cQuantity > 0) then
								list:ItemAdd (0)
								local s = "  |cffcfafcf"
								if numC == 1 then
									if cReqQuantity > 1 then
										s = s .. format ("%s/%s", cQuantity, cReqQuantity)
									else
										s = s .. cName
									end
								else
									s = s .. cName
									if cReqQuantity > 1 then
										s = s .. format (": %s/%s", cQuantity, cReqQuantity)
									end
								end
								showCnt = showCnt + 1
								if showCnt >= 3 then
									s = s .. "..."
								end
								list:ItemSet (2, s)
								if showCnt >= 3 then
									break
								end
							end
						end
					end
				end
			end

			local s = gopts["QWAchZoneShow"] and Nx.Quest:GetZoneAchievement()
			if s then
				list:ItemAdd (0)
				list:ItemSet (2, s)
			end

--			Nx.prtVar ("Watched", watched)

			local watchNum = 1

			for _, distn in ipairs (watched) do

				local n = bit.band (distn, 0xff)

				local cur = curq[n]
				local qId = cur.QId

				if 1 then

					local level, isComplete = cur.Level, cur.CompleteMerge
					local quest = cur.Q
					local qi = cur.QI
					local lbNum = cur.LBCnt

--					local link, item, charges = GetQuestLogSpecialItemInfo (questIndex)

					list:ItemAdd (qId * 0x10000 + qi)

					local trackMode = Quest.Tracking[qId] or 0
					local obj = quest and (quest[3] or quest[2])

					if qId == 0 then
						list:ItemSetButton ("QuestWatchErr", false)

					elseif not obj then

--						Nx.prt ("not obj")
						list:ItemSetButton ("QuestWatchErr", false)

					elseif isComplete or lbNum == 0 then

						local butType = "QuestWatch"
						local pressed = false

						if bit.band (trackMode, 1) > 0 then
							pressed = true
						end

						if Quest:IsTargeted (qId, 0) then
							butType = "QuestWatchTarget"
						end

						local name, zone = Quest:GetSEPos (obj)
						if not zone or not Map.NxzoneToMapId[zone] then
							butType = "QuestWatchErr"
						end

						if isComplete and cur.IsAutoComplete then
							butType = "QuestWatchAC"
							pressed = false
						end

						list:ItemSetButton (butType, pressed)
					else

						list:ItemSetButton ("QuestWatchTip", false)
					end

					if not isComplete and cur.ItemLink and gopts["QWItemScale"] >= 1 then
						list:ItemSetFrame ("WatchItem~" .. cur.QI .. "~" .. cur.ItemImg .. "~" .. cur.ItemCharges)
					end

					list:ItemSetButtonTip (cur.ObjText .. (cur.PartyDesc or ""))

					local color = isComplete and compColor or incompColor

					local lvlStr = ""
					if level > 0 then
						local col = Quest:GetDifficultyColor (level)
						lvlStr = format ("|cff%02x%02x%02x%2d%s ", col.r * 255, col.g * 255, col.b * 255, level, cur.TagShort)
					end

					local nameStr = format ("%s%s%s", lvlStr, color, cur.Title)

					if cur.NewTime and time() < cur.NewTime + 60 then
						nameStr = format ("|cff00%2x00New: %s", self.FlashColor * 200 + 55, nameStr)
					end

					if isComplete then

						local obj = quest and (quest[3] or quest[2])

						if lbNum > 0 or not obj then
							nameStr = nameStr .. (isComplete == 1 and "|cff80ff80 (Complete)" or "|cfff04040 - " .. FAILED)

						else
							local desc = Quest:UnpackSE (obj)
							nameStr = format ("%s |cffffffff(%s)", nameStr, desc)
						end
					end

					if showDist then
						local d = cur.Distance * 4.575

						if d < 1000 then
							nameStr = format ("%s |cff808080%d yds", nameStr, d)
						elseif cur.Distance < 99999 then
							nameStr = format ("%s |cff808080%.1fK yds", nameStr, d / 1000)
						end
					end

					if cur.PartyCnt then
						nameStr = format ("%s |cffb0b0f0(+%s)", nameStr, cur.PartyCnt)
					end

					if cur.Party then
						nameStr = nameStr .. " |cffb0b0f0" .. cur.Party
					end

					list:ItemSet (2, nameStr)


					if cur.TimeExpire then	-- Have a timer?
						list:ItemAdd (0)
						list:ItemSet (2, format ("  |cfff06060%s %s", TIME_REMAINING, SecondsToTime (cur.TimeExpire - time())))
					end

					if isComplete and cur.IsAutoComplete then
						list:ItemAdd (0)
						list:ItemSet (2, format ("|cff%2x0000--- Click ? to complete ---", self.FlashColor * 200 + 55))
					end


					if qi > 0 or cur.Party then

						local desc, done
						local zone, loc
						local lnOffset = -1

						for ln = 1, 31 do

							local obj = quest and quest[ln + 3]

							if not obj and ln > lbNum then
--[[
								if ln == 1 then
									obj = quest[3] or quest[2]
								else
									break
								end
--]]
								break
							end

							zone = nil
							done = isComplete

							if obj then
								desc, zone, loc = Quest:UnpackObjective (obj)
							end

							if ln <= lbNum then
								desc = cur[ln]
								done = cur[ln + 300]
							end

							if not (hideDoneObj and done) then

								if showPerColor then
									if done then
										color = Quest.PerColors[9]
									else

										local s1, _, i, total = strfind (desc, ": (%d+)/(%d+)")
										if s1 then
--											Nx.prt ("%s %s", i, total)
											i = floor (tonumber (i) / tonumber (total) * 8.99) + 1
										else
											i = 1
										end

										color = Quest.PerColors[i]
									end
								else
									color = done and oCompColor or oIncompColor
								end

								if gopts["QWOCntFirst"] then
									local s1, s2 = strmatch (desc, "(.+): (.+)")
									if s2 then
										desc = format ("%s: %s", s2, s1)
									end
								end

								local str = color .. (desc or "?")	--V4

								if not done then
									local d = cur["OD"..ln]
									if d and d < .5 then			-- Not in yards
										str = "*" .. str
									end
								end

								list:ItemAdd (qId * 0x10000 + ln * 0x100 + qi)
								list:ItemSetOffset (16, lnOffset)

								local butType = "QuestWatchErr"

								if zone then
									if zone == 220 then
										butType = nil

									elseif Map.NxzoneToMapId[zone] then
										butType = "QuestWatch"
										if Quest:IsTargeted (qId, ln) then
											butType = "QuestWatchTarget"
										end
									end
								end

--								Nx.prt ("watch %s %s %s", qId, zone or "nil", butType or "nil")

								if not done and butType then

									if bit.band (trackMode, bit.lshift (1, ln)) > 0 then
										list:ItemSetButton (butType, true)
									else
										list:ItemSetButton (butType, nil)
									end
								end

								if not fixedSize then

									local maxCOpt = gopts["QWOMaxLen"] + 10
									local maxC = maxCOpt

									while #str > maxC do

										for cn = maxC, 12, -1 do
											if strbyte (str, cn) == 32 then		-- Find last space
												maxC = cn - 1
												break
											end
										end

										local s = strsub (str, 1, maxC)
										list:ItemSet (2, s)

										str = color .. strsub (str, maxC + 1)
										list:ItemAdd (qId * 0x10000 + ln * 0x100 + qi)
										list:ItemSetOffset (16, lnOffset)

										maxC = maxCOpt
									end
								end

								list:ItemSet (2, str)

								lnOffset = lnOffset - 1
							end
						end
					end

					if not fixedSize and watchNum >= qopts.NXWVisMax then
						list:ItemAdd (0)
						list:ItemSet (2, " ...")
						break
					end

					watchNum = watchNum + 1
				end
			end
		end
	end

	if fixedSize then
		list:FullUpdate()
	else
--		Nx.prt ("QWL Up")
		list:Update()
	end

	-- Grow upwards

	if self.Win:IsSizeMin() then
		self.FirstUpdate = true
		self.Win:SetTitle ("")

	else

		local w, h = list:GetSize()

		if gopts["QWGrowUp"] and not self.FirstUpdate then

			h = h - oldh
--			Nx.prt ("h dif %s", h)
			self.Win:OffsetPos (0, h)
		end

		if w < 127 then
			self.Win:SetTitle ("")
		else
			local _, i = GetNumQuestLogEntries()
			self.Win:SetTitle (format ("|cff40af40%d/25", i))
		end

		self.FirstUpdate = nil
	end

	Nx.Timer:ProfilerEnd ("Watch UpList")

	return watched
end

--------

function Nx.Quest.Watch:ShowUpdate()
	self.Win.RaidHid = nil
	if self.GOpts["QWHideRaid"] then
		if IsInRaid() then
			self.Win.Frm:Hide()
			self.Win.RaidHid = true
		else
			self.Win.Frm:Show()
		end
	end
end

--------
-- Called by Window update
-- Self = win

function Nx.Quest.Watch:WinUpdateFade (fade, force)

	if self.GOpts["QWFadeAll"] or force then

		self.Win:SetTitleColors (1, 1, 1, fade)
		self.List.Frm:SetAlpha (fade)

		self.ButMenu.Frm:SetAlpha (fade)
		self.ButPri.Frm:SetAlpha (fade)
		self.ButShowOnMap.Frm:SetAlpha (fade)
		self.ButATarget.Frm:SetAlpha (fade)
	end
end

--------
-- On list control updates

function Nx.Quest.Watch:OnListEvent (eventName, val1, val2, click, but)

--	Nx.prt ("QuestListUpdate "..eventName)

	if eventName == "button" then

		local Quest = Nx.Quest

		-- val1 = id
		-- val2 = pressed

		local data = self.List:ItemGetData (val1)

		if data then

			local qIndex = bit.band (data, 0xff)
			local qId = bit.rshift (data, 16)

			local typ = but:GetType()

			if click == "LeftButton" then

--				Nx.prt ("Data #%d, Id%d", qIndex, qId)
--				Nx.prt ("List but %s", but:GetType().WatchError or "nil")

				if typ.WatchError then
					Quest:MsgNotInDB ("O")
				else

					if IsAltKeyDown() then
						Quest.List:SendQuestInfo (qIndex)

					else

						if typ.WatchTip then
--[[
							local i, cur = Quest:FindCur (qId, qIndex)
							if cur.ItemLink then
								UseQuestLogSpecialItem (qIndex)
							end
--]]
							val2 = false	-- Can't turn on cause will track missing stuff
							self:Set (data, val2)
--[[
							local i, cur = Quest:FindCur (qId, qIndex)
							if i then

								for n = cur.LBCnt, 1, -1 do

									data = bit.band (data, 0xffff00ff) + n * 0x100

--									Nx.prtVar ("", data)
									self:Set (data, val2, not IsShiftKeyDown())
								end
							end
--]]
						else

							local i, cur = Quest:FindCur (qId, qIndex)
							if cur and cur.CompleteMerge and cur.IsAutoComplete then
--								Nx.prt ("ShowQuestComplete %s", qIndex)
								ShowQuestComplete (qIndex)

							else
								self:Set (data, val2, not IsShiftKeyDown())

							end

						end
					end
				end

			elseif click == "RightButton" then

				if typ.WatchTip then
					return
				end

				if IsAltKeyDown() then
					Quest.IgnoreAlt = true
					ShowUIPanel (QuestLogFrame)
					Quest.IgnoreAlt = nil
					Quest.List.Bar:Select (1)
					Quest.List:Select (qId, qIndex)

				elseif IsShiftKeyDown() then

					Quest:LinkChat (qId)

				else
					self.MenuItemData = data
					self.MenuQIndex = qIndex
					self.MenuQId = qId

					self.WatchMenu:Open()
				end
			end
		end
	end
end

--------
--

function Nx.Quest.Watch:Set (data, on, track)

	local Quest = Nx.Quest

	local qIndex = bit.band (data, 0xff)
	local qId = bit.rshift (data, 16)

	if qId > 0 then

		local i, cur = Quest:FindCur (qId, qIndex)

		if not (cur and cur.Q) then
			Quest:MsgNotInDB()
			return
		end

--		Nx.prt ("Q Set %s %s", i, cur and cur.Name or "nil")

		local q = cur.Q
		if not q[2] and not q[3] then

			Quest:MsgNotInDB()
			return
		end

		self:ClearAutoTarget (true)

		-- 0 is quest name line
		local qObj = bit.band (bit.rshift (data, 8), 0xff)

		local tbits = Quest.Tracking[qId] or 0

		if track then

			Quest.Tracking = {}	-- Kill all
			tbits = 0

			if not Quest:IsTargeted (qId, qObj) then
				on = true	-- Force on if on but not tracked
			end
		end

		if IsControlKeyDown() then
			on = false		-- Force off
		end

		if qObj == 0 then

			if on == false then
				Quest.Tracking[qId] = nil
			else
				Quest.Tracking[qId] = cur.TrackMask		-- Track all
			end
		else

			if on == false then
				Quest.Tracking[qId] = bit.band (tbits, bit.bnot (bit.lshift (1, qObj)))
			else
				Quest.Tracking[qId] = bit.bor (tbits, bit.lshift (1, qObj))
			end
		end

		if track then
			self:ClearCompleted (qId)
		end

		Quest:TrackOnMap (qId, qObj, qIndex > 0, track)

		self:Update()
		Quest.List:Update()

	else

		Quest:MsgNotInDB()
	end

end

--------
-- Add quest to watch
-- (CurQ number)

function Nx.Quest.Watch:Add (curi)

	local Quest = Nx.Quest
	local cur = Quest.CurQ[curi]

	local qId = cur.QId > 0 and cur.QId or cur.Title
	local qStatus = Nx:GetQuest (qId)

	if not qStatus or qStatus ~= "W" then		-- Pointless compare?

		Nx:SetQuest (qId, "W")
		Quest:PartyStartSend()
	end
end

--------
-- Clear completed quests

function Nx.Quest.Watch:ClearCompleted (qIdMatch)

	local Quest = Nx.Quest

	self:Update()	-- Get list in sync with quests if added or removed

	local list = self.List

	for ln = 1, list:ItemGetNum() do

		local i = list:ItemGetData (ln)
		if i then

			local qIndex = bit.band (i, 0xff)
			local qId = bit.rshift (i, 16)

			if qId > 0 and (not qIdMatch or qIdMatch == qId) then

				local _, cur = Quest:FindCur (qId)
				if cur then

					local qComplete = cur.CompleteMerge	-- Remember for objectives
					local qObj = bit.band (bit.rshift (i, 8), 0xff)

--					Nx.prt ("Data #%d Id %d Obj %d C=%s", qIndex, qId, qObj, tostring (cur.CompleteMerge))

					local tbits = Quest.Tracking[qId] or 0

					if tbits > 0 then

						local objmask = bit.lshift (1, qObj)

						if qObj == 0 then
							if qComplete then

								local qStatus, qTime = Nx:GetQuest (qId)

								if qStatus ~= "C" then
--									Nx.prt ("track on")
									-- Turn on

									if Nx.Quest:IsTargeted (qId) then
										Quest.Tracking[qId] = bit.bor (tbits, objmask)
										Quest:TrackOnMap (qId, 0, qIndex > 0, true)
									end
								end
							end

						else
							local desc
							local done = qComplete
							local num = cur.LBCnt

							if qObj <= num then
								desc = cur[qObj]
								done = cur[qObj + 300]
							end

							if done then

								local on = bit.band (tbits, objmask)

								if on > 0 then
									-- Turn off
									Quest.Tracking[qId] = bit.band (tbits, bit.bnot (objmask))
									Quest:TrackOnMap (qId, qObj, qIndex > 0)
								end
							end
						end
					end
				end
			end
		end
	end
end

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------

function Nx.Quest:CreateGiverIconMenu (mainMenu, frm)

	local completedMenu = Nx.Menu:Create (frm)
	self.GiverIconMenuCompleted = completedMenu
	self.GiverIconMenuICompleted = mainMenu:AddSubMenu (completedMenu, "Quest Completion...")

	self.GiverIconMenuICompletedT = {}

	for n = 1, 29 do

		local function func (self, item)

			local s = item:GetChecked() and "C" or "c"
			Nx:SetQuest (item.UData, s, time())

			if item:GetChecked() then
				self:CalcPreviousDone (item.UData)
			end

			self:UpdateGiverIconMenu()
			self.GiverIconMenuCompleted:Update()

			local map = Nx.Map:GetMap (1)
			map.Guide:UpdateMapIcons()
		end

		self.GiverIconMenuICompletedT[n] = completedMenu:AddItem (0, "?", func, self)
		self.GiverIconMenuICompletedT[n]:SetChecked()
	end

	--

	local infoMenu = Nx.Menu:Create (frm)
	self.GiverIconMenuInfo = infoMenu
	self.GiverIconMenuIInfo = mainMenu:AddSubMenu (infoMenu, "Quest Info (shift click - goto)...")

	self.GiverIconMenuIInfoT = {}

	for n = 1, 29 do

		local function func (self, item)
--			Nx.prt ("%s", item.Text)

			if not IsShiftKeyDown() then
				local link = self:CreateLink (item.UData, -1, "x")
				SetItemRef (link)
			else

				self:Goto (item.UData)
			end
		end

		self.GiverIconMenuIInfoT[n] = infoMenu:AddItem (0, "?", func, self)
	end
end

function Nx.Quest:OpenGiverIconMenu (icon, typ)

	self.GiverIconMenuICompleted:Show (false)
	self.GiverIconMenuIInfo:Show (false)

	if typ ~= 3000 then
		return
	end

	self.GiverIconMenuCompleted:Show (false)
	self.GiverIconMenuInfo:Show (false)

	if icon.UDataQuestGiverD then
		self.GiverIconMenuICompleted:Show()
		self.GiverIconMenuIInfo:Show()

		self.GiverIconMenuCompletedD = icon.UDataQuestGiverD

		self:UpdateGiverIconMenu()
	end
end

function Nx.Quest:UpdateGiverIconMenu()

	local qdata = self.GiverIconMenuCompletedD

	local qIds = self.QIds
	local curI = 1

	for n = 1, #qdata, 4 do
		local qId = tonumber (strsub (qdata, n, n + 3), 16)

		local quest = self.IdToQuest[qId]
		local qname, _, lvl, minlvl = self:Unpack (quest[1])

		local col = ""

		local status, qTime = Nx:GetQuest (qId)
		if status == "C" then
			col = "|cff808080"
		else
			if qIds[qId] then
				col = "|cffa0f0a0"
			end
		end

		local s = format ("%s%d %s", col, lvl, qname)

--		Nx.prt ("Menu %s", s)

		local menuI = self.GiverIconMenuICompletedT[curI]
		if not menuI then
			break
		end

		menuI:Show()
		menuI:SetText (s)
		menuI.UData = qId
		menuI:SetChecked (status == "C")

		local menuI = self.GiverIconMenuIInfoT[curI]

		menuI:Show()
		menuI:SetText (s)
		menuI.UData = qId

		curI = curI + 1
	end
end

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------

--------
-- Track quest on map

function Nx.Quest:CalcAutoTrack (cur)

	local Nx = Nx
	local Quest = Nx.Quest
	local curq = Quest.CurQ
	local qopts = Nx:GetQuestOpts()

	Quest.Tracking = {}
	local closest = false
	local dist = 99999999

	if cur.Q then

--		Quest.Tracking[cur.QId] = cur.TrackMask

		local closeI = cur.CloseObjI
		if closeI and closeI >= 0 then

			Quest.Tracking[cur.QId] = cur.TrackMask			-- bit.lshift (1, closeI)
			Quest:TrackOnMap (cur.QId, closeI, cur.QI > 0 or cur.Party, true, true)
		end

		for objn = 1, 15 do

			local obj = cur.Q[objn + 3]
			if not obj then
				break
			end

			local obit = bit.lshift (1, objn)
			if bit.band (cur.TrackMask, obit) > 0 then

				if Quest:GetObjectiveType (obj) == 1 then

					local d = cur["OD"..objn]

					if d and d < dist then
						dist = d
						closest = cur
--						Quest.ClosestSpanI = objn
					end
				end
			end
		end
	end

--	Quest.ClosestSpanCur = closest
end

--------
-- Is targeted already?

function Nx.Quest:IsTargeted (qId, qObj, x1, y1, x2, y2)

	local typ, tid = Nx.Map:GetTargetInfo()
	if typ == "Q" then

		local tqid = floor (tid / 100)
		if tqid == qId then		-- Same as us?

			if x1 then
				local tx1, ty1, tx2, ty2 = Nx.Map:GetTargetPos()
				if x1 ~= tx1 or y1 ~= ty1 or x2 ~= tx2 or y2 ~= ty2 then
					return
				end
			end

			if not qObj then
				return true
			end

			if tid % 100 == qObj then
				return true
			end
		end
	end
end

--------
-- Track quest on map

function Nx.Quest:TrackOnMap (qId, qObj, useEnd, target, skipSame)

	local Quest = Nx.Quest
	local Map = Nx.Map
	local BlizIndex = nil
	local quest = Quest.IdToQuest[qId]

--[[	if self.GOpts["QSync"] then
		local i = 1
		while GetQuestLogTitle(i) do
			local _, _, _, _, _, _, _, _, questID = GetQuestLogTitle(i)
			if questID == qId then
				BlizIndex = i
			else
				if (IsQuestWatched(i)) then
					RemoveQuestWatch(i)
				end
			end
		i = i + 1
		end
	end
]]--
	if quest then

		local tbits = Quest.Tracking[qId] or 0
--[[
		if tbits == 0 then	-- Nothing tracked?

			local typ, tid = Map:GetTargetInfo()
			if typ == "Q" then

				local tqid = floor (tid / 100)
				if tqid == qId then		-- Same as us?
					self.Map:ClearTargets()
				end
			end
			return
		end
--]]
		local track = bit.band (tbits, bit.lshift (1, qObj))

		local questObj
		local name, zone, loc

		if qObj == 0 then
			questObj = useEnd and quest[3] or quest[2]
			name, zone, loc = Quest:UnpackSE (questObj)
		else
			questObj = quest[qObj + 3]
			name, zone, loc = Quest:UnpackObjective (questObj)
		end

--		Nx.prt ("TrackOnMap %s %s %s %s %s", qId, qObj, track, name, zone)

		if track > 0 and zone then
--[[			if self.GOpts["QSync"] then
				if BlizIndex then
					if not (IsQuestWatched(BlizIndex)) then
						AddQuestWatch(BlizIndex)
					end
				end
			end
]]--
	local QMap = NxMap1.NxMap
	if not InCombatLockdown() then
		local cur = self.QIds[qId]
		if cur then
			if not cur.Complete then
				QMap.QuestWin:DrawNone();
				if Nx.CharOpts["MapShowQuestBlobs"] then
					QMap.QuestWin:DrawBlob(qId,true)
					QMap:ClipZoneFrm( QMap.Cont, QMap.Zone, QMap.QuestWin, 1 )
					QMap.QuestWin:SetFrameLevel(QMap.Level)
					QMap.QuestWin:SetFillAlpha(255 * QMap.QuestAlpha)
					QMap.QuestWin:SetBorderAlpha( 255 * QMap.QuestAlpha )
					QMap.QuestWin:Show()
				else
					QMap.QuestWin:Hide()
				end
			end
		end
	end

			local mId = Map.NxzoneToMapId[zone]
			if mId then

				if target then

					local x1, y1, x2, y2

					if qObj > 0 then

						local map = Map:GetMap (1)
						local px = map.PlyrX
						local py = map.PlyrY

						-- FIX!!!!!!!!!!!!

--						x1, y1, x2, y2 = Quest:GetClosestObjectiveRect (questObj, mId, px, py)
						x1, y1 = Quest:GetClosestObjectivePos (questObj, loc, mId, px, py)
						x2 = x1
						y2 = y1
					else

						x1, y1, x2, y2 = Quest:GetObjectiveRect (questObj, loc)
						x1, y1 = Map:GetWorldPos (mId, x1, y1)
						x2, y2 = Map:GetWorldPos (mId, x2, y2)
					end

					local cur = self.QIds[qId]
--					local _, cur = self:FindCur (qId)
					if cur then
						if qObj > 0 then
							name = cur[qObj] or name
--							Nx.prt ("TrackOnMap name %s", name)
						end

						if cur.Complete then
							name = name .. " |cff80ff80(Complete)"
						end
					end

					if skipSame then
						if self:IsTargeted (qId, qObj, x1, y1, x2, y2) then

							Map:SetTargetName (name)
							return
						end
					end

					self.Map:SetTarget ("Q", x1, y1, x2, y2, false, qId * 100 + qObj, name, false, mId)
--					Nx.prt ("TrackOnMap %s %s %s", qId, qObj, name)

					self.Map.Guide:ClearAll()
				end

				self.Map:GotoPlayer()

			else
				Nx.Quest:MsgNotInDB ("Z")
--				Nx.prt ("quest zone %s", zone)
			end

		else	-- Clear tracking

			local typ, tid = Map:GetTargetInfo()
			if typ == "Q" then

				local tqid = floor (tid / 100)
				if tqid == qId then		-- Same quest as us?

					if tbits == 0 or (tid == qId * 100 + qObj) then
						if self.GOpts["QSync"] then
							RemoveQuestWatch(BlizIndex)
						end
						self.Map:ClearTargets()
						if not InCombatLockdown() then
							local QMap = NxMap1.NxMap
							QMap.QuestWin:DrawNone();
							QMap.QuestWin:Hide()
						end
					end
				end
			end
		end
	end
end

--------
-- Unpack quest info
-- Format: (b) is byte
--  name len (b), name str, side (b), level (b), min lvl (b), next id (b3), category (b)

function Nx.Quest:Unpack (info)

	local strbyte = strbyte
	local i = strbyte (info, 1) - 35 + 1
	local name = strsub (info, 2, i)
	local side, lvl, minlvl, n1, n2, n3 = strbyte (info, i + 1, i + 6)
	local nextId = (n1 - 35) * 48841 + (n2 - 35) * 221 + n3 - 35
--	if nextId > 0 then
--		nextId = (nextId + 3) / 2 - 7
--	end

	return name, side - 35, lvl - 35, minlvl - 35, nextId
end

--------
-- Unpack quest name

function Nx.Quest:UnpackName (info)

	local i = strbyte (info, 1) - 35 + 1
	return strsub (info, 2, i)
end

--------
-- Unpack quest next id

function Nx.Quest:UnpackNext (info)

	local sb = strbyte
	local i = sb (info, 1) - 35 + 1
	return (sb (info, i + 4) - 35) * 48841 + (sb (info, i + 5) - 35) * 221 + sb (info, i + 6) - 35
end

--------
-- Unpack quest category

function Nx.Quest:UnpackCategory (info)

	local i = strbyte (info, 1) - 35 + 1 + 7
	return strbyte (info, i) - 35
end

--------
-- Unpack start/end
-- Format: name index (byte x2), zone (byte), location data (may start with space)
-- Example: 00,1, xxyy
-- Example: 00,1,xywh

function Nx.Quest:UnpackSE (obj)

	if not obj then
		return
	end

	local i = (strbyte (obj) - 35) * 221 + (strbyte (obj, 2) - 35)
	local name = self.QuestStartEnd[i]

	if not name then
--		Nx.prt ("UnpackSE err %s (%s)", i, obj)
		name = "?"
	end

	if #obj == 2 then
		return name
	end

	local zone = strbyte (obj, 3) - 35

	return name, zone, 4
end

--------
-- Unpack objective or start/end
-- Format: name length (byte), name string, zone (byte), location data (may start with space)
-- Example: 3,the,1, xxyy
-- Example: 3,end,1,xywh

function Nx.Quest:UnpackObjective (obj)

	if not obj then
		return
	end

	local i = strbyte (obj) - 35 + 1
	local desc = strsub (obj, 2, i)

	if #obj == i then
		return desc
	end

	local zone = strbyte (obj, i + 1) - 35

	return desc, zone, i + 2
end

--------
-- Get type of objective (not start/end)

function Nx.Quest:GetObjectiveType (obj)

	local loc = strbyte (obj) - 35 + 3
	local typ = strbyte (obj, loc) or 0			-- Can be nil somehow

	if typ <= 33 then  -- Points
		return 0
	end

	return 1		-- Spans
end

--------
-- Get centered position of start/end

function Nx.Quest:GetSEPos (str)

	local name, zone, loc = self:UnpackSE (str)

	if zone then
		return name, zone, self:GetPosLoc (str, loc)		-- x, y
	end
end

--------
-- Get centered position of objective

function Nx.Quest:GetObjectivePos (str)

	local name, zone, loc = self:UnpackObjective (str)

	if zone then
		return name, zone, self:GetPosLoc (str, loc)		-- x, y
	end
end

--------
-- Get centered position from location string

function Nx.Quest:GetPosLoc (str, loc)

	local cnt
	local ox = 0
	local oy = 0

	local typ = strbyte (str, loc)

	if typ == 32 then  -- Point

		cnt = floor ((#str - loc) / 4)

		local x, y

		for locN = loc + 1, loc + cnt * 4, 4 do

--			local loc1 = strsub (str, locN, locN + 3)
--			assert (loc1 ~= "")

			x, y = self:UnpackLocPtOff (str, locN)
			ox = ox + x
			oy = oy + y
		end

	elseif typ == 33 then  -- Relative point (for Icecrown airships)

		cnt = 1
		ox, oy = self:UnpackLocPtRelative (str, loc + 1)

	else -- Multiple locations

		loc = loc - 1
		local loopCnt = floor ((#str - loc) / 4)
		cnt = 0

		for locN = loc + 1, loc + loopCnt * 4, 4 do

			local loc1 = strsub (str, locN, locN + 3)
--			assert (loc1 ~= "")

--			prtVar ("Loc1", loc1)

			local x, y, w, h = self:UnpackLocRect (loc1)

			w = w / 1002 * 100
			h = h / 668 * 100

			local area = w * h
			cnt = cnt + area
			ox = ox + (x + w * .5) * area
			oy = oy + (y + h * .5) * area

--			Nx.prt ("#%f %f %f %f %f (%f)", cnt, x, y, w, h, area)
		end
	end

	ox = ox / cnt
	oy = oy / cnt

	return ox, oy
end

--------

function Nx.Quest:UnpackLocPtRelative (str, loc)

	local cnt
	local ox, oy = Nx.Quest:UnpackLocPtOff (str, loc)

	ox = ox - 50
	oy = oy - 50

	for n = 1, GetNumBattlefieldVehicles() do

		local x, y, unitName, possessed, typ, dir, player = GetBattlefieldVehicleInfo (n)
		if x and not player then

			if typ == Nx.AirshipType then
				cnt = 1

				dir = dir / PI * 180
				oy = oy / 1.5
				ox, oy = ox * cos (dir) + oy * sin (dir), (ox * -sin (dir) + oy * cos (dir)) * 1.5
				ox = x * 100 + ox
				oy = y * 100 + oy

--				Nx.prt ("%s Airship %s %s %s", name, typ, ox, oy)
				break
			end
		end
	end

	if not cnt then
		ox = ox + 62
		oy = oy + 42
	end

	return ox, oy
end

--------


--[[
function Nx.Quest:Route (watched)

	if not IsControlKeyDown() then
		return
	end

	local Nx = Nx
	local Quest = Nx.Quest
	local qopts = Nx:GetQuestOpts()
	local Map = Nx.Map
	local map = Map:GetMap (1)
	local px = map.PlyrX
	local py = map.PlyrY
	local playerLevel = UnitLevel ("player")

	local curq = self.CurQ
	if not curq then	-- Bad stuff?
		return
	end

	local points = {}

	for _, distn in ipairs (watched) do

		local n = bit.band (distn, 0xff)

		local cur = curq[n]
		local qi = cur.QI
		local qId = cur.QId

		local id = qId > 0 and qId or cur.Title
		local qStatus = Nx:GetQuest (id)
		local qWatched = (qStatus == "W")
		local quest = cur.Q

		if quest and qWatched then

			local cnt = (cur.Complete or cur.LBCnt == 0) and 0 or 99

			for qObj = 0, cnt do

				local questObj

				if qObj == 0 then
					questObj = qi > 0 and quest[3] or quest[2]	-- Start if goto or no end?
				else
					questObj = quest[qObj + 3]
				end

				if not questObj then
					break
				end

				if bit.band (cur.TrackMask, bit.lshift (1, qObj)) > 0 then

					local wx = cur["OX"..qObj]
					if wx then
						local pt = {}
						tinsert (points, pt)

						local wy = cur["OY"..qObj]

						local x, y = map:GetZonePos (map.MapId, wx, wy)
						pt.X = x
						pt.Y = y
					end
				end
			end
		end
	end

	map:RouteQuests (points)
end
--]]

--------
-- Calc watch distance

function Nx.Quest:CalcDistances (n1, n2)

	local Nx = Nx
	local Quest = Nx.Quest
	local qopts = Nx:GetQuestOpts()
	local Map = Nx.Map
	local map = Map:GetMap (1)
	local px = map.PlyrX
	local py = map.PlyrY
	local playerLevel = UnitLevel ("player")

	local curq = self.CurQ
	if not curq then	-- Bad stuff?
		return
	end

	for n = n1, n2 do

		local cur = curq[n]

		if not cur then
			break
		end

		local qi = cur.QI
		local qId = cur.QId

		local id = qId > 0 and qId or cur.Title
		local qStatus = Nx:GetQuest (id)
		local qWatched = (qStatus == "W")
		local quest = cur.Q

		cur.Priority = 1
		cur.Distance = 999999999
		cur.CloseObjI = -1

		if cur.Complete and cur.IsAutoComplete then
			cur.Distance = 0
		end

--		if quest and (qWatched or Nx.Free) then
		if quest then

			local cnt = (cur.CompleteMerge or cur.LBCnt == 0) and 0 or 99

			for qObj = 0, cnt do

				local questObj

				if qObj == 0 then
					questObj = (qi > 0 or cur.Party) and quest[3] or quest[2]	-- Start if goto or no end?
				else
					questObj = quest[qObj + 3]
				end

				if not questObj then
					break
				end

				if bit.band (cur.TrackMask, bit.lshift (1, qObj)) > 0 then

					local _, zone, loc

					if qObj == 0 then
						_, zone, loc = self:UnpackSE (questObj)
					else
						_, zone, loc = self:UnpackObjective (questObj)
					end

					if zone then

						local mId = Map.NxzoneToMapId[zone]
						if mId then

							local x, y = self:GetClosestObjectivePos (questObj, loc, mId, px, py)
							local dist = ((x - px) ^ 2 + (y - py) ^ 2) ^ .5

							if dist < cur.Distance then
								cur.CloseObjI = qObj
								cur.Distance = dist
							end

							cur["OX"..qObj] = x
							cur["OY"..qObj] = y
							cur["OD"..qObj] = dist
						end
					end
				end
			end

--PAIDS!
			local pri = 0

			-- Player lvl 30. PriLevel = 20
			-- Q1  100 Lvl 30: 0 ldif = 0
			-- Q2  400 Lvl 20: 10 ldif = 200, .1, 90% = 360
			-- Q3 2000 Lvl 25: 5 ldif = 100, .05, 95% = 1900

			-- Player lvl 30. PriLevel = 200
			-- Q1  100 Lvl 30: 0 ldif = 0
			-- Q2  400 Lvl 20: 10 ldif = 2000, .99, 1% = 4
			-- Q3 2000 Lvl 25: 5 ldif = 1000, .5, 50% = 1000

			-- Formula: cur.Distance * priDist * cur.Priority * 10 + cur.Priority * 100

			if cur.CompleteMerge then
				pri = qopts.NXWPriComplete * 8	-- +-1600

			else
				-- 20 default. 10 lvls max diff * 200 = +-2000
				local l = min (playerLevel - cur.Level, 10)
				l = max (l, -10)
				pri = l * qopts.NXWPriLevel
			end

			cur.Priority = 1 - pri / 2010

			cur.InZone = Quest:CheckShow (map.RMapId, qId)
--PAIDE!
		end
	end
end

--------
-- Get closest position of objective or start/end

function Nx.Quest:GetClosestObjectivePos (str, loc, mapId, px, py)

	local Map = Nx.Map

	if strbyte (str, loc) <= 33 then  -- Point

		local x1, y1, x2, y2 = self:GetObjectiveRect (str, loc)
		x1, y1 = Map:GetWorldPos (mapId, (x1 + x2) / 2, (y1 + y2) / 2)
		return x1, y1

	else -- Multiple locations

		local closeDist = 999999999
		local closeX, closeY

		loc = loc - 1
		local loopCnt = floor ((#str - loc) / 4)
		cnt = 0

		for locN = loc + 1, loc + loopCnt * 4, 4 do

			local x, y

			local loc1 = strsub (str, locN, locN + 3)
			assert (loc1 ~= "")

			local x, y, w, h = self:UnpackLocRect (loc1)
			w = w / 1002 * 100
			h = h / 668 * 100

			local wx1, wy1 = Map:GetWorldPos (mapId, x, y)
			local wx2, wy2 = Map:GetWorldPos (mapId, x + w, y + h)

			x = wx1		-- Top left
			y = wy1

			if px >= wx1 and px <= wx2 then
				if py >= wy1 and py <= wy2 then		-- Within span?
					return px, py
				end
				x = px

			elseif px >= wx2 then	-- Right of span?
				x = wx2
			end

			if py >= wy1 then		-- Y within span?
				y = py
			end
			if py >= wy2 then	-- Below span?
				y = wy2
			end

			local dist = (x - px) ^ 2 + (y - py) ^ 2
			if dist < closeDist then
				closeDist = dist
				closeX = x
				closeY = y
			end

--			Nx.prt ("#%f %f %f %f %f (%f)", cnt, x, y, w, h, area)
		end

		return closeX, closeY
	end
end

--------
-- Get closest rectangle of objective or start/end

--[[

function Nx.Quest:GetClosestObjectiveRect (str, mapId, px, py)

	local Map = Nx.Map
	local Quest = Nx.Quest

	local name, zone, loc = Quest:UnpackObjective (str)

	if not zone then
		return
	end

	local close
	local closeX, closeY
	local closeDist = 999999999

	if strbyte (str, loc) <= 33 then  -- Point

		local x1, y1, x2, y2 = self:GetObjectiveRect (str, loc)
		x1, y1 = Map:GetWorldPos (mapId, x1, y1)
		x2, y2 = Map:GetWorldPos (mapId, x2, y2)
		return x1, y1, x2, y2

	else -- Multiple locations

		loc = loc - 1
		local loopCnt = floor ((#str - loc) / 4)
		cnt = 0

		for locN = loc + 1, loc + loopCnt * 4, 4 do

			local loc1 = strsub (str, locN, locN + 3)
			assert (loc1 ~= "")

			local x, y, w, h = Quest:UnpackLocRect (loc1)
			w = w / 1002 * 100
			h = h / 668 * 100

			local wx1, wy1 = Map:GetWorldPos (mapId, x, y)
			local wx2, wy2 = Map:GetWorldPos (mapId, x + w, y + h)

			-- Inside box?
--			if px >= wx1 and px <= wx2 and py >= wy1 and py <= wy2 then
--				return px - 40, py - 40, px + 40, py + 40
--			end

			x = wx1		-- Top left
			y = wy1

			if px >= wx1 and px <= wx2 then
				if py >= wy1 and py <= wy2 then		-- Within span?
					closeDist = 0
					close = loc1
--					return px, py
				end
				x = px

			elseif px >= wx2 then	-- Right of span?
				x = wx2
			end

			if py >= wy1 then		-- Y within span?
				y = py
			end
			if py >= wy2 then	-- Below span?
				y = wy2
			end

			local dist = (x - px) ^ 2 + (y - py) ^ 2
			if dist < closeDist then
				closeDist = dist
				close = loc1
			end

--			Nx.prt ("#%f %f %f %f %f (%f)", cnt, x, y, w, h, area)
		end

		local x, y, w, h = Quest:UnpackLocRect (close)
		w = w / 1002 * 100
		h = h / 668 * 100
		local x1, y1 = Nx.Map:GetWorldPos (mapId, x - 3, y - 3)
		local x2, y2 = Nx.Map:GetWorldPos (mapId, x + w + 3, y + h + 3)

		return x1, y1, x2, y2
	end
end

--]]

--------
-- Get size of objective or start/end

function Nx.Quest:GetObjectiveRect (str, loc)

	local Quest = Nx.Quest

	local x1 = 100
	local y1 = 100
	local x2 = 0
	local y2 = 0
	local cnt

	if strbyte (str, loc) == 32 then  -- Point

		cnt = floor ((#str - loc) / 4)
		local x, y

		for locN = loc + 1, loc + cnt * 4, 4 do

--			local loc1 = strsub (str, locN, locN + 3)
--			assert (loc1 ~= "")

			x, y = Quest:UnpackLocPtOff (str, locN)
			x1 = min (x1, x)
			y1 = min (y1, y)
			x2 = max (x2, x)
			y2 = max (y2, y)
		end

	elseif strbyte (str, loc) == 33 then  -- Point

		x1, y1 = Quest:UnpackLocPtRelative (str, loc + 1)
		x2, y2 = x1, y1

	else -- Multiple locations

		loc = loc - 1

		cnt = floor ((#str - loc) / 4)

		for locN = loc + 1, loc + cnt * 4, 4 do

			local loc1 = strsub (str, locN, locN + 3)
--			assert (loc1 ~= "")

			local x, y, w, h = Quest:UnpackLocRect (loc1)

			x1 = min (x1, x)
			y1 = min (y1, y)
			x2 = max (x2, x + w / 1002 * 100)
			y2 = max (y2, y + h / 668 * 100)

--			Nx.prt ("Rect %f %f %f %f", x, y, w, h)
		end
	end

--	Nx.prt ("RectMinMax %f %f %f %f", x1, y1, x2, y2)

	return x1, y1, x2, y2
end

--------
-- Unpack location data " xywh" or "xxyy"
-- (string, offset)

function Nx.Quest:UnpackLoc (locStr, off)

	local isPt = strbyte (locStr, off) <= 33		-- Space or !

	if isPt then

		local x1, x2, y1, y2 = strbyte (locStr, 1 + off, 4 + off)
		return	((x1 - 35) * 221 + (x2 - 35)) / 100,
					((y1 - 35) * 221 + (y2 - 35)) / 100
	end

	local x, y, w, h = strbyte (locStr, 0 + off, 3 + off)

	return	(x - 35) * .5,		-- * 100 / 200, Optimised
				(y - 35) * .5		-- * 100 / 200

--	return	(x - 35) * .5 + (w - 35) * 2.505,	-- * 100 / 200, * 1002 / 200, Optimised (center)
--				(y - 35) * .5 + (h - 35) * 1.67		-- * 100 / 200, * 668 / 200
end

--------
-- Unpack location data "xywh"
-- (string)

function Nx.Quest:UnpackLocRect (locStr)

	local x, y, w, h = strbyte (locStr, 1, 4)

	return	(x - 35) * .5,		-- * 100 / 200	Optimised
				(y - 35) * .5,		-- * 100 / 200
				(w - 35) * 5.01,	-- * 1002 / 200,
				(h - 35) * 3.34	-- * 668 / 200
end

--------
-- Unpack location data point "xxyy"
-- (string)

function Nx.Quest:UnpackLocPt (locStr)

	local x1, x2, y1, y2 = strbyte (locStr, 1, 4)
	return	((x1 - 35) * 221 + (x2 - 35)) / 100,
				((y1 - 35) * 221 + (y2 - 35)) / 100
end

--------
-- Unpack location data point "xxyy"
-- (string)

function Nx.Quest:UnpackLocPtOff (locStr, off)

	local x1, x2, y1, y2 = strbyte (locStr, off, 3 + off)
	return	((x1 - 35) * 221 + (x2 - 35)) / 100,
				((y1 - 35) * 221 + (y2 - 35)) / 100
end

--------
-- Calculate first level 24 bit quest hash

--[[
function Nx.Quest:Hash (title, level)

	local str = title..level
	local h1 = 0
	local h2 = 0
	local h3 = 0
	local b1
	local b2
	local b3

	local strLen = #str
	local len = floor (strLen / 3) * 3

--	Nx.prt (format ("Hash %d %d"))

	for n = 1, len, 3 do

		b1, b2, b3 = strbyte (str, n, n + 2)
		h1 = h1 + b1
		h2 = h2 + b2
		h3 = h3 + b3
	end

	if strLen - len == 1 then
		h1 = h1 + strbyte (str, strLen)

	elseif strLen - len == 2 then
		h1 = h1 + strbyte (str, strLen - 1)
		h2 = h2 + strbyte (str, strLen)
	end

	return bit.band (h1, 0xff) + bit.band (h2, 0xff) * 0x100 + bit.band (h3, 0xff) * 0x10000
end
--]]

--------
-- Find quest in quests data from Blizzard title, level, description and objective

--[[
function Nx.Quest:Find (title, level, desc, obj)

	local hash = self:Hash (title, level)

	local quest = Nx.Quests[hash]

	if quest then

		local hashPat = self:UHash (quest[1])
		if #hashPat > 0 then

			local found

			for n = 1, #hashPat, 4 do

				local mode = strbyte (hashPat, n, n)
				local off = tonumber (strsub (hashPat, n + 1, n + 2), 16) + 1
				local match = strsub (hashPat, n + 3, n + 3)

--				Nx.prt ("QFind #%d %d %d %s", n, mode, off, match)

				if mode == 48 then

					found = strsub (obj, off, off) == match

				elseif mode == 49 then

					found = strsub (obj, -off, -off) == match

				elseif mode == 50 then

					found = strsub (desc, off, off) == match

				elseif mode == 51 then

					found = strsub (desc, -off, -off) == match

				elseif mode >= 97 then

					local cnt = mode - 96
					local match = ","

					if mode >= 103 then
						cnt = mode - 102
						match = "."
					end

					local i = 1
					for findN = 1, cnt do
						i = strfind (desc, match, i)
						if not i then
							break
						end
						i = i + 1
					end

					if i then
						off = off + i - 1
						found = strsub (desc, off, off) == match
					end

				else
					Nx.prt ("QFind bad mode %d", mode)

				end

				if found then
					return Nx.Quests[hash + (n - 1) / 4 * 0x1000000]
				end

			end

			quest = nil
		end
	end

	if not quest then

		if level > 0 then	-- Only recurse once
			-- Quest level my be -1, so Jamie exports as 0
			return self:Find (title, 0, desc, obj)
		end

		if Nx.Quest.Debug then
			Nx.prt ("QFind Failed to find %s %d", title, level)
		end
	end

	return quest
end
--]]

-------------------------------------------------------------------------------
-- Com send / rcv

function Nx.Quest:BuildComSend()

	local _
	local cur = self.Watch.ClosestCur
	local obj = 0
	local flgs = 2			-- Not a targeted quest flag

	if self.QLastChanged then	-- Quest change? Com nils this once send to zone

		cur = self.QLastChanged
--		Nx.prt ("Q Send Change %s", cur.Title)

	else
		local typ, tid = Nx.Map:GetTargetInfo()
		if typ == "Q" then

			local qid = floor (tid / 100)
			_, cur = self:FindCur (qid)
			obj = tid % 100
			flgs = 0
		end
	end

	if cur then

		if cur.Complete then
			flgs = flgs + 1
		end

		local str = format ("%04x%c%c%c", cur.QId, obj+35, flgs+35, cur.LBCnt+35)

		for n = 1, cur.LBCnt do

			local s1, _, cnt, total = strfind (cur[n], ": (%d+)/(%d+)")
			if s1 then
				total = tonumber (total)
				if total > 50 then
					cnt = cnt / total * 60
					total = 60
				end
				cnt = cnt + 2
			else
				cnt = 0
				if cur[n + 100] then		-- Done?
					cnt = 1
				end
				total = 0
			end

			str = str .. format ("%c%c", cnt + 35, total + 35)
		end

--		Nx.prt ("QSend %s", str)

		return str, 4
	end

	return "", 0
end

function Nx.Quest:DecodeComRcv (info, msg)

	--	msg = "0000###"

	if #msg < 7 then	-- Too short?
		return	-- error, so nil length
	end

	local lbcnt = strbyte (msg, 7) - 35

	if not self.Enabled then
		return 7 + lbcnt * 2		-- Message length
	end

	local qId = tonumber (strsub (msg, 1, 4), 16) or 0
	local quest = self.IdToQuest[qId]

	if not quest then						-- Unknown quest?
		info.QStr = format ("\nQuest %s", qId)
		return
	end

	local name, side, lvl = self:Unpack (quest[1])

	local obji = strbyte (msg, 5) - 35
	local flgs = strbyte (msg, 6) - 35

	local targetStr = ""

	if bit.band (flgs, 2) == 0 then
		targetStr = "*"
	end

	local str = format ("\n|r%s%d |cffcfcf0f%s", targetStr, lvl, name)

	if bit.band (flgs, 1) > 0 then
		str = str .. " (Complete)"
	end

	if #msg >= 7 + lbcnt * 2 then

		for n = 1, lbcnt do
			local off = (n - 1) * 2
			local cnt = strbyte (msg, 8 + off) - 35
			local total = strbyte (msg, 9 + off) - 35

			local obj = quest[n + 3]
			if obj then
				local oname = self:UnpackObjective (obj)

				if obji == n then
					oname = "|cffcfcfff" .. oname
				else
					oname = "|cffafafaf" .. oname
				end

				if cnt == 0 then
					str = str .. format ("\n  %s", oname)
				elseif cnt == 1 then
					str = str .. format ("\n  %s (done)", oname)
				else
					str = str .. format ("\n  %s %d/%d", oname, cnt - 2, total)
				end
			end
		end

--	else
--		Nx.prt ("DecodeComRcv error quest %s", qId)

	end

	info.QStr = str

	return 7 + lbcnt * 2		-- Message length
end

-------------------------------------------------------------------------------
-------------------------------------------------------------------------------
-- Party quests

function Nx.Quest.OnParty_members_changed()

	local self = Nx.Quest

--	Nx.prt ("OnParty_members_changed")


	self.Watch:ShowUpdate()


	local pq = self.PartyQ

	for name in pairs (pq) do

		local found

		for n = 1, GetNumSubgroupMembers() do

			local pname = UnitName ("party" .. n)
			if name == pname then
				found = true
				break
			end
		end

		if not found then
			pq[name] = nil
--			Nx.prt ("Old %s", name)

			Nx.Timer:Start ("QPartyUpdate", 1, self, self.PartyUpdateTimer)
		end
	end

	if IsInRaid() then	-- In raid?
		return
	end

	if GetNumSubgroupMembers() == 0 then	-- Left party?
		return
	end

	local doSend

	for n = 1, GetNumSubgroupMembers() do

		local unit = "party" .. n
		local name = UnitName (unit)

		if not pq[name] then
			doSend = true
			pq[name] = {}
--			Nx.prt ("New %s %s", unit, name)
		end
	end

	if doSend then
		self:PartyStartSend()
	end
end

--------
-- Handle party message

function Nx.Quest:OnPartyMsg (plName, msg)

	if not self.GOpts["QPartyShare"] then
		return
	end

	-- msg = "Qp1iiiifo111122223333"

--	Nx.prt ("OnPartyMsg %s: %s", plName, msg)

	local pq = self.PartyQ
	local pl = pq[plName]

	if pl then

		if strbyte (msg, 3) == 49 then	-- "1" clear?
			pl = {}
			pq[plName] = pl
		end

		local Quest = Nx.Quest
		local off = 4

		for n = 1, 99 do

			if #msg < off + 5 then	-- No more?
				break
			end

			local qId = tonumber (strsub (msg, off, off + 3), 16) or 0
			local flgs, oCnt = strbyte (msg, off + 4, off + 5)
			flgs = flgs - 35
			oCnt = oCnt - 35

			if #msg < off + 5 + oCnt * 4 then	-- Too short?
				break
			end

			local quest = self.IdToQuest[qId]
			if quest then

				local q = pl[qId] or {}
				pl[qId] = q

				q.Complete = bit.band (flgs, 1) == 1 and 1 or nil

--				Nx.prt ("%s: %s %x %s", plName, qId, flgs, oCnt)

				for i = 1, oCnt do

					local o = off + 6 + (i - 1) * 4
					local cnt = tonumber (strsub (msg, o, o + 1), 16) or 0
					local total = tonumber (strsub (msg, o + 2, o + 3), 16) or 0

					q[i] = cnt
					q[i + 100] = total
				end
			end

			off = off + 6 + oCnt * 4
		end
	end

	Nx.Timer:Start ("QPartyUpdate", .7, self, self.PartyUpdateTimer)
end

--------

function Nx.Quest:PartyUpdateTimer()

	self:RecordQuests()
	self.Watch:Update()
end

--------

function Nx.Quest:PartyStartSend()

	if IsInRaid() or GetNumSubgroupMembers() == 0 then
		return
	end

	if self.GOpts["QPartyShare"] then
		Nx.Timer:Start ("QSendParty", .5, self, self.PartyBuildSendData)
	end
end

function Nx.Quest:PartyBuildSendData()

	local data = {}

	self.PartySendData = data
	self.PartySendDataI = 1

	local sendStr = ""

	for n, cur in ipairs (self.CurQ) do

		local qId = cur.QId

		if not cur.Goto and Nx:GetQuest (qId) == "W" then

			local flgs = 0

			if cur.Complete then
				flgs = flgs + 1
			end

			local str = format ("%04x%c%c", qId, flgs + 35, cur.LBCnt + 35)

			for n = 1, cur.LBCnt do

				local _, _, cnt, total = strfind (cur[n], ": (%d+)/(%d+)")
				cnt = tonumber (cnt)
				total = tonumber (total)

				if cnt and total then
					if cnt > 200 then
						cnt = 200
					end
				else
					cnt = 0
					if cur[n + 100] then		-- Done?
						cnt = 1
					end
					total = 0
				end

				str = str .. format ("%02x%02x", cnt, total)
			end

			sendStr = sendStr .. str

			if #sendStr > 80 then
				tinsert (data, sendStr)
				sendStr = ""
			end
		end
	end

	if #sendStr > 0 or #data == 0 then
		tinsert (data, sendStr)
	end

	Nx.Timer:Start ("QSendParty", 0, self, self.PartySendTimer)

	return 0
end

function Nx.Quest:PartySendTimer()

	local qi = self.PartySendDataI
	local data = self.PartySendData[qi]

	if data then
		local s = qi == 1 and "1" or " "
		Nx.Com:Send ("p", "Qp" .. s .. data)
--		Nx.prt ("PQSend %s", data)
	end

	self.PartySendDataI = qi + 1

	if self.PartySendData[self.PartySendDataI] then
		return .15
	end
end

-------------------------------------------------------------------------------
-- EOF