summaryrefslogtreecommitdiff
path: root/Data/BuiltIn/Libraries/lua-addons/addons/obiaway/Obiaway.lua
blob: 523b13df8c99e65910a761e01585df6db3ab8263 (plain)
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)