Inicio › Documentación › Laboratorios personalizados · origen_ilegalv2
origen_ilegalv2 incluye un Lab Process Runner genérico que se encarga de todo el código repetitivo de un laboratorio de producción: puntos de interacción, markers, barras de progreso, animaciones, validación en el servidor, consumo de items y modificadores de mejoras. Para añadir un nuevo laboratorio de droga solo necesitas escribir una tabla de configuración y llamar a una función.
El runner tiene dos lados que se reflejan entre sí:
| Lado | Fichero | Responsabilidad |
|---|---|---|
| Cliente | client/lab_process_runner.lua |
lib.points, markers, HelpText, barras de progreso, animaciones, eventos del ciclo de vida |
| Servidor | server/lab_process_runner.lua |
Validación de callbacks, comprobación de items, consumo/entrega de items, modificadores de mejoras, cooldowns |
Ambos lados se registran con la misma clave de tipo de laboratorio y se comunican mediante callbacks de ox_lib con nombre automático:
origen_gang:labs:{labType}:stage_{stageId}Config.LabHeroin = {
Enabled = true,
-- Ordered production stages
Stages = {
{ id = 'mix', label_key = 'mixing', help_key = 'mix', done_key = 'mix_done', timer = 10000 },
{ id = 'filter', label_key = 'filtering', help_key = 'filter', done_key = 'filter_done', timer = 8000 },
{ id = 'bag', label_key = 'bagging', help_key = 'bag', done_key = 'bag_done', timer = 6000 },
},
ProductionCooldown = 600000, -- 10 minutes between full cycles (ms)
Interactions = {
PointDistance = 40.0, -- lib.points render radius
DrawDistance = 8.0, -- marker draw distance
InteractDistance = 1.8, -- HelpText / [E] distance
},
-- World coords for each stage (interior coords inside the lab bucket)
Coords = {
mix = vector3(1006.09, -3200.59, -38.52),
filter = vector3(1007.89, -3201.17, -38.99),
bag = vector3(1014.25, -3194.93, -38.99),
},
-- Progress bar durations per stage (ms)
Timers = {
mix = 10000,
filter = 8000,
bag = 6000,
},
-- Items consumed/delivered per stage
-- inputs: consumed when the stage starts
-- outputs: delivered when the stage completes
-- requires: validated but NOT consumed (tools)
Recipe = {
mix = {
inputs = {
{ item = 'opium_raw', amount = 2 },
{ item = 'acetic_acid', amount = 1 },
},
},
bag = {
inputs = { { item = 'empty_bag', amount = 3 } },
outputs = { { item = 'heroin_bag', amount = 3 } },
},
},
}El orden de Stages es el orden de ejecución obligatorio. El runner lo impone: un jugador no puede saltar a la fase N+1 sin completar antes la fase N.
if not Config.LabHeroin or not Config.LabHeroin.Enabled then return end
LabProcessRunner.Register('heroin', Config.LabHeroin)
-- Optional: expose a reset export for external resources
exports('HeroinLabResetPlayerState', function(source)
LabProcessRunner.ResetPlayerState('heroin', source)
end)if not Config.LabHeroin or not Config.LabHeroin.Enabled then return end
LabProcessRunner.Register('heroin', Config.LabHeroin)Config.LabTypesEl runner no crea el laboratorio en la base de datos: debes registrar un tipo de laboratorio para que el panel de administración pueda crear instancias de él:
Config.LabTypes = {
-- ...existing types...
heroin = {
label = 'Heroin Lab',
interior = {
spawn = vector4(997.17, -3200.64, -37.39, 270.0),
laptop = vector4(1002.01, -3194.89, -38.99, 356.98),
}
},
}fxmanifest.luashared_scripts {
-- ...
'config/lab_heroin.lua',
}
server_scripts {
-- ...
'server/lab_heroin.lua',
}
client_scripts {
-- ...
'client/lab_heroin.lua',
}Para cada fase necesitas tres claves de traducción (en locales/translations/en.lua):
-- Progress bar label
['notify.heroin_mixing'] = 'Mixing...',
['notify.heroin_filtering'] = 'Filtering...',
['notify.heroin_bagging'] = 'Packaging...',
-- HelpText (shown when in range)
['notify.heroin_help_mix'] = 'Press [E] to mix chemicals',
['notify.heroin_help_filter'] = 'Press [E] to filter the mixture',
['notify.heroin_help_bag'] = 'Press [E] to bag the product',
-- Stage-done notifications
['notify.heroin_mix_done'] = 'Mixture ready',
['notify.heroin_filter_done'] = 'Filtration complete',
['notify.heroin_bag_done'] = 'Product packaged', -- last stage receives output amount as arg
-- Error messages used by the runner
['notify.heroin_not_in_lab'] = 'You are not inside the lab',
['notify.heroin_wrong_lab_type'] = 'Wrong lab type',
['notify.heroin_on_cooldown'] = 'Wait before starting another cycle',
['notify.heroin_already_in_progress'] = 'Production already in progress',
['notify.heroin_wrong_stage'] = 'Complete the previous step first',
['notify.heroin_requires_basic_upgrade'] = 'Upgrade the lab before processing',StagesCada entrada define un paso de la producción:
| Campo | Tipo | Obligatorio | Descripción |
|---|---|---|---|
id |
string | ✅ | Identificador único de la fase. Se usa en las claves de Coords, Timers, Markers, Animations y Recipe |
label_key |
string | ✅ | Sufijo de traducción para la barra de progreso: notify.{labType}_{label_key} |
help_key |
string | — | Sufijo de traducción para el HelpText: notify.{labType}_help_{help_key}. Por defecto, label_key |
done_key |
string | — | Sufijo de traducción para la notificación de fase completada. Por defecto, {label_key}_done |
timer |
number | — | Temporizador de la fase en ms (informativo: el runner usa Timers[id] en tiempo de ejecución) |
RecipeIndexada por stage_id. Cada entrada admite:
| Campo | Tipo | Descripción |
|---|---|---|
inputs |
{ item, amount }[] |
Se consumen al empezar la fase |
outputs |
{ item, amount }[] |
Se entregan al completar la fase |
requires |
{ item, amount }[] |
Se validan pero NO se consumen (herramientas, licencias) |
Solo las fases que consumen o producen items necesitan una entrada. Las fases sin entrada en Recipe son transiciones «libres».
MarkersIndexada por stage_id. Todos los campos son opcionales: el runner recurre a un marker por defecto:
Markers = {
mix = {
type = 22,
offset = { x = 0.0, y = 0.0, z = 0.0 },
scale = { x = 0.25, y = 0.25, z = 0.25 },
color = { r = 0, g = 0, b = 0, a = 180 },
bobUpAndDown = true,
faceCamera = true,
rotationOrder = 2,
rotate = false,
},
},AnimationsIndexada por stage_id. Dos modos:
Simple (TaskPlayAnim):
mix = {
networked = false,
dict = 'anim@amb@business@meth@meth_monitoring_cooking@',
clip = 'idle_a',
flag = 49,
blendIn = 4.0,
blendOut = -8.0,
freeze = false,
},En red (NetworkCreateSynchronisedScene), con props:
cook = {
networked = true,
dict = 'anim@amb@business@meth@meth_monitoring_cooking@cooking@',
clip = 'chemical_pour_short_cooker',
flag = 49,
blendIn = 1.5,
blendOut = -4.0,
freeze = true,
offset = vector3(4.79, 2.13, -0.41),
rotation = vector3(0.0, 0.0, 0.0),
objects = {
{ hash = 'bkr_prop_meth_sacid', clip = 'chemical_pour_short_sacid' },
{ hash = 'bkr_prop_meth_ammonia', clip = 'chemical_pour_short_ammonia' },
},
},RequiresUpgradePor defecto, la primera fase de cualquier laboratorio basado en el runner requiere tener desbloqueada una mejora del módulo style en esa instancia del laboratorio. Para desactivar esta restricción (útil mientras pruebas o para laboratorios de acceso libre):
Config.LabHeroin = {
RequiresUpgrade = false, -- skip the upgrade gate
-- ...
}MaxDistPerStageDistancia máxima (en metros), global o por fase, para la validación de la interacción en el servidor:
-- Global: same distance for every stage
MaxDistPerStage = 4.0,
-- Per-stage override
MaxDistPerStage = {
mix = 3.0,
filter = 5.0,
bag = 3.0,
},Cuando un laboratorio tiene mejoras del módulo style desbloqueadas (desde origen_gang_lab_upgrade_unlocks), el runner aplica automáticamente:
| Modificador | Efecto | Acumulación |
|---|---|---|
process_time_mult |
Multiplica todos los temporizadores de las fases (mínimo 0.70, es decir, un 30 % máximo de reducción) | Multiplicativa |
output_bonus_pct |
% de probabilidad de dar +1 unidad en la salida de la última fase (máximo 25 %) | El máximo de todas las variantes |
Se calculan en el servidor al inicio de cada ciclo y se devuelven al cliente como timers y modifiers en la respuesta del callback de la primera fase.
El laboratorio de marihuana (Config.LabWeed) no usa el runner genérico. Tiene un flujo propio:
| Paso | Acción | Items |
|---|---|---|
| 0 — Plantas | Recoge 14 plantas (reaparecen tras PlantRespawnTime s) |
→ wetcannabis ×1 cada una |
| 1 — Secado | Coloca wetcannabis en uno de los 4 puntos de secado independientes |
wetcannabis → drycannabis tras DryWait ms |
| 2 — Molido | Usa el molinillo (requiere weedgrinder, que no se consume) |
drycannabis → grindedweed |
| 3 — Envasado | Usa la envasadora | grindedweed + bluntwrap → blunt |
Los pasos 1–3 son independientes: no hay una secuencia obligatoria entre ellos. Solo necesitas el item de entrada correspondiente.
El tiempo de los puntos de secado se controla en el servidor (marca de tiempo ready_at); el temporizador del cliente es solo visual.