-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathObjectives.lua
More file actions
480 lines (381 loc) · 11.3 KB
/
Copy pathObjectives.lua
File metadata and controls
480 lines (381 loc) · 11.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
_addon.name = 'Objectives'
_addon.author = 'Meliora'
_addon.version = '0.2.0'
_addon.commands = {'ob'}
local packets = require('packets')
local entries = windower.addon_path .. 'data/entries.lua'
local objectives = {}
local objective_ids = {}
local capture_enabled = false
local pending_accepts = {}
local roe = {
active = {},
complete = {},
max_count = 30
}
function log_message(message)
windower.add_to_chat(122, ('[%s] %s'):format(_addon.name, message))
end
local function load_objectives()
local ok, data = pcall(dofile, entries)
if ok and type(data) == 'table' then
objectives = data
objective_ids = {}
for _, entry in ipairs(objectives) do
if entry and entry.id then
objective_ids[tonumber(entry.id)] = entry
end
end
return true
end
objectives = {}
objective_ids = {}
log_message('failed to load roe entries')
return false
end
local function save_objectives()
local lines = {'return {'}
for index, entry in ipairs(objectives) do
local fields = {('id=%d'):format(entry.id), ('name=%q'):format(entry.name)}
if entry.category and entry.category ~= '' then
fields[#fields + 1] = ('category=%q'):format(entry.category)
else
fields[#fields + 1] = 'category=""'
end
lines[#lines + 1] = (' [%d] = {%s},'):format(index, table.concat(fields, ', '))
end
lines[#lines + 1] = '}, {"id", "name", "category"}'
local file = io.open(entries, 'w')
if not file then
log_message('failed to save names.lua')
return false
end
file:write(table.concat(lines, '\n'))
file:write('\n')
file:close()
return true
end
local function accept_roe(id)
id = tonumber(id)
if not id or roe.complete[id] or roe.active[id] then
return false
end
if id >= 4008 and id <= 4021 then
return false
end
local packet = packets.new('outgoing', 0x10C, {
['RoE Quest'] = id
})
packets.inject(packet)
return true
end
local function cancel_roe(id)
id = tonumber(id)
if not id then
return false
end
local progress = roe.active[id]
if progress == nil or progress ~= 0 then
return false
end
local packet = packets.new('outgoing', 0x10D, {
['RoE Quest'] = id
})
packets.inject(packet)
return true
end
local function sorted_objectives()
local entries = {}
for index, entry in ipairs(objectives) do
entries[#entries + 1] = {
index = index,
id = entry.id,
name = entry.name,
category = entry.category
}
end
table.sort(entries, function(left, right)
return left.id < right.id
end)
return entries
end
local function available_slots()
local count = 0
for _ in pairs(roe.active) do
count = count + 1
end
return roe.max_count - count
end
local function normalize_category(category)
if type(category) ~= 'string' then
return nil
end
category = category:lower():gsub('^%s+', ''):gsub('%s+$', '')
if category == '' then
return nil
end
return category
end
local function build_selected_list(category, cap_to_max)
local selected = {}
local normalized_category = normalize_category(category)
for _, entry in ipairs(objectives) do
local entry_category = normalize_category(entry.category)
if not normalized_category or entry_category == normalized_category then
selected[#selected + 1] = entry
end
end
if cap_to_max and not normalized_category and #selected > roe.max_count then
local capped = {}
for i = 1, roe.max_count do
capped[i] = selected[i]
end
return capped, normalized_category
end
return selected, normalized_category
end
local function count_set_candidates(selected, slot_limit)
local count = 0
for _, entry in ipairs(selected) do
local id = entry.id
if id and not roe.complete[id] and not roe.active[id] and not (id >= 4008 and id <= 4021) then
count = count + 1
if slot_limit and count >= slot_limit then
break
end
end
end
return count
end
local function count_unset_candidates(selected)
local count = 0
for _, entry in ipairs(selected) do
local progress = roe.active[entry.id]
if progress ~= nil and progress == 0 then
count = count + 1
end
end
return count
end
local function set_objectives(category)
if not load_objectives() then
return
end
local selected, normalized_category = build_selected_list(category, true)
if #selected == 0 then
if normalized_category then
log_message(('no RoE entries found for category "%s"'):format(normalized_category))
else
log_message('no RoE entries found in database')
end
return
end
local slots = available_slots()
if slots <= 0 then
log_message('no free RoE slots available')
return
end
local scope = normalized_category and (' for category "' .. normalized_category .. '"') or ''
local candidate_count = count_set_candidates(selected, slots)
if candidate_count <= 0 then
return
end
log_message(('Starting to set %d RoE objective(s)%s.'):format(candidate_count, scope))
local queued = 0
local skipped = 0
for _, entry in ipairs(selected) do
if queued >= slots then
break
end
local id = entry.id
if accept_roe(id) then
queued = queued + 1
coroutine.sleep(1.5)
else
skipped = skipped + 1
end
end
log_message('Finished setting RoE' .. scope .. '.')
end
local function unset_objectives(category)
if not load_objectives() then
return
end
local selected, normalized_category = build_selected_list(category, false)
if #selected == 0 then
if normalized_category then
log_message(('no RoE entries found for category "%s"'):format(normalized_category))
else
log_message('no RoE entries found in database')
end
return
end
local scope = normalized_category and (' for category "' .. normalized_category .. '"') or ''
local candidate_count = count_unset_candidates(selected)
if candidate_count <= 0 then
return
end
log_message(('Starting to unset %d RoE objective(s)%s.'):format(candidate_count, scope))
local queued = 0
local skipped = 0
for i = #selected, 1, -1 do
local id = selected[i].id
if cancel_roe(id) then
queued = queued + 1
coroutine.sleep(1.5)
else
skipped = skipped + 1
end
end
log_message('Finished unsetting RoE' .. scope .. '.')
end
local function process_incoming_chunk(id, data)
if id == 0x111 then
roe.active = {}
for i = 1, roe.max_count do
local offset = 5 + ((i - 1) * 4)
local roe_id, progress = data:unpack('b12b20', offset)
if roe_id > 0 then
roe.active[roe_id] = progress
end
end
return
end
if id == 0x112 then
local order = data:unpack('H', 133)
local bits = {data:unpack(('b1'):rep(1024), 4)}
for i, value in ipairs(bits) do
if value == 1 then
local roe_id = i + (1024 * order) - 1
roe.complete[roe_id] = true
end
end
end
end
local function record_id(id)
if not capture_enabled then
return
end
if not id or id == 0 then
return
end
if objective_ids[id] then
return
end
end
local function enqueue_pending_id(id)
if not capture_enabled or not id or id == 0 then
return
end
pending_accepts[#pending_accepts + 1] = id
end
local function extract_undertaken_name(text)
if type(text) ~= 'string' then
return nil
end
local prefix = 'You have undertaken '
if text:sub(1, #prefix) ~= prefix then
return nil
end
local name = text:sub(#prefix + 1)
name = name:gsub('[^\32-\126]', '')
name = name:gsub('^%s+', ''):gsub('%s+$', '')
name = name:match('([%w].*)') or name
name = name:match('^(.*[%w%)])') or name
name = name:gsub('%.%d+$', '')
name = name:gsub('%.$', '')
name = name:gsub('^%s+', ''):gsub('%s+$', '')
if name == '' then
return nil
end
return name
end
local function append_objective_mapping(id, name)
id = tonumber(id)
if not id or not name or name == '' then
return
end
local existing = objective_ids[id]
if existing then
return
end
local entry = {
id = id,
name = name,
category = ""
}
objectives[#objectives + 1] = entry
objective_ids[id] = entry
if save_objectives() then
log_message(('added entry: %d -> %s'):format(id, name))
end
end
load_objectives()
windower.register_event('outgoing chunk', function(id, original, modified, injected, blocked)
if blocked or injected or id ~= 0x10C then
return
end
local packet = packets.parse('outgoing', modified)
if not packet then
return
end
local roe_id = packet['RoE Quest']
record_id(roe_id)
enqueue_pending_id(roe_id)
end)
windower.register_event('incoming chunk', function(id, data)
process_incoming_chunk(id, data)
end)
windower.register_event('load', function()
local last_update = windower.packets.last_incoming(0x111)
if last_update then
process_incoming_chunk(0x111, last_update)
end
local last_log = windower.packets.last_incoming(0x112)
if last_log then
process_incoming_chunk(0x112, last_log)
end
end)
windower.register_event('incoming text', function(original, modified)
if not capture_enabled or #pending_accepts == 0 then
return
end
local name = extract_undertaken_name(modified) or extract_undertaken_name(original)
if not name then
return
end
local id = table.remove(pending_accepts, 1)
append_objective_mapping(id, name)
end)
windower.register_event('addon command', function(command, arg)
local cmd = command and command:lower() or 'help'
if cmd == 'set' then
set_objectives(arg)
return
end
if cmd == 'unset' then
unset_objectives(arg)
return
end
if cmd == 'capture' then
if arg then
local value = arg:lower()
if value == 'on' then
capture_enabled = true
elseif value == 'off' then
capture_enabled = false
else
log_message('usage: //ob capture')
return
end
else
capture_enabled = not capture_enabled
end
log_message('capture is now ' .. (capture_enabled and 'on' or 'off'))
return
end
log_message('Commands:')
log_message('ob set [category] -- Set [category] objectives. Skip category to set all')
log_message('ob unset [category] -- Unset [category] objectives. Skip category to unset all')
log_message('ob capture [category] -- Capture/add new RoE IDs to the entries file')
end)