Developer

7 min readUpdated Today

Overview

Integration uses server exports for quest state and XP, handler hooks in open/handlers_server.lua and open/handlers_client.lua for quest flow control, and a server event for quest completion.

Server Exports

Quest State

lua
1local quest = exports.ml_missions:GetActiveQuest(source)
2local inQuest = exports.ml_missions:IsPlayerInQuest(source)
  • GetActiveQuest(source): Returns the active quest data table, or nil
  • IsPlayerInQuest(source): Returns true if the player has an active quest

XP / Reputation

Per-NPC reputation tracked in the ml_missions_xp table.

lua
1local xp = exports.ml_missions:getXP(source, npcId)
2exports.ml_missions:setXP(source, npcId, 100)
3exports.ml_missions:addXP(source, npcId, 25)
4exports.ml_missions:removeXP(source, npcId, 10)
  • getXP(source, npcId): Returns current XP value
  • setXP(source, npcId, amount): Sets XP to a specific amount (min 0)
  • addXP(source, npcId, amount): Adds XP
  • removeXP(source, npcId, amount): Removes XP (cannot go below 0)

Server Events

lua
1AddEventHandler('ml_missions:server:questCompleted', function(source, questId, squadMembers)
2    -- squadMembers is a table of source IDs (nil if solo)
3end)

Fires when any quest is completed, after rewards are distributed.

Server Handlers

Located in open/handlers_server.lua.

Quest Flow

open/handlers_server.lua
1function Handlers.IsPlayerDead(source)
2    local ped = GetPlayerPed(source)
3    if not ped or ped == 0 then return false end
4    return GetEntityHealth(ped) <= 0
5end
6
7function Handlers.CanPlayerStartQuest(source, questId, questConfig)
8    return true, nil -- return false, 'reason' to block
9end
10
11function Handlers.ModifyRewards(source, questId, moneyData, itemsData)
12    return moneyData, itemsData
13end
  • IsPlayerDead: Override to integrate custom death/ambulance scripts
  • CanPlayerStartQuest: Return false + reason string to block quest start
  • ModifyRewards: Intercept and modify money/item arrays before distribution

Quest Lifecycle

open/handlers_server.lua
1function Handlers.OnQuestComplete(source, questId, questConfig, rewardData)
2end
3
4function Handlers.OnQuestFailed(source, questId, reason)
5end
6
7function Handlers.OnObjectiveComplete(source, questId, objectiveId, objectiveConfig)
8end
  • OnQuestComplete: Fires after rewards are distributed. rewardData contains rewards, rewardConfig, and reputationPoints.
  • OnQuestFailed: Fires when a quest fails (timeout, death, manual cancel)
  • OnObjectiveComplete: Fires when a single objective is completed

Vehicle Garage Hook

When a GIVE_VEHICLE objective has keepOnComplete = true, ml_missions registers the vehicle natively (qbx_vehicles / player_vehicles / owned_vehicles). Override this hook to route it to a custom garage.

open/handlers_server.lua
1function Handlers.OnVehicleKeptForPlayer(source, payload)
2    -- payload = { netId, model, plate, colors, damage, questId }
3    return nil -- nil/false = run native handler ; true = you handled the save
4end
  • OnVehicleKeptForPlayer: Return true to take over the save and skip the native handler. Destination garage is set via Config.Settings.KeepOnComplete.

Permission Hooks

Both hooks resolve through Bridge.HasPermission against the tiers in Config.Permissions.

open/handlers_server.lua
1Handlers.HasCreatorPermission = function(source) ... end
2Handlers.HasLivePermission = function(source) ... end
  • HasCreatorPermission(source): Gates /missioncreator, mission/NPC editing and the Live Missions tab.
  • HasLivePermission(source): Gates force-complete / force-fail / teleport in the Live Missions tab. Falls back to HasCreatorPermission when Config.Permissions.LiveActions is nil.

Client Handlers

Located in open/handlers_client.lua.

Interaction

open/handlers_client.lua
1function Handlers.CanInteractWithNPC(npcId, npcData)
2    return true -- return false to hide target
3end
4
5function Handlers.BeforeOpenUI(uiName)
6    return true -- return false to block UI
7end
  • CanInteractWithNPC: Gate NPC target visibility. Return false to hide.
  • BeforeOpenUI: Block UI opening. Return false to cancel.

Mission Lifecycle

open/handlers_client.lua
1function Handlers.OnMissionStart(questId, questConfig)
2end
3
4function Handlers.OnMissionComplete(questId, questConfig)
5end
6
7function Handlers.OnMissionFailed(questId, reason)
8end
9
10function Handlers.OnObjectiveComplete(objectiveIndex, objectiveConfig)
11end
  • OnMissionStart: Fires when a quest starts on the client
  • OnMissionComplete: Fires when a quest is completed
  • OnMissionFailed: Fires when a quest fails
  • OnObjectiveComplete: Fires when an objective is completed

Objective Types

