8 local debug_build=false
9 local changes_lua = true
12 function SplitVersion(ver)
13 return string.gsub(ver, " on .*", ""), string.gsub(ver, ".* on ", "")
16 function AuthorizedVersion(ver)
17 qhv, wowv = SplitVersion(ver)
18 return qhv ~= "Development Version"
21 function PreWrath(ver)
22 qhv, wowv = SplitVersion(ver)
23 return wowv:sub(1,1) ~= '3'
26 for i, a in ipairs({...}) do
27 local mode, option = select(3, string.find(a, "^([%+%-]?)(.*)$"))
31 if option == "zip" then archive_zip = mode
32 elseif option == "7z" then archive_7z = mode
33 elseif option == "compile" then compile = mode
34 elseif option == "external" then external = mode
35 elseif option == "icons" then icons = mode
36 elseif option == "changes" then changes_lua = mode
37 elseif option == "debug" then debug_build = mode
38 elseif option == "condense" then condense = mode
40 print("Unknown option: "..option)
46 local cache_loader = loadfile("build-cache.lua")
52 if not cache.removed then cache.removed = {} end
53 if not cache.known then cache.known = {} end
54 if not cache.ignored then cache.ignored = {} end
55 if not cache.uid then cache.uid = {} end
58 for uid, data in pairs(cache.uid) do file2uid[data.file] = uid end
60 -- so upgrade.lua doesn't freak out
61 GetTime = function () end
63 QuestHelper_Loadtime = {}
65 loadfile("dump.lua")()
66 loadfile("fileutil.lua")()
67 QuestHelper = {IsWrath32 = function () return true end}
68 loadfile("../upgrade.lua")()
69 loadfile("compiler.lua")()
70 loadfile("pepperfish.lua")()
71 --loadfile("external.lua")()
73 profiler = newProfiler("time", 1000000)
76 QuestHelper_BuildZoneLookup()
79 local stream = io.open("../changes.txt", "r")
82 local function linkparse(a, b)
83 b = b:gsub("^%s*(.-)%s*$", "%1")
91 local function totext(text)
92 return text:gsub("%[%[http://www.mandible.net|(.*)%]%]", "%1")
93 :gsub("%[%s*([^%s%]]+)(.-)%]", linkparse)
94 :gsub("<code>(.-)</code>", "|cff40bbff%1|r")
95 :gsub("^%s*==%s*(.-)%s*==%s*$", "|cffffff00 %1|r\n")
96 :gsub("^%*%*%s", " • ")
105 line = stream:read("*l")
106 if not line then break end
107 if line:find("^%s*==.*==%s*$") then
112 text = text .. (count == 1 and "" or "\n") .. totext(line)
113 elseif not line:find("^%s*$") then
114 text = text .. totext(line)
120 stream = io.open("../changes.lua", "w")
122 stream:write(("QuestHelper_ChangeLog=%q\n"):format(text))
125 print("Error writing ../changes.lua")
128 print("Error opening ../changes.txt")
134 --print("Updating Astrolabe.")
135 --FileUtil.updateSVNRepo("http://svn.esamynn.org/astrolabe/branches/wotlk_data_temp", "../Astrolabe")
136 print("Updating ChatThrottleLib.")
137 FileUtil.updateSVNRepo("svn://svn.wowace.com/wow/chatthrottlelib/mainline/trunk", "../libs/ChatThrottleLib")
141 local function saveCache()
142 local stream = io.open("build-cache.lua", "w")
143 local buffer, prebuf = CreateBuffer(), CreateBuffer()
144 DumpVariable(buffer, prebuf, cache, "cache")
145 stream:write(DumpingComplete(buffer, prebuf))
149 local function QuestHelper_IsCorrupted(data)
150 if type(data.QuestHelper_Objectives) == "table" then for version, package in pairs(data.QuestHelper_Objectives) do
151 if AuthorizedVersion(version) and type(package) == "table" then for category, objectives in pairs(package) do
152 if type(objectives) == "table" and PreWrath(version) then for name, objective in pairs(objectives) do
153 if type(objective) == "table" and objective.pos and type(objective.pos) == "table" then for i, pos in pairs(objective.pos) do
154 if pos[1] and pos[1] >= 65 then
155 print("SOMETHING IS SERIOUSLY WRONG, found Wrath coordinates in non-Wrath files (version " .. version .. ", continent " .. pos[1] .. ")")
164 -- Download the latest copy of the translated item/npc names.
166 os.execute("wget http://smariot.hopto.org/wowdata.7z -N")
168 if FileUtil.fileExists("wowdata.7z") then
169 local hash = FileUtil.fileHash("wowdata.7z")
170 if cache.wowdata_hash ~= hash then
171 -- If the archive has changed, extract the lua file from the archive.
172 cache.wowdata_hash = hash
173 os.execute("7z e -y wowdata.7z wowdata.lua")
178 if FileUtil.fileExists("wowdata.lua") then
179 loadfile("wowdata.lua")()
182 local all_input_files, unknown_input_files = {}, {}
184 local since_last_save = 0
186 FileUtil.forEachFile("LocalInput", function (name)
187 if FileUtil.extension(name) == "lua" or
188 FileUtil.extension(name) == "bak" then
190 local hash = FileUtil.fileHash(name)
191 if cache.ignored[hash] then return end
193 if since_last_save == 100 then
194 print("Collecting garbage and saving . . .")
195 collectgarbage("collect")
199 since_last_save = since_last_save + 1
201 cache.ignored[hash] = true
203 local input = loadfile(name)
208 if not (data.QuestHelper_Locale and data.QuestHelper_Objectives) then
209 print("! "..name.." isn't a QuestHelper SavedVariables file.")
213 local upgradable_data = {}
214 setfenv(input, upgradable_data)
216 QuestHelper_UpgradeDatabase(upgradable_data)
217 if QuestHelper_IsPolluted(upgradable_data) then
218 print("!! "..name.." is polluted")
221 if QuestHelper_IsCorrupted(upgradable_data) then
222 print("!! "..name.." is corrupted")
225 if not upgradable_data.QuestHelper_UID then
226 print("!! "..name.." has no UID")
230 local tempname = os.tmpname()
231 local stream = io.open(tempname, "w")
233 print("Copying/Sorting "..name)
234 stream:write(ScanAndDumpVariable(data, nil, true) or "")
236 hash = FileUtil.fileHash(tempname)
238 local input_name = "Input/"..hash..".lua"
239 if not cache.removed[input_name] then
240 if not cache.known[input_name] then
241 FileUtil.copyFile(tempname, input_name)
242 unknown_input_files[input_name] = name
244 cache.known[input_name] = name
247 cache.removed[input_name] = name
250 print("!!! Can't get hash of "..tempname..", for "..name)
252 FileUtil.unlinkFile(tempname)
255 print("!!! "..name.." couldn't be executed.")
258 print("!!! "..name.." couldn't be loaded.")
263 collectgarbage("collect")
266 FileUtil.forEachFile("Input", function (name)
267 if cache.removed[name] then
268 print("!!! Obsolete: ", cache.removed[name].." ("..name..")")
271 if not cache.known[name] then
272 unknown_input_files[name] = unknown_input_files[name] or name
275 all_input_files[name] = cache.known[name] or unknown_input_files[name]
279 local function ProcessObjective(category, name, objective, result)
280 local istring = "obj."..category.."."..name
282 if category ~= "item" then
284 if objective.pos then for i, pos in pairs(objective.pos) do
288 result[istring..".seen"] = (result[istring..".seen"] or 0) + seen
291 if objective.vendor then
292 result[istring..".vend"] = (result[istring..".vend"] or 0) + #objective.vendor
295 if objective.drop then for monster, count in pairs(objective.drop) do
296 result[istring] = (result[istring] or 0) + count
300 local function ProcessQuest(faction, level, name, quest, result)
301 local qstring = "quest."..faction.."."..level.."."..name
302 result[qstring] = (result[qstring] or 0)+((quest.finish or quest.pos) and 1 or 0)
304 if quest.item then for item_name, data in pairs(quest.item) do
305 ProcessObjective("item", item_name, data, result)
308 if quest.alt then for _, quest2 in pairs(quest.alt) do
309 ProcessQuest(faction, level, name, quest2, result)
313 local function LoadFile(file)
314 local data = loadfile(file)
318 if not pcall(setfenv(data, loaded)) then
319 print("!!!!! oh god something is wrong "..file)
325 QuestHelper_UpgradeDatabase(loaded)
327 if loaded.QuestHelper_UID then
328 result.uid = loaded.QuestHelper_UID
329 result.time = loaded.QuestHelper_SaveDate
331 if type(loaded.QuestHelper_Quests) == "table" then for version, package in pairs(loaded.QuestHelper_Quests) do
332 if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
333 if type(levels) == "table" then for level, quest_list in pairs(levels) do
334 if type(quest_list) == "table" then for name, quest in pairs(quest_list) do
335 ProcessQuest(faction, level, name, quest, result)
341 if type(loaded.QuestHelper_Objectives) == "table" then for version, package in pairs(loaded.QuestHelper_Objectives) do
342 if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
343 if type(objectives) == "table" then for name, objective in pairs(objectives) do
344 ProcessObjective(category, name, objective, result)
354 local function ObsoletedBy(data1, data2)
355 if data1.uid or data2.uid then
356 return data1.loc == data2.loc and data1.uid == data2.uid and (data1.time or 0) >= (data2.time or 0)
359 for key, value in pairs(data1) do
360 local value2 = data2[key]
361 if value2 == nil or value2 < value then
371 -- This entire mess is built to find obsolete files and remove them.
372 for new_name, original_name in pairs(unknown_input_files) do
373 print("Checking: ", original_name)
374 local data = file_data[new_name]
376 data = LoadFile(new_name)
377 file_data[new_name] = data
379 cache.known[new_name] = original_name
380 checked[new_name] = true
383 local uid, last_save = data.uid, data.time
386 local existing = cache.uid[uid]
388 cache.uid[uid] = {file=new_name, save=last_save}
389 file2uid[new_name] = uid
391 if existing.save >= last_save then
392 print("!!! Obsolete: ", original_name)
393 print("!!! By: ", all_input_files[existing.file])
396 file_data[new_name] = nil
397 all_input_files[new_name] = nil
398 cache.removed[new_name] = original_name
399 cache.known[new_name] = nil
400 unknown_input_files[new_name] = nil
402 print("!!! Obsolete: ", all_input_files[existing.file])
403 print("!!! By: ", original_name)
406 os.remove(existing.file)
407 file_data[existing.file] = nil
408 file2uid[existing.file] = nil
409 file2uid[new_name] = uid
410 cache.removed[existing.file] = all_input_files[existing.file]
411 all_input_files[existing.file] = nil
412 cache.known[existing.file] = nil
413 existing.file = new_name
417 for existing_name, existing_original_name in pairs(all_input_files) do
418 if not checked[existing_name] and not file2uid[existing_name] then
419 local data2 = file_data[existing_name]
421 data2 = LoadFile(existing_name)
422 file_data[existing_name] = data2
425 if data2 and not data2.uid then
426 if ObsoletedBy(data, data2) then
427 print("!!! Obsolete: ", original_name)
428 print("!!! By: ", existing_original_name)
432 file_data[new_name] = nil
433 all_input_files[new_name] = nil
434 cache.removed[new_name] = original_name
435 cache.known[new_name] = nil
436 unknown_input_files[new_name] = nil
438 elseif ObsoletedBy(data2, data) then
439 print("!!! Obsolete: ", existing_original_name)
440 print("!!! By: ", original_name)
443 os.remove(existing_name)
444 file_data[existing_name] = nil
445 all_input_files[existing_name] = nil
446 cache.removed[existing_name] = existing_original_name
447 cache.known[existing_name] = nil
448 unknown_input_files[existing_name] = nil
457 checked, file_data = nil, nil
460 collectgarbage("collect")
462 --print("Compiling Lightheaded/eql3 data. . .")
465 local total_count = 0
467 local pairfrequencies = {}
468 for name, origin in pairs(all_input_files) do
469 total_count = total_count + 1 -- I fuckin' hate Lua
471 for name, origin in pairs(all_input_files) do
472 counter = counter + 1
473 print("Compiling " .. counter .. "/" .. total_count .. ": ", origin)
474 --CompileInputFile(name)
475 local ret, msg = pcall(CompileInputFile, name, pairfrequencies)
477 print("!!!!! FAILURE")
482 print("Processing final data")
483 local finaldata = CompileFinish()
485 local finalstatic = io.open("../static.lua", "w")
486 finalstatic:write("QuestHelper_File[\"static.lua\"] = \"Development Version\"\n")
487 finalstatic:write("QuestHelper_Loadtime[\"static.lua\"] = GetTime()\n")
488 finalstatic:write("QuestHelper_StaticData={")
490 for k, v in pairs(finaldata) do
491 collectgarbage("collect")
492 local foloc = string.format("static_%s.lua", k)
493 local finalout = "../" .. foloc
494 local tempout = finalout
495 if condense then tempout = string.format("static_%s.large", k) end
497 print("Writing: " .. tempout)
498 local stream = io.open(tempout, "w")
499 stream:write(string.format("QuestHelper_File[\"%s\"] = \"Development Version\"\n", foloc))
500 stream:write(string.format("QuestHelper_Loadtime[\"%s\"] = GetTime()\n", foloc))
501 stream:write(string.format("if GetLocale() ~= \"%s\" then return end\n", k)) -- wellp
502 stream:write(ScanAndDumpVariable(v, string.format("QuestHelper_StaticData_%s", k)))
506 print("Condensing " .. finalout)
507 assert(os.execute(string.format("lua LuaSrcDiet.lua --maximum %s -o %s", tempout, finalout)) ~= -1)
509 local data_loader = loadfile("LuaSrcDiet.lua")
510 local data = {arg={"--maximum", tempout, "-o", finalout}, TEST=true, string=string, print=print, table=table, ipairs=ipairs, error=error, io=io, tonumber=tonumber --[[ assert=assert, FileUtil=FileUtil, tostring=tostring ]]} -- there has to be a better way to do this
511 setfenv(data_loader, data)
517 finalstatic:write(string.format(" %s=QuestHelper_StaticData_%s,", k, k))
518 --assert(FileUtil.fileContains("../QuestHelper.toc", foloc), "didn't contain " .. foloc)
521 finalstatic:write("}")
522 io.close(finalstatic)
525 local qhfrequencies = {}
526 local wowfrequencies = {}
528 for k, v in pairs(pairfrequencies) do
529 table.insert(qhwowv, {freq = v, tag = k})
530 local qh, wow = SplitVersion(k)
531 qhfrequencies[qh] = (qhfrequencies[qh] or 0) + v
532 wowfrequencies[wow] = (wowfrequencies[wow] or 0) + v
538 for k, v in pairs(qhfrequencies) do table.insert(qhv, {freq = v, tag = k}) end
539 for k, v in pairs(wowfrequencies) do table.insert(wowv, {freq = v, tag = k}) end
541 table.sort(qhwowv, function (a, b) return a.freq < b.freq end)
542 table.sort(qhv, function (a, b) return a.freq < b.freq end)
543 table.sort(wowv, function (a, b) return a.freq < b.freq end)
545 for k, v in pairs(qhwowv) do
546 print(string.format("%d: %s", v.freq, v.tag))
549 for k, v in pairs(qhv) do
550 print(string.format("%d: %s", v.freq, v.tag))
553 for k, v in pairs(wowv) do
554 print(string.format("%d: %s", v.freq, v.tag))
556 elseif not FileUtil.fileExists("../static.lua") then
557 print("../static.lua doesn't exist; you can't skip the compile step.")
562 print("Creating: Icons.tga")
563 --if not FileUtil.fileExists("../Art/Icons.tga") then
564 -- print("You'll need to manually create Art/Icons.tga, ImageMagick's SVG support seems to have been broken recently.")
566 FileUtil.convertImage("Data/art.svg", "../Art/Icons.tga")
569 local version_string = "UNKNOWN"
571 if archive_zip or archive_7z then
572 FileUtil.unlinkDirectory("QuestHelper")
574 FileUtil.createDirectory("QuestHelper")
575 FileUtil.createDirectory("QuestHelper/Art")
576 FileUtil.createDirectory("QuestHelper/Fonts")
580 -- Try to coax git to give us something to use for the version.
581 local stream = io.popen("git describe --tags HEAD", "r")
583 info = stream:read("*line")
585 -- Replace the first dash with a dot.
586 info = string.gsub(info, "^(.*)%-(.*)%-g(.*)$", "%1%.%2%-%3", 1)
592 -- Didn't get a nice looking tag to use, try to at least get a hash for the version.
593 stream = io.popen("git-log -1 --pretty=\"format:%2-%h\"", "r")
595 info = stream:read("*line")
601 -- We have no idea what version this is, will use the string already in the TOC.
605 FileUtil.copyFile("../QuestHelper.toc", "QuestHelper", "^(##%s-Version%s-):%s*(.*)%s*$",
607 version_string = string.gsub(info, "%%2", b) return a..": "..version_string
610 local file_version_pattern = "%1"..string.format("%q", version_string)
612 FileUtil.forEachFile("..", function (file)
613 local ext = FileUtil.extension(file)
614 if ext == "lua" or ext == "xml" then
616 FileUtil.copyFile(file, "QuestHelper")
618 FileUtil.copyFile(file, "QuestHelper",
619 "(assert%s*%b())", "--[[ %1 ]]",
620 "(QuestHelper:Assert%s*%b())", "--[[ %1 ]]",
621 "(QuestHelper_File%[[^%]]*%]%s*=%s*)\"[^\"]+\"", file_version_pattern)
626 FileUtil.forEachFile("../Art", function (file)
627 local ext = FileUtil.extension(file)
628 if ext == "blp" or ext == "tga" then
629 FileUtil.copyFile(file, "QuestHelper/Art")
633 FileUtil.forEachFile("../Fonts", function (file)
634 local ext = FileUtil.extension(file)
635 if ext == "txt" or ext == "ttf" then
636 FileUtil.copyFile(file, "QuestHelper/Fonts")
640 for i, dir in ipairs({"libs", "lang"}) do
641 FileUtil.copyDirectoryRecursively("../"..dir, "QuestHelper/"..dir)
644 FileUtil.copyFile("../libs/AstrolabeQH/Astrolabe.lua", "QuestHelper/libs/AstrolabeQH", "(QuestHelper_File%[[^%]]*%]%s*=%s*)\"[^\"]+\"", file_version_pattern)
645 FileUtil.copyFile("../libs/AstrolabeQH/AstrolabeMapMonitor.lua", "QuestHelper/libs/AstrolabeQH", "(QuestHelper_File%[[^%]]*%]%s*=%s*)\"[^\"]+\"", file_version_pattern)
646 FileUtil.copyFile("../libs/AstrolabeQH/DongleStub.lua", "QuestHelper/libs/AstrolabeQH", "(QuestHelper_File%[[^%]]*%]%s*=%s*)\"[^\"]+\"", file_version_pattern)
648 FileUtil.copyFile("../MinimapArrow.tga", "QuestHelper")
649 FileUtil.copyFile("../arrow_image.blp", "QuestHelper")
650 FileUtil.copyFile("../arrow_image_down.blp", "QuestHelper")
651 FileUtil.copyFile("../triangle.tga", "QuestHelper")
652 FileUtil.copyFile("../line.tga", "QuestHelper")
653 FileUtil.copyFile("../sigil.tga", "QuestHelper")
656 local archive = "../QuestHelper-"..version_string..".zip"
657 print("Creating "..archive)
658 FileUtil.unlinkFile(archive)
659 FileUtil.createZipArchive("QuestHelper", archive)
663 local archive = "../QuestHelper-"..version_string..".7z"
664 print("Creating "..archive)
665 FileUtil.unlinkFile(archive)
666 FileUtil.create7zArchive("QuestHelper", archive)
669 FileUtil.unlinkDirectory("QuestHelper")
675 --local outfile = io.open("profile.txt", "w+")
676 --profiler:report(outfile)