Configuration

12 min readUpdated Yesterday

Overview

  • shared/config.lua: Core settings, features, the recycler definition, recipes, upgrades, durability, fuel, fixed stations
  • shared/data/item_models.lua: 3D prop mappings for the sinking animation
  • server/config_server.lua: Discord webhooks and rate limiting

The recipes and upgrades shipped in shared/config.lua are working examples. Replace them with the items your server uses.

Most values can also be changed in game from the staff panel. See Staff Panel below.

General

shared/config.lua
1Config.Debug = false
2Config.Language = 'en'
3Config.MaxRecyclersPerPlayer = 1
4Config.DuiResolution = { width = 1920, height = 1080 }
5Config.InteractionDistance = 3.0
6Config.PlacementDistance = 5.0
  • Debug: Console output for troubleshooting
  • Language: Locale code. See the locales/ folder.
  • MaxRecyclersPerPlayer: Maximum player-placed recyclers per character
  • DuiResolution: Render resolution of the screen on the prop. 960x540 is lighter, 1920x1080 is sharper
  • InteractionDistance: Range in meters to interact with a placed machine
  • PlacementDistance: Maximum distance from the player a new machine can be placed

Staff Panel

shared/config.lua
1Config.Admin = {
2    command = 'recyclers',
3    acePermission = nil,
4    minimizeKey = 'F9',
5}
  • command: Chat command that opens the panel. false removes it
  • acePermission: ACE checked before opening, such as 'ml_recycler.admin'. nil uses the framework admin permission
  • minimizeKey: Key that hides the open panel and brings it back. F1 to F12, or one letter or digit

The panel has six tabs: Map, List, Stations, Recipes, Upgrades and Settings. Changes made in Stations, Recipes, Upgrades and Settings are stored in the ml_recycler_config table and apply without a restart. Each of those tabs has a Reset to config button that drops the panel values.

Panel values override the file

A value saved in the panel replaces the same value from shared/config.lua until it is reset from the panel. Editing the file does not change a value the panel already overrides.

File only

Debug, language, theme, screen resolution, camera, sounds, sinking items, rarity colors, prop and screen distances, the base prop list of PlacementRestriction, the ml_fuel jerrycan settings and the zombie resource name are read only from shared/config.lua.

The panel refuses changes that would cost players items: deleting a recipe while that item is processing, deleting an upgrade that is installed or required by another upgrade, and any change to slots or upgrades that would hide slots holding items.

Placement Rules

shared/config.lua
1Config.PlacementRestriction = {
2    enabled = false,
3    radius = 25.0,
4    requiredProps = {},
5}
6
7Config.PickupCooldown = {
8    enabled = true,
9    seconds = 300,
10}
  • PlacementRestriction.enabled: When true, a recycler can only be placed within radius meters of one of the prop model hashes in requiredProps. The placement preview checks it on the client
  • PickupCooldown.enabled: locks pickup for seconds after placing

Feature Toggles

shared/config.lua
1Config.Features = {
2    bulkRecycling = true,
3    queueSystem = true,
4    fuelSystem = true,
5    breakdownAnimations = true,
6    ambientSounds = true,
7    propSinking = true,
8    lootReveal = true,
9    upgradeSystem = true,
10    durabilitySystem = true,
11}
  • bulkRecycling: Insert a stack of the same item in one action
  • queueSystem: Items queue when all slots are busy and auto-promote as slots free up
  • fuelSystem: Fuel is consumed while processing. When false, the fuel bar is hidden and refuel/consumption are disabled
  • breakdownAnimations: Particle effects during processing
  • ambientSounds: 3D loop sound through xsound while running
  • propSinking: Inserted item models sink into the slot as they process
  • lootReveal: after a collection, show the loot reveal with the collector's name to nearby players
  • upgradeSystem: Allow upgrade installation
  • durabilitySystem: Degradation, slowdown, malfunction and repair

Sound, Camera and Sinking Items

