Skip to content

🔫 Dynamic Weapon Controller

Documentation relating to the spooni_weapon_controller.

1. Installation

spooni_weapon_controller works Standalone.

To install spooni_weapon_controller:

  • Download the resource
  • Drag and drop the resource into your resources folder
    • spooni_weapon_controller
  • Add this ensure in your server.cfg
      ensure spooni_weapon_controller
  • Now you can configure and translate the script as you like
    • config.lua
  • At the end, restart the server

If you have any problems, you can always open a ticket in the Spooni Discord.

2. Usage

Take full control over how weapons behave on your RedM server with the Dynamic Weapon Controller. This powerful system allows you to fine-tune damage, recoil, accuracy, and other weapon stats – either globally or per player – based on configurable skill progression.

Designed for maximum flexibility, this script works with any framework and is fully open-source, so you can adapt it exactly to your server's needs.

3. For developers

Config.lua
lua
-- This file controls how weapon damage, recoil, and accuracy
-- scale with player skill levels. Adjust the values below
-- to change progression type or define custom curves per level.

Config = {
    debug = true,

    -- Experience awarded per relevant action (e.g., shot fired)
    PerkExperience = {
        amount = 1,  -- Perk points gained per action
    },

    -- Define how stats evolve from minLevel to maxLevel
    PerkCurve = {
        minLevel = 0,   -- Lowest skill level
        maxLevel = 1000,  -- Highest skill level

        -- Choose interpolation type: "linear", "exponential", or "custom"
        interpolation = "linear",
        -- If exponential: power to raise t (e.g., 2 for quadratic curve)
        expPower = 2.0,

        -- If custom: supply a table of overrides per level
        -- Example: customValues = { [10] = { minAccuracy = 0.12, maxAccuracy = 2.0, ... }, ... }
        customValues = {},

        -- Base and target values for each stat (used for linear/exponential)
        accuracy = { start = 0.20, finish = 0.00 },
        maxAccuracy = { start = 2.50, finish = 1.00 },
        accuracyGainSpeed = { start = 0.005, finish = 0.010 },
        bowInaccuracyThreshold = { start = 0.08, finish = 0.00 },
        scopeRecoil = { start = 4.50, finish = 1.00 },
        reloadFailChance = { start = 0.5, finish = 0.0 }, -- 50%-0% chance to fail reload
        reloadSpeedFactor = { start = 0.5, finish = 1.5 }, -- >1 = faster, <1 = slower --time for disabling firing after reload !NOT DELAYING ANIMATION!
    },

    reloadFailedNotification = true,  --if true, a notification will be shown when reload fails


    -- Per-weapon damage multipliers (lower = weaker damage)
    DamageModifiers = {
        [`WEAPON_BOW`]             = 0.5,
        [`WEAPON_MELEE_HATCHET`]   = 0.4,
        [`WEAPON_THROWN_TOMAHAWK`] = 0.5,
    },
    -- Disable headshot damage modifier, that player can't do greater damage with headshot
    DisableHeadshotDamage = true,

    -- Global recoil step per shot multiplier
    RecoilStep = 0.35,

    -- Per-weapon recoil multipliers (higher = more recoil)
    RecoilModifiers = {
        [`WEAPON_REPEATER_WINCHESTER`] = 1.0,
        [`WEAPON_RIFLE_BOLTACTION`]    = 2.5,
    },

}







-------------------------------------------------
-- Helper functions for Developer
-------------------------------------------------


-- Helper: linear interpolation
local function lerp(a, b, t)
    return a + (b - a) * t
end

-- Compute t based on interpolation type
local function getInterpT(curve, level)
    local minL, maxL = curve.minLevel, curve.maxLevel
    local range = maxL - minL
    local t = (range > 0) and ((level - minL) / range) or 0

    if curve.interpolation == "linear" then
        return t
    elseif curve.interpolation == "exponential" then
        return math.pow(t, curve.expPower or 1)
    elseif curve.interpolation == "custom" then
        return nil  -- custom values handled separately
    else
        return t  -- fallback to linear
    end
end

-- Build table of computed perk values per level
Config.PerkLevels = {}
do
    local c = Config.PerkCurve
    for lvl = c.minLevel, c.maxLevel do
        -- If a custom override exists for this level, use it directly
        if c.interpolation == "custom" and c.customValues[lvl] then
            Config.PerkLevels[lvl] = c.customValues[lvl]
        else
            -- Compute interpolation factor
            local t = getInterpT(c, lvl)
            -- Populate stats via interpolation
            Config.PerkLevels[lvl] = {
                minAccuracy               = lerp(c.accuracy.start, c.accuracy.finish, t),
                maxAccuracy               = lerp(c.maxAccuracy.start, c.maxAccuracy.finish, t),
                accuracyIncreaseSpeed     = lerp(c.accuracyGainSpeed.start, c.accuracyGainSpeed.finish, t),
                maxPitchDeltaForBowInaccuracy = lerp(c.bowInaccuracyThreshold.start, c.bowInaccuracyThreshold.finish, t),
                maxScopeRecoil            = lerp(c.scopeRecoil.start, c.scopeRecoil.finish, t),
                reloadFailChance          = lerp(c.reloadFailChance.start, c.reloadFailChance.finish, t),
                reloadSpeedFactor         = lerp(c.reloadSpeedFactor.start, c.reloadSpeedFactor.finish, t),
            }
        end
    end
end


function Config.GetPerk(level)
    local c = Config.PerkCurve
    -- Clamp level to valid range
    local lvl = math.max(c.minLevel, math.min(c.maxLevel, level))
    return Config.PerkLevels[lvl]
end