summaryrefslogtreecommitdiff
path: root/Data/BuiltIn/Libraries/lua-addons/addons/GearSwap/flow.lua
blob: ef5a1eecd1037906e65b02758d01b5068086e3f6 (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
--Copyright (c) 2013~2016, Byrthnoth
--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 <addon name> 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 <your name> 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.



-----------------------------------------------------------------------------------
--Name: equip_sets(swap_type,ts,val1,val2)
--Desc: General purpose equipment pipeline / user function caller. 
--Args:
---- swap_type - Determines equip_sets' behavior in terms of which user function it
--      attempts to call
---- ts - index of command_registry or nil for pretarget/commands
---- val1 - First argument to be passed to the user function
---- val2 - Second argument to be passed to the user function
-----------------------------------------------------------------------------------
--Return (varies by swap type):
---- pretarget : empty string to blank packet or full string
---- Everything else : nil
-----------------------------------------------------------------------------------
function equip_sets(swap_type,ts,...)
    local results
    local var_inps = {...}
    local val1 = var_inps[1]
    local val2 = var_inps[2]
    table.reassign(_global,command_registry[ts] or {pretarget_cast_delay = 0,precast_cast_delay=0,cancel_spell = false, new_target=false,target_arrow={x=0,y=0,z=0}})
    _global.current_event = tostring(swap_type)
    
    if _global.current_event == 'precast' and val1 and val1.english and val1.english:find('Geo-') then
        _global.target_arrow = initialize_arrow_offset(val1.target)
    end
    
    windower.debug(tostring(swap_type)..' enter')
    if showphase or debugging.general then msg.debugging(8,windower.to_shift_jis(tostring(swap_type))..' enter') end
    
    local cur_equip = table.reassign({},update_equipment())
    
    table.reassign(equip_list,{})
    table.reassign(player.equipment,to_names_set(cur_equip))
    for i,v in pairs(slot_map) do
        if not player.equipment[i] then
            player.equipment[i] = player.equipment[toslotname(v)]
        end
    end
    
    logit('\n\n'..tostring(os.clock)..'(15) equip_sets: '..tostring(swap_type))
    if val1 then
        if type(val1) == 'table' and val1.english then
            logit(' : '..val1.english)
        else
            logit(' : Unknown type val1- '..tostring(val1))
        end
    else
        logit(' : nil-or-false')
    end
    if val2 then
        if type(val2) == 'table' and val2.type then logit(' : '..val2.type)
        else
            logit(' : Unknown type val2- '..tostring(val2))
        end
    else
        logit(' : nil-or-false')
    end
    
    if type(swap_type) == 'string' then
        msg.debugging("Entering "..swap_type)
    else
        msg.debugging("Entering User Event "..tostring(swap_type))
    end
    
    if not val1 then val1 = {}
        if debugging.general then
            msg.debugging(8,'val1 error')
        end
    end

    
    if type(swap_type) == 'function' then
        results = { pcall(swap_type,...) }
        if not table.remove(results,1) then error('\nUser Event Error: '..results[1]) end
    elseif swap_type == 'equip_command' then
        equip(val1)
    else
        user_pcall(swap_type,...)
    end
    
--[[    local c
    if type(swap_type) == 'function' then
        c = coroutine.create(swap_type)
    elseif swap_type == 'equip_command' then
        equip(val1)
    elseif type(swap_type) == 'string' and user_env[swap_type] and type(user_env[swap_type]) == 'function' then
        c = coroutine.create(user_env[swap_type])
    elseif type(swap_type) == 'string' and user_env[swap_type] then
        msg.addon_msg(123,windower.to_shift_jis(tostring(str))..'() exists but is not a function')
    end
    
    if c then
        while coroutine.status(c) == 'suspended' do
            local err, typ, val = coroutine.resume(c,unpack(var_inputs))
            if not err then
                error('\nGearSwap has detected an error in the user function '..tostring(swap_type)..':\n'..typ)
            elseif typ then
                if typ == 'sleep' and type(val) == 'number' and val >= 0 then
                    -- coroutine slept
                    err, typ, val = coroutine.schedule(c,val)
                else
                    -- Someone yielded or slept with a nonsensical argument.
                    err, typ, val = coroutine.resume(c)
                end
            else
                -- coroutine finished
            end
        end 
    end]]
    
    
    if type(swap_type) == 'string' and (swap_type == 'pretarget' or swap_type == 'filtered_action') then -- Target may just have been changed, so make the ind now.
        ts = command_registry:new_entry(val1)
--    elseif type(swap_type) == 'string' and swap_type == 'precast' and not command_registry[ts] and debugging.command_registry then
--        print_set(spell,'precast nil error') -- spell's scope changed to local
    end
    
    if player.race ~= 'Precomposed NPC' then
        -- Short circuits the routine and gets out before equip processing
        -- if there's no swapping to be done because the user is a monster.
        
        for v,i in pairs(default_slot_map) do
            if equip_list[i] and encumbrance_table[v] then
                not_sent_out_equip[i] = equip_list[i]
                equip_list[i] = nil
                msg.debugging(i..' slot was not equipped because you are encumbered.')
            end
        end
        
        table.update(equip_list_history,equip_list)
        
        -- Attempts to identify the player-specified item in inventory
        -- Starts with (i=slot name, v=item name) 
        -- Ends with (i=slot id and v={bag_id=bag_id, slot=inventory slot}).
        local equip_next,priorities = unpack_equip_list(equip_list,cur_equip)
        
        if (_settings.show_swaps and table.length(equip_next) > 0) or _settings.demo_mode then --and table.length(equip_next)>0 then
            local tempset = to_names_set(equip_next)
            print_set(tempset,tostring(swap_type))
        end
        
        if (buffactive.charm or player.charmed) or (player.status == 2 or player.status == 3) then -- dead or engaged dead statuses
            local failure_reason
            if (buffactive.charm or player.charmed) then
                failure_reason = 'Charmed'
            elseif player.status == 2 or player.status == 3 then
                failure_reason = 'KOed'
            end
            msg.debugging("Cannot change gear right now: "..tostring(failure_reason))
            logit('\n\n'..tostring(os.clock)..'(69) failure_reason: '..tostring(failure_reason))
        else
            local chunk_table = L{}
            for eq_slot_id,priority in priorities:it() do
                if equip_next[eq_slot_id] and not encumbrance_table[eq_slot_id] and not _settings.demo_mode then
                    local minichunk = equip_piece(eq_slot_id,equip_next[eq_slot_id].bag_id,equip_next[eq_slot_id].slot)
                    chunk_table:append(minichunk)
                end
            end

            if swap_type == 'midcast' and command_registry[ts] and command_registry[ts].proposed_packet and not _settings.demo_mode then
                windower.packets.inject_outgoing(command_registry[ts].proposed_packet:byte(1),command_registry[ts].proposed_packet)
            end

            if chunk_table.n >= 3 then
                local big_chunk = string.char(0x51,0x24,0,0,chunk_table.n,0,0,0)
                for i=1,chunk_table.n do
                    big_chunk = big_chunk..chunk_table[i]
                end
                while string.len(big_chunk) < 0x48 do big_chunk = big_chunk..string.char(0) end
                windower.packets.inject_outgoing(0x51,big_chunk)
            elseif chunk_table.n > 0 then
                for i=1,chunk_table.n do
                    local chunk = string.char(0x50,4,0,0)..chunk_table[i]
                    windower.packets.inject_outgoing(0x50,chunk)
                end
            end
        end
    else
        if swap_type == 'midcast' and command_registry[ts] and command_registry[ts].proposed_packet and not _settings.demo_mode then
            windower.packets.inject_outgoing(command_registry[ts].proposed_packet:byte(1),command_registry[ts].proposed_packet)
        end
    end
    
    windower.debug(tostring(swap_type)..' exit')
    
    if type(swap_type) == 'function' then
        return unpack(results)
    end
    
    return equip_sets_exit(swap_type,ts,val1)
end


-----------------------------------------------------------------------------------
--Name: equip_sets_exit(swap_type,ind,val1)
--Desc: Cleans up the global table and leaves equip_sets properly.
--Args:
---- swap_type - Current swap type for equip_sets
---- ts - Current index of command_registry
---- val1 - First argument of equip_sets
-----------------------------------------------------------------------------------
--Returns:
---- none
-----------------------------------------------------------------------------------
function equip_sets_exit(swap_type,ts,val1)
    if command_registry[ts] then
        table.update(command_registry[ts],_global)
    end
    if type(swap_type) == 'string' then
        if swap_type == 'pretarget' then
            
            if command_registry[ts].cancel_spell then
                msg.debugging("Action canceled ("..storedcommand..' '..val1.target.raw..")")
                storedcommand = nil
                command_registry:delete_entry(ts)
                return true
            elseif not ts or not command_registry[ts] or not storedcommand then
                msg.debugging('This case should not be hittable - 1')
                return true
            end
            
            if command_registry[ts].new_target then
                val1.target = command_registry[ts].new_target -- Switch target, if it is requested.
            end
            
            -- Compose a proposed packet for the given action (this should be possible after pretarget)
            command_registry[ts].spell = val1
            if val1.target and val1.target.id and val1.target.index and val1.prefix and unify_prefix[val1.prefix] then
                if val1.prefix == '/item' then
                    -- Item use packet handling here
                    if bit.band(val1.target.spawn_type, 2) == 2 and find_inventory_item(val1.id) then
                        -- 0x36 packet
                        if val1.target.distance <= 6 then
                            command_registry[ts].proposed_packet = assemble_menu_item_packet(val1.target.id,val1.target.index,val1.id)
                        else
                            windower.add_to_chat(67, "Target out of range.")
                        end
                    elseif find_usable_item(val1.id) then
                        -- 0x37 packet
                        command_registry[ts].proposed_packet = assemble_use_item_packet(val1.target.id,val1.target.index,val1.id)
                    end
                    if not command_registry[ts].proposed_packet then
                        command_registry:delete_entry(ts)
                    end
                elseif outgoing_action_category_table[unify_prefix[val1.prefix]] then
                    if filter_precast(val1) then
                        command_registry[ts].proposed_packet = assemble_action_packet(val1.target.id,val1.target.index,outgoing_action_category_table[unify_prefix[val1.prefix]],val1.id,command_registry[ts].target_arrow)
                        if not command_registry[ts].proposed_packet then
                            command_registry:delete_entry(ts)
                            
                            msg.debugging("Unable to create a packet for this command because the target is still invalid after pretarget ("..storedcommand..' '..val1.target.raw..")")
                            storedcommand = nil
                            return storedcommand..' '..val1.target.raw
                        end
                    end
                else
                    msg.debugging(8,"Hark, what weird prefix through yonder window breaks? "..tostring(val1.prefix))
                end
            end
            
            if ts and command_registry[ts] and val1.target then
                if st_targs[val1.target.raw] then
                -- st targets
                    st_flag = true
                elseif not val1.target.name then
                -- Spells with invalid pass_through_targs, like using <t> without a target
                    command_registry:delete_entry(ts)
                    msg.debugging("Change target was used to pick an invalid target ("..storedcommand..' '..val1.target.raw..")")
                    local ret = storedcommand..' '..val1.target.raw
                    storedcommand = nil
                    return ret
                else
                -- Spells with complete target information
                -- command_registry[ts] is deleted for cancelled spells
                    if command_registry[ts].pretarget_cast_delay == 0 then
                        equip_sets('precast',ts,val1)
                    else
                        windower.send_command('@wait '..command_registry[ts].pretarget_cast_delay..';lua i '.._addon.name..' pretarget_delayed_cast '..ts)
                    end
                    return true
                end
            elseif not ts or not command_registry[ts] then
                msg.debugging('This case should not be hittable - 2')
                return true
            end

        elseif swap_type == 'precast' then
            -- Update the target_arrow
            if val1.prefix ~= '/item' then
                command_registry[ts].proposed_packet = assemble_action_packet(val1.target.id,val1.target.index,outgoing_action_category_table[unify_prefix[val1.prefix]],val1.id,command_registry[ts].target_arrow)
            end
            return precast_send_check(ts)
        elseif swap_type == 'filtered_action' and command_registry[ts] and command_registry[ts].cancel_spell then
            storedcommand = nil
            command_registry:delete_entry(ts)
            return true
        elseif swap_type == 'midcast' and _settings.demo_mode then
            command_registry[ts].midaction = false
            equip_sets('aftercast',ts,val1)
        elseif swap_type == 'aftercast' then
            if ts then
                command_registry:delete_entry(ts)
            end
        elseif swap_type == 'pet_aftercast' then
            if ts then
                command_registry:delete_entry(ts)
            end
        end
    end
end


-----------------------------------------------------------------------------------
--Name: user_pcall(str,val1,val2,exit_funct)
--Desc: Calls a user function, if it exists. If not, throws an error.
--Args:
---- str - Function's key in user_env.
-----------------------------------------------------------------------------------
--Returns:
---- none
-----------------------------------------------------------------------------------
function user_pcall(str,...)
    if user_env then
        if type(user_env[str]) == 'function' then
            bool,err = pcall(user_env[str],...)
            if not bool then error('\nGearSwap has detected an error in the user function '..str..':\n'..err) end
        elseif user_env[str] then
            msg.addon_msg(123,windower.to_shift_jis(tostring(str))..'() exists but is not a function')
        end
    end
end


-----------------------------------------------------------------------------------
--Name: pretarget_delayed_cast(ts)
--Desc: Triggers an outgoing action packet (if the passed key is valid).
--Args:
---- ts - Timestamp argument to precast_delayed_cast
-----------------------------------------------------------------------------------
--Returns:
---- none
-----------------------------------------------------------------------------------
function pretarget_delayed_cast(ts)
    ts = tonumber(ts)
    if ts then
        equip_sets('precast',ts,command_registry[ts].spell)
    else
        msg.debugging("Bad index passed to pretarget_delayed_cast")
    end
end



-----------------------------------------------------------------------------------
--Name: precast_send_check(ts)
--Desc: Determines whether or not to send the current packet.
--      Cancels if _global.cancel_spell is true
--          If command_registry[ts].precast_cast_delay is not 0, cues precast_delayed_cast with the proper
--          delay instead of sending immediately.
--Args:
---- ts - key of command_registry
-----------------------------------------------------------------------------------
--Returns:
---- true (to block) or the outgoing packet
-----------------------------------------------------------------------------------
function precast_send_check(ts)
    if ts and command_registry[ts] then
        if command_registry[ts].cancel_spell then
            command_registry:delete_entry(ts)
        else
            if command_registry[ts].precast_cast_delay == 0 then
                send_action(ts)
                return
            else
                windower.send_command('@wait '..command_registry[ts].precast_cast_delay..';lua i '.._addon.name..' precast_delayed_cast '..ts)
            end
        end
    end
    return true
end


-----------------------------------------------------------------------------------
--Name: precast_delayed_cast(ts)
--Desc: Triggers an outgoing action packet (if the passed key is valid).
--Args:
---- ts - Timestamp argument to precast_delayed_cast
-----------------------------------------------------------------------------------
--Returns:
---- none
-----------------------------------------------------------------------------------
function precast_delayed_cast(ts)
    ts = tonumber(ts)
    if ts then
        send_action(ts)
    else
        msg.debugging("Bad index passed to precast_delayed_cast")
    end
end


-----------------------------------------------------------------------------------
--Name: send_action(ts)
--Desc: Sends the cued action packet, if it exists.
--Args:
---- ts - index for a command_registry entry that includes an action packet (hopefully)
-----------------------------------------------------------------------------------
--Returns:
---- none
-----------------------------------------------------------------------------------
function send_action(ts)
    command_registry[ts].midaction = true
    equip_sets('midcast',ts,command_registry[ts].spell)
end