Inicio › Documentación › Tareas y mejoras · origen_ilegalv2
El Panel de organización (OrgPanels) te permite conectar cualquier recurso externo (misiones, atracos, robos) al tablero de progreso de la banda. Tu recurso registra tareas y mejoras; origen_ilegalv2 se encarga de la persistencia, la validación, la visualización en la NUI y los hooks.
| Concepto | Qué es |
|---|---|
| Tarea | Un hito que una banda puede completar. Registra completed y times_completed. Puede ser única o repetible. |
| Mejora | Un desbloqueo permanente para una banda. Requiere completar una o varias tareas antes de poder activarse. |
| Feature key | Un string guardado en effect.feature de una mejora. Los recursos externos usan esta clave (no la upgrade_key) para comprobar el acceso. Desacopla tu recurso de los nombres de las mejoras. |
Flujo de datos:
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 IsGangFeatureUnlockedOrgPanels carga sus catálogos cuando arranca el recurso. Registra siempre dentro de onResourceStart para garantizar que el módulo está listo:
AddEventHandler('onResourceStart', function(resourceName)
if GetCurrentResourceName() ~= resourceName then return end
exports['origen_ilegalv2']:AddOrganizationTask({ ... })
exports['origen_ilegalv2']:AddOrganizationUpgrade({ ... })
end)Llamar a estos exports a nivel superior (fuera de un evento) puede provocar una condición de carrera si origen_ilegalv2 aún no ha terminado de cargar.
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' },
})| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
task_key |
string | ✅ | Identificador único. Se usa en required_tasks de las mejoras y en todas las llamadas a exports |
label |
string | ✅ | Nombre visible en el panel de la banda |
description |
string | — | Descripción más larga, mostrada en la NUI |
category |
string | — | Etiqueta libre usada para agrupar las tareas en el panel |
is_repeatable |
boolean | — | Con true, la tarea puede completarse más de una vez (cada llamada incrementa times_completed). Por defecto: false |
meta |
table | — | Datos arbitrarios guardados como JSON. Legibles desde los hooks y GetGangTasks. El sistema central no los usa |
enabled |
boolean | — | Ponlo en false para ocultar la tarea del panel sin borrarla. Por defecto: true |
Si task_key ya existe en la base de datos, la fila se actualiza (upsert). Es seguro llamarla en cada arranque.
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
},
})| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
upgrade_key |
string | ✅ | Identificador único |
label |
string | ✅ | Nombre visible en el panel de la banda |
description |
string | — | Descripción más larga |
type |
string | — | Etiqueta de tipo libre para agrupar en la NUI (p. ej. 'robbery_unlock', 'logistics', 'economy') |
required_tasks |
table o string | — | Array de valores task_key que deben estar completados. También acepta un string CSV: 'task_a, task_b' |
effect |
table | — | Datos arbitrarios. effect.feature es la clave que otros recursos consultan mediante IsGangFeatureUnlocked |
enabled |
boolean | — | Por defecto: true |
required_tasks = {
'task_recon_north',
'task_supply_route',
'task_eliminate_rivals',
},Todas las tareas indicadas deben estar completadas antes de poder desbloquear la mejora. La NUI muestra a la banda el estado de cada tarea.
Llámalo cada vez que un miembro de la banda termine la actividad correspondiente dentro del juego:
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
end| Campo | Tipo | Descripción |
|---|---|---|
ok |
boolean | true si todo ha ido bien |
error |
string|nil | 'already_completed' / 'task_not_found' / 'invalid_gang' |
times_completed |
number | Cuántas veces se ha completado tras esta llamada |
IsGangTaskCompleted(gangId, taskKey) → booleanComprobación síncrona en memoria. Úsala dentro de callbacks del servidor antes de permitir una acción:
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)Normalmente las mejoras las desbloquea el líder de la banda, o un miembro con el permiso de rango manage_org_upgrades, desde la NUI. También puedes desbloquearlas desde código, algo útil para recompensas automáticas o eventos del servidor:
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
end| Campo | Tipo | Descripción |
|---|---|---|
ok |
boolean | true si todo ha ido bien |
error |
string|nil | 'already_unlocked' / 'missing_dependencies' / 'upgrade_not_found' / 'invalid_gang' |
missing_tasks |
{ task_key, label }[] |
Solo presente cuando error == 'missing_dependencies' |
Este es el patrón recomendado. Tu recurso no conoce la upgrade_key, solo el string feature. Más adelante puedes renombrar la mejora sin tocar otros recursos.
-- 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)GetGangTasks(gangId) → table[]Devuelve el catálogo completo enriquecido con el progreso de cada banda:
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[]Devuelve el catálogo completo de mejoras con el estado de desbloqueo y la resolución de dependencias:
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
-- }El fichero de hooks está en custom/server/org_panels_hooks.lua. El recurso lo carga y lo ejecuta dentro de origen_ilegalv2, sin necesidad de conexiones entre recursos.
Edita solo el cuerpo de cada función. No cambies el nombre de las funciones ni de la tabla OrgPanelsHooks.
OrgPanelsHooks.onTaskCompleted(ctx)Se dispara cada vez que una tarea se marca como completada para una banda, incluidas las repetibles.
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)Se dispara exactamente una vez por cada par (gangId, upgradeKey), ya que las mejoras son permanentes.
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)Se dispara cuando alguien intenta desbloquear una mejora pero las tareas necesarias aún no están completadas. Útil para registrar logs o dar información adicional.
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
endA continuación tienes una versión resumida de cómo queda una integración completa desde un recurso externo:
-- 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)