[ESX/QBCore] Advanced Police Shield

:shield: Advanced Police Shield

Equip your officers with realistic ballistic or riot shields using this FiveM script. Simple to configure, easy to use, and packed with features, it’s a must-have for law enforcement RP servers. Noticing that many existing police shield scripts are either overpriced or riddled with bugs, I decided to create a better alternative.

:shopping_cart: Get it here: gamzkystore.com

:question: Support: Discord

:movie_camera: Detailed Preview: Youtube

Features

  • Shield activation: Equip shields based on player command or items. (Both are configurable)
  • Multiple shield types: Includes ballistic and riot shields, customizable to fit your needs. It’s also possible to add other custom shields.
  • Shield placement and dropping: Drop or place shields for tactical versatility.
  • Weapon compatibility check: Ensures shields can only be used with configurable allowed weapons.
  • Performance optimized: Resmon Idle: 0.00ms. While carrying shield: 0.01ms.
  • Highly configurable: Customize shield models, animations, and behaviors to your server’s requirements.
  • Disable certain actions: Automatically restrict actions like ladder climbing or controls while the shield is active.
  • Export implementation: There’s also an option to disable the default /shield command or items and use exports for custom integrations, giving server owners full control over implementation.
  • Frameworks: The script works by default for ESX and QBCore, but can easily be adapted to any other framework. It even works standalone.
  • Target: Supports ‘ox_target’ and ‘qb-target’ out of the box. You can also add your own targeting script through the bridge function.

Resmon: Idle: 0.00ms - Shield equipped: 0.01ms

How It Works

  • Item: Use the item that is configured for the shield
  • Command: /shield [type]: Equip or unequip a shield (e.g., ballistic or riot).
  • Unequip shield: Press H to unequip shield.
  • Drop Shield: Press X to drop your shield.
  • Place Shield: Press G to place your shield on the ground.
    (All keys are configurable)

The code snippets below are accessible in the encrypted version:

Config
Config = {
    enableCommand = true,   -- Enables the /shield command
    commandName = 'shield', -- The command name (In this case /shield)

    enableItems = true,     -- Enables the shield item to used through items
    removeItem = true,      -- Remove the shield item when the player uses the shield item

    shields = {
        ['ballistic'] = {
            itemName = 'ballistic_shield',
            modelHash = `prop_ballistic_shield_custom`,
            jobs = { ['police'] = 0, ['sheriff'] = 0 },
            attach = {
                boneId = 36029,
                anim = { dict = 'combat@gestures@gang@pistol_1h@glances', name = '0' },
                pos = vector3(0.0, -0.03, -0.07),
                rot = vector3(-35.0, 180.0, -40.0),
            },
            weapons = {
                `WEAPON_PISTOL`,
                `WEAPON_PISTOL_MK2`,
                `WEAPON_COMBATPISTOL`,
                `WEAPON_STUNGUN`,
            }
        },
        ['riot'] = {
            itemName = 'riot_shield',
            modelHash = `prop_riot_shield`,
            jobs = { ['police'] = 0, ['sheriff'] = 0 },
            attach = {
                boneId = 36029,
                anim = { dict = 'combat@gestures@gang@pistol_1h@glances', name = '0' },
                pos = vector3(0.05, -0.03, -0.01),
                rot = vector3(-35.0, 180.0, -40.0),
            },
            weapons = {
                `WEAPON_PISTOL`,
                `WEAPON_PISTOL_MK2`,
                `WEAPON_COMBATPISTOL`,
                `WEAPON_STUNGUN`,
            }
        },
    },

    disableLadderClimb = true,
    disabledControls = {
        22, -- Space
        23, -- F
    },

    -- Unequip shield controls
    removeShield = { enable = true, label = '[H] Unequip', key = 74 },

    -- Drop shield controls
    dropShield = { enable = true, label = '[X] Drop', key = 73, },

    -- Place shield controls
    placeShield = { enable = true, label = '[G] Place', key = 47 },

    -- Place shield animation
    placeAnim = {
        dict = 'anim@mp_fireworks',
        name = 'place_firework_3_box',
        flags = 1,
        duration = 1500,
    }
}

Config.Locales = {
    ['pickup_shield'] = 'Pickup shield',
    ['invalid_shield'] = '~r~Invalid shield type.',
    ['cant_use_shield'] = '~r~You cannot use this shield right now.',
    ['not_allowed'] = '~r~You are not allowed to use this shield.',
    ['disallowed_weapon'] = '~r~You cannot use this shield with this weapon.',
    ['no_shield_item'] = '~r~You do not have the required item.',
    ['unequip_shield'] = '~r~Unequip your shield first.',
}
Accessible Client Bridge Functions
ESX = nil
QBCore = nil

if (GetResourceState('es_extended') == 'started') then
    ESX = exports['es_extended']:getSharedObject()
elseif (GetResourceState('qb-core') == 'started') then
    QBCore = exports['qb-core']:GetCoreObject()
end

Functions = {}

