Accueil › Documentation › Laboratoires personnalisés · origen_ilegalv2
origen_ilegalv2 fournit un Lab Process Runner générique qui gère tout le code répétitif d’un laboratoire de production : points d’interaction, markers, barres de progression, animations, validation côté serveur, consommation d’items et modificateurs d’améliorations. Pour ajouter un nouveau laboratoire de drogue, il te suffit d’écrire une table de configuration et d’appeler une fonction.
Le runner a deux côtés qui se répondent :
| Côté | Fichier | Responsabilité |
|---|---|---|
| Client | client/lab_process_runner.lua |
lib.points, markers, HelpText, barres de progression, animations, événements du cycle de vie |
| Serveur | server/lab_process_runner.lua |
Validation des callbacks, vérification des items, consommation/remise des items, modificateurs d’améliorations, cooldowns |
Les deux côtés s’enregistrent sous la même clé de type de laboratoire et communiquent via des callbacks ox_lib nommés automatiquement :
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 } },
},
},
}L’ordre de Stages est l’ordre d’exécution obligatoire. Le runner l’impose : un joueur ne peut pas passer à l’étape N+1 sans avoir terminé l’étape 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.LabTypesLe runner ne crée pas le laboratoire dans la base de données : tu dois enregistrer un type de laboratoire pour que le panneau d’administration puisse en créer des instances :
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',
}Pour chaque étape, il te faut trois clés de traduction (dans 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',StagesChaque entrée définit une étape de production :
| Champ | Type | Obligatoire | Description |
|---|---|---|---|
id |
string | ✅ | Identifiant unique de l’étape. Utilisé dans les clés de Coords, Timers, Markers, Animations et Recipe |
label_key |
string | ✅ | Suffixe de traduction pour la barre de progression : notify.{labType}_{label_key} |
help_key |
string | — | Suffixe de traduction pour le HelpText : notify.{labType}_help_{help_key}. Par défaut, label_key |
done_key |
string | — | Suffixe de traduction pour la notification de fin d’étape. Par défaut, {label_key}_done |
timer |
number | — | Minuteur de l’étape en ms (à titre informatif : le runner utilise Timers[id] à l’exécution) |
RecipeIndexée par stage_id. Chaque entrée accepte :
| Champ | Type | Description |
|---|---|---|
inputs |
{ item, amount }[] |
Consommés au début de l’étape |
outputs |
{ item, amount }[] |
Remis à la fin de l’étape |
requires |
{ item, amount }[] |
Vérifiés mais NON consommés (outils, licences) |
Seules les étapes qui consomment ou produisent des items ont besoin d’une entrée. Les étapes sans entrée dans Recipe sont des transitions « libres ».
MarkersIndexée par stage_id. Tous les champs sont facultatifs : le runner se rabat sur un marker par défaut :
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,
},
},AnimationsIndexée par stage_id. Deux modes :
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 réseau (NetworkCreateSynchronisedScene), avec des 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' },
},
},RequiresUpgradePar défaut, la première étape de tout laboratoire basé sur le runner exige qu’une amélioration du module style soit débloquée dans cette instance du laboratoire. Pour désactiver cette restriction (utile pendant les tests ou pour des laboratoires en accès libre) :
Config.LabHeroin = {
RequiresUpgrade = false, -- skip the upgrade gate
-- ...
}MaxDistPerStageDistance maximale (en mètres), globale ou par étape, pour la validation de l’interaction côté serveur :
-- Global: same distance for every stage
MaxDistPerStage = 4.0,
-- Per-stage override
MaxDistPerStage = {
mix = 3.0,
filter = 5.0,
bag = 3.0,
},Quand un laboratoire a des améliorations du module style débloquées (depuis origen_gang_lab_upgrade_unlocks), le runner applique automatiquement :
| Modificateur | Effet | Cumul |
|---|---|---|
process_time_mult |
Multiplie tous les minuteurs des étapes (minimum 0.70, soit 30 % de réduction au maximum) | Multiplicatif |
output_bonus_pct |
% de chances de donner +1 unité sur la sortie de la dernière étape (maximum 25 %) | Le maximum de toutes les variantes |
Ils sont calculés côté serveur au début de chaque cycle et renvoyés au client sous la forme timers et modifiers dans la réponse du callback de la première étape.
Le laboratoire de weed (Config.LabWeed) n’utilise pas le runner générique. Il a son propre flux :
| Étape | Action | Items |
|---|---|---|
| 0 — Plantes | Récolte 14 plantes (elles repoussent après PlantRespawnTime s) |
→ wetcannabis ×1 chacune |
| 1 — Séchage | Place wetcannabis sur l’un des 4 points de séchage indépendants |
wetcannabis → drycannabis après DryWait ms |
| 2 — Broyage | Utilise le broyeur (nécessite weedgrinder, qui n’est pas consommé) |
drycannabis → grindedweed |
| 3 — Ensachage | Utilise la machine d’ensachage | grindedweed + bluntwrap → blunt |
Les étapes 1 à 3 sont indépendantes : il n’y a pas de séquence imposée entre elles. Il te suffit de l’item d’entrée correspondant.
Le temps des points de séchage est contrôlé côté serveur (horodatage ready_at) ; le minuteur du client est purement visuel.