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
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
|
--
-- obiaway v1.0.7
--
-- Copyright ©2013-2015, ReaperX, Bangerang
-- All rights reserved.
--
-- Redistribution and use in source and binary forms, with or without
-- modification, are permitted provided that the following conditions are met:
--
-- * Redistributions of source code must retain the above copyright
-- notice, this list of conditions and the following disclaimer.
-- * Redistributions in binary form must reproduce the above copyright
-- notice, this list of conditions and the following disclaimer in the
-- documentation and/or other materials provided with the distribution.
-- * Neither the name of obiaway nor the
-- names of its contributors may be used to endorse or promote products
-- derived from this software without specific prior written permission.
-- THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
-- ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-- WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
-- DISCLAIMED. IN NO EVENT SHALL ReaperX or Bangerang BE LIABLE FOR ANY
-- DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
-- (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
-- LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
-- ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-- (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
-- SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--
-- Puts elemental obi away when they are no longer needed due
-- to day/weather/storm effect change and gets elemental obi based
-- on same conditions. Uses itemizer addon.
--
-- The advantage of this system compared to moving obi back during
-- aftercast is that it avoids excessive inventory movement,
-- so malfunctions due to inventory filling up completely are
-- less likely, and timing issues with very fast spells (spell
-- fires before obi is moved) occur at worst on the first spell
-- not but subsequent ones.
--
-- Known bugs:
--
-- 1. Using the get, put, or sort commands too quickly can have
-- undesirable effects, often not moving obi at all. This is due to
-- limitations on how fast items can be moved from one inventory to
-- another.
--
-- 2. When weather changes due to zoning, get_obi_in_inventory()
-- is called before inventory has loaded and returns nothing.
--
-- 3. Obi is not moved when currently equipped.
--
--
-- To-do:
-- - Add support for other languages.
--
--
-- addon info
_addon.name = "obiaway"
_addon.author = "ReaperX, Bangerang"
_addon.version = "1.0.7"
_addon.commands = {'oa', 'ob', 'obi', 'obiaway'}
_addon.language = 'english'
-- library includes
require('sets')
require('logger')
config = require('config')
res = require('resources')
-- settings
default_settings = {}
default_settings.notify = true
default_settings.location = 'sack'
default_settings.lock = false
default_settings.color = 209
default_settings.ignore_zones = S{
"Ru'Lude Gardens", "Upper Jeuno", "Lower Jeuno", "Port Jeuno", "Port Windurst", "Windurst Waters", "Windurst Woods", "Windurst Walls",
"Heavens Tower", "Port San d'Oria", "Northern San d'Oria", "Southern San d'Oria", "Chateau d'Oraguille", "Port Bastok", "Bastok Markets",
"Bastok Mines", "Metalworks", "Aht Urhgan Whitegate", "Tavnazian Safehold", "Nashmau", "Selbina", "Mhaura", "Norg", "Rabao", "Kazham",
"Eastern Adoulin", "Western Adoulin", "Leafallia", "Celennia Memorial Library", "Mog Garden"}
settings = config.load(default_settings)
-- tokens
tokens = {}
tokens.lock_warned = false
tokens.inv_full_warned = false
tokens.bag_full_warned = false
-- lists
obi_names = T{
Light = 'Korin',
Dark = 'Anrin',
Fire = 'Karin',
Earth = 'Dorin',
Water = 'Suirin',
Wind = 'Furin',
Ice = 'Hyorin',
Lightning = 'Rairin'
}
----------------------------------------
--- UTITLITY Functions
----------------------------------------
-- Automatically formats output text for addon. Accepts msg as a string.
function obi_output(msg)
prefix = 'Obi: '
windower.add_to_chat(settings.color, prefix..msg)
end
-- Accepts a boolean value and returns an appropriate string value. i.e. true -> 'on'
function bool_to_str(bool)
return bool and 'on' or 'off'
end
-- checks if a value is in a table and then returns its key. Ex: a table contains Key = Value. returns Key. returns false if no match.
function in_table(tbl, item)
for key, value in pairs(tbl) do
if value == item then return key end
end
return false
end
-- converts name of a bag (str) to an id number. returns 0 (inventory) when no argument passed.
function inv_str_to_id(str)
if not str then return 0 end
if str == 'inventory' then
return 0
elseif str == 'safe' then
return 1
elseif str == 'storage' then
return 2
elseif str == 'locker' then
return 3
elseif str == 'satchel' then
return 5
elseif str == 'sack' then
return 6
elseif str == 'case' then
return 7
elseif str == 'wardrobe' then
return 8
else
print('Obiaway: Function inv_str_to_id invalid argument')
return
end
end
-- Function which counts how many slots remain in a bag. if no location is passed, checks inventory.
function free_space(location)
local id = inv_str_to_id(location)
local inv = windower.ffxi.get_bag_info(id)
n = inv.max - inv.count
return n
end
-- check's if an inventory is full. returns true if full. if no location argument is passed checks main inventory.
-- also handle's an inventory full warning. requires two tokens which keep track of whether or not an inventory
-- full warning was already given. one for the main inventory and a separate for a bag. this is to prevent
-- "Inventory full." spam in the chat log.
function inventory_full(command, location)
local id = inv_str_to_id(location)
if id == 0 then location = 'inventory' end
if free_space(location) == 0 then
if command then
obi_output('%s is full.':format(string.ucfirst(location)))
elseif not tokens.inv_full_warned and id == 0 then
tokens.inv_full_warned = true
obi_output('%s is full.':format(string.ucfirst(location)))
elseif not tokens.bag_full_warned then
tokens.bag_full_warned = true
obi_output('%s is full.':format(string.ucfirst(location)))
end
return true
elseif free_space(location) > 0 then -- resets the tokens when space is free
if id == 0 then tokens.inv_full_warned = false else tokens.bag_full_warned = false end
return false
end
print('Obiaway: Function inventory_full unknown error.')
end
----------------------------------------
--- ADDON functions
----------------------------------------
-- locks obi sorting. accepts true or false.
function lock_obi(toggle)
if toggle then
settings.lock = true
tokens.lock_warned = true
if settings.notify then
obi_output('Obi locked.')
end
elseif not toggle then
settings.lock = false
tokens.lock_warned = false
if settings.notify then
obi_output('Unlocking obi...')
end
end
end
-- Builds a table of boolean values that indicate which obi are in inventory and how many.
-- Ex:
-- obi = {
-- "Fire" = false
-- "Ice" = false
-- "Wind" = true
-- "Earth" = false
-- "Lightning" = true
-- "Water" = false
-- "Light" = false
-- "Dark" = true
-- n = 3
-- }
--
-- function designer: ReaperX
function get_obi_in_inventory(location)
local id = inv_str_to_id(location)
local obi = {}
local inv = windower.ffxi.get_items(id)
if not inv then return end
for i=1,inv.max do
id = inv[i].id
if ( id>=15435 and id<=15442) then
obi["Fire"] = obi["Fire"] or (id == 15435)
obi["Ice"] = obi["Ice"] or (id == 15436)
obi["Wind"] = obi["Wind"] or (id == 15437)
obi["Earth"] = obi["Earth"] or (id == 15438)
obi["Lightning"] = obi["Lightning"] or (id == 15439)
obi["Water"] = obi["Water"] or (id == 15440)
obi["Light"] = obi["Light"] or (id == 15441)
obi["Dark"] = obi["Dark"] or (id == 15442)
end
end
-- count obi in inventory
obi["n"] = table.count(obi, true)
return obi
end
-- Builds a table (elements) which contains storm buffs, weather effects, and day of the week is active.
-- Adds +1 to corresponding element variable for each effect active.
-- Ex: If fire weather and earthsday are active will return:
-- elements = {
-- "Fire" = true
-- "Earth" = true
-- "Water" = false
-- "Wind" = false
-- "Ice" = false
-- "Lightning" = false
-- "Light" = false
-- "Dark" = false
-- "None" = false
-- }
--
-- function designer: ReaperX
function get_all_elements()
local elements = {}
elements["Fire"] = 0
elements["Earth"] = 0
elements["Water"] = 0
elements["Wind"] = 0
elements["Ice"] = 0
elements["Lightning"] = 0
elements["Light"] = 0
elements["Dark"] = 0
elements["None"] = 0
-- check for active Day and Weather
local info = windower.ffxi.get_info()
local day_element = res.elements[res.days[info.day].element].english
elements[day_element] = elements[day_element] + 1
local weather_element = res.elements[res.weather[info.weather].element].english
elements[weather_element] = elements[weather_element] + 1
-- check for active SCH buffs
local buffs = windower.ffxi.get_player().buffs
if in_table(buffs, 178) then
elements["Fire"] = elements["Fire"] + 1
elseif in_table(buffs, 183) then
elements["Water"] = elements["Water"] + 1
elseif in_table(buffs, 181) then
elements["Earth"] = elements["Earth"] + 1
elseif in_table(buffs, 180) then
elements["Wind"] = elements["Wind"] + 1
elseif in_table(buffs, 179) then
elements["Ice"] = elements["Ice"] + 1
elseif in_table(buffs, 182) then
elements["Lightning"] = elements["Lightning"] + 1
elseif in_table(buffs, 184) then
elements["Light"] = elements["Light"] + 1
elseif in_table(buffs, 185) then
elements["Dark"] = elements["Dark"] + 1
end
return elements
end
----------------------------------------
--- SORTING functions
----------------------------------------
function get_needed_obi(command)
if inventory_full(command) then return false end
local obi = get_obi_in_inventory()
local elements = get_all_elements()
for name, element in obi_names:it() do
if not obi[element] and elements[element] > 0 then
windower.send_command('get "%s Obi" %s;':format(name, settings.location))
if settings.notify then
obi_output('Getting %s Obi from %s.':format(name, settings.location))
end
coroutine.sleep(.5)
end
end
return true
end
function put_unneeded_obi(command)
if inventory_full(command, settings.location) then return false end
local obi = get_obi_in_inventory()
local elements = get_all_elements()
for name, element in obi_names:it() do
if obi[element] and elements[element] == 0 then
windower.send_command('put "%s Obi" %s;':format(name, settings.location))
if settings.notify then
obi_output('Putting %s Obi away into %s.':format(name, settings.location))
end
coroutine.sleep(.5)
end
end
return true
end
function get_all_obi(command)
if inventory_full(command) then return false end
local obi = get_obi_in_inventory()
local obi_bag = get_obi_in_inventory(settings.location)
if free_space() < obi_bag["n"] then
obi_output('Not enough space in inventory...')
return false
end
local elements = get_all_elements()
local obi = get_obi_in_inventory()
for name, element in obi_names:it() do
if not obi[element] then
windower.send_command('get "%s Obi" %s;':format(name, settings.location))
if settings.notify then
obi_output('Getting %s Obi from %s.':format(name, settings.location))
end
coroutine.sleep(.5)
end
end
return true
end
function put_all_obi(command)
if inventory_full(command, settings.location) then return false end
local obi = get_obi_in_inventory()
if free_space(settings.location) < obi["n"] then
obi_output('Not enough space in %s...':format(settings.location))
return false
end
local elements = get_all_elements()
for name, element in obi_names:it() do
if obi[element] then
windower.send_command('put "%s Obi" %s;':format(name, settings.location))
if settings.notify then
obi_output('Putting %s Obi away into %s.':format(name, settings.location))
end
coroutine.sleep(.5)
end
end
return true
end
-- function called on automatic events. sorts obi based on location.
function auto_sort_obi()
-- if inventory and obi bag are full at the same time, do nothing. 'cause we can't.
if inventory_full(false) and inventory_full(false, settings.location) then return false end
if not settings.lock then -- if sorting lock is not on, then do this stuff:
if not settings.ignore_zones:contains(res.zones[windower.ffxi.get_info().zone].english) then -- Not in a city:
put_unneeded_obi(false)
get_needed_obi(false)
else -- In a city:
put_all_obi(false)
end
end
return true
end
----------------------------------------
--- EVENTS
----------------------------------------
windower.register_event('gain buff', auto_sort_obi:cond(function(id) return id >= 178 and id <= 185 end))
windower.register_event('lose buff', auto_sort_obi:cond(function(id) return id >= 178 and id <= 185 end))
windower.register_event('day change', 'weather change', auto_sort_obi)
windower.register_event('addon command', function(command, ...)
command = command and command:lower() or 'help'
params = L{...}:map(string.lower)
if command == 'help' or command == 'h' then
obi_output("obiaway v".._addon.version..". Authors: ".._addon.author)
obi_output("//obiaway [options]")
obi_output(" help : Displays this help text.")
obi_output(" sort : Automatically sorts obi.")
obi_output(" get [ all | needed ]")
obi_output(" Gets obi from bag.")
obi_output(" put [ all | unneeded ] [ sack | satchel | case | wardrobe ]")
obi_output(" Puts obi away. Can optionally specify a location.")
obi_output(" lock [ on | off ] (%s)":format(bool_to_str(settings.lock)))
obi_output(" Locks obi to current location.")
obi_output(" notify [ on | off ] (%s)":format(bool_to_str(settings.notify)))
obi_output(" Sets obiaway notifcations on or off.")
obi_output(" location [ sack | satchel | case | wardrobe ] (%s)":format(settings.location))
obi_output(" Sets inventory from which to get and put obi.")
elseif command == 'sort' or command == 's' then
if settings.lock then lock_obi(false) end
if settings.notify then
obi_output('Sorting obi...')
coroutine.sleep(0.5)
end
auto_sort_obi(true)
elseif command == 'get' or command == 'g' then
if params[1] == 'all' or params [1] == 'a' then
obi_output('Getting all obi from %s...':format(settings.location))
get_all_obi(true)
coroutine.sleep(1)
elseif params[1] == 'needed' or params [1] == 'n' then
obi_output('Getting needed obi from %s...':format(settings.location))
get_needed_obi(true)
else
error("Invalid argument. Usage: //obiaway get [ all | needed ]")
end
elseif command == 'put' or command == 'p' then
if params[1] == 'all' or params [1] == 'a' then
if S{'sack','case','satchel','wardrobe'}:contains(params[2]) then
settings.location = params[2]
obi_output("Obiaway location set to: %s":format(settings.location))
end
obi_output('Putting all obi into %s...':format(settings.location))
put_all_obi(true)
coroutine.sleep(1)
elseif params[1] == 'unneeded' or params[1] == 'needed' or params [1] == 'n' then
if S{'sack','case','satchel','wardrobe'}:contains(params[2]) then
settings.location = params[2]
obi_output("Obiaway location set to: %s":format(settings.location))
else
obi_output('Putting unneeded obi into %s...':format(settings.location))
put_unneeded_obi(true)
end
else
error("Invalid argument. Usage: //obiaway put [ all | needed ] [ sack | satchel | case | wardrobe ]")
end
elseif command == 'lock' or command == 'l' then
if params[1] == 'on' then
lock_obi(true)
elseif params[1] == 'off' then
lock_obi(false)
else
error("Invalid argument. Usage: //obiaway lock [ on | off ]")
end
elseif command == 'unlock' then
lock_obi(false)
elseif command == 'notify' or command == 'n' then
if params[1] == 'on' then
settings.notify = true
obi_output("Notifications are now on")
elseif params[1] == 'off' then
settings.notify = false
obi_output("Notifications are now off.")
else
error("Invalid argument. Usage: //obiaway notify [ on | off ]")
end
elseif command == 'location' or command == 'loc' then
if S{'sack','case','satchel','wardrobe'}:contains(params[1]) then
settings.location = params[1]
obi_output("Obiaway location set to: %s":format(settings.location))
else
error("Invalid argument. Usage: //obiaway location [ sack | satchel | case | wardrobe ]")
end
else
error("Unrecognized command. See //obiaway help.")
end
end)
|