[ESX/QBCore] Animal Control Job

:dog2: Animal Control Job Script

Ever wanted to bring some order to the wild side of Los Santos? With the Animal Control Job, players can now step into the boots of an animal control officer! This engaging and highly customizable script lets players capture stray animals across a variety of locations. Whether itโ€™s a coyote in the desert or a stray cat in an urban area, this script provides a fun experience for roleplay servers.

Get it here: gamzkystore.com

Support: Discord

Preview video:

Key features

  • Begin your animal control journey by interacting with the NPC at the Animal Ark in Sandy Shores.
  • Diverse animal types including boars, cats, coyotes, dogs (various breeds), and hens, each spawn in unique habitats.
  • Reward system with configurable payouts for capturing and delivering animals.
  • Gameplay involving bait placement to lure animals, capturing them with crates, and transporting them in vehicles.
  • Rentable job vehicles with a deposit system and multiple spawn points, returnable upon job completion.
  • Realistic props for bait and animal crates.
  • Fail-safe mechanics that cancel the job if animals die, disappear, or the player abandons the vehicle.
  • Customizable localization for all messages and dialogues.
  • Player-friendly features like tracking blips for animals and instructions throughout the job process.
  • Protection against cheaters by tracking and validating the progress server-sided.
  • Synchronizes with other players.

Resmon

Idle: 0.00ms - When near animal: 0.02ms

How it works

  1. Start the job: Interact with the NPC to begin your job.
  2. Rent a vehicle: Pay a deposit and receive your job vehicle.
  3. Locate the animal: Go to the designated zone and and locate the animal.
  4. Distract the animal: Use bait to distract the animal before capturing it.
  5. Capture and return: Secure the animal in a crate and transport it back safely.
  6. Complete the task: Return the vehicle and receive your reward.

The script is made for ESX and QBCore, but thanks to the bridge functions you can easily adapt this to any other framework!

Config file
Config = {
    start = {
        blip = {
            sprite = 141,
            color = 26,
            scale = 1.2,
            label = 'Animal Control Job',
        },
        ped = {
            coords = vector4(562.26, 2741.43, 41.87, 186.60),
            model = `a_m_y_beachvesp_01`,
            scenario = 'WORLD_HUMAN_CLIPBOARD',
            voiceName = 'S_M_M_TRUCKER_01_WHITE_FULL_01',
            greetLines = { 'GENERIC_HI', 'GENERIC_HOWS_IT_GOING' },
            failedLines = { 'GENERIC_WHATEVER' },
        }
    },
    animal = {
        entityBlip = true, -- If true, a blip will be created for the animal entity so its easier to track down the animal.
        blip = {
            sprite = 141,
            color = 26,
            scale = 1.0,
            label = 'Animal Control Job',
        },
    },
    vehicle = {
        depositMoney = 1000,                            -- The amount of money the player has to deposit to rent the vehicle. Set to 0 to disable.
        model = `bison`,                                -- The model of the job vehicle.
        deliverPoint = vector3(561.09, 2727.07, 41.60), -- The coordinates where the player will deliver the vehicle after the job is done.
        warpPlayer = true,                              -- Warp player into vehicle when spawning
        spawnPoints = {
            vector4(559.65, 2719.41, 41.60, 3.15),
            vector4(562.70, 2719.88, 41.60, 3.43),
            vector4(565.76, 2720.20, 41.60, 5.01),
            vector4(568.86, 2720.27, 41.60, 4.13),
            vector4(571.89, 2720.40, 41.60, 4.09),
            vector4(574.98, 2720.91, 41.60, 4.85),
            vector4(578.06, 2720.86, 41.60, 1.81),
            vector4(581.14, 2721.12, 41.60, 3.97),
            vector4(584.40, 2721.39, 41.60, 3.63),
        }
    },
    crate = {
        model = `sf_prop_sf_crate_animal_01a`, -- The model of the animal crate.
        position = vector3(-0.15, 0.6, 0.0),
        rotation = vector3(0.0, 90.0, -30.0),
        attachBone = 24818,       -- The bone to attach the crate to.
        freezeWhenDropped = true, -- If the crate should be frozen when dropped.
        disabledKeys = {          -- The keys which are disabled when holding the crate.
            21,                   -- Sprint
            23,                   -- Enter vehicle
            24,                   -- Attack
            25,                   -- Aim
        },
    },
}