shared/config.lua
1Config.SoundDefaults = {
2    file = './sounds/recycler_loop.ogg',
3    volume = 0.6,
4    maxDistance = 30.0,
5}
6
7Config.Camera = {
8    offsetX = 0.15,
9    offsetY = -1.02,
10    offsetZ = 0.60,
11    lookZ = -0.64,
12    fov = 51.0,
13}
14
15Config.PropSinking = {
16    startZ = 0.45,
17    endZ = -0.1,
18    propScale = 0.8,
19    rotationSpeed = 8.0,
20    fallbackProp = 'hei_prop_heist_box',
21    slotOffsets = {
22        [1] = { x = -0.10, y = 0.10 },
23        [2] = { x = 0.10, y = 0.10 },
24        [3] = { x = -0.10, y = -0.10 },
25        [4] = { x = 0.10, y = -0.10 },
26        [5] = { x = 0.00, y = 0.15 },
27        [6] = { x = 0.00, y = -0.15 },
28    },
29    itemModels = {},
30}
  • SoundDefaults: loop file, volume and hearing distance of the ambient sound
  • Camera: position of the camera that zooms on the screen, relative to the machine, and its field of view
  • startZ, endZ: start and end height of an item sinking into a slot
  • propScale, rotationSpeed: size and spin of the sinking item
  • fallbackProp: model used when an item has no model in shared/data/item_models.lua
  • slotOffsets: position of each slot on the machine
  • itemModels: leave empty. Item models are set in shared/data/item_models.lua

Loot Collection

shared/config.lua
1Config.Queue = {
2    AllowSteal = true,
3}
  • AllowSteal: true lets any player who can use the recycler collect its loot. Set false to reserve each slot's results for the character who deposited the items.

This setting applies to player-placed recyclers and fixed stations. Reservations survive reconnects and resource restarts.

The screen shows a lock on ready loot reserved for another character. Machine access permissions still apply.

Deposits without a recorded owner stay public.

The Recycler

shared/config.lua
1Config.Recycler = {
2    label = _L('recycler_label'),
3    item = 'recycler',
4    baseSlots = 3,
5    maxSlots = 6,
6    processSpeedMultiplier = 1.0,
7    spawnDistance = 50.0,
8    screenDistance = 10.0,
9}
  • label: Name shown in the UI and notifications
  • item: Inventory item used to place the recycler
  • baseSlots: Slots available before upgrades (each slot processes one item)
  • maxSlots: Absolute slot cap. Base plus a slot-expansion upgrade can never exceed this
  • processSpeedMultiplier: Base speed. 1.0 is normal, below 1.0 is faster
  • spawnDistance: Distance at which the prop streams in for nearby players
  • screenDistance: Distance at which the screen becomes visible
Recipe tier

Recipes can gate outputs and bonuses behind a recycler tier. Every machine runs at tier 1 by default. An upgrade with effects = { tier = 2 } raises the machine it is installed on, and tier = 2 on Config.Recycler raises every machine. The highest value applies.

Fuel

Read only when Config.Features.fuelSystem = true.

shared/config.lua
1Config.Fuel = {
2    capacity = 100,
3    perMinute = 2,
4    items = {
5        ['industrial_battery'] = 25,
6        ['fuel_canister']      = 50,
7    },
8    mlFuel = {
9        enabled = true,
10        hintItems = {},
11        acceptedFuelTypes = { 'diesel' },
12        typeLabels = { ['diesel'] = 'Diesel', ['fuel'] = 'Gasoline' },
13        litersToUnits = 5,
14    },
15}
  • capacity: Tank size in units. The extra_tank upgrade raises it
  • perMinute: Units burned per minute of active processing
  • items: One-shot fuel items. ['item'] = units. The item is removed on use
  • mlFuel.enabled: Accept reusable jerrycans from ml_fuel. Any item carrying metadata.fuelAmount and metadata.fuelType is treated as a jerrycan: it is kept in the inventory and only emptied, never consumed
  • mlFuel.acceptedFuelTypes: Fuel types the machine accepts. Must match keys in ml_fuel Config.FuelTypes
  • mlFuel.typeLabels: UI label per fuel type
  • mlFuel.litersToUnits: How many recycler fuel units one liter of ml_fuel becomes

Recipes

The key is the input item. Each recipe defines the time and the possible outputs.

