RedM Resource

lrc-horsethief

A progressive horse theft mission system for RedM. Players take contracts from an NPC, ride out to steal specific horses guarded by enemies, break them in, and deliver them back for a reward. Supports both RSG-Core and VORP-Core out of the box.

FrameworksRSG-Core · VORP-Core
Editable Filesconfig.lua · configmissions.lua · fw_func.lua
Encryptedclient/cl_main.lua · server/sv_main.lua
01

Overview

How the resource works from start to finish.

Mission flow

Player approaches the contact NPC

A prompt appears within Config.PromptRange units. Pressing it opens the mission selection menu.

Player selects a mission

Missions unlock progressively — Mission 1 is always available. Each subsequent mission requires completing the previous one a set number of times (Config.CompletionsToUnlock).

Player rides to the marked area

A map blip and GPS route guide the player. When they enter Config.TriggerRadius, enemies and the target horse spawn nearby.

Player clears enemies and mounts the horse

Once mounted, a 15-second calm timer begins. The player must stay on the horse continuously. Getting bucked off resets the timer.

Player delivers the horse to the NPC

After calming, a delivery blip and GPS route appear. Riding to within Config.DeliveryRadius of the NPC completes the mission and awards cash and/or items.


02

File structure

Every file in the resource and what it does.

lrc-horsethief/ ├── config.lua — NPC, blips, cooldown, enemy setup, level bonuses ├── configmissions.lua — all mission definitions (horse, enemies, rewards, UI text) ├── fw_func.lua — framework bridge: notifications, money, items ├── fxmanifest.lua — resource manifest (do not edit) ├── locale_init.lua — locale string definitions (en / ro) ├── client/ │ └── cl_main.lua — encrypted client logic ├── server/ │ └── sv_main.lua — encrypted server logic └── ui/ ├── index.html — mission selection UI └── assets/ ├── images/ │ ├── m1.png … m10.png — mission card images └── fonts/ · *.png — UI assets
Highlighted files are the only ones you need to edit. The encrypted client/ and server/ files and the UI shell are untouched by configuration.

03

Quick start

Minimum steps to get the resource running on your server.

Drop the resource into your server

Place lrc-horsethief inside your resources folder and add ensure lrc-horsethief to your server.cfg.

Set your language

Open config.lua and set Config.Lang to 'en' or 'ro'.

Place your NPC

Set the coords inside Config.ContactNPCs in config.lua to a location that suits your server's map.

Review mission rewards

Open configmissions.lua and check each mission's rewards.cash value against your server's economy. Adjust as needed.

Start the resource

Run ensure lrc-horsethief in your txAdmin console or server console. The NPC will spawn and the missions will be live.


04

config.lua

The main configuration file. Controls the NPC, map blips, prompts, cooldown, level bonuses, mission area sizes, and enemy loadouts.

Language

config.lua
Config.Lang = 'en'  -- 'en' or 'ro'

Switches all in-game notification text and UI strings between English and Romanian. The locale strings themselves live in locale_init.lua — do not edit that file directly unless you are adding a new language.

Contact NPC — Config.ContactNPCs

config.lua
Config.ContactNPCs = {
    {
        model  = 'MP_LM_STEALHORSE_BUYERS_01',
        coords = vector4(1242.2626, -437.9520, 89.4799, 60.3918),
    },
    -- add more entries here if needed
}

Each entry in this table spawns one contact NPC in the world. You can have multiple NPCs — each one opens the same mission menu independently. This is useful if you want to place the contact in more than one location on the map.

KeyTypeDescription
modelstringThe ped model hash name to spawn as the contact NPC.
coordsvector4World position (x, y, z) and heading (w) where the NPC stands. Use a trainer or coords script to find the right position.
Adding a second NPC location: copy the entire { model = ..., coords = ... } block, paste it after the first one (separated by a comma), and update the coords.

Prompt & proximity — Config.PromptRange

config.lua
Config.PromptRange  = 2.5           -- metres from the NPC before the prompt appears
Config.PromptTitle  = 'Horse Thief'  -- title shown above the prompt hint
Config.PromptAction = 'Interact'     -- label on the prompt button
KeyTypeDescription
PromptRangenumberDistance in metres. When the player is within this range, the interaction prompt becomes visible and pressable.
PromptTitlestringThe header text displayed above the prompt group on screen.
PromptActionstringThe label on the prompt button itself (e.g. "Talk", "Interact", "Open").

