summaryrefslogtreecommitdiff
path: root/Data/BuiltIn/Libraries/lua-addons/addons/battlemod/battlemod.lua
blob: 5ae3426899a8413d6451b3d98c79ceba38a686e7 (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
require 'tables'
require 'sets'
file = require 'files'
config = require 'config'
require 'strings'
res = require 'resources'
require 'actions'
require 'pack'
bit = require 'bit'

require 'generic_helpers'
require 'parse_action_packet'
require 'statics'

_addon.version = '3.31'
_addon.name = 'BattleMod'
_addon.author = 'Byrth, maintainer: SnickySnacks'
_addon.commands = {'bm','battlemod'}

windower.register_event('load',function()
    if debugging then windower.debug('load') end
    options_load()
end)

windower.register_event('login',function (name)
    if debugging then windower.debug('login') end
    options_load:schedule(10)
end)

windower.register_event('addon command', function(command, ...)
    if debugging then windower.debug('addon command') end
    local args = {...}
    command = command and command:lower()
    if command then
        if command:lower() == 'commamode' then
            commamode = not commamode
            windower.add_to_chat(121,'Battlemod: Comma Mode flipped! - '..tostring(commamode))
        elseif command:lower() == 'oxford' then
            oxford = not oxford
            windower.add_to_chat(121,'Battlemod: Oxford Mode flipped! - '..tostring(oxford))
        elseif command:lower() == 'targetnumber' then
            targetnumber = not targetnumber
            windower.add_to_chat(121,'Battlemod: Target Number flipped! - '..tostring(targetnumber))
        elseif command:lower() == 'condensetargetname' then
            condensetargetname = not condensetargetname
            windower.add_to_chat(121,'Battlemod: Target Name Condensation flipped! - '..tostring(condensetargetname))                                                                
        elseif command:lower() == 'swingnumber' then
            swingnumber = not swingnumber
            windower.add_to_chat(121,'Battlemod: Round Number flipped! - '..tostring(swingnumber))
        elseif command:lower() == 'sumdamage' then
            sumdamage = not sumdamage
            windower.add_to_chat(121,'Battlemod: Sum Damage flipped! - '..tostring(sumdamage))
        elseif command:lower() == 'condensecrits' then
            condensecrits = not condensecrits
            windower.add_to_chat(121,'Battlemod: Condense Crits flipped! - '..tostring(condensecrits))
        elseif command:lower() == 'cancelmulti' then
            cancelmulti = not cancelmulti
            windower.add_to_chat(121,'Battlemod: Multi-canceling flipped! - '..tostring(cancelmulti))
        elseif command:lower() == 'reload' then
            current_job = 'NONE'
            options_load()
        elseif command:lower() == 'unload' then
            windower.send_command('@lua u battlemod')
        elseif command:lower() == 'simplify' then
            simplify = not simplify
            windower.add_to_chat(121,'Battlemod: Text simplification flipped! - '..tostring(simplify))
        elseif command:lower() == 'condensedamage' then
            condensedamage = not condensedamage
            windower.add_to_chat(121,'Battlemod: Condensed Damage text flipped! - '..tostring(condensedamage))
        elseif command:lower() == 'condensetargets' then
            condensetargets = not condensetargets
            windower.add_to_chat(121,'Battlemod: Condensed Targets flipped! - '..tostring(condensetargets))
        elseif command:lower() == 'showownernames' then
            showownernames = not showownernames
            windower.add_to_chat(121,'Battlemod: Show pet owner names flipped! - '..tostring(showownernames))
        elseif command:lower() == 'crafting' then
            crafting = not crafting
            windower.add_to_chat(121,'Battlemod: Display crafting results flipped! - '..tostring(crafting))
      elseif command:lower() == 'showblocks' then
            showblocks = not showblocks
            windower.add_to_chat(121,'Battlemod: Show blocks with shield flipped! - '..tostring(showblocks))
        elseif command:lower() == 'showguards' then
            showguards = not showguards
            windower.add_to_chat(121,'Battlemod: Show guarding on hits flipped! - '..tostring(showguards))
        elseif command:lower() == 'showcritws' then
            showcritws = not showcritws
            windower.add_to_chat(121,'Battlemod: Show critical hit on ws/mob tp flipped! - '..tostring(showcritws))
        elseif command:lower() == 'showrollinfo' then
            showrollinfo = not showrollinfo
            windower.add_to_chat(121,'Battlemod: Show lucky/unlucky rolls flipped! - '..tostring(showrollinfo))
        elseif command:lower() == 'colortest' then
            local counter = 0
            local line = ''
            for n = 1, 262 do
                if not color_redundant:contains(n) and not black_colors:contains(n) then
                    if n <= 255 then
                        loc_col = string.char(0x1F, n)
                    else
                        loc_col = string.char(0x1E, n - 254)
                    end
                    line = line..loc_col..string.format('%03d ', n)
                    counter = counter + 1
                end
                if counter == 16 or n == 262 then
                    windower.add_to_chat(6, line)
                    counter = 0
                    line = ''
                end
            end
            windower.add_to_chat(122,'Colors Tested!')
        elseif command:lower() == 'help' then
            print('   :::   '.._addon.name..' ('.._addon.version..')   :::')
            print('Toggles: (* subtoggles)')
            print('           1. simplify              - Condenses battle text using custom messages ('..tostring(simplify)..')')
            print('           2. condensetargets       - Collapse similar messages with multiple targets ('..tostring(condensetargets)..')')
            print('               * targetnumber       - Toggle target number display ('..tostring(targetnumber)..')')
            print('               * condensetargetname - Toggle target name condensation ('..tostring(condensetargetname)..')')
            print('               * oxford             - Toggle use of oxford comma ('..tostring(oxford)..')')
            print('               * commamode          - Toggle comma-only mode ('..tostring(commamode)..')')
            print('           3. condensedamage        - Condenses damage messages within attack rounds ('..tostring(condensedamage)..')')
            print('               * swingnumber        - Show # of attack rounds ('..tostring(swingnumber)..')')
            print('               * sumdamage          - Sums condensed damage if true, comma-separated if false ('..tostring(sumdamage)..')')
            print('               * condensecrits      - Condenses critical hits and normal hits together ('..tostring(condensecrits)..')')
            print('           4. cancelmulti           - Cancels multiple consecutive identical lines ('..tostring(cancelmulti)..')')
            print('           5. showonernames         - Shows the name of the owner on pet messages ('..tostring(showownernames)..')')
            print('           6. crafting              - Enables early display of crafting results ('..tostring(crafting)..')')
            print('           7. showblocks            - Shows if a hit was blocked with shield ('..tostring(showblocks)..')')
            print('           8. showguards            - Shows if a hit was guarded ('..tostring(showguards)..')')
            print('           9. showcritws            - Shows if a ws or mob ability was a critical hit (shows on multihit if atleast 1 hit was a crit) ('..tostring(showcritws)..')')
            print('           10. showrollinfo         - Shows lucky/unlucky rolls ('..tostring(showrollinfo)..')')
            print('Utilities: 1. colortest             - Shows the 509 possible colors for use with the settings file')
            print('           2. reload                - Reloads settings file')
            print('           3. unload                - Unloads Battlemod')
        end
    end
end)

windower.register_event('incoming text',function (original, modified, color, color_m, blocked)
    if debugging then windower.debug('incoming text') end
    local redcol = color%256
    
    if redcol == 121 and cancelmulti then
        a,z = string.find(original,'Equipment changed')
        
        if a and not block_equip then
            flip_block_equip:schedule(1)
            block_equip = true
        elseif a and block_equip then
            modified = true
        end
    elseif redcol == 123 and cancelmulti then
        a,z = string.find(original,'You were unable to change your equipped items')
        b,z = string.find(original,'You cannot use that command while viewing the chat log')
        c,z = string.find(original,'You must close the currently open window to use that command')
        
        if (a or b or c) and not block_cannot then
            flip_block_cannot:schedule(1)
            block_cannot = true
        elseif (a or b or c) and block_cannot then
            modified = true
        end
    end
    if block_modes:contains(color) then
        local endline = string.char(0x7F, 0x31)
        local item = string.char(0x1E)
        if not bm_message(original) then
            if original:endswith(endline) then --allow add_to_chat messages with the modes we blocking
                return true
            end
        elseif original:endswith(endline) and string.find(original, item) then --block items action messages
            return true
        end
    end
    
    return modified,color
end)

function bm_message(original)
    local check = string.char(0x1E)
    local check2 = string.char(0x1F)
    if string.find(original, check) or string.find(original, check2) then
        return true
    end
end

function flip_block_equip()
    block_equip = not block_equip
end

function flip_block_cannot()
    block_cannot = not block_cannot
end

function options_load()
    if windower.ffxi.get_player() then
        Self = windower.ffxi.get_player()
    end
    if not windower.dir_exists(windower.addon_path..'data\\') then
        windower.create_dir(windower.addon_path..'data\\')
    end
    if not windower.dir_exists(windower.addon_path..'data\\filters\\') then
        windower.create_dir(windower.addon_path..'data\\filters\\')
    end
     
    local settingsFile = file.new('data\\settings.xml',true)
    local filterFile=file.new('data\\filters\\filters.xml',true)
    local colorsFile=file.new('data\\colors.xml',true)
    
    if not file.exists('data\\settings.xml') then
        settingsFile:write(default_settings)
        print('Default settings xml file created')
    end
    
    local settingtab = config.load('data\\settings.xml',default_settings_table)
    config.save(settingtab)
    
    for i,v in pairs(settingtab) do
        _G[i] = v
    end
    
    if not file.exists('data\\filters\\filters.xml') then
        filterFile:write(default_filters)
        print('Default filters xml file created')
    end
    local tempplayer = windower.ffxi.get_player()
    if tempplayer then
        if tempplayer.main_job ~= 'NONE' then
            filterload(tempplayer.main_job)
        elseif windower.ffxi.get_mob_by_id(tempplayer.id)['race'] == 0 then
            filterload('MON')
        else
            filterload('DEFAULT')
        end
    else
        filterload('DEFAULT')
    end
    if not file.exists('data\\colors.xml') then
        colorsFile:write(default_colors)
        print('Default colors xml file created')
    end
    local colortab = config.load('data\\colors.xml',default_color_table)
    config.save(colortab)
    for i,v in pairs(colortab) do
        color_arr[i] = colconv(v,i)
    end
end

function filterload(job)
    if current_job == job then return end
    if file.exists('data\\filters\\filters-'..job..'.xml') then
        default_filt = false
        filter = config.load('data\\filters\\filters-'..job..'.xml',default_filter_table,false)
        config.save(filter)
        windower.add_to_chat(4,'Loaded '..job..' Battlemod filters')
    elseif not default_filt then
        default_filt = true
        filter = config.load('data\\filters\\filters.xml',default_filter_table,false)
        config.save(filter)
        windower.add_to_chat(4,'Loaded default Battlemod filters')
    end
    current_job = job
end

ActionPacket.open_listener(parse_action_packet)

windower.register_event('incoming chunk',function (id,original,modified,is_injected,is_blocked)
    if debugging then windower.debug('incoming chunk '..id) end
    
------- ITEM QUANTITY -------
    if id == 0x020 and parse_quantity then
        --local packet = packets.parse('incoming', original)
        local item = original:unpack('H',0x0D)
        local count = original:unpack('I',0x05)
        if item == 0 then return end
        if item_quantity.id == item then
            item_quantity.count = count..' '
        end

------- NOUNS AND PLURAL ENTITIES -------    
    elseif id == 0x00E then
        local mob_id = original:unpack('I',0x05)
        local mask = original:unpack('C',0x0B)
        local chat_info = original:unpack('C',0x28)
        if bit.band(mask,4) == 4 then
            if bit.band(chat_info,32) == 0 and not common_nouns:contains(mob_id) then
                table.insert(common_nouns, mob_id)
            elseif bit.band(chat_info,64) == 64 and not plural_entities:contains(mob_id) then
                table.insert(plural_entities, mob_id)
            elseif bit.band(chat_info,64) == 0 and plural_entities:contains(mob_id) then --Gears can change their grammatical number when they lose 2 gear?
                for i, v in pairs(plural_entities) do
                    if v == mob_id then
                        table.remove(plural_entities, i)
                        break
                    end
                end
            end
        end
    elseif id == 0x00B then --Reset tables on Zoning
        common_nouns = T{}
        plural_entities = T{}
        
------- ACTION MESSAGE -------
    elseif id == 0x29 then
        local am = {}
        am.actor_id = original:unpack('I',0x05)
        am.target_id = original:unpack('I',0x09)
        am.param_1 = original:unpack('I',0x0D)
        am.param_2 = original:unpack('H',0x11)%2^9 -- First 7 bits
        am.param_3 = math.floor(original:unpack('I',0x11)/2^5) -- Rest
        am.actor_index = original:unpack('H',0x15)
        am.target_index = original:unpack('H',0x17)
        am.message_id = original:unpack('H',0x19)%2^15 -- Cut off the most significant bit
        
        local actor = player_info(am.actor_id)
        local target = player_info(am.target_id)
        local actor_article = common_nouns:contains(am.actor_id) and 'The ' or ''
        local target_article = common_nouns:contains(am.target_id) and 'The ' or ''
        targets_condensed = false
        
        -- Filter these messages
        if not check_filter(actor,target,0,am.message_id) then return true end
        
        if not actor or not target then -- If the actor or target table is nil, ignore the packet
        elseif am.message_id == 800 then -- Spirit bond message
            local status = color_it(res.buffs[am.param_1][language],color_arr.statuscol)
            local targ = color_it(target.name or '',color_arr[target.owner or target.type])
            local number = am.param_2
            local color = color_filt(res.action_messages[am.message_id].color, am.target_id==Self.id)
            if simplify then
                local msg = line_noactor
                    :gsub('${abil}',status or '')
                    :gsub('${target}',targ)
                    :gsub('${numb}',number or '')
                windower.add_to_chat(color, msg)
            else
                local msg = res.action_messages[am.message_id][language]
                msg = grammatical_number_fix(msg, number, am.message_id)
                if plural_entities:contains(am.actor_id) then
                    msg = plural_actor(msg, am.message_id)
                end
                if plural_entities:contains(am.target_id) then
                    msg = plural_target(msg, am.message_id)
                end
                local msg = clean_msg(msg
                    :gsub('${status}',status or '')
                    :gsub('${target}',target_article..targ)
                    :gsub('${number}',number or ''), am.message_id)
                windower.add_to_chat(color, msg)
            end
        elseif am.message_id == 206 and condensetargets then -- Wears off messages
            -- Condenses across multiple packets
            local status
            
            if enfeebling:contains(am.param_1) and res.buffs[param_1] then
                status = color_it(res.buffs[param_1][language],color_arr.enfeebcol)
            elseif color_arr.statuscol == rcol then
                status = color_it(res.buffs[am.param_1][language],string.char(0x1F,191))
            else
                status = color_it(res.buffs[am.param_1][language],color_arr.statuscol)
            end
            
            if not multi_actor[status] then multi_actor[status] = player_info(am.actor_id) end
            if not multi_msg[status] then multi_msg[status] = am.message_id end
            
            if not multi_targs[status] and not stat_ignore:contains(am.param_1) then
                multi_targs[status] = {}
                multi_targs[status][1] = target
                multi_packet:schedule(0.5, status)
            elseif not (stat_ignore:contains(am.param_1)) then
                multi_targs[status][#multi_targs[status]+1] = target
            else
            -- This handles the stat_ignore values, which are things like Utsusemi,
            -- Sneak, Invis, etc. that you don't want to see on a delay
                multi_targs[status] = {}
                multi_targs[status][1] = target
                multi_packet(status)
            end
            am.message_id = false
        elseif passed_messages:contains(am.message_id) then
            local item,status,spell,skill,number,number2
            local outstr = res.action_messages[am.message_id][language]
            if plural_entities:contains(am.actor_id) then
                outstr = plural_actor(outstr, am.message_id)
            end
            if plural_entities:contains(am.target_id) then
                outstr = plural_target(outstr, am.message_id)
            end
            
            local fields = fieldsearch(outstr)
            
            if fields.status then
                if log_form_messages:contains(am.message_id) then
                    status = res.buffs[am.param_1].english_log
                else
                    status = nf(res.buffs[am.param_1],language)
                end
                if enfeebling:contains(am.param_1) then
                    status = color_it(status,color_arr.enfeebcol)
                else
                    status = color_it(status,color_arr.statuscol)
                end
            end
            
            if fields.spell then
                if not res.spells[am.param_1] then
                    return false
                end
                spell = nf(res.spells[am.param_1],language)
            end
            
            if fields.item then
                if not res.items[am.param_1] then
                    return false
                end
                item = nf(res.items[am.param_1],'english_log')
            end
            
            if fields.number then
                number = am.param_1
            end
            
            if fields.number2 then
                number2 = am.param_2
            end
            
            if fields.skill and res.skills[am.param_1] then
                skill = res.skills[am.param_1][language]:lower()
            end
            
            if am.message_id > 169 and am.message_id <179 then
                if am.param_1 > 2147483647 then
                    skill = 'to be level -1 ('..ratings_arr[am.param_2-63]..')'
                else
                    skill = 'to be level '..am.param_1..' ('..ratings_arr[am.param_2-63]..')'
                end
            end
            outstr = (clean_msg(outstr
                :gsub('${actor}\'s',actor_article..color_it(actor.name or '',color_arr[actor.owner or actor.type])..'\'s'..actor.owner_name)
                :gsub('${actor}',actor_article..color_it(actor.name or '',color_arr[actor.owner or actor.type])..actor.owner_name)
                :gsub('${status}',status or '')
                :gsub('${item}',color_it(item or '',color_arr.itemcol))
                :gsub('${target}\'s',target_article..color_it(target.name or '',color_arr[target.owner or target.type])..'\'s'..target.owner_name)
                :gsub('${target}',target_article..color_it(target.name or '',color_arr[target.owner or target.type])..target.owner_name)
                :gsub('${spell}',color_it(spell or '',color_arr.spellcol))
                :gsub('${skill}',color_it(skill or '',color_arr.abilcol))
                :gsub('${number}',number or '')
                :gsub('${number2}',number2 or '')
                :gsub('${skill}',skill or '')
                :gsub('${lb}','\7'), am.message_id))
            windower.add_to_chat(res.action_messages[am.message_id]['color'],outstr)
            am.message_id = false
        elseif debugging and res.action_messages[am.message_id] then 
        -- 38 is the Skill Up message, which (interestingly) uses all the number params.
        -- 202 is the Time Remaining message, which (interestingly) uses all the number params.
            print('debug_EAM#'..am.message_id..': '..res.action_messages[am.message_id][language]..' '..am.param_1..'   '..am.param_2..'   '..am.param_3)
        elseif debugging then
            print('debug_EAM#'..am.message_id..': '..'Unknown'..' '..am.param_1..'   '..am.param_2..'   '..am.param_3)
        end
        if not am.message_id then
            return true
        end

------------ SYNTHESIS ANIMATION --------------
    elseif id == 0x030 and crafting then
        if windower.ffxi.get_player().id == original:unpack('I',5) or windower.ffxi.get_mob_by_target('t') and windower.ffxi.get_mob_by_target('t').id == original:unpack('I',5) then
            local crafter_name = (windower.ffxi.get_player().id == original:unpack('I',5) and windower.ffxi.get_player().name) or windower.ffxi.get_mob_by_target('t').name
            local result = original:byte(13)
            if result == 0 then
                windower.add_to_chat(8,' ------------- NQ Synthesis ('..crafter_name..') -------------')
            elseif result == 1 then
                windower.add_to_chat(8,' ---------------- Break ('..crafter_name..') -----------------')
            elseif result == 2 then
                windower.add_to_chat(8,' ------------- HQ Synthesis ('..crafter_name..') -------------')
            else
                windower.add_to_chat(8,'Craftmod: Unhandled result '..tostring(result))
            end
        end
    elseif id == 0x06F and crafting then
        if original:byte(5) == 0 or original:byte(5) == 12 then
            local result = original:byte(6)
            if result == 1 then
                windower.add_to_chat(8,' -------------- HQ Tier 1! --------------')
            elseif result == 2 then
                windower.add_to_chat(8,' -------------- HQ Tier 2! --------------')
            elseif result == 3 then
                windower.add_to_chat(8,' -------------- HQ Tier 3! --------------')
            end
        end
        
    ------------- JOB INFO ----------------
    elseif id == 0x01B then
        filterload(res.jobs[original:byte(9)][language..'_short'])
    end
end)

function multi_packet(...)
    local ind = table.concat({...},' ')
    local targets = assemble_targets(multi_actor[ind],multi_targs[ind],0,multi_msg[ind])
    local outstr = targets_condensed and plural_target(res.action_messages[multi_msg[ind]][language], multi_msg[ind]) or res.action_messages[multi_msg[ind]][language]
    outstr = clean_msg(outstr
        :gsub('${target}\'s',targets)
        :gsub('${target}',targets)
        :gsub('${status}',ind), multi_msg[ind])
    windower.add_to_chat(res.action_messages[multi_msg[ind]].color,outstr)
    multi_targs[ind] = nil
    multi_msg[ind] = nil
    multi_actor[ind] = nil
end