shared/config.lua
1Config.RecycleRecipes = {
2    ['broken_phone'] = {
3        baseTime = 20,
4        outputs = {
5            { item = 'glass',     min = 1, max = 2, chance = 100 },
6            { item = 'copper',    min = 1, max = 2, chance = 60 },
7            { item = 'microchip', min = 1, max = 1, chance = 15, tierRequired = 3 },
8        },
9        tierBonuses = {
10            [2] = { chanceBoost = 10, quantityBoost = 1 },
11            [3] = { chanceBoost = 25, quantityBoost = 2 },
12        },
13    },
14}
  • baseTime: processing time in seconds. Effective time is baseTime * count * processSpeedMultiplier with bulkRecycling, then upgrade and durability modifiers
  • outputs: Possible results. Each has item, min/max quantity, and chance (0-100). Add tierRequired = 2 to gate an output behind a recycler tier, and rarity with a key of Config.Rarity to color it (default 'common')
  • tierBonuses: Applied automatically by recycler tier. chanceBoost adds chance points to every output, quantityBoost adds to each output max

Rarity

Colors and labels for the loot preview.

shared/config.lua
1Config.Rarity = {
2    common    = { color = '#9ca3af', label = _L('rarity_common') },
3    uncommon  = { color = '#22c55e', label = _L('rarity_uncommon') },
4    rare      = { color = '#3b82f6', label = _L('rarity_rare') },
5    epic      = { color = '#a855f7', label = _L('rarity_epic') },
6    legendary = { color = '#f59e0b', label = _L('rarity_legendary') },
7}

Upgrades

shared/config.lua
1Config.MaxUpgradesPerRecycler = 3
2
3Config.Upgrades = {
4    ['turbo_motor']  = { label = _L('upgrade_turbo_motor'),  item = 'turbo_motor',  icon = 'turbo_motor',  effects = { processSpeedBonus = -0.20 } },
5    ['extra_tank']   = { label = _L('upgrade_extra_tank'),   item = 'extra_tank',   icon = 'extra_tank',   effects = { fuelCapacityBonus = 0.50 } },
6    ['silencer_kit'] = { label = _L('upgrade_silencer_kit'), item = 'silencer_kit', icon = 'silencer_kit', effects = { soundDistanceMultiplier = 0.40 } },
7    ['solar_panel']  = { label = _L('upgrade_solar_panel'),  item = 'solar_panel',  icon = 'solar_panel',  effects = { fuelRegenPerMinute = 1 } },
8}
  • MaxUpgradesPerRecycler: Upgrade slots per machine
  • effects.processSpeedBonus: Multiplier change to processing time. -0.20 is 20% faster
  • effects.fuelCapacityBonus: Fraction added to tank size. 0.50 is +50%
  • effects.soundDistanceMultiplier: multiplies the zombie noise radius. 0.40 makes it 60% smaller
  • effects.fuelRegenPerMinute: Passive fuel units generated per minute
  • effects.slotBonus: Extra slots, capped at maxSlots
  • effects.tier: Machine tier while installed. Unlocks outputs with tierRequired and the matching tierBonuses
  • requires: Upgrade key, or list of keys, that must be installed first. Example: requires = 'slot_expansion_1'. Removal is refused while another installed upgrade requires it

Upgrades are consumed on install and returned to the player on removal, carrying their state. To make a slot-expansion module, add an upgrade with effects = { slotBonus = N }, give it an item, and the machine unlocks N extra slots up to maxSlots. The upgrade_slot_expansion label is already in the locale files.

Optional wear

Upgrades are permanent by default. Add a wear table to an upgrade definition to give it a durability curve that drains on use and auto-removes the module at zero:

lua
1['turbo_motor'] = {
2    label = _L('upgrade_turbo_motor'), item = 'turbo_motor', icon = 'turbo_motor',
3    effects = { processSpeedBonus = -0.20 },
4    wear = { enabled = true, startDurability = 100, degradePerProcess = 1 },
5}

Durability

