update repo
[QuestHelper.git] / Development / compiler.lua
blob338f516f3d1310d7017d261f0dca686b07777f49
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 AddFlightRoute(locale, faction, start, destination, hash, value)
379 if ValidFaction(faction) and
380 type(start) == "string" and
381 type(destination) == "string" and
382 type(hash) == "number" and
383 ((value == true and hash == 0) or (type(value) == "number" and value > 0)) then
384 local l = StaticData[locale]
385 if not l then
386 l = {}
387 StaticData[locale] = l
389 local faction_list = l.flight_routes
390 if not faction_list then
391 faction_list = {}
392 l.flight_routes = faction_list
394 local start_list = faction_list[faction]
395 if not start_list then
396 start_list = {}
397 faction_list[faction] = start_list
399 local end_list = start_list[start]
400 if not end_list then
401 end_list = {}
402 start_list[start] = end_list
404 local hash_list = end_list[destination]
405 if not hash_list then
406 hash_list = {}
407 end_list[destination] = hash_list
409 if value == true then
410 hash_list[hash] = hash_list[hash] or true
411 else
412 local average = hash_list[hash]
413 if type(average) ~= "table" then
414 average = CreateAverage()
415 hash_list[hash] = average
417 AppendToAverage(average, value)
422 local function addVendor(list, npc)
423 for _, existing in ipairs(list) do
424 if existing == npc then
425 return
429 table.insert(list, npc)
432 local function addVendors(list, to_add)
433 if not list then list = {} end
435 for _, npc in ipairs(to_add) do
436 if not ignored[npc] then
437 addVendor(list, npc)
441 return list
444 local function AddObjective(locale, category, name, objective)
445 if type(category) == "string"
446 and type(name) == "string"
447 and not ignored[name]
448 and type(objective) == "table" then
449 local o = GetObjective(locale, category, name)
451 if objective.quest == true then o.quest = true end
453 if type(objective.pos) == "table" then
454 if not o.pos then o.pos = {} end
455 MergePositionLists(o.pos, objective.pos)
458 if category == "monster" then
459 if type(objective.id) == "number" then
460 local wdm = WoWData.npc[objective.id]
461 if not wdm then
462 wdm = {name={}}
463 WoWData.npc[objective.id] = wdm
465 wdm.name[locale] = name
468 if type(objective.looted) == "number" and objective.looted >= 1 then
469 o.looted = (o.looted or 0) + objective.looted
471 if ValidFaction(objective.faction) then
472 o.faction = objective.faction
474 elseif category == "item" then
475 if type(objective.id) == "number" then
476 local wdi = WoWData.item[objective.id]
477 if not wdi then
478 wdi = {name={}}
479 WoWData.item[objective.id] = wdi
481 wdi.name[locale] = name
484 if type(objective.opened) == "number" and objective.opened >= 1 then
485 o.opened = (o.opened or 0) + objective.opened
487 if type(objective.vendor) == "table" then
488 o.vendor = addVendors(o.vendor, objective.vendor)
490 if type(objective.drop) == "table" then
491 if not o.drop then o.drop = {} end
492 for monster, count in pairs(objective.drop) do
493 if type(monster) == "string" and not ignored[monster] and type(count) == "number" then
494 o.drop[monster] = (o.drop[monster] or 0) + count
498 if type(objective.contained) == "table" then
499 if not o.contained then o.contained = {} end
500 for item, count in pairs(objective.contained) do
501 if type(item) == "string" and type(count) == "number" then
502 o.contained[item] = (o.contained[item] or 0) + count
510 local function CollapseQuest(locale, quest)
511 local name_score = quest.finish and DropListMass(quest.finish) or 0
512 local pos_score = quest.pos and PositionListMass(quest.pos)*0.25 or 0
514 if name_score > pos_score then
515 quest.finish = CollapseDropList(quest.finish)
516 quest.pos = nil
517 else
518 quest.finish = nil
519 if quest.pos then
520 TidyPositionList(quest.pos)
524 if quest.item and not next(quest.item) then
525 quest.item = nil
528 if quest.finish then
529 -- This NPC is for a quest. Need to know them.
530 GetObjective(locale, "monster", quest.finish).quest = true
533 return quest.pos == nil and quest.finish == nil and quest.item == nil
536 local function CollapseObjective(locale, objective)
537 if not objective.quest then return true end
538 objective.quest = nil
540 if objective.vendor and not next(objective.vendor, nil) then objective.vendor = nil end
542 if objective.pos and (PositionListMass(objective.pos) >
543 ((objective.drop and DropListMass(objective.drop) or 0) +
544 (objective.contained and DropListMass(objective.contained) or 0))) then
545 objective.drop = nil
546 objective.contained = nil
548 TidyPositionList(objective.pos)
550 if not next(objective.pos, nil) then
551 objective.pos = nil
553 else
554 objective.pos = nil
556 if objective.drop and not next(objective.drop) then objective.drop = nil end
557 if objective.contained and not next(objective.contained) then objective.contained = nil end
560 if objective.looted then
561 objective.looted = math.max(1, math.ceil(objective.looted))
564 if objective.opened then
565 objective.opened = math.max(1, math.ceil(objective.opened))
568 if objective.vendor and next(objective.vendor) then
569 table.sort(objective.vendor)
570 else
571 objective.vendor = nil
574 return objective.drop == nil and objective.contained == nil and objective.pos == nil and objective.vendor == nil
577 local function AddInputData(data, pairfrequencies)
578 if data.QuestHelper_StaticData then
579 -- Importing a static data file.
580 local static = data.QuestHelper_StaticData
581 data.QuestHelper_StaticData = nil
583 for locale, info in pairs(static) do
584 data.QuestHelper_Locale = locale
585 data.QuestHelper_Quests = info.quest
586 data.QuestHelper_Objectives = info.objective
587 data.QuestHelper_FlightRoutes = info.flight_routes
588 data.QuestHelper_FlightInstructors = info.flight_instructors
590 for cat, list in pairs(data.QuestHelper_Objectives) do
591 for name, info in pairs(list) do
592 info.quest = true
596 AddInputData(data)
599 return
602 QuestHelper_UpgradeDatabase(data)
604 if type(data.QuestHelper_Locale) == "string" then
605 local locale = data.QuestHelper_Locale
607 local seen_pairs = {}
609 if type(data.QuestHelper_Quests) == "table" then for version, package in pairs(data.QuestHelper_Quests) do
610 seen_pairs[version] = true
611 if AuthorizedVersion(version) and type(package) == "table" then for faction, levels in pairs(package) do
612 if type(levels) == "table" then for level, quest_list in pairs(levels) do
613 if type(quest_list) == "table" then for quest_name, quest_data in pairs(quest_list) do
614 AddQuest(locale, faction, level, quest_name, quest_data)
615 end end
616 end end
617 end end
618 end end
620 local function PreWrath(ver)
621 return ver:sub(1,1) ~= '3'
624 if type(data.QuestHelper_Objectives) == "table" then for version, package in pairs(data.QuestHelper_Objectives) do
625 seen_pairs[version] = true
626 if AuthorizedVersion(version) and type(package) == "table" then for category, objectives in pairs(package) do
627 if type(objectives) == "table" and PreWrath(version) then for name, objective in pairs(objectives) do
628 if type(objective) == "table" and objective.pos and type(objective.pos) == "table" then
629 for i, pos in pairs(objective.pos) do
630 QuestHelper_ConvertCoordsToWrath(pos, true)
633 end end
634 if type(objectives) == "table" then for name, objective in pairs(objectives) do
635 AddObjective(locale, category, name, objective)
636 end end
637 end end
638 end end
640 if type(data.QuestHelper_FlightInstructors) == "table" then for version, package in pairs(data.QuestHelper_FlightInstructors) do
641 seen_pairs[version] = true
642 if AuthorizedVersion(version) and type(package) == "table" then for faction, list in pairs(package) do
643 if type(list) == "table" then for location, npc in pairs(list) do
644 AddFlightInstructor(locale, faction, location, npc)
645 end end
646 end end
647 end end
649 if type(data.QuestHelper_FlightRoutes) == "table" then for version, package in pairs(data.QuestHelper_FlightRoutes) do
650 seen_pairs[version] = true
651 if AuthorizedVersion(version) and type(package) == "table" then for faction, start_list in pairs(package) do
652 if type(start_list) == "table" then for start, destination_list in pairs(start_list) do
653 if type(destination_list) == "table" then for destination, hash_list in pairs(destination_list) do
654 if type(hash_list) == "table" then for hash, value in pairs(hash_list) do
655 AddFlightRoute(locale, faction, start, destination, hash, value)
656 end end
657 end end
658 end end
659 end end
660 end end
662 for k in pairs(seen_pairs) do
663 pairfrequencies[k] = (pairfrequencies[k] or 0) + 1
668 local function QuestItemsAreSimilar(item, quest_list)
669 -- TODO: Write this function. Should make sure all the quests get item from the same place.
670 return #quest_list <= 1
673 local function RemoveQuestByData(data)
674 for locale, l in pairs(StaticData) do
675 for faction, levels in pairs(l.quest) do
676 for level, quest_list in pairs(levels) do
677 for quest, quest_data in pairs(quest_list) do
678 if data == quest_data then
679 if quest_data.alt then
680 local alt = quest_data.alt
681 local hash = next(alt, nil)
682 local quest_data2 = alt[hash]
683 alt[hash] = nil
684 quest_list[quest] = quest_data2
685 if next(alt, nil) then
686 quest_data2.alt = alt
687 quest_data2.hash = hash
689 else
690 quest_list[quest] = nil
692 elseif quest_data.alt then
693 for hash, quest_data2 in pairs(quest_data.alt) do
694 if data == quest_data2 then
695 quest_data.alt[hash] = nil
696 break
699 if not next(quest_data.alt) then
700 quest_data.alt = nil
701 quest_data.hash = nil
705 if not next(levels[level], nil) then levels[level] = nil end
707 if not next(l.quest[faction], nil) then l.quest[faction] = nil end
712 function CompileInputFile(filename, pairfrequencies)
713 local data_loader = loadfile(filename)
714 if data_loader then
715 local data = {}
716 setfenv(data_loader, data)
717 data_loader()
718 AddInputData(data, pairfrequencies)
719 else
720 print("'"..filename.."' couldn't be loaded!")
724 function handleTranslations()
725 for locale, l in pairs(StaticData) do
726 if l.objective then
727 local item_map = {}
728 local monster_map = {}
729 local quest_map = {}
731 for id, data in pairs(WoWData.item) do
732 if data.name[locale] then
733 item_map[data.name[locale]] = id
737 for id, data in pairs(WoWData.npc) do
738 if data.name[locale] then
739 monster_map[data.name[locale]] = id
743 for id, data in pairs(WoWData.quest) do
744 if data.name[locale] then
745 quest_map[data.level.."/"..data.hash[locale].."/"..data.name[locale]] = id
749 local function item2dat(data, item)
750 if not item then item = {} end
752 item.quest = item.quest or data.quest
754 if data.opened then
755 item.opened = (item.opened or 0) + data.opened
756 data.opened = nil
759 if data.pos then
760 if not item.pos then
761 item.pos = data.pos
762 else
763 MergePositionLists(item.pos, data.pos)
766 data.pos = nil
769 if data.vendor then
770 if not item.vendor then
771 item.vendor = {}
774 for i, npc in ipairs(data.vendor) do
775 local id = monster_map[npc]
776 if id then
777 addVendor(item.vendor, id)
782 if data.drop then
783 if not item.drop then
784 item.drop = {}
787 for name, count in pairs(data.drop) do
788 local id = monster_map[name]
789 if id then
790 item.drop[id] = (item.drop[id] or 0) + count
791 data.drop[name] = nil
796 if data.contained then
797 if not item.contained then
798 item.contained = {}
801 for name, count in pairs(data.contained) do
802 local id = item_map[name]
803 if id then
804 item.contained[id] = (item.contained[id] or 0) + count
805 data.contained[name] = nil
810 return item
813 if l.objective.item then for name, data in pairs(l.objective.item) do
814 local id = item_map[name]
815 if id then
816 item2dat(data, WoWData.item[id])
817 local item = WoWData.item[id]
819 end end
821 if l.objective.monster then for name, data in pairs(l.objective.monster) do
822 local id = monster_map[name]
823 if id then
824 local npc = WoWData.npc[id]
826 npc.quest = npc.quest or data.quest
827 npc.faction = npc.faction or data.faction
829 if data.looted then
830 npc.looted = (npc.looted or 0) + data.looted
831 data.looted = nil
834 if data.pos then
835 if not npc.pos then
836 npc.pos = data.pos
837 else
838 MergePositionLists(npc.pos, data.pos)
841 data.pos = nil
844 end end
846 local function q2static(faction, name, data)
847 local id = quest_map[name]
848 if id then
849 local quest = WoWData.quest[id]
850 quest.faction[faction] = true
852 print("Copying Quest", faction, name)
854 if data.finish and next(data.finish) then
855 quest.finish = monster_map[CollapseDropList(data.finish)] or quest.finish
858 if data.item then
859 quest.item = quest.item or {}
861 for name, idata in pairs(data.item) do
862 local id = item_map[name]
863 if id then
864 quest.item[id] = item2dat(idata, quest.item[id])
871 if l.quest then for faction, fqlist in pairs(l.quest) do
872 for level, qlist in pairs(fqlist) do
873 for name, qdata in pairs(qlist) do
874 if qdata.hash then
875 q2static(faction, level.."/"..qdata.hash.."/"..name, qdata)
878 if qdata.alt then for hash, qdata2 in pairs(qdata.alt) do
879 q2static(faction, level.."/"..hash.."/"..name, qdata2)
880 end end
883 end end
887 for id, item in pairs(WoWData.item) do
888 for locale, name in pairs(item.name) do
889 print("Adding item ", locale, name)
890 local data = GetObjective(locale, "item", name)
892 data.quest = data.quest or item.quest
894 if item.opened then
895 data.opened = item.opened
898 if item.pos then
899 data.pos = data.pos or {}
900 MergePositionLists(data.pos, item.pos)
903 if item.vendor then
904 data.vendor = data.vendor or {}
906 for i, id in ipairs(item.vendor) do
907 local name = WoWData.npc[id] and WoWData.npc[id].name[locale]
908 if name then
909 addVendor(data.vendor, name)
914 if item.drop then
915 data.drop = data.drop or {}
916 for id, count in pairs(item.drop) do
917 local name = WoWData.npc[id] and WoWData.npc[id].name[locale]
918 if name then
919 data.drop[name] = (data.drop[name] or 0) + count
924 if item.contained then
925 data.contained = data.contained or {}
926 for id, count in pairs(item.contained) do
927 local name = WoWData.item[id] and WoWData.item[id].name[locale]
928 if name then
929 data.contained[name] = (data.contained[name] or 0) + count
936 for id, npc in pairs(WoWData.npc) do
937 for locale, name in pairs(npc.name) do
938 print("Adding NPC ", locale, name)
939 local data = GetObjective(locale, "monster", name)
941 data.quest = data.quest or npc.quest
942 data.faction = data.faction or npc.faction
944 if npc.looted then
945 data.looted = npc.looted
948 if npc.pos then
949 data.pos = data.pos or {}
950 MergePositionLists(data.pos, npc.pos)
955 for id, quest in pairs(WoWData.quest) do
956 for faction in pairs(quest.faction) do
957 for locale, name in pairs(quest.name) do
958 print("Adding Quest ", locale, faction, quest.level, quest.hash[locale], name)
959 local data = GetQuest(locale, faction, quest.level, name, quest.hash[locale])
961 if quest.finish then
962 local fname = WoWData.npc[quest.finish] and WoWData.npc[quest.finish].name[locale]
963 if fname then
964 data.finish = {[fname] = 1}
968 if quest.item then
969 for id, item in pairs(quest.item) do
970 local iname = WoWData.item[id] and WoWData.item[id].name[locale]
971 if iname then
972 local qdata = data
974 if not qdata.item then qdata.item = {} end
975 local data = qdata.item[iname] or {}
976 qdata.item[iname] = data
978 if item.pos then
979 data.pos = data.pos or {}
980 MergePositionLists(data.pos, item.pos)
983 if item.drop then
984 data.drop = data.drop or {}
985 for id, count in pairs(item.drop) do
986 local name = WoWData.npc[id] and WoWData.npc[id].name[locale]
987 if name then
988 data.drop[name] = (data.drop[name] or 0) + count
993 if item.contained then
994 data.contained = data.contained or {}
995 for id, count in pairs(item.contained) do
996 local name = WoWData.item[id] and WoWData.item[id].name[locale]
997 if name then
998 data.contained[name] = (data.contained[name] or 0) + count
1009 -- TODO: quests.
1012 function CompileFinish()
1013 handleTranslations()
1014 print("Finished translations")
1016 for locale, l in pairs(StaticData) do
1017 local quest_item_mass = {}
1018 local quest_item_quests = {}
1020 local function WatchQuestItems(quest)
1021 if quest.finish then GetObjective(locale, "monster", quest.finish).quest = true end
1023 if quest.item then
1024 for item, data in pairs(quest.item) do
1025 quest_item_mass[item] = (quest_item_mass[item] or 0)+
1026 (data.drop and DropListMass(data.drop) or 0)+
1027 (data.contained and DropListMass(data.contained) or 0)+
1028 (data.pos and PositionListMass(data.pos) or 0)
1030 quest_item_quests[item] = quest_item_quests[item] or {}
1031 table.insert(quest_item_quests[item], quest)
1036 print("Processing quests ", locale)
1038 for faction, levels in pairs(l.quest) do
1039 local delete_faction = true
1040 for level, quest_list in pairs(levels) do
1041 local delete_level = true
1042 for quest, quest_data in pairs(quest_list) do
1043 if quest_data.alt then
1044 for hash, quest_data2 in pairs(quest_data.alt) do
1045 if CollapseQuest(locale, quest_data2) then
1046 quest_data.alt[hash] = nil
1047 else
1048 quest_data2.hash = nil
1049 WatchQuestItems(quest_data2)
1053 if not next(quest_data.alt, nil) then
1054 quest_data.alt = nil
1055 quest_data.hash = nil
1059 if CollapseQuest(locale, quest_data) then
1060 if quest_data.alt then
1061 local alt = quest_data.alt
1062 local hash = next(alt, nil)
1063 local quest_data2 = alt[hash]
1064 alt[hash] = nil
1065 quest_list[quest] = quest_data2
1066 if next(alt, nil) then
1067 quest_data2.alt = alt
1068 quest_data2.hash = hash
1071 delete_level = false
1072 else
1073 quest_list[quest] = nil
1075 else
1076 WatchQuestItems(quest_data)
1077 delete_level = false
1081 if delete_level then levels[level] = nil else delete_faction = false end
1083 if delete_faction then l.quest[faction] = nil end
1086 if l.flight_instructors then for faction, list in pairs(l.flight_instructors) do
1087 for area, npc in pairs(list) do
1088 -- Need to remember the flight instructors, for use in routing.
1089 GetObjective(locale, "monster", npc).quest = true
1091 end end
1093 for item, quest_list in pairs(quest_item_quests) do
1094 -- If all the items are similar, then we don't want quest item entries for them,
1095 -- we want to use the gobal item objective instead.
1096 if QuestItemsAreSimilar(item, quest_list) then
1097 quest_item_mass[item] = 0
1101 print("Processing quest items ", locale)
1102 -- Will go through the items and either delete them, or merge the quest items into them, and then
1103 -- mark the relevent monsters as being quest objectives.
1104 if l.objective["item"] then
1105 for name, objective in pairs(l.objective["item"]) do
1106 -- If this is a quest item, mark anything that drops it as being a quest monster.
1107 local quest_mass = quest_item_mass[name] or 0
1109 if objective.vendor and next(objective.vendor, nil) then
1110 quest_mass = 0 -- If the item can be bought, then it shouldn't be quest specific.
1113 local item_mass = (objective.pos and PositionListMass(objective.pos) or 0)+
1114 (objective.drop and DropListMass(objective.drop) or 0)+
1115 (objective.contained and DropListMass(objective.contained) or 0)
1117 if quest_mass > item_mass then
1118 -- Delete this item, we'll deal with the the quests using the items after.
1119 l.objective["item"][name] = nil
1120 else
1121 if quest_item_quests[name] then
1122 for i, quest_data in pairs(quest_item_quests[name]) do
1123 local quest_item = quest_data.item and quest_data.item[name]
1124 if quest_item then
1125 if quest_item.drop then
1126 if not objective.drop then objective.drop = {} end
1127 MergeDropLists(objective.drop, quest_item.drop)
1130 if quest_item.contained then
1131 if not objective.contained then objective.contained = {} end
1132 MergeDropLists(objective.contained, quest_item.contained)
1135 if quest_item.pos then
1136 if not objective.pos then objective.pos = {} end
1137 MergePositionLists(objective.pos, quest_item.pos)
1140 quest_data.item[name] = nil
1141 if not next(quest_data.item, nil) then
1142 quest_data.item = nil
1144 if not quest_data.finish and not quest_data.pos then
1145 RemoveQuestByData(quest_data)
1151 quest_item_quests[name] = nil
1152 objective.quest = true
1155 if objective.quest then
1156 if objective.drop then
1157 TidyDropList(locale, objective.drop)
1158 for monster, count in pairs(objective.drop) do
1159 GetObjective(locale, "monster", monster).quest = true
1163 if objective.contained then
1164 TidyContainedList(locale, objective.contained)
1165 for item, count in pairs(objective.contained) do
1166 GetObjective(locale, "item", item).quest = true
1170 if objective.vendor then
1171 for i, npc in ipairs(objective.vendor) do
1172 GetObjective(locale, "monster", npc).quest = true
1180 -- For any quest items that didn't get handled above, we'll clean them up and leave them be.
1181 for item, quest_list in pairs(quest_item_quests) do
1182 for _, quest_data in ipairs(quest_list) do
1183 -- Item should already exist in quest, not going to check.
1184 local item_data = quest_data.item[item]
1186 local pos_mass = 0
1187 if item_data.pos then
1188 pos_mass = PositionListMass(item_data.pos)
1189 TidyPositionList(item_data.pos)
1192 local drop_mass = 0
1193 if item_data.drop then
1194 drop_mass = DropListMass(item_data.drop)
1195 TidyDropList(locale, item_data.drop)
1198 local contained_mass = 0
1199 if item_data.contained then
1200 contained_mass = DropListMass(item_data.contained)
1201 TidyContainedList(locale, item_data.contained)
1204 if drop_mass+contained_mass > pos_mass then
1205 item_data.pos = nil
1206 if item_data.drop then
1207 for monster, count in pairs(item_data.drop) do
1208 GetObjective(locale, "monster", monster).quest = true
1212 if item_data.contained then
1213 for item, count in pairs(item_data.contained) do
1214 GetObjective(locale, "item", item).quest = true
1217 else
1218 item_data.drop = nil
1219 item_data.contained = nil
1222 if not item_data.pos and not item_data.drop then
1223 quest_data.item[item] = nil
1224 if not next(quest_data.item, nil) then
1225 quest_data.item = nil
1227 if not quest_data.finish and not quest_data.pos then
1228 RemoveQuestByData(quest_data)
1235 print("Processing objectives ", locale)
1237 for category, objectives in pairs(l.objective) do
1238 local delete_category = true
1239 for name, objective in pairs(objectives) do
1240 if CollapseObjective(locale, objective) then
1241 objectives[name] = nil
1242 else
1243 delete_category = false
1246 if delete_category then l.objective[category] = nil end
1249 local function IgnoreItem(start, dest, hash_list, distance, count)
1250 return 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")
1253 if l.flight_routes then
1254 for faction, start_list in pairs(l.flight_routes) do
1255 local delete_faction = true
1256 for start, dest_list in pairs(start_list) do
1257 local delete_start = true
1258 for dest, hash_list in pairs(dest_list) do
1259 local delete_dest = true
1261 local delete_hashes = {}
1263 for hash, value in pairs(hash_list) do
1264 if type(value) == "table" and hash ~= "interrupt_count" and hash ~= "no_interrupt_count" then
1265 local count = #value
1267 hash_list[hash] = CollapseAverage(value)
1269 if IgnoreItem(start, dest, hash_list, hash_list[hash], count) then
1270 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))
1271 table.insert(delete_hashes, hash)
1274 delete_dest = false -- We leave this out here because it *is* a valid flight path, we just don't have valid information for it
1275 elseif value == true and hash == 0 then
1276 delete_dest = false
1280 for k, v in pairs(delete_hashes) do
1281 hash_list[v] = nil
1284 hash_list.interrupt_count = nil
1285 hash_list.no_interrupt_count = nil
1287 if delete_dest then
1288 dest_list[dest] = nil
1289 else
1290 delete_start = false
1293 if delete_start then
1294 start_list[start] = nil
1295 else
1296 delete_faction = false
1299 if delete_faction then
1300 l.flight_routes[faction] = nil
1306 local old_data = StaticData
1307 StaticData = {}
1308 return old_data