tweak the nag system slightly
[QuestHelper.git] / nag.lua
blob397d04084c6531a7c606dc562a74ddc65b31e716
1 QuestHelper_File["nag.lua"] = "Development Version"
2 QuestHelper_Loadtime["nag.lua"] = GetTime()
4 local function FindStaticQuest(faction, level, name, hash)
5 local data = QuestHelper_StaticData[QuestHelper.locale]
6 data = data and data.quest
7 data = data and data[faction]
8 data = data and data[level]
9 data = data and data[name]
10 if data and data.hash and data.hash ~= hash then
11 data = data.alt and data.alt[hash]
12 end
13 return data
14 end
16 local function FindStaticObjective(cat, name)
17 local data = QuestHelper_StaticData[QuestHelper.locale]
18 data = data and data.objective
19 data = data and data[cat]
20 return data and data[name]
21 end
23 local function ListUpdated(list, static, compare, weight)
24 if not list then return false end
25 if not static then return true end
27 local high = 0
29 if #static >= 5 then
30 return false
31 end
33 for _, b in ipairs(static) do
34 high = math.max(high, weight(b))
35 end
37 for _, a in ipairs(list) do
38 local found = false
40 for _, b in ipairs(static) do
41 if weight(a) < high*0.2 or compare(a, b) then
42 found = true
43 break
44 end
45 end
47 if not found then return true end
48 end
49 return false
50 end
52 local function PositionWeight(a)
53 return a[4]
54 end
56 local function PositionCompare(a, b)
57 return a[1] == b[1] and
58 (a[2]-b[2])*(a[2]-b[2])+(a[3]-b[3])*(a[3]-b[3]) < 0.05*0.05
59 end
61 local function VendorCompare(a, b)
62 return a == b
63 end
65 local function VendorWeight(a)
66 return 1
67 end
69 local function PositionListUpdated(list, static)
70 return ListUpdated(list, static, PositionCompare, PositionWeight)
71 end
73 local function VendorListUpdated(list, static)
74 return ListUpdated(list, static, VendorCompare, VendorWeight)
75 end
77 local function DropListUpdated(list, static)
78 if not list then return false end
79 if not static then return next(list, nil) ~= nil end
80 local high = 0
82 for name in pairs(list) do
83 local monster_obj = FindStaticObjective("monster", name)
84 if monster_obj and monster_obj.looted and monster_obj.looted > 0 then
85 high = math.max(high, (static[name] or 0)/monster_obj.looted)
86 end
87 end
89 for name, v in pairs(list) do
90 local monster_obj1 = QuestHelper:GetObjective("monster", name)
91 local monster_obj2 = FindStaticObjective("monster", name)
93 local looted = math.ceil((monster_obj1.o.looted or 0)+((monster_obj2 and monster_obj2.looted) or 0))
94 if looted > 0 then
95 v = math.max(1, math.floor(v))/looted
96 if v > high*0.2 and not static[name] then return true end
97 end
98 end
99 return false
102 local function DropListMass(list)
103 if not list then return 0 end
104 local mass = 0
105 for item, count in pairs(list) do
106 mass = mass + count
108 return mass
111 local function PositionListMass(list)
112 if not list then return 0 end
113 local mass = 0
114 for _, pos in ipairs(list) do
115 mass = mass + pos[4]
117 return mass
120 local function CompareStaticQuest(info, faction, level, name, hash, data, verbose)
121 local static = FindStaticQuest(faction, level, name, hash)
123 if not static then
124 if data.finish or data.pos then
125 if verbose then QuestHelper:TextOut("Quest "..QuestHelper:HighlightText(name).." was missing.") end
126 info.new.quest = (info.new.quest or 0) + 1
128 return
131 local updated = false
133 if data.finish and data.finish ~= static.finish then
134 if verbose then QuestHelper:TextOut("Quest "..QuestHelper:HighlightText(name).." was missing finish NPC "..QuestHelper:HighlightText(data.finish)..".") end
135 updated = true
136 elseif not static.finish and PositionListUpdated(data.pos, static.pos) then
137 if verbose then QuestHelper:TextOut("Quest "..QuestHelper:HighlightText(name).." was missing finish location.") end
138 updated = true
139 elseif data.item then
140 for item_name, item in pairs(data.item) do
141 local static_item = (static.item and static.item[item_name]) or FindStaticObjective("item", item_name)
143 if not static_item then
144 if verbose then QuestHelper:TextOut("Quest "..QuestHelper:HighlightText(name).." was missing item "..QuestHelper:HighlightText(item_name)..".") end
145 updated = true
146 break
147 elseif item.drop then
148 if DropListUpdated(item.drop, static_item.drop) then
149 if DropListMass(item.drop) > PositionListMass(static_item.pos) then
150 if verbose then QuestHelper:TextOut("Quest "..QuestHelper:HighlightText(name).." was missing drop for item "..QuestHelper:HighlightText(item_name)..".") end
151 updated = true
152 break
155 elseif item.pos and not static_item.drop and PositionListUpdated(item.pos, static_item.pos) then
156 if verbose then QuestHelper:TextOut("Quest "..QuestHelper:HighlightText(name).." was missing position for item "..QuestHelper:HighlightText(item_name)..".") end
157 updated = true
158 break
163 if updated then
164 info.update.quest = (info.update.quest or 0)+1
168 local function CompareStaticObjective(info, cat, name, data, verbose)
169 if info.quest then
170 local static = FindStaticObjective(cat, name)
171 if not static then
172 if data.pos or data.drop or data.vendor then
173 if verbose then QuestHelper:TextOut(string.gsub(cat, "^(.)", string.upper).." "..QuestHelper:HighlightText(name).." was missing.") end
174 info.new[cat.."_obj"] = (info.new[cat.."_obj"] or 0)+1
176 return
179 local updated = false
181 if data.vendor then
182 updated = VendorListUpdated(data.vendor, static.vendor)
183 if updated and verbose then QuestHelper:TextOut(string.gsub(cat, "^(.)", string.upper).." "..QuestHelper:HighlightText(name).." was missing vendor.") end
184 elseif data.drop and not static.vendor then
185 updated = DropListUpdated(data.drop, static.drop) and DropListMass(data.drop) > PositionListMass(static.pos)
186 if updated and verbose then QuestHelper:TextOut(string.gsub(cat, "^(.)", string.upper).." "..QuestHelper:HighlightText(name).." was missing monster drop.") end
187 elseif data.pos and not static.vendor and not static.drop then
188 if updated and verbose then QuestHelper:TextOut(Qstring.gsub(cat, "^(.)", string.upper).." "..QuestHelper:HighlightText(name).." was missing position.") end
189 updated = PositionListUpdated(data.pos, static.pos)
192 if updated then
193 info.update[cat.."_obj"] = (info.update[cat.."_obj"] or 0)+1
198 function QuestHelper:Nag(cmd)
199 local verbose, local_only = false, true
201 if QuestHelper_IsPolluted() then
202 self:TextOut(QHFormat("NAG_POLLUTED"))
203 return
206 if cmd then
207 if string.find(cmd, "verbose") then verbose = true end
208 if string.find(cmd, "all") then local_only = false end
211 local info =
213 new = {},
214 update = {}
217 for version, data in pairs(QuestHelper_Quests) do
218 for faction, level_list in pairs(data) do
219 if not local_only or faction == self.faction then
220 for level, name_list in pairs(level_list) do
221 for name, data in pairs(name_list) do
222 CompareStaticQuest(info, faction, level, name, data.hash, data, verbose)
223 if data.alt then
224 for hash, data in pairs(data.alt) do
225 CompareStaticQuest(info, faction, level, name, hash, data, verbose)
234 for version, data in pairs(QuestHelper_Objectives) do
235 for cat, name_list in pairs(data) do
236 for name, obj in pairs(name_list) do
237 CompareStaticObjective(info, cat, name, obj, verbose)
242 for version, data in pairs(QuestHelper_FlightInstructors) do
243 for faction, location_list in pairs(data) do
244 if not local_only or faction == self.faction then
245 for location, npc in pairs(location_list) do
246 local data = QuestHelper_StaticData[self.locale]
247 data = data and data.flight_instructors
248 data = data and data[faction]
249 data = data and data[location]
251 if not data or data ~= npc then
252 if verbose then self:TextOut(QuestHelper:HighlightText(faction).." flight master "..QuestHelper:HighlightText(npc).." was missing.") end
253 info.new["fp"] = (info.new["fp"] or 0)+1
260 for version, data in pairs(QuestHelper_FlightRoutes) do
261 for faction, start_list in pairs(data) do
262 if not local_only or faction == self.faction then
263 for start, dest_list in pairs(start_list) do
264 for dest, hash_list in pairs(dest_list) do
265 for hash, data in pairs(hash_list) do
266 if hash ~= "no_interrupt_count" and hash ~= "interrupt_count" then
267 local static = QuestHelper_StaticData[self.locale]
268 static = static and static.flight_routes
269 static = static and static[faction]
270 static = static and static[start]
271 static = static and static[dest]
272 static = static and static[hash]
274 if not static or static == true and type(data) == "number" then
275 if verbose then self:TextOut("Flight time from "..QuestHelper:HighlightText((select(3, string.find(start, "^(.*),")) or start)).." to "..QuestHelper:HighlightText((select(3, string.find(dest, "^(.*),")) or dest)).." was missing.") end
276 info.new["route"] = (info.new["route"] or 0)+1
286 local total = 0
288 for what, count in pairs(info.new) do
289 local what1 = count == 1 and QHText("NAG_SINGLE_"..string.upper(what)) or
290 QHFormat("NAG_MULTIPLE_"..string.upper(what), count)
292 total = total + count
293 local count2 = info.update[what]
294 if count2 then
295 total = total + count2
296 local what2 = count2 == 1 and QHText("NAG_SINGLE_"..string.upper(what)) or
297 QHFormat("NAG_MULTIPLE_"..string.upper(what), count2)
298 self:TextOut(QHFormat("NAG_MULTIPLE_NEW", what1, what2))
299 else
300 self:TextOut(QHFormat("NAG_SINGLE_NEW", what1))
304 for what, count in pairs(info.update) do
305 if not info.new[what] then
306 local what = count == 1 and QHText("NAG_SINGLE_"..string.upper(what)) or
307 QHFormat("NAG_MULTIPLE_"..string.upper(what), count)
308 total = total + count
309 self:TextOut(QHFormat("NAG_ADDITIONAL", what))
313 if total == 0 then
314 self:TextOut(QHText("NAG_NOT_NEW"))
315 else
316 self:TextOut(QHText("NAG_NEW"))
317 self:TextOut(QHText("NAG_INSTRUCTIONS"))
322 local day = 24 * 60 * 60
324 function QHNagInit()
325 if not QuestHelper_Pref.nag_next_time then
326 QuestHelper_Pref.nag_next_time = time() + 7 * day + 14 * day * math.random() -- at least a week, at most 3 weeks
327 QuestHelper_Pref.nag_type = "OFF"
330 if QuestHelper_Pref.nag_next_time < time() then
331 if QuestHelper_Pref.nag_type == "OFF" then
332 -- we now begin nagging for 72 hours
333 QuestHelper_Pref.nag_next_time = time() + 3 * day
334 QuestHelper_Pref.nag_type = "ON"
335 else
336 -- we now stop nagging for 1-2 weeks
337 QuestHelper_Pref.nag_next_time = time() + 7 * day + 7 * day * math.random()
338 QuestHelper_Pref.nag_type = "OFF"
342 return QuestHelper_Pref.nag_type == "ON"