Functions.Notify = function(message)
    if ESX then
        ESX.ShowNotification(message, 'info', 5000)
    elseif QBCore then
        QBCore.Functions.Notify(message, 'primary', 5000)
    end
end

Functions.CanInteract = function()
    local playerPed = PlayerPedId()

    if IsPedDeadOrDying(playerPed, true) then
        return false
    end

    if IsPedInAnyVehicle(playerPed, false) then
        return false
    end

    return true
end

Functions.CanUseShield = function(shieldType)
    local playerPed = PlayerPedId()

    if IsPedDeadOrDying(playerPed, true) then
        return false
    end

    if IsPedInAnyVehicle(playerPed, false) then
        return false
    end

    return true
end

Functions.CreateShieldTarget = function(shield, canInteract, onSelect)
    exports.ox_target:addModel(shield.modelHash, {
        icon = 'fa-solid fa-shield',
        label = Config.Locales['pickup_shield'],
        canInteract = canInteract,
        onSelect = onSelect,
        groups = shield.jobs,
        distance = 2.0,
    })
end

Functions.CreateTextUI = function()
    local textParts = {}

    if Config.removeShield.enable then
        table.insert(textParts, Config.removeShield.label)
    end

    if Config.dropShield.enable then
        table.insert(textParts, Config.dropShield.label)
    end

    if Config.placeShield.enable then
        table.insert(textParts, Config.placeShield.label)
    end

    if #textParts > 0 then
        lib.showTextUI(table.concat(textParts, ' - '), { position = 'bottom-center' })
    end
end

Functions.HideTextUI = function()
    lib.hideTextUI()
end
Accessible Server Bridge Functions
ESX = nil
QBCore = nil

if (GetResourceState('es_extended') == 'started') then
    ESX = exports['es_extended']:getSharedObject()
elseif (GetResourceState('qb-core') == 'started') then
    QBCore = exports['qb-core']:GetCoreObject()
end

Functions = {}

Functions.CanUseShield = function(playerId, shieldType)
    if ESX then
        local xPlayer = ESX.GetPlayerFromId(playerId)
        local playerJobName = xPlayer.job.name
        local playerJobGrade = xPlayer.job.grade

        for job, grade in pairs(Config.shields[shieldType].jobs) do
            if (playerJobName == job) and (playerJobGrade >= grade) then
                return true
            end
        end

        return false
    elseif QBCore then
        local Player = QBCore.Functions.GetPlayer(playerId)
        local playerJobName = Player.PlayerData.job?.name or 'unemployed'
        local playerJobGrade = Player.PlayerData.job?.grade?.level or 0

        for job, grade in pairs(Config.shields[shieldType].jobs) do
            if (playerJobName == job) and (playerJobGrade >= grade) then
                return true
            end
        end
    end

    return false
end

Functions.HasItem = function(playerId, itemName)
    if ESX then
        local xPlayer = ESX.GetPlayerFromId(playerId)
        return xPlayer.getInventoryItem(itemName).count > 0
    elseif QBCore then
        local Player = QBCore.Functions.GetPlayer(playerId)
        return Player.Functions.GetItemByName(itemName).amount > 0
    end
end

Functions.OnShieldUse = function(playerId, shieldType)
    local itemName = Config.shields[shieldType].itemName

    if Config.removeItem and Config.enableItems then
        if ESX then
            local xPlayer = ESX.GetPlayerFromId(playerId)
            xPlayer.removeInventoryItem(itemName, 1)
        elseif QBCore then
            local Player = QBCore.Functions.GetPlayer(playerId)
            Player.Functions.RemoveItem(itemName, 1)
        end
    end
end

Functions.OnShieldRemove = function(playerId, shieldType)
    local itemName = Config.shields[shieldType].itemName

    if Config.removeItem and Config.enableItems then
        if ESX then
            local xPlayer = ESX.GetPlayerFromId(playerId)
            xPlayer.addInventoryItem(itemName, 1)
        elseif QBCore then
            local Player = QBCore.Functions.GetPlayer(playerId)
            Player.Functions.AddItem(itemName, 1)
        end
    end
end

Functions.RegisterItem = function(itemName, onItemUse)
    if (GetResourceState('ox_inventory') == 'started') then
        exports(itemName, function(event, item, inventory, slot, data)
            local playerId = inventory.id
            if (event == 'usingItem') then
                onItemUse(playerId)
                return false
            end
        end)
    elseif (GetResourceState('es_extended') == 'started') then
        ESX.RegisterUsableItem(itemName, function(source)
            onItemUse(source)
        end)
    elseif (GetResourceState('qb-core') == 'started') then
        QBCore.Functions.CreateUseableItem(itemName, function(source)
            onItemUse(source)
        end)
    else
        print('gs_policeshield: [ERROR] No inventory framework detected')
    end
end
Code is accessible No, but there is an unencrypted version
Subscription-based No
Lines (approximately) 800
Requirements ox_lib
Support Yes
3 Likes

Pretty cool! Good job