Skip to content

Detection Hooks ​

The AddDetectionHook export lets your resource inspect every detection before FiniAC processes it. A hook can let the detection through unchanged, edit its data, or cancel it entirely. Cancelled detections are never logged and never trigger a screenshot.

Why Use Detection Hooks? ​

  • Suppress detections your own scripts, such as ArmedPed being triggered by guards from heist scripts
  • Add context from your framework to a detection, such as the player's job or the inventory they had open
  • Feed detections into your own logging or moderation tooling

AddDetectionHook Export ​

typescript
interface FiniACHookPlayer {
  source: number;
  name: string;
  identifiers: {
    license?: string;
    license2?: string;
    discord?: string;
    fivem?: string;
    xbl?: string;
    live?: string;
    ip?: string;
  };
}

interface FiniACHookDetection {
  type: string;
  data: string[];
}

global.exports.FiniAC.AddDetectionHook(
  (
    player: FiniACHookPlayer,
    detection: FiniACHookDetection,
  ): FiniACHookDetection | false | void => {
    // return false to cancel, return detection to edit, return nothing to keep as is
  },
);
lua
---@class FiniACHookPlayer
---@field source number The player's server ID
---@field name string The player's name
---@field identifiers { license?: string, license2?: string, discord?: string, fivem?: string, xbl?: string, live?: string, ip?: string }

---@class FiniACHookDetection
---@field type string The detection name, for example "NoclipV2"
---@field data (string|number)[] The detection data

---@param player FiniACHookPlayer
---@param detection FiniACHookDetection
---@return FiniACHookDetection|false|nil
exports.FiniAC:AddDetectionHook(function(player, detection)
    -- return false to cancel, return detection to edit, return nil to keep as is
end)

Return value:

  • false: The detection is cancelled
  • A table with the same type and data shape: The detection's type and data are replaced with what you returned, then FiniAC continues processing it
  • nil or no return: The detection continues unchanged

How detection hooks run:

  • Hooks run on the server, synchronously, in the order they were registered. Each hook sees the result of the previous one.
  • Hooks must not yield. Calling Wait() inside a hook counts as an invalid return.

Registering Your Hook ​

FiniAC finishes starting shortly after the resource starts, so the export is not available in the first moments after start FiniAC. Two signals cover both start orders:

  • FiniAC:Started (server event): Fired every time FiniAC finishes starting. Register your hook in its handler, this also re-registers it after a FiniAC restart.
  • FiniAC:Started (convar, 1 while FiniAC is running): Check it when your own resource starts, in case FiniAC was already running.

Usage Examples ​

lua
local function registerHooks()
    ---@param player FiniACHookPlayer
    ---@param detection FiniACHookDetection
    exports.FiniAC:AddDetectionHook(function(player, detection)
        -- Cancel BlacklistedWeapon detections while the player in paintball
        if detection.type == 'BlacklistedWeapon' and Player(player.source).state.inPaintball then
            return false
        end

        -- Add context to a detection and let it through
        if detection.type == 'BadTaze' then
            table.insert(detection.data, ('job:%s'):format(MyFramework.getJob(player.source)))
            return detection
        end

        -- Nothing returned: the detection continues unchanged
    end)
end

-- FiniAC starts (or restarts) after this resource
AddEventHandler('FiniAC:Started', registerHooks)

-- FiniAC was already running when this resource started
if GetConvarInt('FiniAC:Started', 0) == 1 then
    registerHooks()
end
typescript
// FiniACHookPlayer and FiniACHookDetection as declared above

function registerHooks(): void {
  global.exports.FiniAC.AddDetectionHook(
    (player: FiniACHookPlayer, detection: FiniACHookDetection) => {
      // Cancel BlacklistedWeapon detections while the player in paintball
      if (
        detection.type === "BlacklistedWeapon" &&
        Player(player.source).state.inPaintball
      ) {
        return false;
      }

      // Add context to a detection and let it through
      if (detection.type === "BadTaze") {
        detection.data.push(`job:${MyFramework.getJob(player.source)}`);
        return detection;
      }

      // Nothing returned: the detection continues unchanged
    },
  );
}

// FiniAC starts (or restarts) after this resource
on("FiniAC:Started", registerHooks);

// FiniAC was already running when this resource started
if (GetConvarInt("FiniAC:Started", 0) === 1) {
  registerHooks();
}