-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathActions.lua
908 lines (801 loc) · 30.1 KB
/
Actions.lua
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
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
--[[----------------------------------------------------------------------------
LiteMount/Actions.lua
Mounting actions.
Copyright 2011 Mike Battersby
----------------------------------------------------------------------------]]--
local _, LM = ...
local C_Spell = LM.C_Spell or C_Spell
local C_MountJournal = LM.C_MountJournal or C_MountJournal
local L = LM.Localize
--
-- This is the support for saving and restoring druid forms which is all done
-- in the Dismount action. Form IDs that you put here must be cancelled
-- automatically on mounting (otherwise trying to restore them when you are
-- already in them will cancel them).
--
-- See: https://wow.gamepedia.com/API_GetShapeshiftFormID
--
local savedFormName = nil
local restoreFormIDs = {
[1] = true, -- Cat Form
[5] = true, -- Bear Form
[8] = true, -- Bear Form (Classic)
[31] = true, -- Moonkin Form
[35] = true, -- Moonkin Form
[36] = true, -- Treant Form
}
local FLOWCONTROLS = { }
-- trueState values
-- 0 false and never was true
-- 1 true
-- 2 false and previously was true
FLOWCONTROLS['IF'] =
function (args, context, isTrue)
local trueState = isTrue and 1 or 0
table.insert(context.flowControl, trueState)
LM.Debug(' * IF test is ' .. table.concat(context.flowControl, ','))
end
FLOWCONTROLS['ELSEIF'] =
function (args, context, isTrue)
local trueState = context.flowControl[#context.flowControl]
trueState = trueState == 1 and 2 or isTrue and 1 or 0
context.flowControl[#context.flowControl] = trueState
LM.Debug(' * ELSEIF test is ' .. table.concat(context.flowControl, ','))
end
FLOWCONTROLS['ELSE'] =
function (args, context, isTrue)
local trueState = context.flowControl[#context.flowControl]
trueState = trueState + 1
context.flowControl[#context.flowControl] = trueState
LM.Debug(' * ELSE test is ' .. table.concat(context.flowControl, ','))
end
FLOWCONTROLS['END'] =
function (args, context, isTrue)
table.remove(context.flowControl)
LM.Debug(' * END is ' .. table.concat(context.flowControl, ','))
end
local ACTIONS = { }
-- Modifies the list of usable mounts so action list lines after this one
-- get the restricted list. Always returns no action.
ACTIONS['Limit'] = {
handler =
function (args, context)
LM.Debug(" * Add limits: " .. args:ToString())
table.insert(context.limits, args)
end
}
-- These ones are really just for user rules. The whole syntax of the Limit
-- args is a mess and doesn't parse well. If I had my time again I would
-- probably use & and | which are more obvious. Or, you know, start with a proper
-- grammar-based parser.
local function MountListToDisplay(args)
local limits = args:ParseList()
return LM.tMap(limits, LM.Mount.FilterToDisplay, true)
end
ACTIONS['LimitSet'] = {
name = L.LM_LIMIT_MOUNTS,
description = L.LM_LIMITSET_DESCRIPTION,
argType = 'expression',
toDisplay = MountListToDisplay,
handler =
function (args, context)
ACTIONS.Limit.handler(args:Prepend('='), context)
end,
}
ACTIONS['LimitInclude'] = {
name = L.LM_INCLUDE_MOUNTS,
description = L.LM_LIMITINCLUDE_DESCRIPTION,
argType = 'expression',
toDisplay = MountListToDisplay,
handler =
function (args, context)
ACTIONS.Limit.handler(args:Prepend('+'), context)
end,
}
ACTIONS['LimitExclude'] = {
name = L.LM_EXCLUDE_MOUNTS,
description = L.LM_LIMITEXCLUDE_DESCRIPTION,
argType = 'expression',
toDisplay = MountListToDisplay,
handler =
function (args, context)
ACTIONS.Limit.handler(args:Prepend('-'), context)
end,
}
ACTIONS['Endlimit'] = {
argType = 'none',
handler =
function (args, context)
local oldLimits = table.remove(context.limits)
if oldLimits then
LM.Debug(" * removed limits: " .. oldLimits:ToString())
end
end,
}
local function GetUsableSpell(arg)
-- You can look up any spell from any class by number so we have to
-- test numbers to see if we know them
local argN = tonumber(arg)
if argN and not IsPlayerSpell(argN) then
return
end
-- For names, GetSpellInfo returns nil if it's not in your spellbook
-- so we don't need to call IsPlayerSpell
local info = C_Spell.GetSpellInfo(argN or arg)
if not info then
return
end
-- Glide won't cast while mounted
if info.spellID == 131347 and IsMounted() then
return
end
-- Zen Flight only works if you can fly
if info.spellID == 125883 then
if not ( IsFlyableArea() or IsAdvancedFlyableArea() ) then
return
end
end
-- Some spells share names (e.g., Surge Forward is both an Evoker ability
-- and a Skyriding ability). If the spell has a subtext it can be
-- distinguished by bracketing it after the name. This only works if you
-- pass the spell in by ID since otherwise you'll get whichever one
-- GetSpellInfo(name) decides to return.
local subtext = C_Spell.GetSpellSubtext(argN or arg)
local nameWithSubtext = string.format('%s(%s)', info.name, subtext or "")
if C_Spell.IsSpellUsable(info.name) then
local cooldownInfo = C_Spell.GetSpellCooldown(info.name)
if cooldownInfo and cooldownInfo.startTime == 0 then
return info.name, info.spellID, nameWithSubtext
end
end
end
local function SpellArgsToDisplay(args)
local out = {}
for _, v in ipairs(args:ParseList()) do
local info = C_Spell.GetSpellInfo(v)
if info then
table.insert(out, string.format("%s (%d)", info.name, info.spellID))
else
table.insert(out, v)
end
end
return out
end
ACTIONS['Spell'] = {
name = L.LM_SPELL_ACTION,
description = L.LM_SPELL_DESCRIPTION,
argType = 'list',
toDisplay = SpellArgsToDisplay,
handler =
function (args, context)
for _, arg in ipairs(args:ParseList()) do
LM.Debug(' * trying spell: ' .. tostring(arg))
local name, id, nameWithSubtext = GetUsableSpell(arg)
if nameWithSubtext then
LM.Debug(" * setting action to spell " .. nameWithSubtext)
return LM.SecureAction:Spell(nameWithSubtext, context.rule.unit)
end
end
end
}
-- Buff is the same as Spell but checks if you have a matching aura and
-- doesn't recast. Note that it checks only for buffs on the assumption
-- that you can't cast a debuff on yourself, and that it checks by name
-- because for some spells (e.g., Levitate) the ID doesn't match.
ACTIONS['Buff'] = {
toDisplay = SpellArgsToDisplay,
argType = 'list',
handler =
function (args, context)
for _, arg in ipairs(args:ParseList()) do
LM.Debug(' * trying buff: ' .. tostring(arg))
local name, id, nameWithSubtext = GetUsableSpell(arg)
if name and not LM.UnitAura(context.rule.unit or 'player', name) then
LM.Debug(" * setting action to spell " .. nameWithSubtext)
return LM.SecureAction:Spell(nameWithSubtext, context.rule.unit)
end
end
end
}
-- Set context.precast to a spell name to try to macro in before mounting journal
-- mounts. This is a bit less strict than Spell and Buff because the macro
-- still works even if the spell isn't usable, and has the advantage of
-- avoiding the IsSpellUsable failures when targeting others.
ACTIONS['PreCast'] = {
name = L.LM_PRECAST_ACTION,
description = L.LM_PRECAST_DESCRIPTION,
argType = 'list',
toDisplay = SpellArgsToDisplay,
handler =
function (args, context)
for _, arg in ipairs(args:ParseList()) do
local info = C_Spell.GetSpellInfo(arg)
if info and IsPlayerSpell(info.spellID) and info.castTime == 0 then
LM.Debug(" * setting preCast to spell " .. info.name)
context.preCast = info.name
context.preUse = nil
return
end
end
end
}
ACTIONS['CancelAura'] = {
toDisplay = SpellArgsToDisplay,
argType = 'list',
handler =
function (args, context)
for _, arg in ipairs(args:ParseList()) do
local info = LM.UnitAura('player', arg)
if info then
-- Levitate (for example) is marked canApplyAura == false so this is a
-- half-workaround. You still won't cancel Levitate somone else put on you.
if info.canApplyAura then
return LM.SecureAction:CancelAura(info.name)
elseif info.sourceUnit == 'player' and C_Spell.GetSpellInfo(info.name) then
return LM.SecureAction:CancelAura(info.name)
end
end
end
end
}
-- In vehicle -> exit it
ACTIONS['LeaveVehicle'] = {
argType = 'none',
handler =
function (args, context)
if CanExitVehicle() then
LM.Debug(" * setting action to leavevehicle")
return LM.SecureAction:LeaveVehicle()
end
end
}
local function GetFormNameWithSubtext()
local idx = GetShapeshiftForm()
if idx and idx > 0 then
local spellID = select(4, GetShapeshiftFormInfo(idx))
local n = C_Spell.GetSpellName(spellID)
local s = C_Spell.GetSpellSubtext(spellID) or ''
return string.format('%s(%s)', n, s)
end
end
-- This includes dismounting from mounts and also canceling other mount-like
-- things such as shapeshift forms. The logic here is torturous.
ACTIONS['Dismount'] = {
name = BINDING_NAME_DISMOUNT,
argType = 'none',
handler =
function (args, context)
local action
-- Shortcut dismount from journal mounts. This has the (wanted) side
-- effect of dismounting you even from mounts that aren't enabled,
-- and the (wanted) side effect of dismounting while in moonkin form
-- without cancelling it.
if IsMounted() then
LM.Debug(" * setting action to dismount")
action = LM.SecureAction:Execute(Dismount)
else
-- Otherwise we look for the mount from its buff and return the cancel
-- actions.
local m = LM.MountRegistry:GetActiveMount()
if m and m:IsCancelable() then
LM.Debug(" * setting action to cancel " .. m.name)
action = m:GetCancelAction()
end
end
-- This obeys "Auto Dismount in Flight", otherwise it would need a
-- macrotext and not work from the actionbar.
if action and savedFormName and savedFormName ~= GetFormNameWithSubtext() then
local autoDismount = not IsFlying() or GetCVarBool('autoDismountFlying')
if autoDismount or GetShapeshiftFormID() then
LM.Debug(" * override action to restore form: " .. savedFormName)
action = LM.SecureAction:Spell(savedFormName)
end
end
if action then
savedFormName = nil
return action
elseif LM.Options:GetOption('restoreForms') then
-- Save current form, if any
local currentFormID = GetShapeshiftFormID()
if currentFormID and restoreFormIDs[currentFormID] then
savedFormName = GetFormNameWithSubtext()
LM.Debug(" * saving current form " .. savedFormName)
end
end
end
}
-- CancelForm has been absorbed into Dismount
ACTIONS['CancelForm'] = {
argType = 'none',
handler = function (args, context) end
}
-- Got a player target, try copying their mount
ACTIONS['CopyTargetsMount'] = {
argType = 'none',
handler =
function (args, context)
local unit = context.rule.unit or "target"
-- if LM.Options:GetOption('copyTargetsMount') and UnitIsPlayer(unit) then
if LM.Options:GetOption('copyTargetsMount') then
LM.Debug(" * trying to clone %s's mount", unit)
local m = LM.MountRegistry:GetMountFromUnitAura(unit)
if m and m:IsCastable() then
LM.Debug(" * setting action to mount %s", m.name)
return m:GetCastAction(context)
end
end
end
}
ACTIONS['ApplyRules'] = {
argType = 'none',
handler =
function (args, context)
local ruleSet = LM.Options:GetCompiledRuleSet(context.id)
LM.Debug(" * checking %d rules for button %d", #ruleSet, context.id)
local act, n = ruleSet:Run(context)
if act then
LM.Debug(" * found matching rule %d", n)
return act
end
LM.Debug(" * no rules matched")
end
}
local switchSpellID = C_MountJournal.GetDynamicFlightModeSpellID()
local switchSpellInfo = C_Spell.GetSpellInfo(switchSpellID or 0)
ACTIONS['SwitchFlightStyle'] = {
name = switchSpellInfo and switchSpellInfo.name,
argType = 'valueOrNone',
toDisplay =
function (args)
if #args == 0 then
return { switchSpellInfo.name }
else
-- XXX if there is a localization for Steady Flight it would
-- be better to return it instead of L.FLY
local typeName = L[args[1]] or UNKNOWN
return { switchSpellInfo.name .. ': ' .. typeName }
end
end,
handler =
function (args, context)
if IsPlayerSpell(switchSpellID) then
local argList = args:ParseList()
local _, currentStyle = LM.Environment:GetFlightStyle()
if #argList == 0 or currentStyle ~= argList[1] then
LM.Debug(" * setting action to spell " .. switchSpellInfo.name)
return LM.SecureAction:Spell(switchSpellID, context.rule.unit)
end
end
end
}
local mawCastableArg = LM.RuleArguments:Get("MAWUSABLE", ",", "CASTABLE")
local castableArg = LM.RuleArguments:Get("CASTABLE")
local smartActions = {
{
-- Only original aquatic mounts are sped up by the buff in Vashj'ir,
-- newer hybrid ones like Ottuks are slow.
condition = "[submerged,map:203]",
arg = LM.RuleArguments:Get('mt:231', '/', 'mt:254'),
debug = "Aquatic Mount (Vashj'ir)",
},
{
condition = "[submerged]",
arg = LM.RuleArguments:Get('SWIM'),
debug = "Aquatic Mount (underwater)",
},
{
condition = "[flyable,advflyable]",
arg = LM.RuleArguments:Get('DRAGONRIDING'),
debug = "Skyriding Mount",
},
{
condition = "[flyable]",
arg = LM.RuleArguments:Get('FLY'),
debug = 'Flying Mount',
},
{
condition = "[drivable]",
arg = LM.RuleArguments:Get('DRIVE'),
debug = 'D.R.I.V.E.',
},
{
condition = "[floating,nowaterwalking]",
arg = LM.RuleArguments:Get('SWIM'),
debug = "Aquatic Mount (on the surface)",
},
{
condition = "[]",
arg = LM.RuleArguments:Get('RUN', ',', '~', 'SLOW'),
debug = "Ground Mount",
},
{
condition = "[]",
arg = LM.RuleArguments:Get('RUN', ',', 'SLOW'),
debug = "Slow Ground Mount",
},
}
ACTIONS['Mount'] = {
name = L.LM_MOUNT_ACTION,
description = L.LM_MOUNT_DESCRIPTION,
argType = 'expression',
toDisplay = MountListToDisplay,
handler =
function (args, context)
local limits = CopyTable(context.limits)
if LM.Conditions:Check("[maw]", context) then
table.insert(limits, mawCastableArg)
else
table.insert(limits, castableArg)
end
if #args > 0 then
table.insert(limits, args)
end
local filteredList = LM.MountRegistry:Limit(limits)
LM.Debug(" * args: " .. args:ToString())
LM.Debug(" * limits:")
for i, l in ipairs(limits) do
LM.Debug(" % 2d. %s", i, l:ToString())
end
LM.Debug(" * filtered list contains " .. #filteredList .. " mounts")
if next(filteredList) == nil then return end
local randomStyle = context.rule.priority and LM.Options:GetOption('randomWeightStyle')
local m
if context.rule.smart then
for _, info in ipairs(smartActions) do
if not m and LM.Conditions:Check(info.condition, context) then
LM.Debug(" * trying " .. info.debug)
local expr = info.arg:ParseExpression()
local mounts = filteredList:ExpressionSearch(expr)
LM.Debug(" * found " .. #mounts .. " mounts.")
m = mounts:Random(context.random, randomStyle)
if context.rule.strict and info.condition ~= '[]' and not m then
return
end
end
end
else
m = filteredList:Random(context.random, randomStyle)
end
if m then
LM.Debug(" * setting action to mount %s", m.name)
return m:GetCastAction(context), m
end
end
}
ACTIONS['SmartMount'] = {
name = L.LM_SMARTMOUNT_ACTION,
description = L.LM_SMARTMOUNT_DESCRIPTION,
argType = 'expression',
toDisplay = MountListToDisplay,
handler =
function (args, context)
context.rule.priority = true
context.rule.smart = true
return ACTIONS.Mount.handler(args, context)
end
}
ACTIONS['PriorityMount'] = {
name = L.LM_PRIORITYMOUNT_ACTION,
description = L.LM_PRIORITYMOUNT_DESCRIPTION,
argType = 'expression',
toDisplay = MountListToDisplay,
handler =
function (args, context)
context.rule.priority = true
return ACTIONS.Mount.handler(args, context)
end
}
ACTIONS['Macro'] = {
argType = 'none',
handler =
function (args, context)
local macrotext = LM.Options:GetOption('unavailableMacro')
if macrotext ~= "" then
if GetRunningMacro() then
LM.Debug(" * unavailable macro not possible from actionbar")
else
LM.Debug(" * setting action to unavailable macro")
return LM.SecureAction:Macro(macrotext)
end
end
end
}
ACTIONS['Script'] = {
argType = 'macrotext',
handler =
function (args, context)
if GetRunningMacro() then
LM.Debug(" * Script action not available from actionbar")
else
local macroText = args:ToString()
LM.Debug(" * setting action to script line: " .. macroText)
return LM.SecureAction:Macro(macroText)
end
end
}
ACTIONS['CantMount'] = {
argType = 'none',
handler =
function (args, context)
-- This isn't a great message, but there isn't a better one that
-- Blizzard have already localized. See FrameXML/GlobalStrings.lua.
-- LM.Warning("You don't know any mounts you can use right now.")
LM.Warning(SPELL_FAILED_NO_MOUNTS_ALLOWED)
LM.Debug(" * setting action to can't mount now")
return LM.SecureAction:NoAction()
end
}
-- In a theoretical world these could be rules, but the number of times you can do
-- something at all useful in combat at the moment is 1. It's hard to imagine
-- anything else useful you'd want to do in combat that isn't covered by the much
-- simpler combatMacro. If that were done it would need some way to override the normal
-- CASTABLE check for mounts since they won't be castable right away.
--
-- E.g, Mount [map:2234] DRAGONRIDING
local function SummonJournalMountDirect(...)
if IsMounted() then
Dismount()
else
local mounts = LM.MountRegistry:FilterSearch(..., 'JOURNAL', 'CASTABLE')
local m = mounts:Random()
if m then C_MountJournal.SummonByID(m.mountID) end
end
end
local function CombatHandlerOverride(args, context)
-- For speed these should try to return ASAP.
local id, name = LM.Environment:GetEncounterInfo()
if id and name then
LM.Debug(" * matched encounter %s (%d)", name, id)
end
-- Tindral Sageswift, Amirdrassil raid (Dragonflight)
if LM.Environment:IsMapInPath(2234) then
return LM.SecureAction:Execute(function () SummonJournalMountDirect('DRAGONRIDING') end)
end
-- The Dawnbreaker dungeon (The War Within)
-- Two boss fight (Speaker Shadowcrown and Rasha'nan) have flying in combat
-- enabled by a debuff, Radiant Light.
-- https://www.wowhead.com/spell=449042/radiant-light
-- Unfortunately it may not be enabled when you enter combat so we have to
-- override the whole instance.
local instanceID = select(8, GetInstanceInfo())
if instanceID == 2662 then
-- Because you can fly out of combat the CASTABLE checks work correctly
-- and there's no need to be fancy.
return LM.SecureAction:Execute(function () SummonJournalMountDirect('DRAGONRIDING') end)
end
end
-- Combat handler is a bit magic because it's called from PLAYER_REGEN_DISABLED
-- rather than by user activation, so you can't tell if it's being called from
-- a macro (and thus won't work).
ACTIONS['Combat'] = {
argType = 'none',
handler =
function (args, context)
-- If specific combat macro is set always use it
if LM.Options:GetOption('useCombatMacro') then
LM.Debug(" * setting action to options combat macro")
local macrotext = LM.Options:GetOption('combatMacro')
return LM.SecureAction:Macro(macrotext)
end
-- Check for an override combat setting
local act = CombatHandlerOverride(args, context)
if act then
LM.Debug(" * setting action to %s", act:GetDescription())
return act
end
-- Otherwise use the default actions
LM.Debug(" * setting action to default combat macro")
local macrotext = LM.Actions:DefaultCombatMacro()
return LM.SecureAction:Macro(macrotext)
end
}
ACTIONS['Stop'] = {
argType = 'none',
handler =
function (args, context)
-- return true and set up to do nothing
LM.Debug(" * setting action to nothing for stop")
return LM.SecureAction:NoAction()
end
}
local function IsCastableItem(itemID)
if not itemID then
return false
end
if PlayerHasToy(itemID) then
if not C_ToyBox.IsToyUsable(itemID) then
return false
end
elseif not C_Item.IsUsableItem(itemID) then
return false
elseif C_Item.IsEquippableItem(itemID) and not C_Item.IsEquippedItem(itemID) then
return false
end
local s, d, e = C_Container.GetItemCooldown(itemID)
if s == 0 and (e == true or e == 1) then
return true
end
return false
end
-- A derpy version of SecureCmdItemParse that doesn't support bags but does
-- support item IDs as well as slot names. The assumption is that if you have
-- the item then GetItemName will always return values immediately.
local function UsableItemParse(arg)
local name, itemID, slotNum
local slotOrID = tonumber(arg)
if slotOrID and slotOrID <= INVSLOT_LAST_EQUIPPED then
slotNum = slotOrID
elseif slotOrID then
name = C_Item.GetItemNameByID(slotOrID)
itemID = slotOrID
else
local slotName = "INVSLOT_"..arg:upper()
if _G[slotName] then
slotNum = _G[slotName]
else
name = arg
itemID = C_Item.GetItemInfoInstant(arg)
end
end
return name, itemID, slotNum
end
-- Is this really not in the game anywhere?
local InventorySlotTable = {
[INVSLOT_AMMO] = AMMOSLOT,
[INVSLOT_HEAD] = HEADSLOT,
[INVSLOT_NECK] = NECKSLOT,
[INVSLOT_SHOULDER] = SHOULDERSLOT,
[INVSLOT_BODY] = SHIRTSLOT,
[INVSLOT_CHEST] = CHESTSLOT,
[INVSLOT_WRIST] = WRISTSLOT,
[INVSLOT_HAND] = HANDSSLOT,
[INVSLOT_WAIST] = WAISTSLOT,
[INVSLOT_LEGS] = LEGSSLOT,
[INVSLOT_FEET] = FEETSLOT,
[INVSLOT_FINGER1] = FINGER0SLOT .. " (1)",
[INVSLOT_FINGER2] = FINGER1SLOT .. " (2)",
[INVSLOT_TRINKET1] = TRINKET0SLOT .. " (1)",
[INVSLOT_TRINKET2] = TRINKET1SLOT .. " (2)",
[INVSLOT_BACK] = BACKSLOT,
[INVSLOT_MAINHAND] = MAINHANDSLOT,
[INVSLOT_OFFHAND] = SECONDARYHANDSLOT,
[INVSLOT_RANGED] = RANGEDSLOT,
[INVSLOT_TABARD] = TABARDSLOT,
}
local function ItemArgsToDisplay(args)
local out = {}
for _, v in ipairs(args:ParseList()) do
local name, id, slot = UsableItemParse(v)
if name then
table.insert(out, string.format("%s (%d)", name, id))
elseif slot then
table.insert(out, InventorySlotTable[slot] or slot)
else
table.insert(out, v)
end
end
return out
end
ACTIONS['Use'] = {
name = L.LM_USE_ACTION,
description = L.LM_USE_DESCRIPTION,
argType = 'list',
toDisplay = ItemArgsToDisplay,
handler =
function (args, context)
for _, arg in ipairs(args:ParseList()) do
local name, itemID, slotNum = UsableItemParse(arg)
if slotNum then
LM.Debug(' * trying slot ' .. tostring(slotNum))
local s, d, e = GetInventoryItemCooldown('player', slotNum)
if s == 0 and e == 1 then
LM.Debug(' * Setting action to use slot ' .. slotNum)
return LM.SecureAction:Item(slotNum, context.rule.unit)
end
else
LM.Debug(' * trying item ' .. tostring(name))
if name and IsCastableItem(itemID) then
LM.Debug(' * setting action to use item ' .. name)
return LM.SecureAction:Item(name, context.rule.unit)
end
end
end
end
}
ACTIONS['PreUse'] = {
name = L.LM_PREUSE_ACTION,
description = L.LM_PREUSE_DESCRIPTION,
argType = 'list',
toDisplay = ItemArgsToDisplay,
handler =
function (args, context)
local action = ACTIONS['Use'].handler(args, context)
if action and action.item then
LM.Debug(" * setting preUse to item " .. action.item)
context.preUse = action.item
context.preCast = nil
return
end
end
}
do
for a, info in pairs(ACTIONS) do
info.action = a
end
end
--[[------------------------------------------------------------------------]]--
LM.Actions = { }
local function GetDruidMountForms()
local forms = {}
for i = 1,GetNumShapeshiftForms() do
local spell = select(4, GetShapeshiftFormInfo(i))
if spell == LM.SPELL.TRAVEL_FORM or spell == LM.SPELL.MOUNT_FORM then
tinsert(forms, i)
end
end
return table.concat(forms, "/")
end
-- This is the macro that gets set as the default and will trigger if
-- we are in combat. Don't put anything in here that isn't specifically
-- combat-only, because out of combat we've got proper code available.
-- Note that macros are limited to 255 chars, even inside a SecureActionButton.
function LM.Actions:DefaultCombatMacro()
local mt = "/dismount [mounted]\n/stopmacro [mounted]\n"
local _, playerClass = UnitClass("player")
if playerClass == "DRUID" then
local forms = GetDruidMountForms()
local mount = LM.MountRegistry:GetMountBySpell(LM.SPELL.TRAVEL_FORM)
if mount and mount:GetPriority() > 0 then
mt = mt .. format("/cast [noform:%s] %s\n", forms, mount.name)
mt = mt .. format("/cancelform [form:%s]\n", forms)
end
elseif playerClass == "SHAMAN" then
local mount = LM.MountRegistry:GetMountBySpell(LM.SPELL.GHOST_WOLF)
if mount and mount:GetPriority() > 0 then
local s = C_Spell.GetSpellName(LM.SPELL.GHOST_WOLF)
mt = mt .. "/cast [noform] " .. s .. "\n"
mt = mt .. "/cancelform [form]\n"
end
end
mt = mt .. "/leavevehicle\n"
return mt
end
function LM.Actions:GetArgType(action)
if FLOWCONTROLS[action] then
return 'none'
elseif ACTIONS[action] then
return ACTIONS[action].argType
end
end
function LM.Actions:GetFlowControlHandler(action)
return FLOWCONTROLS[action]
end
function LM.Actions:GetHandler(action)
local a = ACTIONS[action]
if a then return a.handler end
end
function LM.Actions:IsFlowSkipped(context)
return ContainsIf(context.flowControl, function (v) return v ~= 1 end)
end
-- This is really terrible and only works for a minimal amount of things
-- which rules might do, which is so far only one mount or group or type etc.
-- It will probably break if I ever make rules actions even slightly flexible.
function LM.Actions:ToDisplay(action, args)
local a = ACTIONS[action]
if a then
local name = a.name or action
if args == nil then
return name
elseif a.toDisplay then
return name, table.concat(a.toDisplay(args), "\n")
else
return name, args:ToString()
end
else
return action.." |A:gmchat-icon-alert:15:15|a", args:ToString()
end
end
function LM.Actions:GetDescription(action)
local a = ACTIONS[action]
if a then return a.description end
end