fix the framerate drop at login
[QuestHelper.git] / Development / compiler.lua
blobf1085a4587843f5293d0e12a12d69fb727efa9e0
1 WoWData = {item={},quest={},npc={}} -- The build script will replace this with actual data if it can.
3 local ignored = {"Unknown", "Field Repair Bot 74A", "Field Repair Bot 110G"}
5 local StaticData = {}
7 function CreateAverage()
8 return {}
9 end
11 function AppendToAverage(average, value)
12 table.insert(average, value)
13 end
15 function CollapseAverage(average)
16 table.sort(average)
17 local to_remove = math.floor(#average*0.2)
18 for i = 1,to_remove do
19 table.remove(average, 1)
20 table.remove(average)
21 end
22 if #average == 0 then
23 return nil
24 end
25 local sum = 0
26 for _, v in pairs(average) do
27 sum = sum + v
28 end
29 return sum/#average
30 end
32 function GetQuest(locale, faction, level, name, hash)
33 local l = StaticData[locale]
34 if not l then
35 l = {}
36 l.quest = {}
37 l.objective = {}
38 StaticData[locale] = l
39 end
40 local a = l.quest[faction]
41 if not a then
42 a = {}
43 l.quest[faction] = a
44 end
45 local b = a[level]
46 if not b then
47 b = {}
48 a[level] = b
49 end
50 local q = b[name]
51 if not q then
52 q = {}
53 b[name] = q
54 q.hash = hash
55 q.alt = {}
56 end
58 if hash and hash ~= q.hash then
59 if q.alt[hash] then
60 return q.alt[hash]
61 end
62 end
64 if hash and not q.hash then
65 -- If the old quest didn't have a hash, we'll assume this is it. If we're wrong, we'll
66 -- hopefully overwrite it with the correct data.
67 q.hash = hash
68 elseif not hash and q.hash then
69 -- If the old quest had a hash, but this one doesn't, we'll return a dummy object
70 -- so that we don't overwrite it with the wrong quest data.
71 q = {}
72 elseif hash ~= q.hash then
73 local q2 = {}
74 q2.hash = hash
75 q.alt[hash] = q2
76 q = q2
77 end
79 return q
80 end
82 function GetObjective(locale, category, name)
83 local l = StaticData[locale]
84 if not l then
85 l = {}
86 l.quest = {}
87 l.objective = {}
88 StaticData[locale] = l
89 end
90 local list = l.objective[category]
91 if not list then
92 list = {}
93 l.objective[category] = list
94 end
95 local obj = list[name]
96 if not obj then
97 obj = {}
98 list[name] = obj
99 end
100 return obj
103 local function Distance(a, b)
104 -- Doing this, because the distances are going to be rounded later, and I don't want it to create
105 -- a situation where if you ran TidyPositionList twice, it would have a different result.
107 local x, y = math.floor(a[2]*10000+0.5)/10000-math.floor(b[2]*10000+0.5)/10000,
108 math.floor(a[3]*10000+0.5)/10000-math.floor(b[3]*10000+0.5)/10000
110 return math.sqrt(x*x+y*y)
113 local function TidyDropList(locale, list)
114 local high = 0
116 for monster, count in pairs(list) do
117 local monster_obj = GetObjective(locale, "monster", monster)
118 if monster_obj.looted and monster_obj.looted > 0 then
119 high = math.max(high, math.max(1, math.floor(count))/math.ceil(monster_obj.looted))
123 for monster, count in pairs(list) do
124 local monster_obj = GetObjective(locale, "monster", monster)
125 count = math.max(1, math.floor(count))
127 if monster_obj.looted and monster_obj.looted > 0 and count/math.ceil(monster_obj.looted) > high*0.2 then
128 list[monster] = count
129 else
130 list[monster] = nil
136 local function TidyContainedList(locale, list)
137 local high = 0
139 for item, count in pairs(list) do
140 local item_obj = GetObjective(locale, "item", item)
141 if item_obj.opened and item_obj.opened > 0 then
142 high = math.max(high, math.max(1, math.floor(count))/math.ceil(item_obj.opened))
146 for item, count in pairs(list) do
147 local item_obj = GetObjective(locale, "item", item)
148 count = math.max(1, math.floor(count))
150 if item_obj.opened and item_obj.opened > 0 and count/math.ceil(item_obj.opened) > high*0.2 then
151 list[item] = count
152 else
153 list[item] = nil
158 local function TidyPositionList(list, min_distance)
159 min_distance = min_distance or 0.03
160 while true do
161 if #list == 0 then return end
162 local changed = false
163 local i = 1
164 while i <= #list do
165 local nearest, distance = nil, 0
166 for j = i+1, #list do
167 local d = Distance(list[i], list[j])
168 if not nearest or d < distance then
169 nearest, distance = j, d
172 if nearest and distance < min_distance then
173 local a, b = list[i], list[nearest]
174 a[2] = (a[2]*a[4]+b[2]*b[4])/(a[4]+b[4])
175 a[3] = (a[3]*a[4]+b[3]*b[4])/(a[4]+b[4])
176 a[4] = a[4]+b[4]
177 table.remove(list, nearest)
178 changed = true
179 else
180 i = i + 1
183 if not changed then
184 -- Because we moved nodes around, we'll check again to make sure we didn't move too close together
185 break
189 local highest = 0
191 for i, data in ipairs(list) do
192 data[4] = math.pow(data[4], 0.73575888234288) -- Raising it to this number to make huge numbers seem closer together, the positions are probably correct and not some mistake.
193 highest = math.max(highest, data[4])
196 local i = 1 -- Remove anything that doesn't seem very likely.
197 while i <= #list do
198 if list[i][4] < highest*0.2 then
199 table.remove(list, i)
200 else
201 list[i][4] = math.max(1, math.floor(list[i][4]*100/highest+0.5))
202 i = i + 1
206 for i, j in ipairs(list) do
207 j[2] = math.floor(j[2]*10000+0.5)/10000
208 j[3] = math.floor(j[3]*10000+0.5)/10000
211 table.sort(list, function(a, b)
212 if a[4] > b[4] then return true end
213 if a[4] < b[4] then return false end
214 if a[1] < b[1] then return true end
215 if a[1] > b[1] then return false end
216 if a[2] < b[2] then return true end
217 if a[2] > b[2] then return false end
218 return a[3] < b[3]
219 end)
221 -- Only use the first 5 positions.
222 for i = 6,#list do table.remove(list) end
225 local function DropListMass(list)
226 local mass = 0
227 for item, count in pairs(list) do
228 mass = mass + count
230 return mass
233 local function PositionListMass(list)
234 local mass = 0
235 for _, pos in ipairs(list) do
236 mass = mass + pos[4]
238 return mass
241 local function CollapseDropList(list)
242 local result, c = nil, 0
243 for item, count in pairs(list) do
244 if not result or count > c then
245 result, c = item, count
248 return result
251 local function MergeDropLists(list, add)
252 for item, count in pairs(add) do
253 list[item] = (list[item] or 0) + count
257 local function MergePositionLists(list, add)
258 for _, pos in ipairs(add) do
259 local index, x, y, w = pos[1], pos[2], pos[3], pos[4]
260 if type(index) == "number" and QuestHelper_NameLookup[index] and
261 type(w) == "number" and w > 0 then
262 local bp, distance = nil, 0
263 for i, pos2 in ipairs(list) do
264 if index == pos2[1] then
265 local d = math.sqrt((x-pos2[2])*(x-pos2[2])+(y-pos2[3])*(y-pos2[3]))
266 if not bp or d < distance then
267 bp, distance = pos2, d
271 if bp and distance < 0.03 then
272 bp[2] = (bp[2]*bp[4]+x*w)/(bp[4]+w)
273 bp[3] = (bp[3]*bp[4]+y*w)/(bp[4]+w)
274 bp[4] = bp[4]+w
275 else
276 table.insert(list, {index,x,y,w})
282 local function AddQuestEnd(quest, npc)
283 if not quest.finish then quest.finish = {} end
284 quest.finish[npc] = (quest.finish[npc] or 0) + 1
287 local function AddQuestPos(quest, pos)
288 if not quest.pos then quest.pos = {} end
289 MergePositionLists(quest.pos, pos)
292 local function AddQuestItems(quest, list)
293 for item, data in pairs(list) do
294 if type(data.drop) == "table" then
295 if not quest.item then quest.item = {} end
296 if not quest.item[item] then quest.item[item] = {} end
297 if not quest.item[item].drop then quest.item[item].drop = {} end
298 MergeDropLists(quest.item[item].drop, data.drop)
299 elseif type(data.pos) == "table" then
300 if not quest.item then quest.item = {} end
301 if not quest.item[item] then quest.item[item] = {} end
302 if not quest.item[item].pos then quest.item[item].pos = {} end
303 MergePositionLists(quest.item[item].pos, data.pos)
308 local function ValidFaction(faction)
309 return faction == 1 or faction == 2
312 local function AddQuest(locale, faction, level, name, data)
313 if ValidFaction(faction)
314 and type(level) == "number" and level >= 1 and level <= 100
315 and type(name) == "string" and type(data) == "table" then
317 local _, _, real_name = string.find(name, "^%["..level.."[^%s]-%]%s?(.+)$")
319 if real_name then
320 -- The Quest Level AddOn mangles level names.
321 name = real_name
324 local q = GetQuest(locale, faction, level, name, type(data.hash) == "number" and data.hash or nil)
326 if type(data.id) == "number" then
327 local wdq = WoWData.quest[data.id]
328 if not wdq then
329 wdq = {name={},hash={},faction={}}
330 WoWData.quest[data.id] = wdq
332 wdq.name[locale] = name
333 wdq.hash[locale] = data.hash or wdq.hash[locale]
334 wdq.level = level
337 if type(data.finish) == "string" then
338 AddQuestEnd(q, data.finish)
339 elseif type(data.pos) == "table" then
340 AddQuestPos(q, data.pos)
343 if type(data.item) == "table" then
344 AddQuestItems(q, data.item)
347 if type(data.hash) == "number" and type(data.alt) == "table" then
348 for hash, quest in pairs(data.alt) do
349 quest.hash = hash
350 AddQuest(locale, faction, level, name, quest)
356 local function AddFlightInstructor(locale, faction, location, npc)
357 if ValidFaction(faction) and type(location) == "string" and type(npc) == "string" then
358 local l = StaticData[locale]
359 if not l then
360 l = {}
361 StaticData[locale] = l
363 local faction_list = l.flight_instructors
364 if not faction_list then
365 faction_list = {}
366 l.flight_instructors = faction_list
369 local location_list = faction_list[faction]
370 if not location_list then
371 location_list = {}
372 faction_list[faction] = location_list
374 location_list[location] = npc
378 local function CreateFlightTable(locale, faction, start, destination, new_style)
379 if ValidFaction(faction) and
380 type(start) == "string" and
381 type(destination) == "string" then
382 local l = StaticData[locale]
383 if not l then
384 l = {}
385 StaticData[locale] = l
387 local faction_list = l.flight_routes
388 if not faction_list then
389 faction_list = {}
390 l.flight_routes = faction_list
392 local start_list = faction_list[faction]
393 if not start_list then
394 start_list = {}
395 faction_list[faction] = start_list
397 local end_list = start_list[start]
398 if not end_list then
399 end_list = {}
400 start_list[start] = end_list
402 local hash_list = end_list[destination]
403 if not hash_list then
404 hash_list = {}
405 end_list[destination] = hash_list
408 -- Check to see if the new_style thing is correct
409 local dest_new_style = hash_list.no_interrupt_count or hash_list.interrupt_count
411 -- If we have new style, and the destination is old style, we reset the destination
412 if (not dest_new_style) and new_style then
413 hash_list = {}
414 end_list[destination] = hash_list
415 dest_new_style = true
418 if (not dest_new_style) ~= (not new_style) then
419 return nil
422 return hash_list
423 else
424 return nil
428 local function AddFlightRoute(hash_list, hash, value, new_style)
429 if type(hash) == "number" and
430 ((value == true and hash == 0) or (type(value) == "number" and value > 0)) then
431 assert(hash_list)
433 if value == true then
434 hash_list[hash] = hash_list[hash] or true
435 else
436 local average = hash_list[hash]
437 if type(average) ~= "table" then
438 average = CreateAverage()
439 hash_list[hash] = average
441 AppendToAverage(average, value)
446 local function addVendor(list, npc)
447 for _, existing in ipairs(list) do
448 if existing == npc then
449 return
453 table.insert(list, npc)
456 local function addVendors(list, to_add)
457 if not list then list = {} end
459 for _, npc in ipairs(to_add) do
460 if not ignored[npc] then
461 addVendor(list, npc)
465 return list
468 local function AddObjective(locale, category, name, objective)
469 if type(category) == "string"
470 and type(name) == "string"
471 and not ignored[name]
472 and type(objective) == "table" then
473 local o = GetObjective(locale, category, name)
475 if objective.quest == true then o.quest = true end
477 if type(objective.pos) == "table" then
478 if not o.pos then o.pos = {} end
479 MergePositionLists(o.pos, objective.pos)
482 if category == "monster" then
483 if type(objective.id) == "number" then
484 local wdm = WoWData.npc[objective.id]
485 if not wdm then
486 wdm = {name={}}
487 WoWData.npc[objective.id] = wdm
489 wdm.name[locale] = name
492 if type(objective.looted) == "number" and objective.looted >= 1 then
493 o.looted = (o.looted or 0) + objective.looted
495 if ValidFaction(objective.faction) then
496 o.faction = objective.faction
498 elseif category == "item" then
499 if type(objective.id) == "number" then
500 local wdi = WoWData.item[objective.id]
501 if not wdi then
502 wdi = {name={}}
503 WoWData.item[objective.id] = wdi
505 wdi.name[locale] = name
508 if type(objective.opened) == "number" and objective.opened >= 1 then
509 o.opened = (o.opened or 0) + objective.opened
511 if type(objective.vendor) == "table" then
512 o.vendor = addVendors(o.vendor, objective.vendor)
514 if type(objective.drop) == "table" then
515 if not o.drop then o.drop = {} end
516 for monster, count in pairs(objective.drop) do
517 if type(monster) == "string" and not ignored[monster] and type(count) == "number" then
518 o.drop[monster] = (o.drop[monster] or 0) + count
522 if type(objective.contained) == "table" then
523 if not o.contained then o.contained = {} end
524 for item, count in pairs(objective.contained) do
525 if type(item) == "string" and type(count) == "number" then
526 o.contained[item] = (o.contained[item] or 0) + count
534 local function CollapseQuest(locale, quest)
535 local name_score = quest.finish and DropListMass(quest.finish) or 0
536 local pos_score = quest.pos and PositionListMass(quest.pos)*0.25 or 0
538 if name_score > pos_score then
539 quest.finish = CollapseDropList(quest.finish)
540 quest.pos = nil
541 else
542 quest.finish = nil
543 if quest.pos then
544 TidyPositionList(quest.pos)
548 if quest.item and not next(quest.item) then
549 quest.item = nil
552 if quest.finish then
553 -- This NPC is for a quest. Need to know them.
554 GetObjective(locale, "monster", quest.finish).quest = true
557 return quest.pos == nil and quest.finish == nil and quest.item == nil
560 local function CollapseObjective(locale, objective)
561 if not objective.quest then return true end
562 objective.quest = nil
564 if objective.vendor and not next(objective.vendor, nil) then objective.vendor = nil end
566 if objective.pos and (PositionListMass(objective.pos) >
567 ((objective.drop and DropListMass(objective.drop) or 0) +
568 (objective.contained and DropListMass(objective.contained) or 0))) then
569 objective.drop = nil
570 objective.contained = nil
572 TidyPositionList(objective.pos)
574 if not next(objective.pos, nil) then
575 objective.pos = nil
577 else
578 objective.pos = nil
580 if objective.drop and not next(objective.drop) then objective.drop = nil end
581 if objective.contained and not next(objective.contained) then objective.contained = nil end
584 if objective.looted then
585 objective.looted = math.max(1, math.ceil(objective.looted))
588 if objective.opened then
589 objective.opened = math.max(1, math.ceil(objective.opened))
592 if objective.vendor and next(objective.vendor) then
593 table.sort(objective.vendor)
594 else
595 objective.vendor = nil
598 return objective.drop == nil and objective.contained == nil and objective.pos == nil and objective.vendor == nil
601 local function AddInputData(data, pairfrequencies)
602 if data.QuestHelper_StaticData then
603 -- Importing a static data file.
604 local static = data.QuestHelper_StaticData
605 data.QuestHelper_StaticData = nil
607 for locale, info in pairs(static) do
608 data.QuestHelper_Locale = locale
609 data.QuestHelper_Quests = info.quest
610 data.QuestHelper_Objectives = info.objective
611 data.QuestHelper_FlightRoutes = info.flight_routes
612 data.QuestHelper_FlightInstructors = info.flight_instructors
614 for cat, list in pairs(data.QuestHelper_Objectives) do
615 for name, info in pairs(list) do
616 info.quest = true
620 AddInputData(data)
623 return
626 QuestHelper_UpgradeDatabase(data)
628 if type(data.QuestHelper_Locale) == "string" then
629 local locale = data.QuestHelper_Locale
631 local seen_pairs = {}
633 if type(data.QuestHelper_Quests) == "table" then for version, package in pairs(data.QuestHelper_Quests) do
634 seen_pairs[version] = true
635 if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
636 if type(levels) == "table" and PreWrath(version) then for level, quest_list in pairs(levels) do
637 if type(quest_list) == "table" then for quest_name, quest_data in pairs(quest_list) do
638 if type(quest_data) == "table" and quest_data.item and type(quest_data.item) == "table" then for qiname, qidata in pairs(quest_data.item) do
639 if type(qidata) == "table" and qidata.pos and type(qidata.pos) == "table" then for i, pos in pairs(qidata.pos) do
640 QuestHelper_ConvertCoordsToWrath(pos, true)
641 end end
642 end end
643 end end
644 end end
646 if type(levels) == "table" then for level, quest_list in pairs(levels) do
647 if type(quest_list) == "table" then for quest_name, quest_data in pairs(quest_list) do
648 AddQuest(locale, faction, level, quest_name, quest_data)
649 end end
650 end end
651 end end
652 end end
654 if type(data.QuestHelper_Objectives) == "table" then for version, package in pairs(data.QuestHelper_Objectives) do
655 seen_pairs[version] = true
656 if AuthorizedVersion(version) and type(package) == "table" then for category, objectives in pairs(package) do
657 if type(objectives) == "table" and PreWrath(version) then for name, objective in pairs(objectives) do
658 if type(objective) == "table" and objective.pos and type(objective.pos) == "table" then
659 for i, pos in pairs(objective.pos) do
660 QuestHelper_ConvertCoordsToWrath(pos, true)
663 end end
664 if type(objectives) == "table" then for name, objective in pairs(objectives) do
665 AddObjective(locale, category, name, objective)
666 end end
667 end end
668 end end
670 if type(data.QuestHelper_FlightInstructors) == "table" then for version, package in pairs(data.QuestHelper_FlightInstructors) do
671 seen_pairs[version] = true
672 if AuthorizedVersion(version) and type(package) == "table" then for faction, list in pairs(package) do
673 if type(list) == "table" then for location, npc in pairs(list) do
674 AddFlightInstructor(locale, faction, location, npc)
675 end end
676 end end
677 end end
679 if type(data.QuestHelper_FlightRoutes) == "table" then for version, package in pairs(data.QuestHelper_FlightRoutes) do
680 seen_pairs[version] = true
681 if AuthorizedVersion(version) and type(package) == "table" then for faction, start_list in pairs(package) do
682 if type(start_list) == "table" then for start, destination_list in pairs(start_list) do
683 if type(destination_list) == "table" then for destination, hash_list in pairs(destination_list) do
684 local tab = CreateFlightTable(locale, faction, start, destination, (hash_list.no_interrupt_count or hash_list.interrupt_count))
685 if tab then
686 if hash_list.no_interrupt_count then tab.no_interrupt_count = (tab.no_interrupt_count or 0) + hash_list.no_interrupt_count end
687 if hash_list.interrupt_count then tab.interrupt_count = (tab.interrupt_count or 0) + hash_list.interrupt_count end
689 if type(hash_list) == "table" then for hash, value in pairs(hash_list) do
690 AddFlightRoute(tab, hash, value)
691 end end
693 end end
694 end end
695 end end
696 end end
698 for k in pairs(seen_pairs) do
699 pairfrequencies[k] = (pairfrequencies[k] or 0) + 1
704 local function QuestItemsAreSimilar(item, quest_list)
705 -- TODO: Write this function. Should make sure all the quests get item from the same place.
706 return #quest_list <= 1
709 local function RemoveQuestByData(data)
710 for locale, l in pairs(StaticData) do
711 for faction, levels in pairs(l.quest) do
712 for level, quest_list in pairs(levels) do
713 for quest, quest_data in pairs(quest_list) do
714 if data == quest_data then
715 if quest_data.alt then
716 local alt = quest_data.alt
717 local hash = next(alt, nil)
718 local quest_data2 = alt[hash]
719 alt[hash] = nil
720 quest_list[quest] = quest_data2
721 if next(alt, nil) then
722 quest_data2.alt = alt
723 quest_data2.hash = hash
725 else
726 quest_list[quest] = nil
728 elseif quest_data.alt then
729 for hash, quest_data2 in pairs(quest_data.alt) do
730 if data == quest_data2 then
731 quest_data.alt[hash] = nil
732 break
735 if not next(quest_data.alt) then
736 quest_data.alt = nil
737 quest_data.hash = nil
741 if not next(levels[level], nil) then levels[level] = nil end
743 if not next(l.quest[faction], nil) then l.quest[faction] = nil end
748 function CompileInputFile(filename, pairfrequencies)
749 local data_loader = loadfile(filename)
750 if data_loader then
751 local data = {}
752 setfenv(data_loader, data)
753 data_loader()
754 AddInputData(data, pairfrequencies)
755 else
756 print("'"..filename.."' couldn't be loaded!")
760 function handleTranslations()
761 for locale, l in pairs(StaticData) do
762 if l.objective then
763 local item_map = {}
764 local monster_map = {}
765 local quest_map = {}
767 for id, data in pairs(WoWData.item) do
768 if data.name[locale] then
769 item_map[data.name[locale]] = id
773 for id, data in pairs(WoWData.npc) do
774 if data.name[locale] then
775 monster_map[data.name[locale]] = id
779 for id, data in pairs(WoWData.quest) do
780 if data.name[locale] then
781 quest_map[data.level.."/"..data.hash[locale].."/"..data.name[locale]] = id
785 local function item2dat(data, item)
786 if not item then item = {} end
788 item.quest = item.quest or data.quest
790 if data.opened then
791 item.opened = (item.opened or 0) + data.opened
792 data.opened = nil
795 if data.pos then
796 if not item.pos then
797 item.pos = data.pos
798 else
799 MergePositionLists(item.pos, data.pos)
802 data.pos = nil
805 if data.vendor then
806 if not item.vendor then
807 item.vendor = {}
810 for i, npc in ipairs(data.vendor) do
811 local id = monster_map[npc]
812 if id then
813 addVendor(item.vendor, id)
818 if data.drop then
819 if not item.drop then
820 item.drop = {}
823 for name, count in pairs(data.drop) do
824 local id = monster_map[name]
825 if id then
826 item.drop[id] = (item.drop[id] or 0) + count
827 data.drop[name] = nil
832 if data.contained then
833 if not item.contained then
834 item.contained = {}
837 for name, count in pairs(data.contained) do
838 local id = item_map[name]
839 if id then
840 item.contained[id] = (item.contained[id] or 0) + count
841 data.contained[name] = nil
846 return item
849 if l.objective.item then for name, data in pairs(l.objective.item) do
850 local id = item_map[name]
851 if id then
852 item2dat(data, WoWData.item[id])
853 local item = WoWData.item[id]
855 end end
857 if l.objective.monster then for name, data in pairs(l.objective.monster) do
858 local id = monster_map[name]
859 if id then
860 local npc = WoWData.npc[id]
862 npc.quest = npc.quest or data.quest
863 npc.faction = npc.faction or data.faction
865 if data.looted then
866 npc.looted = (npc.looted or 0) + data.looted
867 data.looted = nil
870 if data.pos then
871 if not npc.pos then
872 npc.pos = data.pos
873 else
874 MergePositionLists(npc.pos, data.pos)
877 data.pos = nil
880 end end
882 local function q2static(faction, name, data)
883 local id = quest_map[name]
884 if id then
885 local quest = WoWData.quest[id]
886 quest.faction[faction] = true
888 print("Copying Quest", faction, name)
890 if data.finish and next(data.finish) then
891 quest.finish = monster_map[CollapseDropList(data.finish)] or quest.finish
894 if data.item then
895 quest.item = quest.item or {}
897 for name, idata in pairs(data.item) do
898 local id = item_map[name]
899 if id then
900 quest.item[id] = item2dat(idata, quest.item[id])
907 if l.quest then for faction, fqlist in pairs(l.quest) do
908 for level, qlist in pairs(fqlist) do
909 for name, qdata in pairs(qlist) do
910 if qdata.hash then
911 q2static(faction, level.."/"..qdata.hash.."/"..name, qdata)
914 if qdata.alt then for hash, qdata2 in pairs(qdata.alt) do
915 q2static(faction, level.."/"..hash.."/"..name, qdata2)
916 end end
919 end end
923 for id, item in pairs(WoWData.item) do
924 for locale, name in pairs(item.name) do
925 print("Adding item ", locale, name)
926 local data = GetObjective(locale, "item", name)
928 data.quest = data.quest or item.quest
930 if item.opened then
931 data.opened = item.opened
934 if item.pos then
935 data.pos = data.pos or {}
936 MergePositionLists(data.pos, item.pos)
939 if item.vendor then
940 data.vendor = data.vendor or {}
942 for i, id in ipairs(item.vendor) do
943 local name = WoWData.npc[id] and WoWData.npc[id].name[locale]
944 if name then
945 addVendor(data.vendor, name)
950 if item.drop then
951 data.drop = data.drop or {}
952 for id, count in pairs(item.drop) do
953 local name = WoWData.npc[id] and WoWData.npc[id].name[locale]
954 if name then
955 data.drop[name] = (data.drop[name] or 0) + count
960 if item.contained then
961 data.contained = data.contained or {}
962 for id, count in pairs(item.contained) do
963 local name = WoWData.item[id] and WoWData.item[id].name[locale]
964 if name then
965 data.contained[name] = (data.contained[name] or 0) + count
972 for id, npc in pairs(WoWData.npc) do
973 for locale, name in pairs(npc.name) do
974 print("Adding NPC ", locale, name)
975 local data = GetObjective(locale, "monster", name)
977 data.quest = data.quest or npc.quest
978 data.faction = data.faction or npc.faction
980 if npc.looted then
981 data.looted = npc.looted
984 if npc.pos then
985 data.pos = data.pos or {}
986 MergePositionLists(data.pos, npc.pos)
991 for id, quest in pairs(WoWData.quest) do
992 for faction in pairs(quest.faction) do
993 for locale, name in pairs(quest.name) do
994 print("Adding Quest ", locale, faction, quest.level, quest.hash[locale], name)
995 local data = GetQuest(locale, faction, quest.level, name, quest.hash[locale])
997 if quest.finish then
998 local fname = WoWData.npc[quest.finish] and WoWData.npc[quest.finish].name[locale]
999 if fname then
1000 data.finish = {[fname] = 1}
1004 if quest.item then
1005 for id, item in pairs(quest.item) do
1006 local iname = WoWData.item[id] and WoWData.item[id].name[locale]
1007 if iname then
1008 local qdata = data
1010 if not qdata.item then qdata.item = {} end
1011 local data = qdata.item[iname] or {}
1012 qdata.item[iname] = data
1014 if item.pos then
1015 data.pos = data.pos or {}
1016 MergePositionLists(data.pos, item.pos)
1019 if item.drop then
1020 data.drop = data.drop or {}
1021 for id, count in pairs(item.drop) do
1022 local name = WoWData.npc[id] and WoWData.npc[id].name[locale]
1023 if name then
1024 data.drop[name] = (data.drop[name] or 0) + count
1029 if item.contained then
1030 data.contained = data.contained or {}
1031 for id, count in pairs(item.contained) do
1032 local name = WoWData.item[id] and WoWData.item[id].name[locale]
1033 if name then
1034 data.contained[name] = (data.contained[name] or 0) + count
1045 -- TODO: quests.
1048 function CompileFinish()
1049 handleTranslations()
1050 print("Finished translations")
1052 for locale, l in pairs(StaticData) do
1053 local quest_item_mass = {}
1054 local quest_item_quests = {}
1056 local function WatchQuestItems(quest)
1057 if quest.finish then GetObjective(locale, "monster", quest.finish).quest = true end
1059 if quest.item then
1060 for item, data in pairs(quest.item) do
1061 quest_item_mass[item] = (quest_item_mass[item] or 0)+
1062 (data.drop and DropListMass(data.drop) or 0)+
1063 (data.contained and DropListMass(data.contained) or 0)+
1064 (data.pos and PositionListMass(data.pos) or 0)
1066 quest_item_quests[item] = quest_item_quests[item] or {}
1067 table.insert(quest_item_quests[item], quest)
1072 --if locale == "enUS" then -- I'm hoping the other locales aren't corrupted, as this method of fixing really won't work for any locale without a lot of data
1073 print("Culling opened items ", locale)
1075 local contained_preserved = 0
1076 local contained_rejected = 0
1078 local openablect = {}
1080 for name, item in pairs(l.objective.item) do
1081 table.insert(openablect, item.opened)
1083 table.sort(openablect, function (a,b) return a < b end)
1084 local thresh = openablect[math.floor(#openablect * 0.95)]
1086 for name, item in pairs(l.objective.item) do
1087 item.openable = item.opened and (item.opened >= thresh)
1090 for name, item in pairs(l.objective.item) do
1091 if item.contained then
1092 local tempcontained = {}
1093 for it, itv in pairs(item.contained) do
1094 if l.objective.item[it] and l.objective.item[it].openable then
1095 tempcontained[it] = itv
1096 contained_preserved = contained_preserved + 1
1097 else
1098 contained_rejected = contained_rejected + 1
1101 item.contained = tempcontained
1105 for name, item in pairs(l.objective.item) do
1106 item.openable = nil
1109 print(string.format("Containment cull pass done. %d preserved, %d rejected", contained_preserved, contained_rejected))
1110 --end
1112 print("Processing quests ", locale)
1114 for faction, levels in pairs(l.quest) do
1115 local delete_faction = true
1116 for level, quest_list in pairs(levels) do
1117 local delete_level = true
1118 for quest, quest_data in pairs(quest_list) do
1119 if quest_data.alt then
1120 for hash, quest_data2 in pairs(quest_data.alt) do
1121 if CollapseQuest(locale, quest_data2) then
1122 quest_data.alt[hash] = nil
1123 else
1124 quest_data2.hash = nil
1125 WatchQuestItems(quest_data2)
1129 if not next(quest_data.alt, nil) then
1130 quest_data.alt = nil
1131 quest_data.hash = nil
1135 if CollapseQuest(locale, quest_data) then
1136 if quest_data.alt then
1137 local alt = quest_data.alt
1138 local hash = next(alt, nil)
1139 local quest_data2 = alt[hash]
1140 alt[hash] = nil
1141 quest_list[quest] = quest_data2
1142 if next(alt, nil) then
1143 quest_data2.alt = alt
1144 quest_data2.hash = hash
1147 delete_level = false
1148 else
1149 quest_list[quest] = nil
1151 else
1152 WatchQuestItems(quest_data)
1153 delete_level = false
1157 if delete_level then levels[level] = nil else delete_faction = false end
1159 if delete_faction then l.quest[faction] = nil end
1162 if l.flight_instructors then for faction, list in pairs(l.flight_instructors) do
1163 for area, npc in pairs(list) do
1164 -- Need to remember the flight instructors, for use in routing.
1165 GetObjective(locale, "monster", npc).quest = true
1167 end end
1169 for item, quest_list in pairs(quest_item_quests) do
1170 -- If all the items are similar, then we don't want quest item entries for them,
1171 -- we want to use the gobal item objective instead.
1172 if QuestItemsAreSimilar(item, quest_list) then
1173 quest_item_mass[item] = 0
1177 print("Processing quest items ", locale)
1178 -- Will go through the items and either delete them, or merge the quest items into them, and then
1179 -- mark the relevent monsters as being quest objectives.
1180 if l.objective["item"] then
1181 for name, objective in pairs(l.objective["item"]) do
1182 -- If this is a quest item, mark anything that drops it as being a quest monster.
1183 local quest_mass = quest_item_mass[name] or 0
1185 if objective.vendor and next(objective.vendor, nil) then
1186 quest_mass = 0 -- If the item can be bought, then it shouldn't be quest specific.
1189 local item_mass = (objective.pos and PositionListMass(objective.pos) or 0)+
1190 (objective.drop and DropListMass(objective.drop) or 0)+
1191 (objective.contained and DropListMass(objective.contained) or 0)
1193 if quest_mass > item_mass then
1194 -- Delete this item, we'll deal with the the quests using the items after.
1195 l.objective["item"][name] = nil
1196 else
1197 if quest_item_quests[name] then
1198 for i, quest_data in pairs(quest_item_quests[name]) do
1199 local quest_item = quest_data.item and quest_data.item[name]
1200 if quest_item then
1201 if quest_item.drop then
1202 if not objective.drop then objective.drop = {} end
1203 MergeDropLists(objective.drop, quest_item.drop)
1206 if quest_item.contained then
1207 if not objective.contained then objective.contained = {} end
1208 MergeDropLists(objective.contained, quest_item.contained)
1211 if quest_item.pos then
1212 if not objective.pos then objective.pos = {} end
1213 MergePositionLists(objective.pos, quest_item.pos)
1216 quest_data.item[name] = nil
1217 if not next(quest_data.item, nil) then
1218 quest_data.item = nil
1220 if not quest_data.finish and not quest_data.pos then
1221 RemoveQuestByData(quest_data)
1227 quest_item_quests[name] = nil
1228 objective.quest = true
1231 if objective.quest then
1232 if objective.drop then
1233 TidyDropList(locale, objective.drop)
1234 for monster, count in pairs(objective.drop) do
1235 GetObjective(locale, "monster", monster).quest = true
1239 if objective.contained then
1240 TidyContainedList(locale, objective.contained)
1241 for item, count in pairs(objective.contained) do
1242 GetObjective(locale, "item", item).quest = true
1246 if objective.vendor then
1247 for i, npc in ipairs(objective.vendor) do
1248 GetObjective(locale, "monster", npc).quest = true
1256 -- For any quest items that didn't get handled above, we'll clean them up and leave them be.
1257 for item, quest_list in pairs(quest_item_quests) do
1258 for _, quest_data in ipairs(quest_list) do
1259 -- Item should already exist in quest, not going to check.
1260 local item_data = quest_data.item[item]
1262 local pos_mass = 0
1263 if item_data.pos then
1264 pos_mass = PositionListMass(item_data.pos)
1265 TidyPositionList(item_data.pos)
1268 local drop_mass = 0
1269 if item_data.drop then
1270 drop_mass = DropListMass(item_data.drop)
1271 TidyDropList(locale, item_data.drop)
1274 local contained_mass = 0
1275 if item_data.contained then
1276 contained_mass = DropListMass(item_data.contained)
1277 TidyContainedList(locale, item_data.contained)
1280 if drop_mass+contained_mass > pos_mass then
1281 item_data.pos = nil
1282 if item_data.drop then
1283 for monster, count in pairs(item_data.drop) do
1284 GetObjective(locale, "monster", monster).quest = true
1288 if item_data.contained then
1289 for item, count in pairs(item_data.contained) do
1290 GetObjective(locale, "item", item).quest = true
1293 else
1294 item_data.drop = nil
1295 item_data.contained = nil
1298 if not item_data.pos and not item_data.drop then
1299 quest_data.item[item] = nil
1300 if not next(quest_data.item, nil) then
1301 quest_data.item = nil
1303 if not quest_data.finish and not quest_data.pos then
1304 RemoveQuestByData(quest_data)
1311 print("Processing objectives ", locale)
1313 for category, objectives in pairs(l.objective) do
1314 local delete_category = true
1315 for name, objective in pairs(objectives) do
1316 if CollapseObjective(locale, objective) then
1317 objectives[name] = nil
1318 else
1319 delete_category = false
1322 if delete_category then l.objective[category] = nil end
1325 local function IgnoreItem(start, dest, hash_list, distance, count)
1326 return hash_list.interrupt_count == 0 and dest == "Shattered Sun Staging Area" and (start == "Light's Hope Chapel, Eastern Plaguelands" or start == "Menethil Harbor, Wetlands" or start == "Ironforge, Dun Morogh" or start == "Stormwind, Elwynn")
1329 if l.flight_routes then
1330 for faction, start_list in pairs(l.flight_routes) do
1331 local delete_faction = true
1332 for start, dest_list in pairs(start_list) do
1333 local delete_start = true
1334 for dest, hash_list in pairs(dest_list) do
1335 local delete_dest = true
1337 local delete_hashes = {}
1339 for hash, value in pairs(hash_list) do
1340 if type(value) == "table" and hash ~= "interrupt_count" and hash ~= "no_interrupt_count" then
1341 local count = #value
1343 hash_list[hash] = CollapseAverage(value)
1345 if IgnoreItem(start, dest, hash_list, hash_list[hash], count) then
1346 print(string.format("Deleting path: %s to %s, value %f, with interrupt/nointerrupt %d %d", start, dest, hash_list[hash], hash_list.interrupt_count or 0, hash_list.no_interrupt_count or 0))
1347 table.insert(delete_hashes, hash)
1350 delete_dest = false -- We leave this out here because it *is* a valid flight path, we just don't have valid information for it
1351 elseif value == true and hash == 0 then
1352 delete_dest = false
1356 for k, v in pairs(delete_hashes) do
1357 hash_list[v] = nil
1360 hash_list.interrupt_count = nil
1361 hash_list.no_interrupt_count = nil
1363 if delete_dest then
1364 dest_list[dest] = nil
1365 else
1366 delete_start = false
1369 if delete_start then
1370 start_list[start] = nil
1371 else
1372 delete_faction = false
1375 if delete_faction then
1376 l.flight_routes[faction] = nil
1382 local old_data = StaticData
1383 StaticData = {}
1384 return old_data