-- =====================================================
-- AGENCY ANTICHEAT - CONFIG
-- =====================================================
-- Everything a server owner may want to change lives in this file.
-- It is never encrypted, and an update never overwrites your wording:
-- put your own texts into Config.Text at the bottom.
--
-- How a detection turns into a punishment
-- ---------------------------------------
-- Every detection has a severity: 'high', 'medium' or 'low'.
-- The active MODE decides what a severity does: how many hits
-- (strikes) inside Config.StrikeWindow it takes, and which action follows.
--
--   log   only written to the log, the Discord webhook and the Agency-Admin panel
--   warn  like log, and the player gets a warning on screen
--   kick  the player is kicked with a case number
--   ban   the player is banned (identifiers AND hardware tokens)
--
-- A single detection can skip the mode with its own `action` and `strikes`.

Config = {}

Config.Locale    = 'en'      -- en, de, fr, es, it, pt, nl, pl, cs, ro, hu, sv, da, fi, el, bg, hr, uk, ru, tr, ar, hi, zh, ja, ko
Config.Debug     = false     -- prints every hit to the server console
Config.Framework = 'auto'    -- 'auto' | 'qbcore' | 'esx' | 'standalone'

-- =====================================================
-- 1. MODES
-- =====================================================
-- The active mode is saved on the server. `/acmode strict` or the
-- Agency-Admin panel switch it live, without a restart. This value
-- is only the default for a fresh install.
Config.Mode = 'balanced'

-- A fresh install first runs this many hours in 'learning' (nothing is
-- punished, everything is shown), then switches to Config.Mode by itself.
-- 0 = start in Config.Mode right away.
Config.LearnFirst = 48

Config.Modes = {
    -- Detects everything and punishes nobody. Every hit shows what
    -- 'balanced' would have done. Start here on a new server for a day or
    -- two: you see exactly what your other scripts trigger, risk free.
    learning = {
        high   = { strikes = 1, action = 'log' },
        medium = { strikes = 1, action = 'log' },
        low    = { strikes = 1, action = 'log' },
    },
    -- Detects and logs everything, punishes only what is certain.
    -- Good for the first days, to see what your other scripts trigger.
    relaxed = {
        high   = { strikes = 3, action = 'kick' },
        medium = { strikes = 5, action = 'warn' },
        low    = { strikes = 8, action = 'log'  },
    },
    -- The recommended mode for a live server.
    balanced = {
        high   = { strikes = 2, action = 'ban'  },
        medium = { strikes = 3, action = 'kick' },
        low    = { strikes = 5, action = 'log'  },
    },
    -- Zero tolerance. Use it once your server ran a few days on
    -- balanced without false hits.
    strict = {
        high   = { strikes = 1, action = 'ban'  },
        medium = { strikes = 2, action = 'ban'  },
        low    = { strikes = 3, action = 'kick' },
    },
}

-- Strikes older than this many seconds are forgotten.
Config.StrikeWindow = 300

-- =====================================================
-- 1b. PATTERN RECOGNITION
-- =====================================================
-- Every hit also adds to a risk score per player. One weak signal is
-- nothing, a teleport can be a script. A teleport, thermal vision and a
-- vehicle spawned far away within a few minutes is a pattern: no single
-- check had to be sure, together they are. The score falls back by itself.
Config.Pattern = {
    threshold = 100,                                  -- score that counts as a pattern (detection "pattern")
    points    = { high = 35, medium = 20, low = 10 }, -- per hit, by severity
    variety   = 0.25,                                 -- +25 % per further different detection in the window
    decay     = 1,                                    -- points lost every 10 seconds
}

-- =====================================================
-- 1c. LEARNING YOUR SERVER
-- =====================================================
-- The anticheat gets to know your server while it runs: jumps that several
-- clean players make (elevators, house interiors, garages) stop counting as
-- teleports, and players who played here a while without hits are "known".
Config.Learn = {
    teleportPlayers = 3,     -- clean players needed before a jump counts as part of the server
    knownMinutes    = 120,   -- playtime without hits after which a player is known
}

-- =====================================================
-- 1d. LOCKDOWN (when the server is being overrun)
-- =====================================================
-- Several cheaters at once or a wave of fresh accounts: the server switches
-- to strict, only known players may join, spam limits are halved, and
-- Discord / AgencyMod raise the alarm. Ends by itself.
-- By hand: /aclockdown on|off, the Agency-Admin panel or /anticheat lockdown in AgencyMod.
Config.Lockdown = {
    enabled = true,
    players = 3,             -- different players with a high hit within 60 s
    joins   = 15,            -- joins of unknown players within 60 s
    minutes = 15,            -- how long, extended while the attack goes on
}

