Repository navigation
Expand file tree
/
Copy pathTarget.lua
More file actions
208 lines (186 loc) · 7.93 KB
/
Copy pathTarget.lua
File metadata and controls
208 lines (186 loc) · 7.93 KB
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
local ADDON, ns = ...
-- A spell's target: who it goes to when the player presses the button.
--
-- === Why condition strings and not code ===
--
-- Target selection happens at the moment the button is pressed, that is,
-- mostly in combat. Insecure code has no reach into combat, and all we can do
-- is hand the engine a rule in advance and step away. That rule is a macro
-- condition string, the engine evaluates it, and our code takes no further
-- part in the decision.
--
-- The same technique as paging and visibility (Paging.lua), and for the same
-- reason.
--
-- === Three kinds of spell and why three ===
--
-- The "[help]" condition asks about the TARGET, not about the spell: "is the
-- one currently selected friendly". And the target has to be chosen by what
-- kind of spell lies on the button: a heal wants a friend, an attack an enemy,
-- and a spell that does both will take either.
--
-- So there are three rules, and which one applies to a button is decided
-- separately - at the moment the engine learns what lies on it
-- (Build.Targeting).
--
-- === What is not here ===
--
-- A walk over the spellbook. Bartender4 glues the body of its secure handler
-- together in a loop over the whole book - one line per spell - and for a
-- level seventy mage that is hundreds of lines of Lua handed to the engine on
-- every talent change. It does that to learn inside the handler whether a
-- spell is friendly, on clients that cannot answer that question directly.
--
-- Rack asks directly (IsSpellHelpful, IsSpellHarmful) and, where there is
-- nothing to ask with, makes do with a rule that does not tell friend from
-- foe. That is less precise and more honest: imprecision is visible to the
-- player at once, while a hundred glued lines fail silently.
local Target = {}
ns.Target = Target
-- Spell kinds by who they are good for.
local KINDS = {
help = true, -- friendly: a heal, a shield, a blessing
harm = true, -- hostile: a strike, a curse
all = true, -- either: some druid and priest spells
}
Target.KINDS = KINDS
-- The keys the engine knows by name. An invented one does not make it into a
-- rule: the engine will not answer to it, and a silent rule is a button that
-- stopped casting by mouseover with nothing for the player to diagnose it by.
local MODIFIERS = {
ALT = true,
SHIFT = true,
CTRL = true,
}
Target.MODIFIERS = MODIFIERS
--- The mouseover key, brought to what the engine knows.
--
-- "NONE" means precisely "no key" rather than a key by that name: that is what
-- the setting calls it, and what Bartender calls it.
--
-- @param value any
-- @return string|nil the condition fragment ",mod:ALT" or nothing
local function modifier(value)
if type(value) ~= "string" then return nil end
if not MODIFIERS[value] then return nil end
return ",mod:" .. value
end
Target.Modifier = modifier
--- The target rule for a spell of this kind.
--
-- Pure. Handed to the secure state handler.
--
-- === What the trailing "nil" means ===
--
-- The tail is mandatory and means "nobody in particular", that is, a return to
-- the button's ordinary behaviour: no mouse over anyone - cast at the target,
-- as before. Without the tail the rule would end at the last condition, and
-- when no condition matched the button would keep the unit from the PREVIOUS
-- mouseover.
--
-- === The order is the precedence ===
--
-- The engine takes the first match. The order here is:
--
-- 1. self-cast and focus - because with a unit chosen by mouseover the
-- engine NO LONGER ASKS the self-cast attributes: the unit is set and
-- there is nothing to ask. The rule has to repeat them itself;
-- 2. mouseover - the player's direct instruction here and now;
-- 3. auto-assist - a guess at who they meant.
--
-- A direct instruction always outranks a guess.
--
-- @param kind any "help", "harm" or "all"
-- @param own any bar settings
-- @param common any shared settings
-- @return string|nil the rule, or nothing when there is no unit to choose
function Target.Driver(kind, own, common)
if not KINDS[kind] then return nil end
if type(own) ~= "table" then return nil end
if type(common) ~= "table" then common = {} end
local mouseover = own.mouseover == true
local assist = own.autoAssist == true
-- Neither one nor the other - there is no rule at all, and that is not the
-- same as an empty rule: an empty one the engine would read as "a unit is
-- never chosen", and the button would stop casting at the ordinary target.
if not mouseover and not assist then return nil end
local parts = {}
-- Self-cast. Friendly only: one does not hit oneself.
if kind ~= "harm" and common.selfCast == true then
parts[#parts + 1] = "[mod:SELFCAST]player"
end
-- Focus. Both: a focus is both healed and hit. A dead focus does not count
-- - a spell on a corpse would simply eat the press.
if common.focusCast == true then
parts[#parts + 1] = "[mod:FOCUSCAST,@focus,exists,nodead]focus"
end
if mouseover then
local mod = modifier(common.mouseoverMod) or ""
-- A friendly spell wants a friend under the mouse, a harmful one an
-- enemy. Otherwise a heal would go to an enemy and simply not cast,
-- while the player would think they missed with the mouse.
if kind == "help" then
parts[#parts + 1] = "[@mouseover,exists,nodead,help" .. mod .. "]mouseover"
elseif kind == "harm" then
parts[#parts + 1] = "[@mouseover,exists,nodead,harm" .. mod .. "]mouseover"
else
parts[#parts + 1] = "[@mouseover,exists,nodead" .. mod .. "]mouseover"
end
end
if assist then
-- A heal with an enemy targeted goes to whoever that enemy is hitting.
-- The first condition is "the target is already friendly, do not
-- interfere": without it a heal cast on a friend would go to the
-- FRIEND's target.
if kind == "help" then
parts[#parts + 1] = "[help]nil"
parts[#parts + 1] = "[@targettarget,help,nodead]targettarget"
elseif kind == "harm" then
parts[#parts + 1] = "[harm]nil"
parts[#parts + 1] = "[@targettarget,harm,nodead]targettarget"
end
-- A spell that both heals and harms can have no target-of-target:
-- there is no telling who counts as ours.
end
-- Nothing was gathered: auto-assist on a spell with no friend/foe
-- distinction is the only such case.
if #parts == 0 then return nil end
parts[#parts + 1] = "nil"
return table.concat(parts, "; ")
end
--- Whether this client can tell a friendly spell from a harmful one.
--
-- Asked of the game rather than inferred from the build number: "does this
-- function exist" has a direct answer, while a build number is a guess at it.
--
-- @param globals table|nil table of global names; nil - the real one
-- @return boolean
function Target.Tells(globals)
local where = type(globals) == "table" and globals or _G
return type(where.IsSpellHelpful) == "function"
and type(where.IsSpellHarmful) == "function"
end
--- Which of the three rules suits this spell.
--
-- Answers with a kind name: "help", "harm" or "all". A client that cannot tell
-- answers "all" - the rule with no distinction, and that is a deliberate
-- imprecision rather than an oversight.
--
-- @param spellId any spell id
-- @param globals table|nil
-- @return string
function Target.KindOf(spellId, globals)
local where = type(globals) == "table" and globals or _G
if not Target.Tells(where) then return "all" end
local id = ns.Probe.Numeric(spellId)
if id == nil then return "all" end
local okHelp, helps = pcall(where.IsSpellHelpful, id)
local okHarm, harms = pcall(where.IsSpellHarmful, id)
if not okHelp or not okHarm then return "all" end
-- Both or neither: there is nothing to distinguish.
if (helps and true or false) == (harms and true or false) then
return "all"
end
if helps then return "help" end
return "harm"
end