{"id":"ou1ck5cop3/hook-registry","name":"hook-registry","scope":"ou1ck5cop3","platform":"roblox","description":"A dependency-free event-hook dispatcher for Roblox combat and ability systems","version":"0.1.1","latest":"0.1.1","versions":["0.1.0","0.1.1"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"a03764b54ca6221658d5188fa91639704ce7cb3bb57d9e4fe9fe9ba5c7fdf77e","likes":0,"downloads":0,"install":"forest install ou1ck5cop3/hook-registry","url":"https://forest.dev/p/roblox/ou1ck5cop3/hook-registry","files":"https://api.forest.dev/ai/package/roblox/ou1ck5cop3/hook-registry/files","readme":"# HookRegistry\n\n[![CI](https://github.com/Ou1cK5CoP3/hook-registry/actions/workflows/ci.yml/badge.svg)](https://github.com/Ou1cK5CoP3/hook-registry/actions/workflows/ci.yml)\n\nA small, dependency-free event-hook dispatcher for Roblox combat and\nability systems.\n\n```lua\n-- before — every new ability means editing every call site\nif attacker:HasAbility(\"Thorns\") then ... end\nif attacker:HasAbility(\"Vampiric\") then ... end\nif attacker:HasAbility(\"LastStand\") then ... end\n\n-- after — call site is written once, never touched again\nRegistry:Fire(\"OnHitDealt\", { attacker = plr, dmg = 25 }, gateResolver)\n\n-- ability code is self-contained, added independently, anywhere\nRegistry:Register(\"OnHitDealt\", \"Thorns\", function(ctx)\n\t-- reflect damage back\nend)\n```\n\n## What it does NOT do\n\nThis is deliberately unopinionated:\n\n- It does not know what an \"ability\" is, or how your game stores who\n  owns one. You supply a `gateResolver(gateKey, ctx) -> boolean`\n  function at `:Fire()` time that answers \"should this entry run?\".\n- It does not interpret your `ctx` table beyond one field: `ctx._stop`,\n  which halts remaining entries for that fire call if set. Everything\n  else in `ctx` (a cancel flag, a mutable damage number, whatever your\n  event needs) is a convention you define for your own game.\n- It does not touch Roblox services, `_G`, or any specific data format.\n  It's a plain Luau module with zero external dependencies.\n\nThis is a pattern extracted from a larger production combat system —\nthe ability-specific logic (damage formulas, animations, specific\nability names) was stripped out; what's left is just the dispatch\nmechanism.\n\n## Install\n\nVia [Wally](https://wally.run/):\n\n```toml\n[dependencies]\nHookRegistry = \"ou1ck5cop3/hook-registry@0.1.0\"\n```\n\nOr copy `src/init.lua` directly into your project — it's a single\nfile with no dependencies.\n\n## API\n\n### `HookRegistry.new(hookNames: {string}?) -> Registry`\n\nCreates a new registry instance. Each instance has independent state,\nso you can run multiple registries (e.g. one for combat, one for UI)\nwithout them interfering.\n\n### `Registry:RegisterHook(hookName: string)`\n\nDeclares a new hook type. Calling it twice is a no-op. You can pass\ninitial hook names to `.new()` instead of calling this separately.\n\n### `Registry:Register(hookName, gateKey, fn, priority?)`\n\nRegisters `fn` against `hookName`.\n\n- `gateKey` — passed to your `gateResolver` at fire time. Use `\"*\"`\n  for entries that should always run regardless of what the resolver\n  says (they never call the resolver at all).\n- `priority` — lower numbers fire first. Defaults to `50`.\n\n### `Registry:Unregister(hookName, gateKey, fn) -> boolean`\n\nRemoves a previously registered entry. Returns whether one was found\nand removed. Useful for un-equip flows or hot-reloading.\n\n### `Registry:Fire(hookName, ctx, gateResolver?) -> ctx`\n\nRuns every registered entry for `hookName` in priority order.\n\n- `gateResolver(gateKey, ctx) -> boolean` is called once per\n  non-wildcard entry to decide whether it runs. This is where you plug\n  in your own \"does this player own this ability\" check. Omit it and\n  only `\"*\"` entries will fire.\n- Each entry runs inside `pcall` — an error in one entry is `warn`'d\n  and does not stop the others.\n- An entry can set `ctx._stop = true` to halt the remaining chain\n  cooperatively.\n\n## Example\n\nSee [`example/example.lua`](example/example.lua) for a worked example\nwith attacker-gated, victim-gated, and wildcard entries.\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","readmeTruncated":false}