Config.Animals = {
    ['boar'] = {
        modelHash = `a_c_boar`,
        label = 'Boar',
        habitats = { 'countryside', 'forest' },  -- The habitats the animal can be found in
        reward = 5000,                           -- The reward the player will receive for catching and returning the animal

        baitModel = `apa_mp_h_acc_fruitbowl_02`, -- The model used for the bait prop
        lureDistance = 5.0,                      -- Distance the animal will detect the bait
        requiresBait = true,                     -- If true, players can only catch the animal if they are distracted by a bait
    },
    ['cat'] = {
        modelHash = `a_c_cat_01`,
        label = 'Cat',
        habitats = { 'urban', 'residential' },
        reward = 5000,

        baitModel = `v_ret_247_tuna`,
        lureDistance = 7.5,
        requiresBait = true,
    },
    -- Rest of animals left out for readability... (There are 13 animals in total)
}

Config.Dialogues = {
    ['boar'] = {
        start = {
            'A boar is loose in [zone]. It has wandered out of its habitat.',
            'Use food as bait to distract it and make the capture easier.'
        },
        approach = 'The boar is nearby. Stay calm and use bait to keep it occupied.',
        capture = 'You secured the boar! Bring it back safely for relocation.',
        completion = 'The boar is back where it belongs. Great job on this task!'
    },
    ['cat'] = {
        start = {
            'A cat is roaming in [zone]. It may have wandered far from home.',
            'Cats are cautious. Use food to gain its trust and lure it closer.'
        },
        approach = 'The cat is watching you. Use bait to distract it.',
        capture = 'You secured the cat! Bring it back to base for care.',
        completion = 'The cat is safe back at base. Well done!'
    },
    -- Rest of dialogues left out for readability...
}

Config.Animations = {
    ['place'] = {
        dict = 'pickup_object',
        anim = 'putdown_low',
        time = 1000,
    },
    ['pickup'] = {
        dict = 'random@domestic',
        anim = 'pickup_low',
        time = 1000,
    },
}

Config.Habitats = {
    -- Urban Area
    ['urban'] = {
        label = 'Urban Area',
        zones = {
            { coords = vector3(220.00, -803.00, 30.72),  size = 50.0 },
            { coords = vector3(-300.01, -899.99, 31.08), size = 40.0 },
        }
    },

    -- Residential Area
    ['residential'] = {
        label = 'Residential Area',
        zones = {
            { coords = vector3(-20.00, -1444.99, 30.61), size = 60.0 },
            { coords = vector3(180.06, -1486.36, 28.14), size = 55.0 },
            { coords = vector3(-220.0, -1600.0, 34.0),   size = 50.0 }
        }
    },

    -- Rest of zones left out for readability...
}

Config.Locales = {
    ['animal_blip_label'] = 'Catch: %s',
    ['catch_animal'] = 'Catch animal',
    ['confirm_cancel_job'] =
    'You have no animal in your vehicle. By delivering the vehicle, you will cancel the job. Press [E] to confirm.',
    ['distract_animal'] = 'You need to distract the animal before you can catch it.',
    ['failed_animal_dead'] = 'The job was canceled because the animal is dead. Return your vehicle and start a new job.',
    ['failed_animal_nonexistent'] =
    'The job was canceled because the animal does not exist anymore. Return your vehicle and start a new job.',
    ['failed_too_far'] =
    'The job was canceled because the animal is too far away. Return your vehicle and start a new job.',
    ['failed_vehicle_nonexistent'] =
    'The job was canceled because the vehicle does not exist anymore. Return to base and start a new job.',
    ['job_canceled'] = 'The job was canceled because you delivered your vehicle without an animal in it.',
    ['not_enough_money'] = 'You need $%s to pay the deposit for renting the vehicle.',
    ['paid_deposit'] = 'You paid a deposit of $%s to rent the vehicle.',
    ['pickup_bait'] = 'Pick up bait',
    ['pickup_crate'] = 'Pick up crate',
    ['place_bait'] = 'Place bait',
    ['put_crate_in_vehicle'] = 'Put crate in vehicle',
    ['received_deposit'] = 'You received your deposit of $%s back.',
    ['received_reward'] = 'You received $%s as reward for completing the job.',
    ['return_to_start'] = 'Return your vehicle with the animal to complete the job.',
    ['return_vehicle_complete'] = 'Return vehicle (Complete job)',
    ['return_vehicle'] = 'Return vehicle (Cancel job)',
    ['spawn_not_clear'] = 'There are no available spawn points for your job vehicle.',
    ['start_job_text'] = 'Start job',
}

