Start typing to search packages!
hook-registry
By @ou1ck5cop3
Roblox
MirroredHookRegistry
A small, dependency-free event-hook dispatcher for Roblox combat and ability systems.
-- before — every new ability means editing every call site
if attacker:HasAbility("Thorns") then ... end
if attacker:HasAbility("Vampiric") then ... end
if attacker:HasAbility("LastStand") then ... end
-- after — call site is written once, never touched again
Registry:Fire("OnHitDealt", { attacker = plr, dmg = 25 }, gateResolver)
-- ability code is self-contained, added independently, anywhere
Registry:Register("OnHitDealt", "Thorns", function(ctx)
-- reflect damage back
end)
What it does NOT do
This is deliberately unopinionated:
- It does not know what an "ability" is, or how your game stores who
owns one. You supply a
gateResolver(gateKey, ctx) -> booleanfunction at:Fire()time that answers "should this entry run?". - It does not interpret your
ctxtable beyond one field:ctx._stop, which halts remaining entries for that fire call if set. Everything else inctx(a cancel flag, a mutable damage number, whatever your event needs) is a convention you define for your own game. - It does not touch Roblox services,
_G, or any specific data format. It's a plain Luau module with zero external dependencies.
This is a pattern extracted from a larger production combat system — the ability-specific logic (damage formulas, animations, specific ability names) was stripped out; what's left is just the dispatch mechanism.
Install
Via Wally:
[dependencies]
HookRegistry = "ou1ck5cop3/hook-registry@0.1.0"
Or copy src/init.lua directly into your project — it's a single
file with no dependencies.
API
HookRegistry.new(hookNames: {string}?) -> Registry
Creates a new registry instance. Each instance has independent state, so you can run multiple registries (e.g. one for combat, one for UI) without them interfering.
Registry:RegisterHook(hookName: string)
Declares a new hook type. Calling it twice is a no-op. You can pass
initial hook names to .new() instead of calling this separately.
Registry:Register(hookName, gateKey, fn, priority?)
Registers fn against hookName.
gateKey— passed to yourgateResolverat fire time. Use"*"for entries that should always run regardless of what the resolver says (they never call the resolver at all).priority— lower numbers fire first. Defaults to50.
Registry:Unregister(hookName, gateKey, fn) -> boolean
Removes a previously registered entry. Returns whether one was found and removed. Useful for un-equip flows or hot-reloading.
Registry:Fire(hookName, ctx, gateResolver?) -> ctx
Runs every registered entry for hookName in priority order.
gateResolver(gateKey, ctx) -> booleanis called once per non-wildcard entry to decide whether it runs. This is where you plug in your own "does this player own this ability" check. Omit it and only"*"entries will fire.- Each entry runs inside
pcall— an error in one entry iswarn'd and does not stop the others. - An entry can set
ctx._stop = trueto halt the remaining chain cooperatively.
Example
See example/example.lua for a worked example
with attacker-gated, victim-gated, and wildcard entries.
License
MIT — see LICENSE.
Package Details
Install command (Click to copy)
Version
0.1.1
License
MIT
Safe for commercial use
Automated license review — not legal advice.