NPC blip — Config.ShowNPCBlip

config.lua
Config.ShowNPCBlip = false                 -- set true to show the NPC on the minimap
Config.BlipSprite  = 'blip_ambient_horse'  -- blip icon hash name
Config.BlipLabel   = 'Horse Thief'         -- text shown when hovering the blip
Config.BlipScale   = 0.35                  -- size of the blip icon

When ShowNPCBlip is false, the NPC exists in the world but does not appear on the minimap — players must find them through other means (server lore, signage, etc.). Set it to true to show a minimap marker.

BlipSprite accepts any valid RedM blip hash name. BlipScale controls the size — 0.35 is small and unobtrusive; 0.6–1.0 is more prominent.

Delivery blip — shown during horse return

config.lua
Config.DeliveryBlipSprite = 'blip_saddle'
Config.DeliveryBlipLabel  = 'Deliver Horse'
Config.DeliveryBlipScale  = 0.35
Config.DeliveryRadius     = 10.0   -- metres from NPC to trigger mission completion

Once a horse is calmed, a delivery blip appears on the NPC's position. DeliveryRadius is the proximity (in metres) within which the mission auto-completes and rewards are paid. Increase it if players are finding the detection too tight; decrease it for a more precise delivery zone.

Mission area radii

config.lua
Config.CircleRadius  = 35.0   -- size of the visual circle drawn on the map
Config.TriggerRadius = 70.0   -- entering this range spawns enemies and the horse
Config.SpawnRadius   = 25.0   -- enemies and horse spawn within this range of the center
KeyDescription
CircleRadiusThe radius of the decorative circle blip shown on the minimap around the mission location. Visual only — does not affect gameplay.
TriggerRadiusWhen the player comes within this many metres of the mission center, the ambush spawns. Should be larger than CircleRadius so the ambush triggers before the player enters the visual circle.
SpawnRadiusEnemies and the target horse are scattered randomly within this radius of the mission center point. Keep it smaller than TriggerRadius.

Enemy models, weapons & accuracy

config.lua
Config.EnemyModels = {
    'g_m_m_uniafricanamericangang_01',
    'u_m_m_odriscollbrawler_01',
    -- add or remove ped model names here
}

Config.EnemyAccuracy = {
    easy    = 20,   -- 0 = completely blind, 100 = perfect aim
    default = 35,
    hard    = 55,
}

Config.EnemyWeapons = {
    easy = {
        'weapon_revolver_cattleman',
        'weapon_revolver_doubleaction',
    },
    default = {
        'weapon_revolver_cattleman',
        'weapon_repeater_winchester',
        'weapon_shotgun_doublebarrel',
    },
    hard = {
        'weapon_rifle_springfield',
        'weapon_rifle_boltaction',
        'weapon_shotgun_semiauto',
    },
}

EnemyModels is a shared pool — every spawned enemy picks a random model from this list regardless of mission difficulty. EnemyWeapons and EnemyAccuracy are keyed by difficulty type ('easy', 'default', 'hard'), which is set per-mission inside configmissions.lua.

Adding a model: add the ped model string to EnemyModels. Changing weapons per difficulty: add or remove weapon hash names from the relevant difficulty block in EnemyWeapons. One weapon is picked at random per enemy.

05

configmissions.lua

Defines every mission — the target horse, spawn locations, number of enemies, rewards, and the text shown in the mission selection UI.

Full mission structure

configmissions.lua — single mission entry
[1] = {
    type = 'default',  -- 'easy' | 'default' | 'hard'

    ui = {
        image   = 'assets/images/m1.png',
        label   = 'MISSION 1 [Morgan]',
        warning = 'This horse is protected by armed gangs!',
        details = 'A sturdy Morgan Gelding...\n\nGo steal it.',
    },

    horselocation = {
        vec3(1549.1113, 1073.8922, 181.2135),
        vec3(1769.6315, 950.4037,  119.5529),  -- optional extra location
    },

    horse   = { model = 'a_c_horse_morgan_bayroan' },
    enemies = { count = 8 },

    rewards = {
        enabled = true,
        cash    = 50,
        -- item   = 'horse_deed',  -- single item (optional)
        -- amount = 1,
        -- items  = {              -- multiple items (optional)
        --     { item = 'horse_deed',      amount = 1 },
        --     { item = 'aged_pirate_rum', amount = 2 },
        -- },
    },
},