-- Labels for GTA 5 zones
Config.ZoneLabels = {
    ['AIRP'] = 'Los Santos International Airport',
    ['ALAMO'] = 'Alamo Sea',
    ['ALTA'] = 'Alta',
    -- Rest of zones left out for readability...
}
Client bridge
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

-- Used to check if the player can use any of the target interactions
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.AddTarget = function(targetType, target, icon, label, distance, canInteract, onSelect)
    if (GetResourceState('ox_target') ~= 'started') then
        return print('ox_target is not installed, adjust cl_bridge.lua to the target-script you are using.')
    end

    if (targetType == 'localentity') then
        exports.ox_target:addLocalEntity(target, { {
            icon = icon,
            label = label,
            distance = distance,
            canInteract = canInteract,
            onSelect = onSelect
        } })
    elseif (targetType == 'entity') then
        exports.ox_target:addEntity(target, { {
            icon = icon,
            label = label,
            distance = distance,
            canInteract = canInteract,
            onSelect = onSelect
        } })
    end
end

Functions.PlayPedNotification = function(ped, sender, message)
    if not DoesEntityExist(ped) then
        return
    end

    local mugshotHandle, mugshotTxd = GetPedMugshot(ped)
    BeginTextCommandThefeedPost('STRING')
    AddTextComponentSubstringPlayerName(message)
    EndTextCommandThefeedPostMessagetext(mugshotTxd, mugshotTxd, false, 0, sender, '')
    EndTextCommandThefeedPostTicker(false, false)

    UnregisterPedheadshot(mugshotHandle)
end
Server bridge
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.AddMoney = function(playerId, amount)
    if ESX then
        local xPlayer = ESX.GetPlayerFromId(playerId)
        xPlayer.addAccountMoney('bank', amount)
    elseif QBCore then
        local Player = QBCore.Functions.GetPlayer(playerId)
        Player.Functions.AddMoney('bank', amount)
    end
end

Functions.RemoveMoney = function(playerId, amount)
    if ESX then
        local xPlayer = ESX.GetPlayerFromId(playerId)
        xPlayer.removeAccountMoney('bank', amount)
    elseif QBCore then
        local Player = QBCore.Functions.GetPlayer(playerId)
        Player.Functions.RemoveMoney('bank', amount)
    end
end

Functions.HasMoney = function(playerId, amount)
    if ESX then
        local xPlayer = ESX.GetPlayerFromId(playerId)
        local bankBalance = xPlayer.getAccount('bank').money
        return (bankBalance >= amount)
    elseif QBCore then
        local Player = QBCore.Functions.GetPlayer(playerId)
        local bankBalance = Player.PlayerData.money.bank
        return (bankBalance >= amount)
    end
end

AddEventHandler('gs_animalcontrol:OnJobStarted', function(data)
    local playerId = data.playerId
    local jobData = data.jobData
end)

AddEventHandler('gs_animalcontrol:OnAnimalSpawned', function(data)
    local playerId = data.playerId
    local netId = data.netId -- The network id of the animal
end)

AddEventHandler('gs_animalcontrol:OnJobVehicleSpawned', function(data)
    local playerId = data.playerId
    local netId = data.netId -- The network id of the vehicle
end)

AddEventHandler('gs_animalcontrol:OnAnimalInVehicle', function(data)
    local playerId = data.playerId
end)

AddEventHandler('gs_animalcontrol:OnJobFinished', function(data)
    local success = data.success
end)

Code is accessible No, but there is a fully unencrypted version.
Subscription-based No
Lines (approximately) 2036
Requirements ox_lib
Support Yes
4 Likes