shared/config.lua
1Config.Durability = {
2    degradePerProcess = 2,
3    slowdownThreshold = 50,
4    slowdownMultiplier = 1.20,
5    malfunctionThreshold = 25,
6    malfunctionChance = 15,
7    malfunctionCooldown = 600,
8    brokenThreshold = 0,
9}
  • degradePerProcess: Durability points lost per completed slot
  • slowdownThreshold: Below this percent, processing runs at slowdownMultiplier time
  • malfunctionThreshold: at or below this percent, inserting items can fail with a jam
  • malfunctionChance: Malfunction probability (0-100)
  • malfunctionCooldown: seconds after a malfunction during which the machine cannot jam again
  • brokenThreshold: At this value the machine stops until repaired

Repair Items

shared/config.lua
1Config.RepairItems = {
2    ['repair_kit'] = { restore = 100, label = _L('repair_item_repair_kit') },
3    ['patch_kit']  = { restore = 50,  label = _L('repair_item_patch_kit') },
4    ['lubricant']  = { restore = 25,  label = _L('repair_item_lubricant') },
5}
  • restore: Durability points the item restores on use

Zombie Noise

shared/config.lua
1Config.ZombieNoise = {
2    enabled = false,
3    resourceName = 'ml_zombies',
4    defaultRadius = 50.0,
5    defaultDuration = 0,
6}
  • enabled: A running machine attracts zombies within defaultRadius
  • resourceName: Zombie resource to signal. The hook only fires if it is started
  • defaultDuration: 0 keeps the noise active until processing stops

The silencer_kit upgrade multiplies defaultRadius down through soundDistanceMultiplier.

Fixed Stations

Permanent recyclers you place in the world. Players cannot pick them up.

shared/config.lua
1Config.FixedStations = {
2    {
3        id = 'unique_station_id',
4        coords = vec3(0.0, 0.0, 0.0),
5        heading = 0.0,
6        infiniteFuel = false,
7        infiniteDurability = false,
8        permanentUpgrades = { 'turbo_motor', 'silencer_kit' },
9    },
10}
  • id: Unique station id, stored in the database
  • infiniteFuel: When true, this station never runs out of fuel
  • infiniteDurability: When true, this station never wears down: no slowdown, no malfunctions, no repair items needed. Player-placed recyclers keep degrading normally
  • permanentUpgrades: Upgrades installed for good. Players cannot remove them and the wear system ignores them

Stations can also be placed, moved, renamed and removed from the Stations tab of the staff panel. Removing a station that comes from Config.FixedStations hides it permanently without editing the file; place a new station from the panel to use the spot again.

Permissions

shared/config.lua
1Config.DefaultPermissions = {
2    onlyOwnerCanUse = false,
3    onlyOwnerCanRemove = true,
4    onlyOwnerCanRefuel = false,
5    onlyOwnerCanUpgrade = false,
6}
  • Applies to every player-placed recycler. Fixed stations ignore these permissions: anyone can use, refuel and upgrade them, and nobody can pick them up

UI Theme

shared/config.lua
1Config.UITheme = {
2    preset = nil,
3    colors = {
4        primary = nil,
5        accent = nil,
6        background = nil,
7        surface = nil,
8        text = nil,
9    },
10}
  • preset: theme preset of the panels. nil follows the shared theme set in ml_bridge
  • colors: CSS hex values for single colors. Any nil value follows the shared theme

Discord Webhooks

server/config_server.lua
1ServerConfig.DiscordWebhook = ''
2ServerConfig.RecyclingWebhook = ''
3ServerConfig.PlacementWebhook = ''
4ServerConfig.ErrorWebhook = ''
5ServerConfig.ExploitsWebhook = ''
6ServerConfig.AdminWebhook = ''
7ServerConfig.RateLimitCooldown = 1000
  • DiscordWebhook: Fallback webhook used by any channel left empty
  • RecyclingWebhook: Recycling started, collected, refueled, repaired, upgrade install/remove
  • PlacementWebhook: Recycler placed and picked up
  • ErrorWebhook: Database failures and rollback events
  • ExploitsWebhook: Rate-limit spam, payload type mismatches, distance and origin checks
  • AdminWebhook: Staff panel actions and config changes made in the panel
  • RateLimitCooldown: Minimum milliseconds between actions per player

Leave a channel empty to use DiscordWebhook. Leave all of them empty to disable logging.