Field reference

FieldTypeDescription
typestringDifficulty — controls which weapon pool and accuracy value enemies use. Must be 'easy', 'default', or 'hard' (defined in config.lua).
ui.imagestringRelative path to the card image shown in the mission selection menu. Must be a file inside ui/assets/images/.
ui.labelstringThe mission title shown at the top of the mission card in the UI.
ui.warningstringA short warning line shown below the title. Typically used to warn about enemies.
ui.detailsstringThe mission description paragraph. Use \n\n for paragraph breaks within the string.
horselocationvec3 arrayOne or more world positions. If more than one is provided, the game picks one at random each time the mission starts — adding variety to the same mission.
horse.modelstringThe horse ped model to spawn as the target. Must be a valid RedM horse model hash.
enemies.countnumberHow many enemy peds to spawn when the player enters the trigger radius.
rewards.enabledbooleanSet to false to disable all rewards for this mission entirely.
rewards.cashnumberDollar amount added to the player's cash on successful delivery. Set to 0 or omit for no cash reward.
rewards.item / rewards.amountstring / numberA single item name and quantity to give on completion. Uncomment and fill in.
rewards.itemstableA list of multiple items, each with item and amount. Use this instead of item/amount when giving more than one item type.

06

Adding a new mission

Step-by-step guide to adding Mission 11 (or any subsequent mission) to the resource.

Add a new image

Create or source a horse image and save it as ui/assets/images/m11.png. It must follow the same naming convention — mN.png where N is the mission index.

Open configmissions.lua

Scroll to the bottom of Config.Missions = { ... }, after the last entry (e.g. [10] = { ... }).

Paste a new entry

Copy the block below — it is a ready-to-edit template for Mission 11. Paste it after the closing }, of Mission 10, still inside the outer } of Config.Missions.

Fill in the fields

Set your type, the UI text, one or more horse locations, the horse model, enemy count, and rewards. Save the file.

Restart the resource

Run restart lrc-horsethief in the console. The new mission will appear in-game as the last card in the selection menu, locked until Mission 10 has been completed the required number of times.

configmissions.lua — Mission 11 template
    -- ═══════════════════════════════════════════════════
    [11] = {
        type = 'hard',   -- 'easy' | 'default' | 'hard'

        ui = {
            image   = 'assets/images/m11.png',
            label   = 'MISSION 11 [Horse Name]',
            warning = 'This horse is potentially protected by armed gangs!',
            details = 'Describe the horse and the job here.\n\nSecond paragraph.',
        },

        horselocation = {
            vec3(0.0, 0.0, 0.0),   -- replace with real coords
        },

        horse   = { model = 'a_c_horse_YOURMODELHERE' },
        enemies = { count = 10 },

        rewards = {
            enabled = true,
            cash    = 70,
        },
    },
Mission unlock order: Missions unlock sequentially — players must complete Mission 10 at least Config.CompletionsToUnlock times before Mission 11 becomes available. There is no way to make a mission skip-able without editing that value.
Important: the image file at ui/assets/images/m11.png must exist before you restart the resource. A missing image will cause the card to display a broken image in the selection UI.

07

Mission images

How to replace or add card images shown in the mission selection menu.

Naming convention

Images are named m1.png through mN.png and stored in ui/assets/images/. Each file maps directly to the mission at the same index — m1.png is shown for [1], m2.png for [2], and so on. The path is set per-mission in the ui.image field inside configmissions.lua, so you can also use a completely different filename or subfolder if you prefer.

Replacing an existing image

Simply replace the file at the same path with your new image. No config change is needed — the path is already declared in the mission entry. Recommended dimensions are roughly 400 × 260 px, landscape orientation.

Adding an image for a new mission

Save your image as ui/assets/images/mN.png where N is the new mission index, then reference it in the ui.image field of your new mission entry. The file is served via fxmanifest's wildcard declaration and does not need to be declared manually.


08

Cooldown system