-- =====================================================
-- 1e. AGENCYMOD (Discord bot)
-- =====================================================
-- Nothing to set up: if your Discord server uses AgencyMod and has this FiveM
-- server entered in its FiveM tab, both connect by themselves. Bans then show
-- up in Discord and cheaters are banned from your Discord as well.
Config.AgencyMod = {
    enabled = true,
}
-- =====================================================
-- 2. WHO IS NEVER CHECKED
-- =====================================================
Config.Exempt = {
    -- Players with this ACE are never checked:
    --   add_ace group.admin agency.anticheat.bypass allow
    aces = { 'agency.anticheat.bypass' },

    -- Agency-Admin installed? Then every admin of its panel (ranks, ACE,
    -- framework groups, players added in the panel) may use noclip, godmode,
    -- spectate and everything else without being flagged. Nothing to set up.
    agencyAdmin = true,

    -- Players logged into the txAdmin in-game menu (it has noclip and godmode too).
    txAdmin = true,

    -- Framework staff groups (QBCore permission / ESX group).
    frameworkAdmins = true,
    frameworkGroups = { 'god', 'admin', 'superadmin' },

    -- How often (seconds) the above is looked up again for an online player.
    recheck = 60,
}

-- Who may use the /ac commands. Agency-Admin admins always may
-- (with the panel permissions anticheat_view / anticheat_manage).
Config.AdminAce = 'agency.anticheat'

-- =====================================================
-- 3. BANS
-- =====================================================
Config.Ban = {
    duration    = 0,         -- seconds for automatic bans, 0 = permanent
    tokens      = true,      -- also ban the hardware tokens (stops new accounts on the same PC)
    ip          = false,     -- also ban the IP (off by default: shared and changing IPs)
    extend      = true,      -- a banned PC with a new account: add the new identifiers to the ban
    prefix      = 'AC',      -- ban ids look like AC-7K3P9
    appeal      = '',        -- shown on the ban screen, e.g. 'discord.gg/yourserver'
}

-- =====================================================
-- 4. DISCORD
-- =====================================================
Config.Webhook = {
    url         = '',        -- every hit, kick and ban
    banUrl      = '',        -- optional: bans and kicks only, to a second channel
    name        = 'Agency Anticheat',
    avatar      = '',
    mention     = '',        -- e.g. '<@&123456789>' on bans
    screenshots = true,      -- needs screenshot-basic; attaches a screenshot to kicks and bans
    logHits     = true,      -- false = only actions (warn, kick, ban), no single hits
    dailyReport = true,      -- a short protection report every night at midnight
}

-- =====================================================
-- 5. PERFORMANCE
-- =====================================================
-- The defaults cost well under 0.05 ms on the server and the client.
Config.Performance = {
    serverInterval = 1000,   -- ms between two server checks of every player
    clientInterval = 750,    -- ms between two client checks
    clientSlow     = 15000,  -- ms between the heavier client scans (commands, resources)
    logKeep        = 500,    -- detections kept for the panel
}

-- =====================================================
-- 6. HEARTBEAT
-- =====================================================
-- The server asks every client for a fresh code. A client whose anticheat
-- part was stopped or blocked by a cheat cannot answer.
Config.Heartbeat = {
    enabled      = true,
    interval     = 15,       -- seconds between two questions
    timeout      = 90,       -- no answer for this long = detection
    firstTimeout = 600,      -- the very first answer may take this long (loading screen)
}

-- After joining, spawning or being revived, a player gets this many
-- seconds before movement checks (teleport, speed, noclip) run.
Config.Grace = 20

