Kick External Filters
Overview
The Kick External Filters API allows you to register custom filters that control when the Universal Kicks plugin should or should not interrupt. This enables fine-grained control over kick behavior based on your own conditions - such as blocking kicks while stealthed, allowing only specific targets, or temporarily muting kick logic.
Key Features:
- Block Filters - Prevent kicks when your condition returns false
- Allow Filters - Require at least one allow filter to pass before kicking
- Auto-Expiration - Filters can expire after time, frame count, or usage count
- Detailed Diagnostics - Access filter info for debugging decisions
- Runtime Management - Register, unregister, extend, and modify filters dynamically
This API is internal to the Universal Kicks plugin. Use pcall to safely require it - the module won't exist if the plugin isn't loaded.
Importing The Module
local prefix = "core_"
local kicks_exist, ext = pcall(require, "root/" .. prefix .. "universal_kicks/external_filters")
if not kicks_exist or not ext then
-- Plugin not present, nothing to do
return
end
-- 'ext' is now the filters API
Filter Types
Block Filters (type = "block")
Block filters prevent kicks when they return false. Multiple block filters can exist - if ANY returns false, the kick is blocked.
ext.register("my_block_filter",
function(local_player, solution_table, spell_to_kick_table, kick_target, prediction_data)
if should_block then
return false, "reason_string" -- Block the kick
end
return true -- Allow the kick to proceed
end,
{ type = "block", label = "My Block Filter" }
)
Allow Filters (type = "allow")
Allow filters create a whitelist requirement. When ANY allow filter exists, at least one must return true for the kick to proceed.
ext.register("my_allow_filter",
function(local_player, solution_table, spell_to_kick_table, kick_target, prediction_data)
return kick_target == priority_target -- Only allow kicking this target
end,
{ type = "allow", label = "Priority Target Only" }
)
If you register ANY allow filter, kicks are permitted ONLY when at least one allow filter returns true. This is more restrictive than block filters.
Functions
ext.register
Syntax
ext.register(name: string, func: function, opts?: table): nil
Parameters
| Parameter | Type | Description |
|---|---|---|
name | string | Unique identifier for this filter |
func | function | Filter function (see callback signature below) |
opts | table | Options including type, expiration, and label |
Callback Signature
function(
local_player: game_object,
solution_table: table,
spell_to_kick_table: table,
kick_target: game_object,
prediction_data: table
): boolean, string|nil
| Parameter | Type | Description |
|---|---|---|
local_player | game_object | The local player |
solution_table | table | The kick solution being considered |
spell_to_kick_table | table | Info about the spell to interrupt (includes id) |
kick_target | game_object | The target casting the spell |
prediction_data | table | Kick timing prediction data |
Returns: boolean (allow/block), string|nil (optional reason)
Options Table
| Field | Type | Default | Description |
|---|---|---|---|
type | string | "block" | Filter type: "block" or "allow" |
label | string | nil | Human-readable label for debugging |
time | number | nil | Auto-expire after this many seconds |
count | number | nil | Auto-expire after this many checks |
frames | number | nil | Auto-expire after this many frames |
ext.unregister
ext.unregister(name: string): nil
Removes a registered filter by name.
ext.clear
ext.clear(): nil
Removes all registered filters.
ext.list
ext.list(): table
Returns a snapshot of all active filters. Useful for debug UIs to show secs_left, calls, frames_used.
ext.touch
ext.touch(name: string, opts_patch: table): boolean
Modifies an existing filter's options. Useful for extending time windows or adjusting counts.
Example:
ext.touch("allow_boss_for_2s", { time = 4 }) -- Extend window if still active
ext.apply
ext.apply(
local_player: game_object,
solution_table: table,
spell_to_kick_table: table,
kick_target: game_object,
prediction_data: table
): boolean, external_filter_info|nil
Manually applies all filters. You typically don't call this yourself - the kick system calls it during its decision pass.
Types
external_filter_info
Information returned when a kick is blocked:
| Field | Type | Description |
|---|---|---|
type | string | "block" or "allow" |
name | string|nil | Filter name that triggered |
label | string|nil | Human-readable label |
why | string|nil | Reason from your filter or policy |
policy | string|nil | "block_return_false" or "at_least_one_allow_must_pass" |
frames | number|nil | Configured frames (diagnostic) |
time | number|nil | Configured time (diagnostic) |
count | number|nil | Configured count (diagnostic) |
Complete Examples
Block While Stealthed
local prefix = "core_"
local kicks_exist, ext = pcall(require, "root/" .. prefix .. "universal_kicks/external_filters")
if not kicks_exist or not ext then return end
ext.register("block_while_stealth",
function(local_player, solution_table, spell_to_kick_table, kick_target, prediction_data)
local data = buff_manager:get_buff_data(local_player, enums.buff_db.STEALTH, 50)
local is_stealthed = data and data.is_active == true
if is_stealthed then
return false, "stealth_active" -- Block while stealthed
end
return true -- Allow otherwise
end,
{ type = "block", label = "No Kick While Stealth" }
)
Allow Only Boss Target for 2 Seconds
local boss_ptr = core.units.boss1
ext.register("allow_boss_for_2s",
function(_, _, _, kick_target)
return kick_target == boss_ptr
end,
{ type = "allow", time = 2, label = "Boss Only 2s" }
)
-- While active, kicks are permitted ONLY when target == boss_ptr
-- Remember to ext.unregister("allow_boss_for_2s") if too restrictive
Block a Specific Cast ID
ext.register("block_cast_12345",
function(_, _, cast)
local allow = (cast.id ~= 12345)
return allow, allow and nil or "cast_12345"
end,
{ type = "block", label = "Skip 12345" }
)
Expiration Examples
-- Block once (1 frame)
ext.register("block_once",
function() return false, "once" end,
{ type = "block", frames = 1, label = "One Pass" }
)
-- Block next 5 checks
ext.register("block_next_5_checks",
function() return false, "next_5" end,
{ type = "block", count = 5, label = "Next 5 Checks" }
)
-- Mute for 3 seconds
ext.register("mute_for_3s",
function() return false, "window_3s" end,
{ type = "block", time = 3, label = "Mute 3s" }
)
Inspect Blocking Reasons
local blocked, info = ext.apply(local_player, solution, cast, target, pred)
if blocked then
local msg = "[Universal Kicks] blocked"
msg = msg .. " | reason=" .. tostring(info and info.why or "-")
if info and info.name then
msg = msg .. " | filter=" .. info.name
end
if info and info.label then
msg = msg .. " | label=" .. info.label
end
if info and info.policy then
msg = msg .. " | policy=" .. info.policy
end
core.log(msg)
return
end
Remove or Extend Filters
-- Remove a filter
ext.unregister("block_while_stealth")
-- Extend an allow window
ext.touch("allow_boss_for_2s", { time = 4 }) -- Extend if still active
Tips
- If you register ANY allow filter, kicks are permitted ONLY when at least one returns true
- Prefer
timeorcountexpirations for temporary rules so you don't have to manually unregister - For debug UIs, use
ext.list()and showsecs_left,calls,frames_used - Keep filter functions fast - they're called every time a kick is considered