Prevents players from repeating the same mission back-to-back. Cooldowns are tracked server-side per player and per mission.

config.lua
Config.CooldownEnabled = true
Config.CooldownMinutes = 10

How it works

When a player successfully completes a mission, the server records a timestamp for that player + mission combination. If the player tries to start the same mission again before CooldownMinutes has elapsed, they receive a notification telling them how much time remains and the mission does not start.

Cooldowns are tracked in memory on the server — they reset if the server restarts. If you need persistent cooldowns across restarts, that would require a database integration not included in this resource.

Disabling cooldowns

Set Config.CooldownEnabled = false to remove all cooldown restrictions. Players will be able to repeat any mission immediately after completing it.

Adjusting the duration

Change Config.CooldownMinutes to any whole number. A value of 0 with CooldownEnabled = true is the same as disabling — the cooldown expires instantly. Recommended range is 5–30 minutes depending on your server's economy pace.

Mission unlock threshold — Config.CompletionsToUnlock

config.lua
Config.CompletionsToUnlock = 1   -- completions of mission N needed to unlock mission N+1

This is separate from the cooldown. It controls the progression gate — how many times a player must successfully complete Mission N before Mission N+1 becomes available. The default of 1 means one completion unlocks the next mission. Set it to 3 to require three completions, and so on.

This applies to all missions equally. There is no per-mission override — every tier requires the same number of completions to progress. Completion counts are stored server-side and persist as long as the server is running.

09

Level system

Players earn experience through total mission completions across all missions. Higher levels receive a cash bonus on top of the base mission reward.

config.lua
Config.Levels = {
    { threshold = 0,   level = 1 },  -- 0–49 total completions → Level 1
    { threshold = 50,  level = 2 },  -- 50–99                 → Level 2
    { threshold = 100, level = 3 },  -- 100–149               → Level 3
    { threshold = 150, level = 4 },  -- 150–199               → Level 4
    { threshold = 200, level = 5 },  -- 200+                  → Level 5
}
Config.CashBonusPerLevel = 5  -- $ added per level above 1

How it works

The server tracks a player's total completions across all missions (not per-mission). When a mission is completed, the player's level is checked against the thresholds in Config.Levels. Their level is the highest entry whose threshold is less than or equal to their total completions.