The mission creator supports these objective types:

  • GOTO_COORDS: Navigate to one or more locations
  • KILL_PEDS: Eliminate enemy NPCs (supports zombie mode)
  • DEFEND_AREA: Defend a location against waves of enemies
  • ESCORT: Escort an NPC to a destination
  • TAKE_VEHICLE: Pick up or deliver vehicles
  • INTERACT_PROGRESS: Interact with props using progress bars, minigames, or door unlocks
  • RETRIEVE_ITEM: Collect specific items from the player's inventory
  • CHECK_ITEM: Verify the player owns a specific item
  • HEIST_LOOT: Scripted grab points driven by heist presets, with synced animations, optional minigame and per-point rewards
  • GATHER_REWARD: Collect reward points at specific locations
  • INTERACTION_TALK: NPC dialogue interaction with optional TTS
  • TURN_IN: Auto-generated return-to-NPC objective

Objective Dependencies

Objectives can be linked together:

  • activateOnStart: Activate this objective when another objective starts
  • activateOnEvent: Activate on a specific event (format: "objectiveId:eventName")
  • pauseUntilComplete: Pause this objective until a dependency completes

Dispatch Integration

Each objective can trigger a police dispatch notification:

  • dispatch.enabled: Enable dispatch for this objective
  • dispatch.chance: Trigger probability (0-100)
  • dispatch.jobs: Comma-separated job list (default: 'police')
  • dispatch.message: Custom dispatch message

Heist Presets

Each HEIST_LOOT point references a preset from shared/heist_presets.lua. A preset defines the target prop spawned in the world, an optional prop swap once the loot is taken, and the phased grab animation with attached props, synced for every player nearby.

  • cash_trolley, gold_trolley: bank trolleys emptied into a duffel bag
  • safe_crack: floor safe with an open-door swap
  • cash_stack, gold_stack, cocaine_stack, weed_stack: table grabs
  • jewel_case: display case smash
  • laptop_hack: laptop grab
  • bolt_cutters, container_grind, crate_crowbar: tool entries for chains, containers and crates

Add your own presets in the same file: copy an entry and change the props and animation phases.

Waypoints and Markers

Every objective location can enable an overhead 3D waypoint and a ground marker, together or separately, set per objective in the creator (fields waypoint and marker3d).

Quest Options

Configurable per-quest in the creator:

  • requiredXp: Minimum NPC reputation to start
  • requiredJob: Comma-separated list of jobs or gangs allowed to start the quest. Matching one entry is enough
  • requiredJobsOnline: Array of { job, count } entries. Requires a minimum number of online players per job before the quest can start. Example: { { job = 'police', count = 2 }, { job = 'bcso', count = 1 } }
  • requiredItemName / requiredItemCount: Item prerequisites
  • removeItemOnStart: Consume required item on quest start
  • cooldown: Seconds before the quest can be retaken
  • isPersonalCooldown: true = per-player cooldown, false = global cooldown
  • isOneTimeOnly: Quest can only be completed once per player
  • returnToNpc: Player must return to NPC for turn-in
  • timeLimit: Seconds to complete (0 = no limit)
  • reputationPoints: XP awarded on completion
  • xpPenaltyOnFail: XP deducted on failure
  • squadOptions.enabled: Enable squad mode
  • squadOptions.minMembers: Minimum squad members to start
  • requiredSkills: Array of ml_skills entries the player must have unlocked before starting
  • requiredCategories: Array of { category, minLevel } checks against ml_skills category levels
  • unlockSkills: Skills granted to the player when the quest completes
  • xpAmount / xpCategory: Skill XP awarded on completion and the category it lands in, capped by Config.Skills.MaxXpPerQuest
  • voice: Briefing audio: { enableTTS, audioType = 'elevenlabs' | 'custom', voiceId, customAudioFile }
  • showAllObjectives: Show the full objective list in the HUD instead of revealing steps one at a time

NPC Configuration

  • ped.coords: Main spawn position (vector4)
  • ped.positions: Optional array of additional spawn positions for multi-location NPCs. Each entry: { coords = { x, y, z, w } }. The same NPC spawns at all positions, shares quests and reputation, but the turn-in always routes back to the specific position where the player accepted the quest.

Reward System

Rewards are configured per-quest in the creator. Two formats:

Fixed Loots

Guaranteed drops from a defined pool of items.

Probability Loots

Percentage-based drops with configurable loop count. Each loop rolls against the drop chance independently.

Money

Random amount between a min and max threshold, to a specific account (cash or bank).

Squad Bonuses

Configurable per squad size. Applies a multiplier to money and item rewards when completing as a squad.

Examples

open/handlers_server.lua
1local eventActive = false
2
3Handlers.CanPlayerStartQuest = function(source, questId, questConfig)
4    if eventActive then
5        return false, 'Missions are disabled during events'
6    end
7    return true
8end
open/handlers_server.lua
1Handlers.ModifyRewards = function(source, questId, moneyData, itemsData)
2    local job = Bridge.GetJob(source)
3    if job and job.name == 'vip' then
4        for i, money in ipairs(moneyData) do
5            moneyData[i].amount = money.amount * 2
6        end
7    end
8    return moneyData, itemsData
9end
open/handlers_client.lua
1Handlers.CanInteractWithNPC = function(npcId, npcData)
2    local job = Bridge.GetPlayerData()?.job?.name
3    if npcId == 'police_chief' and job ~= 'police' then
4        return false
5    end
6    return true
7end
open/handlers_server.lua
1Handlers.OnQuestComplete = function(source, questId, questConfig, rewardData)
2    if questConfig.category == 'combat' then
3        exports.ml_skills:AddXp('personal', 50, source)
4    end
5end