Tier 4 — Registries¶
These let your plugin add content of kinds EMS already understands — variables, conditions, step functions, and sequences. EMS validates each contribution, stores it namespaced by id, runs your logic inside pcall, and owns how it's used. Your code feeds data into EMS's own compiler and evaluator; it never replaces them.
A pattern runs through all four: you register a spec table whose id field must equal the id you register under, a duplicate id is rejected rather than overwritten, and your callbacks run isolated. Registration returns true, or false plus a reason.
Register through the handle to make it reversible
The GRIPEMS.API:Register* forms on this page are anonymous — EMS validates and stores them, but they aren't tied to a plugin, so they stay put if the plugin is later disabled. If your plugin uses RegisterPlugin, call the matching handle method instead (handle:RegisterVariableProvider(spec), handle:RegisterCondition(spec), and so on). It takes the same spec — minus the separate id argument, since the handle reads spec.id — registers it the same way, and journals it so disabling the plugin removes it. See reversibility.
API:RegisterVariableProvider(id, spec)¶
A variable provider is a read-only value source for the ~name~ variable system. When EMS resolves a variable, it checks the user's own variables first, then gear variables, and only then asks the registered providers — so a plugin can never shadow a user's variable. Providers are consulted in registration order, and the first one to return a usable scalar wins.
local ok, reason = API:RegisterVariableProvider("acme_haste", {
id = "acme_haste",
name = "Acme Haste Percent",
Resolve = function(self, varName)
if varName == "acme_haste" then
return math.floor(GetHaste()) -- a plain number
end
return nil -- not mine; let the next provider try
end,
})
Spec contract
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | yes | must equal the registry id |
name |
string | yes | human label |
Resolve |
function | yes | Resolve(self, varName) → scalar or nil |
OnRegister |
function | no | called once at register time, inside pcall |
Resolve must return a plain, non-secret scalar — a string, number, or boolean. Return nil for names you don't handle. If you return a table, a function, or a secret-tagged value, EMS ignores it (and logs the ignore against your id), because that value would otherwise be substituted into macrotext that can reach the secure execution path. Keep providers cheap and side-effect-free; they run during sequence compilation.
API:RegisterCondition(id, spec) and API:EvaluateCondition(id)¶
A condition is a named boolean predicate you can use inside a user variable's body at runtime. Conditions are runtime-only — they can't be baked into a native WoW macro conditional, since a plugin can't invent one — so they're evaluated when the variable resolves.
API:RegisterCondition("acme_lowmana", {
id = "acme_lowmana",
name = "Mana below 30%",
Evaluate = function(self)
return UnitPower("player") / UnitPowerMax("player") < 0.30
end,
})
A user then writes a variable body that branches on it:
Spec contract
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | yes | must equal the registry id |
name |
string | yes | human label |
Evaluate |
function | yes | Evaluate(self) → boolean |
OnRegister |
function | no | called once at register time, inside pcall |
EvaluateCondition(id) runs the predicate inside pcall and always returns a clean boolean. An unknown id, a predicate that throws (for example, a comparison against a secret value), a nil or non-boolean result, or a secret-tagged result all collapse to false. So a caller always gets a safe boolean and nothing taint-sensitive escapes — write Evaluate to return a plain true/false and don't worry about the edge cases crashing anything.
API:RegisterStepFunction(id, spec)¶
A step function is a pure step-ordering strategy, like the built-in Priority weighting. You supply Expand, which takes the resolved step texts and returns them in the order you want them executed. EMS owns the secure click body (the same round-robin body it uses for Sequential) and wraps each string you return into a macro step itself — so a plugin never supplies secure code and can't inject anything beyond ordered macrotext.
local ok, reason = API:RegisterStepFunction("acme_reverse", {
id = "acme_reverse",
name = "Reverse",
Expand = function(self, resolvedStepTexts)
local out = {}
for i = #resolvedStepTexts, 1, -1 do
out[#out + 1] = resolvedStepTexts[i]
end
return out
end,
})
Spec contract
| Field | Type | Required | Notes |
|---|---|---|---|
id |
string | yes | must equal the registry id, and must not collide with a built-in |
name |
string | yes | human label |
Expand |
function | yes | Expand(self, resolvedStepTexts) → array of macrotext strings. Each entry of resolvedStepTexts is a full macrotext line after variable substitution — e.g. "/cast [combat] Kill Command" — not a bare spell name. |
OnRegister |
function | no | called once at register time, inside pcall |
resolvedStepTexts holds macrotext, not spell names
A step is a macro line, so an entry looks like /cast [combat] Kill Command or
/use 6:Trinket, already variable-substituted. If you build a lookup table keyed on
spell names and index it with these strings, every lookup misses, your reorder loop
produces nothing, and Expand silently returns its input unchanged — EMS sees a valid
array of strings and has no way to tell you it did nothing. Match on substrings, or
parse the macro line, but do not compare against a bare name.
A registered step function becomes active when a sequence's active version names it as its stepFunction. Registration is rejected if the id collides with a built-in strategy or another registered one. Expand must be pure — return ordered strings, don't touch game state.
API:RegisterSequences(name, version, seqNames, seqTable)¶
Registers a set of rotation sequences your plugin ships. This is the original plugin entry point, re-exposed through the API.
API:RegisterSequences("Acme Rotations", "1.0.0",
{ "Acme Fire", "Acme Frost" },
{
["Acme Fire"] = { --[[ sequence definition ]] },
["Acme Frost"] = { --[[ sequence definition ]] },
})
| Parameter | Type | Meaning |
|---|---|---|
name |
string | your plugin's name |
version |
string | your plugin's version |
seqNames |
table | array of the sequence names you're registering |
seqTable |
table | the sequences, keyed by name |
EMS validates the inputs, namespaces them under your plugin, loads them into the engine, and fires PLUGIN_REGISTERED. A user's own sequence wins on a name clash, so you can't clobber their work. The per-sequence table follows EMS's own sequence format; the simplest path is to build a sequence in EMS, export it, and ship that shape.
RegisterSequences ships a static set. To create, edit, or delete a sequence at runtime — and have it removed when your plugin is disabled — use the handle's authoring methods instead; see Tier 5 - Authoring.
Detecting a tier before you use it¶
All four registries are gated by a capability id (variables, conditions, stepfunctions, sequences). If you want to degrade gracefully on an EMS that predates one, check GetCapabilities() first — see Tier 0.