Skip to main content

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
Plugin Dependency

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" }
)
Allow Filter Behavior

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

ParameterTypeDescription
namestringUnique identifier for this filter
funcfunctionFilter function (see callback signature below)
optstableOptions 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
ParameterTypeDescription
local_playergame_objectThe local player
solution_tabletableThe kick solution being considered
spell_to_kick_tabletableInfo about the spell to interrupt (includes id)
kick_targetgame_objectThe target casting the spell
prediction_datatableKick timing prediction data

Returns: boolean (allow/block), string|nil (optional reason)

Options Table

FieldTypeDefaultDescription
typestring"block"Filter type: "block" or "allow"
labelstringnilHuman-readable label for debugging
timenumbernilAuto-expire after this many seconds
countnumbernilAuto-expire after this many checks
framesnumbernilAuto-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:

FieldTypeDescription
typestring"block" or "allow"
namestring|nilFilter name that triggered
labelstring|nilHuman-readable label
whystring|nilReason from your filter or policy
policystring|nil"block_return_false" or "at_least_one_allow_must_pass"
framesnumber|nilConfigured frames (diagnostic)
timenumber|nilConfigured time (diagnostic)
countnumber|nilConfigured 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 time or count expirations for temporary rules so you don't have to manually unregister
  • For debug UIs, use ext.list() and show secs_left, calls, frames_used
  • Keep filter functions fast - they're called every time a kick is considered