-- =====================================================
-- 7. DETECTIONS
-- =====================================================
-- enabled   on / off (also switchable live in the Agency-Admin panel)
-- severity  high | medium | low   (see the modes above)
-- action    optional, overrides the mode: log | warn | kick | ban
-- strikes   optional, overrides the mode
Config.Detections = {

    -- ---------- player (checked on the server, cannot be hidden by the client) ----------

    -- Godmode is never judged by one value alone: health above the maximum,
    -- or a hit that the shooter's game called lethal and the player survived.
    godmode         = { enabled = true, severity = 'high' },
    lethal_survived = { enabled = true, severity = 'medium' },
    -- The bare "invincible" flag for 10 s while moving. Safe zones and some
    -- jobs set it on purpose, which is why it only logs in 'balanced'.
    -- Your safe zone script can call exports['Agency-Anticheat']:SetExempt(source, seconds).
    invincible      = { enabled = true, severity = 'low' },
    armour          = { enabled = true, severity = 'high', max = 100 },
    superjump       = { enabled = true, severity = 'high' },
    damage_modifier = { enabled = true, severity = 'high', max = 1.0 },   -- weapon and melee damage multiplier
    invisible       = { enabled = true, severity = 'medium' },
    speedhack       = { enabled = true, severity = 'medium', maxSpeed = 16.0 },  -- m/s on foot (sprint is about 7)
    teleport        = { enabled = true, severity = 'low', distance = 250.0 },    -- metres in one second, outside a vehicle
    teleport_player = { enabled = true, severity = 'medium' },   -- the same, landing right next to another player

    -- Combat, cross-checked from both sides of every hit.
    kill_distance   = {
        enabled = true, severity = 'medium', distance = 400.0,   -- metres between shooter and victim
        ignore = { 'WEAPON_SNIPERRIFLE', 'WEAPON_HEAVYSNIPER', 'WEAPON_HEAVYSNIPER_MK2', 'WEAPON_MARKSMANRIFLE', 'WEAPON_MARKSMANRIFLE_MK2', 'WEAPON_PRECISIONRIFLE' },
    },
    rapid_kills     = { enabled = true, severity = 'medium', kills = 6, window = 10 },   -- lethal hits on players
    -- Aim statistics: share of head hits on players, only hits from at least
    -- minDistance metres count. Humans land far below 85 % over 30 hits.
    aimbot          = { enabled = true, severity = 'medium', minHits = 30, minDistance = 25.0, ratio = 0.85, headComponents = { 20 } },
    -- Many hits from different detections in a short time, see Config.Pattern.
    pattern         = { enabled = true, severity = 'high' },
    -- A weapon in the hand that is not in the inventory (QBCore, QBox, ESX, ox_inventory).
    -- Only for weapons in shared/weapons.lua, never on standalone servers.
    weapon_no_item  = { enabled = true, severity = 'low' },

    weapon_blacklist = {
        enabled = true, severity = 'high',
        weapons = {
            'WEAPON_RAILGUN', 'WEAPON_RAILGUNXM3', 'WEAPON_MINIGUN', 'WEAPON_RPG',
            'WEAPON_HOMINGLAUNCHER', 'WEAPON_GRENADELAUNCHER', 'WEAPON_COMPACTLAUNCHER',
            'WEAPON_RAYPISTOL', 'WEAPON_RAYCARBINE', 'WEAPON_RAYMINIGUN', 'WEAPON_FIREWORK',
        },
    },

    ped_blacklist = {
        enabled = true, severity = 'medium',
        models = { 'a_c_chimp', 'a_c_mtlion', 'u_m_y_juggernaut_01', 'u_m_y_zombie_01', 's_m_m_movalien_01' },
    },

    -- ---------- hardware ----------
    -- FiveM hands every server a set of hardware tokens per PC. A resource
    -- cannot see deeper into the hardware than that, and these are used fully:
    hardware_match  = { enabled = true, severity = 'medium' },  -- a new account on a PC that was kicked or flagged before
    hardware_spoof  = { enabled = true, severity = 'low' },     -- a client without any hardware tokens (HWID spoofer)

    -- ---------- client (scanned on the player's PC) ----------

    noclip             = { enabled = true, severity = 'high' },
    spectate           = { enabled = true, severity = 'high' },
    freecam            = { enabled = true, severity = 'medium', distance = 120.0 },  -- camera far away from the own ped
    vision             = { enabled = true, severity = 'low' },     -- night / thermal vision outside a helicopter
    infinite_ammo      = { enabled = true, severity = 'medium', shots = 40 },
    vehicle_speed      = { enabled = true, severity = 'medium', factor = 1.6, minimum = 70.0 },
    vehicle_godmode    = { enabled = true, severity = 'low' },
    injected_resource  = { enabled = true, severity = 'high' },     -- a resource runs on the client that the server never sent
    resource_stop      = { enabled = true, severity = 'high' },     -- a server resource was stopped on the client
    injected_command   = { enabled = true, severity = 'high' },     -- a command registered by an injected resource
    blacklisted_command = {
        enabled = true, severity = 'high',
        commands = { 'chocolate', 'lynx', 'brutan', 'hammafia', 'lumia', 'killmenu', 'panic', 'tiago', 'desudo', 'eulen', 'redengine' },
    },
    heartbeat          = { enabled = true, severity = 'medium' },

    -- ---------- world (game events, cancelled before anyone sees them) ----------

    entity_blacklist = {
        enabled = true, severity = 'high',
        models = {
            'rhino', 'khanjali', 'apc', 'halftrack', 'minitank', 'hydra', 'lazer', 'strikeforce',
            'oppressor', 'oppressor2', 'deluxo', 'vigilante', 'scramjet', 'ruiner2', 'thruster',
            'chernobog', 'cargoplane', 'jet', 'titan', 'bombushka', 'volatol', 'avenger', 'tula',
            'blimp', 'blimp2', 'blimp3',
            'a_c_chimp', 'a_c_mtlion', 'u_m_y_juggernaut_01', 'u_m_y_zombie_01', 's_m_m_movalien_01',
            'prop_windmill_01', 'p_spinning_anus_s', 'prop_ld_ferris_wheel', 'prop_cs_dildo_01',
        },
    },
    -- networked entities a single player may create inside `window` seconds
    entity_spam  = { enabled = true, severity = 'medium', window = 10, vehicle = 8, ped = 10, object = 30 },

    explosion_blacklist = {
        enabled = true, severity = 'high',
        types = { 1, 4, 5, 6, 16, 29, 32, 36, 37, 38 },  -- grenade launcher, rocket, tank shell, hi-octane, ship, blimp, plane rocket, railgun, blimp2, firework
    },
    explosion_spam      = { enabled = true, severity = 'medium', window = 10, max = 8 },
    explosion_invisible = { enabled = true, severity = 'medium' }, -- invisible explosions, a menu signature
    remote_explosion    = { enabled = true, severity = 'medium', distance = 250.0 },  -- an explosion far away from the player who caused it
    remote_spawn        = { enabled = true, severity = 'low', distance = 400.0 },     -- a vehicle or ped created far away from its creator
    particle_spam       = { enabled = true, severity = 'medium', window = 10, max = 30 },
    fire_spam           = { enabled = true, severity = 'medium', window = 10, max = 12 },
    weapon_give         = { enabled = true, severity = 'high' },     -- a client giving weapons to another player
    weapon_remove       = { enabled = true, severity = 'high' },     -- a client taking weapons from another player
    clear_tasks         = { enabled = true, severity = 'high' },     -- pulling other players out of vehicles

    -- ---------- chat and names ----------
    -- Lua patterns, lower case. Menus advertise themselves in chat and names.
    chat_spam = {
        enabled = true, severity = 'low', max = 8,        -- messages per 10 s
        patterns = { 'discord%.gg/', 'discord%.com/invite', 'menu by', 'mod menu', 'modmenu', 'eulen', 'redengine', 'skript%.gg' },
    },
    name_filter = {
        enabled = true, severity = 'low',
        patterns = { 'discord%.gg/', 'https?://', '%.gg/', 'mod menu', 'modmenu', '^admin$', '^%[admin%]', 'eulen', 'redengine' },
    },

    -- ---------- other scripts ----------

    -- exports['Agency-Anticheat']:Flag(source, 'sold 900 items in 2 s') from any server script.
    external = { enabled = true, severity = 'high' },

    -- ---------- events ----------

    -- Events of resources this server does NOT run. Nobody can trigger them
    -- legitimately here, only a menu that fires them blindly. An entry is
    -- armed only while its resource is missing, so it never hits a real one.
    event_honeypot = {
        enabled = true, severity = 'high',
        events = {
            { event = 'esx_ambulancejob:revive',              resource = 'esx_ambulancejob' },
            { event = 'esx_policejob:handcuff',               resource = 'esx_policejob' },
            { event = 'esx_jailer:sendToJail',                resource = 'esx_jailer' },
            { event = 'esx-qalle-jail:jailPlayer',            resource = 'esx-qalle-jail' },
            { event = 'esx_dmvschool:addLicense',             resource = 'esx_dmvschool' },
            { event = 'esx_vehicleshop:setVehicleOwned',      resource = 'esx_vehicleshop' },
            { event = 'esx_billing:sendBill',                 resource = 'esx_billing' },
            { event = 'esx_truckerjob:pay',                   resource = 'esx_truckerjob' },
            { event = 'esx_garbagejob:pay',                   resource = 'esx_garbagejob' },
            { event = 'esx_mechanicjob:startHarvest',         resource = 'esx_mechanicjob' },
            { event = 'esx_drugs:startHarvestWeed',           resource = 'esx_drugs' },
            { event = 'esx_society:withdrawMoney',            resource = 'esx_society' },
            { event = 'police:server:JailPlayer',             resource = 'qb-policejob' },
            { event = 'hospital:server:RevivePlayer',         resource = 'qb-ambulancejob' },
            { event = 'qb-vehiclekeys:server:AcquireVehicleKeys', resource = 'qb-vehiclekeys' },
            -- Your own traps: an event name no script of yours uses.
            -- { event = 'admin:giveAllMoney' },
        },
    },

    -- Rate limits for events of your own scripts: name = most calls per 10 seconds.
    event_spam = {
        enabled = true, severity = 'medium',
        events = {
            -- ['qb-shops:server:UpdateShopItems'] = 20,
        },
    },
}

-- =====================================================
-- 8. YOUR OWN WORDING
-- =====================================================
-- Wins over the language files and survives updates. Same keys as locales/en.lua.
Config.Text = {
    -- kick_message = 'Kicked by the anticheat. Case %s. Appeal in our Discord.',
}
