Tasks & Upgrades · origen_ilegalv2
The Organization Panel (OrgPanels) lets you wire any external resource — missions, heists, robberies — into the gang's progress board. Your resource registers tasks and upgrades; origen_ilegalv2 handles persistence, validation, NUI display, and hooks.
Concepts
| Concept | What it is |
|---|---|
| Task | A milestone a gang can complete. Tracks completed and times_completed. Can be one-off or repeatable. |
| Upgrade | A permanent unlock for a gang. Requires one or more tasks to be completed before it can be activated. |
| Feature key | A string stored in effect.feature on an upgrade. External resources use this key — not the upgrade_key — to check access. Decouples your resource from upgrade names. |
Data flow:
External resource registers task/upgrade on boot
↓
Gang members complete tasks in-game → your resource calls CompleteGangTask
↓
Leader unlocks upgrade from the gang panel (NUI) or your code calls UnlockGangUpgrade
↓
Other resources gate content behind IsGangFeatureUnlockedTiming — when to register
OrgPanels loads its catalogs when the resource starts. Always register inside onResourceStart to guarantee the module is ready:
AddEventHandler('onResourceStart', function(resourceName)
if GetCurrentResourceName() ~= resourceName then return end
exports['origen_ilegalv2']:AddOrganizationTask({ ... })
exports['origen_ilegalv2']:AddOrganizationUpgrade({ ... })
end)Calling these exports at the top level (outside an event) risks a race condition where origen_ilegalv2 has not finished loading yet.
Registering tasks
AddOrganizationTask(taskDef)
exports['origen_ilegalv2']:AddOrganizationTask({
task_key = 'task_recon_north', -- unique key (snake_case recommended)
label = 'North Recon', -- displayed in the gang panel
description = 'Perform recon of the northern zone.',
category = 'heist', -- free-form category for NUI grouping
is_repeatable = false, -- true = can be completed multiple times
meta = { zone = 'north', reward_hint = 'Unlocks bank heist' },
})| Field | Type | Required | Description |
|---|---|---|---|
task_key | string | ✅ | Unique identifier. Used in required_tasks of upgrades and in all export calls |
label | string | ✅ | Display name shown in the gang panel |
description | string | — | Longer description shown in the NUI |
category | string | — | Free-form label used to group tasks in the panel |
is_repeatable | boolean | — | true allows the task to be completed more than once (each call increments times_completed). Default: false |
meta | table | — | Arbitrary data stored as JSON. Readable by hooks and GetGangTasks. Not used by the core system |
enabled | boolean | — | Set to false to hide the task from the panel without deleting it. Default: true |
If task_key already exists in the database, the row is updated (upsert). Safe to call on every boot.
Registering upgrades
AddOrganizationUpgrade(upgradeDef)
exports['origen_ilegalv2']:AddOrganizationUpgrade({
upgrade_key = 'upgrade_robbery_tier2',
label = 'Access: Medium Heists',
description = 'Enables access to medium-tier heist jobs.',
type = 'robbery_unlock',
required_tasks = { 'task_recon_north' }, -- all must be completed before unlocking
effect = {
feature = 'robberies:tier2', -- key your resource checks
min_cops = 4, -- optional extra data
},
})| Field | Type | Required | Description |
|---|---|---|---|
upgrade_key | string | ✅ | Unique identifier |
label | string | ✅ | Display name in the gang panel |
description | string | — | Longer description |
type | string | — | Free-form type label for NUI grouping (e.g. 'robbery_unlock', 'logistics', 'economy') |
required_tasks | table or string | — | Array of task_key values that must be completed. Also accepts a CSV string: 'task_a, task_b' |
effect | table | — | Arbitrary data. effect.feature is the key other resources query via IsGangFeatureUnlocked |
enabled | boolean | — | Default: true |
Multiple required tasks
required_tasks = {
'task_recon_north',
'task_supply_route',
'task_eliminate_rivals',
},All listed tasks must be completed before the upgrade can be unlocked. The NUI shows per-task completion status to the gang.
Completing tasks from your resource
Call this whenever a gang member finishes the corresponding in-game activity:
local function OnReconMissionComplete(source)
local gang = exports['origen_ilegalv2']:GetPlayerGangData(source)
if not gang then return end
local result = exports['origen_ilegalv2']:CompleteGangTask(
gang.id,
'task_recon_north',
{
completed_by = GetPlayerIdentifier(source), -- optional
progress = { duration_seconds = 320 }, -- optional meta, stored in DB
}
)
if result.ok then
print('Task completed. Times:', result.times_completed)
elseif result.error == 'already_completed' then
-- Non-repeatable task was already done — not an error, just a no-op
end
endReturn value
| Field | Type | Description |
|---|---|---|
ok | boolean | true on success |
error | string|nil | 'already_completed' / 'task_not_found' / 'invalid_gang' |
times_completed | number | How many times completed after this call |
Checking task completion
IsGangTaskCompleted(gangId, taskKey) → boolean
Synchronous in-memory check. Use this inside server callbacks before allowing an action:
lib.callback.register('myresource:server:canStartHeist', function(source)
local gang = exports['origen_ilegalv2']:GetPlayerGangData(source)
if not gang then return false end
return exports['origen_ilegalv2']:IsGangTaskCompleted(gang.id, 'task_recon_north')
end)Unlocking upgrades
Upgrades are normally unlocked by the gang leader or a member with the manage_org_upgrades rank permission from the NUI. You can also unlock them from code — useful for automatic rewards or server events:
local result = exports['origen_ilegalv2']:UnlockGangUpgrade(
gangId,
'upgrade_robbery_tier2',
{
unlocked_by = 'system_auto', -- optional, stored in DB
meta = { source = 'event' }, -- optional
}
)
if result.ok then
-- success
elseif result.error == 'already_unlocked' then
-- idempotent, not a real error
elseif result.error == 'missing_dependencies' then
for _, t in ipairs(result.missing_tasks or {}) do
print('Missing task:', t.task_key, t.label)
end
endReturn value
| Field | Type | Description |
|---|---|---|
ok | boolean | true on success |
error | string|nil | 'already_unlocked' / 'missing_dependencies' / 'upgrade_not_found' / 'invalid_gang' |
missing_tasks | { task_key, label }[] | Only present when error == 'missing_dependencies' |
Gating content with feature keys
This is the recommended pattern. Your resource does not know the upgrade_key — only the feature string. You can rename the upgrade later without touching other resources.
-- In origen_robberies or any external resource:
lib.callback.register('origen_robberies:server:getAvailable', function(source)
local gangId = exports['origen_ilegalv2']:GetPlayerGang(source)
if not gangId then return {} end
local hasTier2 = exports['origen_ilegalv2']:IsGangFeatureUnlocked(gangId, 'robberies:tier2')
local hasTier3 = exports['origen_ilegalv2']:IsGangFeatureUnlocked(gangId, 'robberies:tier3')
-- return only the heists the gang has unlocked
end)Reading full state
GetGangTasks(gangId) → table[]
Returns the full catalog enriched with per-gang progress:
local tasks = exports['origen_ilegalv2']:GetGangTasks(gang.id)
-- tasks[i] = {
-- task_key, label, description, category,
-- is_repeatable, meta,
-- completed, -- boolean
-- times_completed, -- number
-- completed_at, -- datetime or nil
-- last_completed_at -- datetime or nil
-- }GetGangUpgrades(gangId) → table[]
Returns the full upgrade catalog with unlock state and dependency resolution:
local upgrades = exports['origen_ilegalv2']:GetGangUpgrades(gang.id)
-- upgrades[i] = {
-- upgrade_key, label, description, type,
-- required_tasks, -- [{ task_key, label, completed }]
-- deps_satisfied, -- boolean: all required tasks done
-- unlocked, -- boolean
-- }Hooks
The hooks file lives at custom/server/org_panels_hooks.lua. It is loaded by the resource and executed inside origen_ilegalv2 — no cross-resource wiring needed.
Edit only the body of each function. Do not rename the functions or the OrgPanelsHooks table.
OrgPanelsHooks.onTaskCompleted(ctx)
Fires every time a task is marked completed for a gang, including repeatable ones.
function OrgPanelsHooks.onTaskCompleted(ctx)
-- ctx.gangId → gang ID
-- ctx.taskKey → task key
-- ctx.taskDef → full task definition (label, category, is_repeatable, meta)
-- ctx.timesCompleted → total completions after this call
-- ctx.identifier → citizenid of who completed it (nil if automatic)
-- ctx.gangInstance → Gang object (members, ranks, addMoney, addLog, etc.)
-- Reward all online members
if ctx.gangInstance then
for identifier, _ in pairs(ctx.gangInstance.members) do
local src = GetPlayerByIdentifier(identifier)
if src then
NotifyPlayer(src, 'Task completed: ' .. ctx.taskDef.label, 'success')
end
end
end
-- Reward the member who completed it
if ctx.identifier then
local src = GetPlayerByIdentifier(ctx.identifier)
if src then
AddPlayerMoney(src, 'black_money', 5000)
end
end
endOrgPanelsHooks.onUpgradeUnlocked(ctx)
Fires exactly once per (gangId, upgradeKey) pair — upgrades are permanent.
function OrgPanelsHooks.onUpgradeUnlocked(ctx)
-- ctx.gangId → gang ID
-- ctx.upgradeKey → upgrade key
-- ctx.upgradeDef → full definition (label, type, required_tasks, effect)
-- ctx.identifier → who unlocked it (nil if automatic)
-- ctx.gangInstance → Gang object
-- Notify all gang members
if ctx.gangInstance then
for identifier, _ in pairs(ctx.gangInstance.members) do
local src = GetPlayerByIdentifier(identifier)
if src then
TriggerClientEvent('myresource:client:upgradeUnlocked', src, {
upgradeKey = ctx.upgradeKey,
label = ctx.upgradeDef.label,
effect = ctx.upgradeDef.effect,
})
end
end
end
-- Branch by type
if ctx.upgradeDef.type == 'robbery_unlock' then
-- specific logic for heist unlocks
end
endOrgPanelsHooks.onUpgradeUnlockBlocked(ctx)
Fires when someone attempts to unlock an upgrade but required tasks are not yet complete. Useful for logging or giving extra feedback.
function OrgPanelsHooks.onUpgradeUnlockBlocked(ctx)
-- ctx.gangId → gang ID
-- ctx.upgradeKey → upgrade that was attempted
-- ctx.upgradeDef → full upgrade definition
-- ctx.missingTasks → [{ task_key, label }] list of incomplete tasks
-- ctx.identifier → who attempted (nil if automatic)
-- ctx.source → player source if the attempt came from the NUI
if ctx.source then
local missing = {}
for _, t in ipairs(ctx.missingTasks) do
missing[#missing + 1] = t.label
end
NotifyPlayer(ctx.source, 'Complete first: ' .. table.concat(missing, ', '), 'error')
end
endComplete integration example
Below is a condensed version of what a full integration from an external resource looks like:
-- 1. Register on boot
AddEventHandler('onResourceStart', function(resourceName)
if GetCurrentResourceName() ~= resourceName then return end
exports['origen_ilegalv2']:AddOrganizationTask({
task_key = 'mission_convoy_00',
label = 'Intercept Convoy',
description = 'Ambush and rob the supply convoy.',
category = 'mission',
is_repeatable = false,
})
exports['origen_ilegalv2']:AddOrganizationUpgrade({
upgrade_key = 'unlock_convoy_hard',
label = 'Hard Convoys',
description = 'Unlocks armored convoy missions.',
type = 'mission_unlock',
required_tasks = { 'mission_convoy_00' },
effect = { feature = 'missions:convoy_hard' },
})
end)
-- 2. Complete the task when mission ends
local function OnConvoySuccess(source)
local gang = exports['origen_ilegalv2']:GetPlayerGangData(source)
if not gang then return end
exports['origen_ilegalv2']:CompleteGangTask(gang.id, 'mission_convoy_00', {
completed_by = GetPlayerIdentifier(source),
})
end
-- 3. Gate hard missions behind the feature
lib.callback.register('my_missions:server:getMissions', function(source)
local gangId = exports['origen_ilegalv2']:GetPlayerGang(source)
local hasHard = gangId and exports['origen_ilegalv2']:IsGangFeatureUnlocked(gangId, 'missions:convoy_hard')
local missions = { { id = 'convoy_easy', label = 'Supply Convoy (Easy)' } }
if hasHard then
missions[#missions + 1] = { id = 'convoy_hard', label = 'Armored Convoy (Hard)' }
end
return missions
end)