Início › Documentação › Laboratórios personalizados · origen_ilegalv2
O origen_ilegalv2 inclui um Lab Process Runner genérico que trata de todo o código repetitivo de um laboratório de produção: pontos de interação, markers, barras de progresso, animações, validação no servidor, consumo de items e modificadores de melhorias. Para adicionar um novo laboratório de droga só precisas de escrever uma tabela de configuração e chamar uma função.
O runner tem dois lados que se espelham:
| Lado | Ficheiro | Responsabilidade |
|---|---|---|
| Cliente | client/lab_process_runner.lua |
lib.points, markers, HelpText, barras de progresso, animações, eventos do ciclo de vida |
| Servidor | server/lab_process_runner.lua |
Validação de callbacks, verificação de items, consumo/entrega de items, modificadores de melhorias, cooldowns |
Ambos os lados registam-se com a mesma chave de tipo de laboratório e comunicam através de callbacks do ox_lib com nome 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 } },
},
},
}A ordem de Stages é a ordem de execução obrigatória. O runner impõe-na: um jogador não pode saltar para a fase N+1 sem concluir primeiro a 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.LabTypesO runner não cria o laboratório na base de dados: tens de registar um tipo de laboratório para que o painel de administração possa criar instâncias dele:
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 precisas de três chaves de tradução (em 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 um passo da produção:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
id |
string | ✅ | Identificador único da fase. Usa-se nas chaves de Coords, Timers, Markers, Animations e Recipe |
label_key |
string | ✅ | Sufixo de tradução da barra de progresso: notify.{labType}_{label_key} |
help_key |
string | — | Sufixo de tradução do HelpText: notify.{labType}_help_{help_key}. Por omissão, label_key |
done_key |
string | — | Sufixo de tradução da notificação de fase concluída. Por omissão, {label_key}_done |
timer |
number | — | Temporizador da fase em ms (informativo: o runner usa Timers[id] em tempo de execução) |
RecipeIndexada por stage_id. Cada entrada suporta:
| Campo | Tipo | Descrição |
|---|---|---|
inputs |
{ item, amount }[] |
Consumidos quando a fase começa |
outputs |
{ item, amount }[] |
Entregues quando a fase termina |
requires |
{ item, amount }[] |
Validados mas NÃO consumidos (ferramentas, licenças) |
Só as fases que consomem ou produzem items precisam de uma entrada. As fases sem entrada em Recipe são transições «livres».
MarkersIndexada por stage_id. Todos os campos são opcionais: o runner recorre a um marker por omissão:
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. Dois modos:
Simples (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,
},Em rede (NetworkCreateSynchronisedScene), com 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 omissão, a primeira fase de qualquer laboratório baseado no runner exige que esteja desbloqueada uma melhoria do módulo style nessa instância do laboratório. Para desativar esta restrição (útil durante os testes ou para laboratórios de acesso livre):
Config.LabHeroin = {
RequiresUpgrade = false, -- skip the upgrade gate
-- ...
}MaxDistPerStageDistância máxima (em metros), global ou por fase, para a validação da interação no servidor:
-- Global: same distance for every stage
MaxDistPerStage = 4.0,
-- Per-stage override
MaxDistPerStage = {
mix = 3.0,
filter = 5.0,
bag = 3.0,
},Quando um laboratório tem melhorias do módulo style desbloqueadas (a partir de origen_gang_lab_upgrade_unlocks), o runner aplica automaticamente:
| Modificador | Efeito | Acumulação |
|---|---|---|
process_time_mult |
Multiplica todos os temporizadores das fases (mínimo 0.70, ou seja, uma redução máxima de 30 %) | Multiplicativa |
output_bonus_pct |
% de probabilidade de dar +1 unidade na saída da última fase (máximo 25 %) | O máximo de todas as variantes |
Calculam-se no servidor no início de cada ciclo e são devolvidos ao cliente como timers e modifiers na resposta do callback da primeira fase.
O laboratório de erva (Config.LabWeed) não usa o runner genérico. Tem um fluxo próprio:
| Passo | Ação | Items |
|---|---|---|
| 0 — Plantas | Apanha 14 plantas (reaparecem após PlantRespawnTime s) |
→ wetcannabis ×1 cada |
| 1 — Secagem | Coloca wetcannabis num dos 4 pontos de secagem independentes |
wetcannabis → drycannabis após DryWait ms |
| 2 — Moagem | Usa o moinho (requer weedgrinder, que não é consumido) |
drycannabis → grindedweed |
| 3 — Embalagem | Usa a máquina de embalar | grindedweed + bluntwrap → blunt |
Os passos 1–3 são independentes: não há uma sequência obrigatória entre eles. Só precisas do item de entrada correspondente.
O tempo dos pontos de secagem é controlado no servidor (marca temporal ready_at); o temporizador do cliente é apenas visual.