The cash bonus is calculated as (level - 1) × CashBonusPerLevel. At Level 1 there is no bonus. At Level 2 the bonus is +$5. At Level 5 the bonus is +$20 (on top of whatever the mission's base cash reward is).

Customising levels

You can add more levels, change the thresholds, or raise the bonus amount freely. Each entry needs a threshold (minimum total completions to reach that level) and a level number. Keep the entries in ascending threshold order.

Example: with CashBonusPerLevel = 10 and a base mission reward of $50, a Level 3 player would receive $50 + (2 × $10) = $70 on completion.

10

Death fail

Optionally fail the active mission if the player dies at any point during it — from the moment the contract is accepted through to delivery.

config.lua
Config.EnableDeathFail = true

How it works

When true, a watcher thread starts the moment a mission is approved. It runs for the entire mission lifetime — travel to the area, the ambush phase, calming the horse, and delivery — and triggers a mission failure the instant IsEntityDead returns true for the local player.

On death, the mission is cancelled server-side (same as any other failure path), all spawned enemies and the target horse are cleaned up, and the player receives an 'error' notification.

Disabling

Set Config.EnableDeathFail = false to turn the feature off entirely. No watcher thread is created and dying during a mission has no effect on its state — the mission continues when the player respawns.

KeyTypeDescription
EnableDeathFailbooleanWhen true, player death during any phase of a mission fails it immediately. When false, death is ignored by the mission system.
Scope: The watcher covers the full mission — from contract accepted to delivery completion. It stops automatically when the mission ends for any reason (success, horse killed, horse lost, or manual cancel), so there is no risk of a stale thread carrying over between missions.

11

Notification system

By default the resource sends notifications through RSG-Core or VORP-Core depending on which framework is running. Both can be swapped out for any other notification script with a few lines in fw_func.lua.

Default behaviour

The resource auto-detects the running framework at startup. On RSG-Core it uses ox_lib if available, otherwise falling back to RSGCore:Notify. On VORP-Core it uses vorp:TipRight. You do not need to configure anything for either of these to work.

FrameworkNotification used
RSG-Core + ox_libexports['ox_lib']:notify({ ... }) (client) / ox_lib:notify (server event)
RSG-Core (no ox_lib)TriggerEvent('RSGCore:Notify', ...) / TriggerClientEvent('RSGCore:Notify', ...)
VORP-CoreTriggerEvent('vorp:TipRight', ...)

Notification types

Throughout the resource, notifications are called with a ntype argument. The values used are:

  • 'success' — mission completed, horse calmed, delivery confirmed
  • 'error' — mission failed, horse killed, cooldown active, already in a mission
  • 'primary' / 'inform' — neutral information (e.g. mount the horse, return to horse)

When replacing the notification system, map these type strings to whatever your target script expects.


12

fw_func.lua — swapping the notification script

fw_func.lua is the only file you need to edit to change how notifications, money, and items are handled. The rest of the resource calls LRCfw.notify and LRCfw.notifyLocal exclusively — nothing else needs touching.

Structure overview

The file defines four internal setup functions — one client and one server function for each framework — and then auto-detects which framework is running to call the right one. Each setup function assigns implementations to the LRCfw table.

fw_func.lua — structure
local function setupClientRSG()
    LRCfw.notifyLocal = function(msg, ntype, duration)
        -- ← edit this to change client notifications on RSG
    end
end

local function setupClientVORP()
    LRCfw.notifyLocal = function(msg, ntype, duration)
        -- ← edit this to change client notifications on VORP
    end
end

local function setupServerRSG()
    LRCfw.notify = function(src, msg, ntype, duration)
        -- ← edit this to change server→client notifications on RSG
    end
end

local function setupServerVORP()
    LRCfw.notify = function(src, msg, ntype, duration)
        -- ← edit this to change server→client notifications on VORP
    end
end

Example — replacing with a custom notification script

Say you use a script called my-notify that exposes a client export exports['my-notify']:Send(msg, type, duration) and a server-side client event 'my-notify:client:show'. Here is how you would swap it in:

fw_func.lua — setupClientRSG (and setupClientVORP)
LRCfw.notifyLocal = function(msg, ntype, duration)
    exports['my-notify']:Send(msg, ntype, duration or 5000)
end
fw_func.lua — setupServerRSG (and setupServerVORP)
LRCfw.notify = function(src, msg, ntype, duration)
    TriggerClientEvent('my-notify:client:show', src, msg, ntype, duration or 5000)
end

Apply the same change to both RSG and VORP setup functions if you want it consistent on both frameworks, or only edit the one that applies to your server.

Parameters passed to both functions:
  • msg — the notification text string
  • ntype — the notification type ('success', 'error', 'primary', 'inform')
  • duration — display time in milliseconds (e.g. 5000 = 5 seconds)
  • src — (server only) the player source ID to send the notification to

Other functions in fw_func.lua

Beyond notifications, the file also wires up money and item rewards. These are framework-specific and generally do not need changing unless you use a custom inventory or economy script.

FunctionSideDescription
LRCfw.notifyServerSends a notification to a specific player by source ID. Called by the server after mission completion, cooldown checks, etc.
LRCfw.notifyLocalClientSends a local notification to the local player. Called directly on the client for immediate feedback (mount prompt, bucked off, etc.).
LRCfw.addMoneyServerAdds cash to the player's character. Wired to p.Functions.AddMoney on RSG and addCurrency on VORP.
LRCfw.addItemServerAdds an item to the player's inventory. Wired to rsg-inventory:AddItem on RSG and vorp_inventory:addItem on VORP.
LRCfw.getCitizenIdServerReturns the player's citizen/character ID. Used internally for cooldown and progress tracking.
LRCfw.hideHUD / showHUDClientHides and restores the HUD when the mission menu is open.
Do not rename or remove any of these functions. The encrypted client and server scripts call them by name. Removing or renaming LRCfw.notify, LRCfw.notifyLocal, LRCfw.addMoney, or LRCfw.addItem will break the resource silently.

13

Support

Full support is provided for every resource.

What's included

Every purchase comes with full support — installation help, configuration questions, and bug fixes. Join the Discord and open a ticket, and 1973 will get back to you directly.