Skip to content

Full Config — Items ​

The complete default configs/shared/items.lua for Bombs & Breakable Walls — items, planting, the bomb builder, and workbenches.

TIP

For a detailed explanation of each option, see the Items section of the Configuration page.

Download items.lua

lua
return {
    -- Inventory item names. Both are registered as usable through the bridge
    -- (bridge/server/inventory.lua), so they work on ox_inventory, qb-inventory, qs, ESX, ...
    -- On ox_inventory the item definition must point at this resource's export, which the bridge
    -- names after the item: server = { export = 'sd-bombs.use<Item>' } with the item's first
    -- letter upper-cased (sd_bomb -> useSd_bomb). Rename an item here and you rename its export.
    Bomb = 'sd_bomb', -- use: aim at where it goes - floor, wall or ceiling - and arm it (consumed once planted)
    -- The image a bomb of each compact size shows in the inventory (metadata.image: a .png in your inventory's image folder,
    -- blender/icons/sd_bomb_<size>.png). The Extra Large keeps sd_bomb's own. false = every bomb shows the item's image.
    BombImages = { small = 'sd_bomb_small', medium = 'sd_bomb_medium', large = 'sd_bomb_large' },
    Manual = 'sd_bomb_manual', -- use: read the Expert's field manual (never consumed)

    -- The bomb builder. Using a casing kit opens a bench: the empty casing in 3D, and every module
    -- item the player carries in a parts bin beside it. Any module goes into any bay of either
    -- face (the parcel supplies a loose countdown display; fit and fasten it on the front).
    -- "Assemble" takes the kit and the parts used and hands back a Bomb item that carries the
    -- build - plant it like any other bomb, and THAT bomb is what the Defuser gets. A Bomb item
    -- without a build (given by an admin, bought in a shop) is still a random one.
    -- Needs an inventory with item metadata (ox_inventory, qb-inventory, qs, ...).
    Builder = {
        Kit = 'sd_bomb_kit', -- the casing. At a workbench in the world it goes from the kit bag onto the mat (taken then); used as an item it opens the builder's page where there is no bench
        MaxModules = 11, -- bays a build may fill, 1-11 (the twelfth is the timer's)
        -- Step 1 of every build: the casing is packed with explosive. One item = one block = one
        -- point of YIELD, and the yield decides how hard the bomb hits and how big a hole it blows
        -- in a breakable wall (configs/shared/breaking.lua > Bomb.Yield).
        Charge = {
            Item = 'sd_explosive',
            Min = 1, -- blocks a bomb needs at least
            Max = 6, -- cells in the casing (the open casing has six: do not raise this past 6)
            Model = 'sd_bomb_charge', -- the block (blender/build_bench.py)
            OpenCasing = 'sd_bomb_casing_open', -- the casing with its front off, to pack them into (its firing unit sits astride the middle divider)
            Front = 'sd_bomb_casing_front', -- the casing's front by itself: what lies on the bench while the case is open
            Cap = 'sd_bomb_cap', -- the detonator cap crimped into a packed block; its lead runs to a terminal of the firing unit
            Probe = 'sd_bomb_probe', -- the multimeter's red probe: what follows the cursor while the meter lies before an open case
            Pin = 'sd_bomb_pin', -- the arming pin (Steps.Pin) and its red 'remove before calibration' flag, in its port on the casing's end
        },
        -- What a build USES UP, besides its parts. Each is a box: an item, dragged off the kit bag onto the bench, and from
        -- then on the bench's own - whoever works there next finds what is left in it. (A box goes back into the bag only
        -- as it came: full, and not tipped out.) false = that supply is not asked for (screws and wire are just there).
        --   Screws    a box of machine screws, Count to the box. Put it down on the bench, pick it up again and HOLD
        --             RIGHT-CLICK: it tips over in the hand, its lid comes off and the screws pour out of it - the further it
        --             is tipped, the faster - falling onto the bench and piling up where they land. Every screw driven at this
        --             bench comes off a heap - three to a module plate, two to a charge bracket - and every screw backed out
        --             goes back on one. (The front's four bolts are the casing's own.) OpenModel = the box with its lid off.
        --   Wire      a sealed carton of hook-up wire: cut its three tape seams, fold back the flaps, then drag each spool
        --             onto any free rack slot. Six channel colours, Lengths of each. The open carton stays movable. With
        --             the pliers (or the strippers) in hand, click a spool: a length of THAT colour is in the jaws. A cap's
        --             lead has to be the colour of the channel the firing plan gives its cell - a lead of another colour
        --             fails the unit's TEST like one under the wrong terminal. A module's lead takes a length of any colour.
        --             Spools = where they stand in the carton, in its own space (blender/build_bench.py prints these).
        --   Bracket   the steel saddle a block of explosive is strapped into. PerKit of them come in every kit's carton and
        --             lie on the bench once the casing is lifted out; one goes into a cell, its two screws are driven, and
        --             only then does that cell take a block. They are an item too (Item): bought by the box, dragged off the
        --             kit bag like any part, and back into it. Give = how many /bomb_toolkit parts hands out. false = bare cells.
        Supplies = {
            Handbook = { Item = 'sd_assembly_book', Model = 'sd_bomb_handbook', OpenModel = 'sd_bomb_handbook_open', Give = 1 },
            Screws = { Item = 'sd_screw_box', Count = 48, Model = 'sd_bomb_screwbox', OpenModel = 'sd_bomb_screwbox_open',
                FillModels = { 'sd_bomb_screwbox_open_0', 'sd_bomb_screwbox_open_1', 'sd_bomb_screwbox_open_2', 'sd_bomb_screwbox_open_3', 'sd_bomb_screwbox_open' } },
            Wire = { Item = 'sd_wire_box', Lengths = 4, Model = 'sd_bomb_wirebox',
                Package = { Size = vec3(0.244, 0.176, 0.060), BodyModel = 'sd_bomb_wirebox_body', FlapModel = 'sd_bomb_wirebox_flap_%s' },
                Spools = { vec3(-0.076, 0.04, 0.024), vec3(0.0, 0.04, 0.024), vec3(0.076, 0.04, 0.024), vec3(-0.076, -0.04, 0.024), vec3(0.0, -0.04, 0.024), vec3(0.076, -0.04, 0.024) } },
            Bracket = { Item = 'sd_charge_bracket', Model = 'sd_bomb_bracket', PerKit = 6, Give = 6 },
            Straps = { Item = 'sd_retaining_strap', Model = 'sd_bomb_strap_slack', Give = 12 },
        },
        -- The work a build takes beyond packing and screwing - each needs its tool on the bench (Bench.Tools).
        -- false = that step is not asked for (and its tool is never needed).
        --   Cut    a kit comes taped shut: slit it open with the knife before anything goes in
        --   Crimp  every packed block gets a detonator cap crimped in (pliers: press and hold on the block, let go in the green)
        --          and the cap's lead clamped under a terminal of the casing's firing unit, before the front goes on
        --   Bolts  the front's four corner fixings are hex bolts: the hex key, not the driver
        --   Wire   every fitted module is wired in (wire strippers) once it is screwed down
        --   Route  ...by hand: its lead is run along the gutters between the bays, clipped down at every corner, to the
        --          harness socket on the timer (a gutter holds two leads: a full face takes planning). false = one click wires it
        --   Test   the finished circuit is proved with the multimeter; any change after that, and it is tested again
        --   Strap  every block packed into a bracket is strapped down: a steel band over it, pumped tight with the band
        --          tensioner (click to pump: stop in the green, one too many and the band snaps) and sealed - before it is wired
        --   Prep   every length of wire is prepared before it goes on (Supplies.Wire only): click a spool and the length is
        --          laid out along the mat's rule - snip it with the side cutters while its end is in the green - then the
        --          strippers come down and take the insulation off its end (click while the sweep is on the marked notch).
        --          How near the middle of both greens the hand was goes into the joint, like the crimp and the clamp
        --   Sign   once the circuit is proved, QC signs the work order off (its big button) - only then is it assembled
        --   Edge   the builder fits the edgework: a kit's carton brings a bag of edge fittings dealt from its lot (batteries,
        --          indicators, port plates) and its serial plate; with the case closed they clip into the five slots of its
        --          top and bottom edges (any of them, or none), the serial plate into its own. The bomb's edgework - what the
        --          Defuser's rules read - is exactly what was fitted. false = the lot deals it, as for a bomb nobody built
        --   Pin    a kit's firing unit ships with an arming pin in it, in a port on the casing's end (click it to pull it, click
        --          again to push it back). Signed off with it still in, the bomb is a DUD: it plants and its clock runs, and at
        --          zero (or on the last strike) it only clicks - the pin still shows on it. Pulled while the case is open, the
        --          unit is live: a self-test with a fault fires that cell's cap (the block is lost, the builder is hurt)
        Steps = { Cut = true, Crimp = true, Bolts = true, Wire = true, Route = true, Test = true, Strap = true, Prep = true, Sign = true, Edge = true, Pin = false }, -- (Pin: no arming pin - taken off the XL)
        -- /bomb_toolkit [what] [player]: hands out what a build takes (admin). what = 'tools' (one of every bench tool,
        -- the default), 'parts' (a kit, six blocks, one of every module, a box of screws and a carton of wire) or 'all'. false = no command.
        GiveCommand = 'bomb_toolkit',
        Strikes = 3, -- what the work order starts on; the builder picks 1-5
        Timer = { Min = 60, Max = 900, Step = 30, Default = 300 }, -- seconds the builder may put on the clock

        -- Workbenches: where bombs are built. Walk up to one (with a casing kit on you) and the
        -- camera comes down onto the bench top, where the casing and your modules are laid out.
        Bench = {
            -- The bench bombs are built on: a prop of our own (stream/sd_bomb_bench.ydr, built by
            -- blender/build_bench.py) with the cutting mat and the parts tray modelled into its top.
            Model = 'sd_bomb_bench',
            -- true = building happens IN THE WORLD, on that bench: the camera comes down over it, the
            -- parts come out of the kit bag at your feet and are put down on it as real props, moved with a
            -- cursor (client/bomb/assembly.lua) - and what is put down STAYS on that bench, for everyone, across
            -- restarts (server/bomb/workspace.lua): the item is taken when the part is put down, not at the end.
            -- Needs the bench model above - its Layout says where the mat and the tray are. false = the builder is a 3D page instead (any bench prop).
            World = true,
            BakedHarness = true, -- finished leads, straps and terminal marks are engine-rendered props; live work stays animated
            -- Where things are on the bench, in ITS space (metres; +Y = away from whoever stands at
            -- it). blender/build_bench.py prints these: re-export the bench, copy its lines over.
            Layout = {
                Top = 0.92,
                Mat = vec3(0.28, -0.07, 0.9230),
                -- The padded drawer extends only for unpacking; both coordinates refer to its receiving pad.
                CaseRest = vec3(-0.49, -0.80, 0.72),
                CaseRestHalf = vec2(0.318, 0.112),
                CaseRestModel = 'sd_bomb_case_drawer',
                CaseDrawer = { Closed = vec3(-0.49, -0.37, 0.72), Rails = 'sd_bomb_case_drawer_rails', Time = 0.8 },
                -- The parts organiser: cols x rows cells, origin = the far-left one's floor. It is stepped: each row stands rise metres
                -- higher than the one in front of it, so the far rows (and the names on their lips) clear the near ones.
                Tray = { origin = vec3(-0.7200, 0.3200, 0.9860), pitch = vec2(0.1600, 0.1600), cols = 4, rows = 3, rise = 0.03 },
                Lamp = { head = vec3(-0.16, 0.1, 1.52), aim = vec3(0.12, -0.06, 0.92) },
                Sheet = { centre = vec3(0.43, 0.3298, 1.0566), size = vec2(0.16, 0.22), pitch = 68.0 }, -- the work order on its copy stand, right of the handbook (pitch: degrees stood up from lying flat)
                Charges = { origin = vec3(-0.265, 0.13, 0.9245), gap = 0.105, rise = 0.0495 }, -- the pad the explosive is stacked on
                -- The Harlow handbook, open on its stand on the back of the top (blender/bench_book.py; its patch
                -- is in Keepout below). spine = the middle of
                -- its spine, level with the pages; yaw / pitch = how it is turned and stood up (Rz(yaw) Rx(pitch): 0 / 0 = lying flat,
                -- reading the right way up from the front); page = one page, metres. Its pages are baked (cd web && npm run book).
                -- Remove it and there is no handbook at the bench.
                Book = { spine = vec3(0.09, 0.32, 1.078), yaw = 0.0, pitch = 68.0, page = vec2(0.165, 0.233), gutter = 0.028, dip = 0.009 },
                Stand = vec3(-0.12, -0.92, 0.0), -- where the builder stands
                -- Parts lie where they are put down, and stay there. Surface = the patch of bench top a part
                -- may lie on (its edge, not its middle); Keepout = patches inside it that stay clear: the casing's place
                -- on the mat, the clipboard, the vice, the rack the case's front stands in.
                -- The tray is apart from both: a part let go over it drops into the cell under it.
                Surface = { min = vec2(-0.94, -0.615), max = vec2(0.94, 0.405) },
                -- The top's own edges (blender/build_bench.py: SIZE, and TOP_FRONT - the top overhangs the frame at the front,
                -- towards the builder, to leave room along it: a wire carton goes down there with the biggest kit on the mat).
                Edge = { min = vec2(-0.95, -0.625), max = vec2(0.95, 0.475) },
                -- The magnetic parts dish left of the mat, below the organiser: poured screws stay in it, pulled together.
                -- Click its screws with the driver to load the bit. at = its middle, r = its inside radius, floor = how high its
                -- bottom is off the bench top (blender/build_bench.py > DISH).
                Dish = { at = vec2(0.635, 0.33), r = 0.062, floor = 0.005 }, -- at the back, right of the work order's stand
                -- The fittings caddy (blender/build_bench.py > CADDY): nine pockets, an edge fitting to each, at the front left.
                -- origin = the back-left pocket's middle, on its floor; cols run right, rows come towards the builder.
                -- The bracket stand (blender/build_bench.py > STAND), in the gap between the caddy and the mat: charge brackets nest
                -- on it in two piles, one behind the other, as they came in the carton - levels high, each rise above the one under
                -- it (two piles of three: a kit's six). Its base runs exactly as far back and forward as the caddy. z = its plate.
                BracketStand = { piles = { vec2(-0.217, -0.1925), vec2(-0.217, -0.3245) }, levels = 3, rise = 0.008, z = 0.926 },
                Caddy = { origin = vec3(-0.72, -0.1735, 0.924), pitch = vec2(0.16, 0.085), cols = 3, rows = 3 }, -- (a small gap in front of the organiser, under its first three columns)
                Bin = { at = vec3(0.80, -0.345, 1.146), half = vec2(0.105, 0.125), depth = 0.22 }, -- scrap bin standing on the top's front-right corner (at = its rim); matches build_bench.py > BIN
                -- The bench's own utility kit (blender/bench_utility.py) - the bench's, not items: every bench has them.
                --   Cup   an enamel cup: pour a box of screws into it (hold the box over it, hold right-click), carry it to the
                --         job - click the cup with a driver to load one screw onto its magnetic bit. Tip it
                --         (right-click) to pour them out again. Home = where a bench's cup starts; Max = screws it holds.
                --   Reel  the wire dispenser on the pegboard: drag individual spools from an open carton into free slots; click a
                --         spool for a length. Spools = where they hang (bench space); Max = lengths a spool holds.
                Cup = { Home = vec2(-0.87, -0.30), Max = 60 }, -- in the front-left corner, beside the fittings caddy
                Reel = { Max = 24, Spools = { vec3(-0.61, 0.365, 1.25), vec3(-0.566, 0.365, 1.25), vec3(-0.522, 0.365, 1.25), vec3(-0.478, 0.365, 1.25), vec3(-0.434, 0.365, 1.25), vec3(-0.39, 0.365, 1.25) } },
                Keepout = {
                    { centre = vec2(0.28, -0.07), half = vec2(0.27, 0.19), carton = vec2(0.325, 0.24) }, -- the case's reserved working area; carton also marks this keepout for size-specific profiles
                    { centre = vec2(-0.56, -0.2585), half = vec2(0.246, 0.1335) }, -- the fittings caddy (fittings snapped into its pockets bypass this)
                    { centre = vec2(-0.217, -0.2585), half = vec2(0.092, 0.1335) },   -- the bracket stand, both piles: level with the caddy beside it (brackets nested on it bypass this)
                    { centre = vec2(0.80, -0.345), half = vec2(0.115, 0.135) }, -- the scrap bin, front-right
                    { centre = vec2(0.835, 0.06), half = vec2(0.115, 0.27) },  -- the rack at the right of the top, side on, and the front leaning in it
                    { centre = vec2(0.09, 0.34), half = vec2(0.2, 0.1) },       -- the handbook's stand
                    { centre = vec2(0.43, 0.32), half = vec2(0.115, 0.08) },    -- the work order's copy stand
                    { centre = vec2(-0.48, 0.16), half = vec2(0.332, 0.252) },  -- the organiser's walls (parts snapped into cells bypass this)
                    { centre = vec2(-0.85, 0.39), half = vec2(0.045, 0.055) }, -- lamp clamp
                    { centre = vec2(0.0, -0.07), half = vec2(0.02, 0.078) }, -- case handles
                    { centre = vec2(0.56, -0.07), half = vec2(0.02, 0.078) },
                    { centre = vec2(0.052, -0.271), half = vec2(0.023, 0.025) }, -- case feet project past its front edge
                    { centre = vec2(0.508, -0.271), half = vec2(0.023, 0.025) },
                },
                -- Storage: the shelf under the bench. Parts put down on it are stored IN the bench - they stay
                -- there like anything on the top. z = the shelf's surface; min / max = the strip along its front
                -- that is free (the toolbox and the carton stand behind it); Keepout = the spray cans.
                Shelf = { z = 0.243, min = vec2(-0.84, -0.415), max = vec2(0.84, -0.085), Keepout = { { centre = vec2(0.055, -0.2), half = vec2(0.105, 0.065) } } },
                -- Where the front of the case stands while the case is open: on the floor, against the bench's
                -- front, by the kit bag (that is where everyone sees it; the builder fetches it off its chip in the bag).
                Park = { at = vec3(-0.56, -0.6, 0.0), half = vec2(0.27, 0.2) },
                -- Where the front of the case is put down while the case is open - it is lifted off by hand and lies on
                -- the bench until it goes back on. at = the middle of it, lying face up; yaw = how it lies; half = the patch
                -- of bench it covers (nothing else lies there meanwhile). A rest with a pitch has it standing up instead:
                -- 'rack' = the universal padded cradle on the back-right (blender/build_bench.py > RACK), leaning back so its top
                -- stays below the tools on the pegboard behind it. pitch = degrees it is stood up from
                -- lying face up (-90 = lying, 0 = upright; -10 leans it back against the rack), at = the middle of the groove its
                -- bottom edge stands in, edge = XL's half-height (compact lids use their own dimensions), half = along the groove and
                -- across it. home = the one that is always free: where a front goes that nobody has said where to put.
                Rests = {
                    rack = { at = vec3(0.8, 0.06, 0.928), yaw = -90.0, pitch = -20.0, edge = 0.1785, half = vec2(0.26, 0.03), home = true },
                },
            },
            -- The kit bag: what the builder carries. On screen it is a compact drawer opened with Dock.Key (Q) or its button
            -- (client/bomb/dock.lua): an icon and a count per kind of part in the player's pockets. A part is
            -- dragged straight off its chip onto the bench - the item leaves the inventory when the part is put
            -- DOWN - and a part in hand is dropped on the bag button to go back in. There is no bag in the world:
            -- Mouth = the point off the bench's corner, low, where a part going back in flies to (and comes out of).
            Bag = { Mouth = vec3(-1.02, -1.0, 0.42) },
            -- The carton a casing kit comes in (blender/build_bench.py > package): it stands on the mat, taped shut, until its
            -- three seams are slit with the knife - press on a seam and draw the blade along it. Size = its width, depth, height.
            -- The carton a kit comes in. Model = whole and taped shut (carried, in the bag), OpenModel = whole, empty,
            -- its flaps back (shoved off the bench). ON the bench it is its BodyModel and four flaps (FlapModel % which),
            -- because its lid is opened by hand. Flaps (blender/build_bench.py prints them): hinge = the middle of the
            -- flap's hinge, in the carton's own space; axis = what it turns about; dir = the way it reaches from its
            -- hinge while shut (+1 / -1 along the other axis); reach = how far; span = its length along the hinge;
            -- open = degrees it is folded back once open. The hinge is the flap's outer top edge (its score line): folded right back,
            -- a flap hangs flush down the outside of its wall.
            Package = { Model = 'sd_bomb_package_xl_layered', OpenModel = 'sd_bomb_package_xl_layered_open', BodyModel = 'sd_bomb_package_xl_layered_body', FlapModel = 'sd_bomb_package_xl_layered_flap_%s',
                Size = vec3(0.64, 0.46, 0.335), InsertModel = 'sd_bomb_package_xl_parts_tray', DisplayInsertModel = 'sd_bomb_package_xl_timer_tray',
                Flaps = {
                    near = { hinge = vec3(0.0, -0.23, 0.335), axis = 'x', dir = 1.0, reach = 0.23, span = 0.64, open = 269.5 },
                    far = { hinge = vec3(0.0, 0.23, 0.335), axis = 'x', dir = -1.0, reach = 0.23, span = 0.64, open = 269.5 },
                    left = { hinge = vec3(-0.32, 0.0, 0.3292885246), axis = 'y', dir = 1.0, reach = 0.16, span = 0.448, open = 269.5 },
                    right = { hinge = vec3(0.32, 0.0, 0.3292885246), axis = 'y', dir = -1.0, reach = 0.16, span = 0.448, open = 269.5 },
                } },
            -- The chips' icons: one image per ITEM, named after it (the ones shipped are the inventory's own,
            -- web/public/pips/icons). Rename an item and its chip shows initials until its image is there too.
            -- Key = the control that opens and shuts the kit bag (44 = INPUT_COVER, Q by default; not Tab - that is the
            -- inventory's), KeyLabel = what its keycap says.
            Dock = { Icons = 'web/build/pips/icons/%s.png', Key = 44, KeyLabel = 'Q' },
            -- The site radio on the top shelf (blender/bench_shelf.py > radio): a prop of its own, and a working one - it plays
            -- GTA's own stations out of the radio itself, for everyone near the bench (the station is part of the bench's state:
            -- kept across restarts, the same for everybody). In the pegboard view (T) click it to switch it on or skip to the next
            -- station, the wheel steps through them, right-click switches it off; walking up to the bench, the bench's target
            -- has it too. Range = metres it is heard up to (only the nearest radio that is on plays: the game gives every player
            -- one such speaker). At = its middle, bench space (where it is clicked). Stations = GTA's station names, in the order a
            -- click steps through them (their names on screen are the game's own). false = no radio.
            Radio = {
                Model = 'sd_bomb_bench_radio', At = vec3(0.77, 0.345, 1.74), Range = 30.0, Emitter = 'SE_Script_Placed_Prop_Emitter_Boombox',
                Stations = { 'RADIO_01_CLASS_ROCK', 'RADIO_02_POP', 'RADIO_03_HIPHOP_NEW', 'RADIO_04_PUNK', 'RADIO_06_COUNTRY', 'RADIO_07_DANCE_01',
                    'RADIO_08_MEXICAN', 'RADIO_09_HIPHOP_OLD', 'RADIO_12_REGGAE', 'RADIO_13_JAZZ', 'RADIO_14_DANCE_02', 'RADIO_15_MOTOWN',
                    'RADIO_16_SILVERLAKE', 'RADIO_17_FUNK', 'RADIO_18_90S_ROCK', 'RADIO_20_THELAB', 'RADIO_21_DLC_XM17', 'RADIO_22_DLC_BATTLE_MIX1_RADIO',
                    'RADIO_23_DLC_XM19_RADIO', 'RADIO_27_DLC_PRHEI4', 'RADIO_34_DLC_HEI4_KULT', 'RADIO_35_DLC_HEI4_MLR', 'RADIO_37_MOTOMAMI',
                    'RADIO_05_TALK_01', 'RADIO_11_TALK_02' },
                -- The six preset buttons under its screen (and on the screen): a station each.
                Presets = { 'RADIO_01_CLASS_ROCK', 'RADIO_02_POP', 'RADIO_03_HIPHOP_NEW', 'RADIO_09_HIPHOP_OLD', 'RADIO_20_THELAB', 'RADIO_04_PUNK' },
                -- Tuning it up close (the bench's target > Tune the radio): the camera comes up to its face and its knobs and buttons
                -- work like a real set's - drag the tuning knob round (or scroll on it) and every notch is the next station, turn the
                -- volume knob down to click it off (or click it), press a preset. The screen only shows what it is on. Knobs = the moving
                -- parts, props hung on the radio, bench space (blender/bench_shelf.py > radio: the radio stands at (0.77, 0.345, 1.664)
                -- facing the builder): At = where each sits on its face, R = a knob's radius. The tuning knob turns a station a notch
                -- (a full turn = every station); the volume knob's scale runs Min..Max (degrees clockwise from its pointer up: quietest to
                -- loudest), and past Min it clicks Off. Loudness goes through the game's own jukebox volume (the Arcade's: an audio
                -- scene and its variable, 0 = full, 1 = ducked right down); Default = the volume a radio starts at. Spin = 1 or
                -- -1: which way the game turns a prop for "clockwise" (flip it if the knobs turn backwards). Camera = Eye / Look, bench space.
                Knobs = { Tune = { Model = 'sd_bomb_radio_knob_tune', At = vec3(0.799, 0.295, 1.709), R = 0.0155 },
                    Volume = { Model = 'sd_bomb_radio_knob_vol', At = vec3(0.849, 0.295, 1.709), R = 0.013, Off = -158.0, Min = -135.0, Max = 135.0, Default = 0.7,
                        Scene = 'dlc_ch_arcade_music_volume', Variable = 'ArcadeRadioVolumeDucking' },
                    Button = { Model = 'sd_bomb_radio_button', X0 = 0.79, Pitch = 0.014, Y = 0.2942, Z = 1.737, Press = 0.0028 }, Spin = 1 },
                Screen = { centre = vec3(0.825, 0.2929, 1.771), size = vec2(0.078, 0.039) },
                Camera = { Eye = vec3(0.805, -0.03, 1.765), Look = vec3(0.805, 0.29, 1.735), Fov = 36.0, InMs = 900, OutMs = 950 },
            },
            -- Under the bench it is dark: a work light, while the camera is down there (G).
            ShelfLight = { Enabled = true, At = vec3(0.0, -0.5, 0.68), Color = { 255, 214, 170 }, Range = 1.8, Intensity = 2.4 },
            Save = 'data/bench_state.json', -- where what lies on the benches is kept across restarts
            -- Tools. Work on the bench is done with a tool IN HAND: click the pegboard and the camera
            -- turns to it, click a tool and it comes down with you; right-click (or Esc) hangs it back
            -- up. Each tool is a prop of its own, standing where the bench model has a place for it.
            --   Home  where it lives (bench space) + Yaw against the bench     Tip   its working end, in its own space
            --   Grab  the patch of pegboard that picks it: centre (x, z) and half-size, on the board's face
            --   Does  what it is for: 'screws' = driving and backing out the modules' screws
            -- (More tools - pliers, cutters - go here as the work that needs them is added.)
            --
            -- A bench comes with NO tools, only the HOLDERS for them on its pegboard: a drilled rail (screwdrivers and
            -- the hex key stand in it), a plier rack of wire loops, sheaths for knives, cradles for meters, the power
            -- driver's shelf - all empty. Tools are items: a builder brings them and puts them in a holder made for
            -- them (drag the tool off the kit bag: the camera turns to the board, let go over a free place) - and
            -- there they stay, the bench's own, for whoever works at it next. A job can be done when a tool
            -- for it hangs on THIS bench (taken down into your hand, it is still the bench's).
            --   Item   the inventory item that is one of it                 Model  its prop (blender/build_bench.py)
            --   Fits   the kind of holder it lives in (a key of Pegs)         Yaw    turned against the bench where it lives
            --   Tip    its working end, in its own space (the script prints these)
            --   Down   degrees of roll at which that end points straight down: how it is held to work
            --   Does   the job it is for:  screws = the modules' screws      bolts = the four in the front's corners
            --          cut = slits the tape a casing kit comes shut with     crimp = a detonator cap into every packed block
            --          strip = wires a fitted module in                      test = proves the circuit before the bomb is assembled
            --          strap = pumps the steel band over a packed block tight, and seals it
            --   Time   seconds one go of that job takes with this tool (a hand screwdriver is slower than the driver)
            Board = { Y = 0.411, X = 0.86, Z = { 1.0, 1.6 } }, -- the pegboard's face: y, half-width, bottom / top
            Pegs = { -- the places in each kind of holder, in bench space (blender/build_bench.py prints these: HOLDERS there decides how many)
                -- (all on the right of the board: seen from the bench, the lamp stands in front of its left half)
                rail = { vec3(0.23, 0.39, 1.4214), vec3(0.305, 0.39, 1.4214), vec3(0.38, 0.39, 1.4214), vec3(0.455, 0.39, 1.4214) },
                loop = { vec3(0.525, 0.408, 1.4262), vec3(0.605, 0.408, 1.4262), vec3(0.685, 0.408, 1.4262), vec3(0.765, 0.408, 1.4262), -- the top row...
                    vec3(0.61, 0.408, 1.1762), vec3(0.695, 0.408, 1.1762), vec3(0.78, 0.408, 1.1762) }, -- ...and the lower right
                sheath = { vec3(0.84, 0.399, 1.45) },
                cradle = { vec3(-0.2, 0.41, 1.12) },
                shelf = { vec3(0.1, 0.359, 1.33) }, -- (the driver's: first along the row, clear of the case front's rack)
            },
            Tools = {
                camkey = { Item = 'sd_tool_camkey', Model = 'sd_bomb_tool_camkey', Fits = 'rail', Tip = vec3(0,0,-.146), Down = 0, Does = 'index', Time = 1 },
                trimgauge = { Item = 'sd_tool_trimgauge', Model = 'sd_bomb_tool_trimgauge', Fits = 'loop', Tip = vec3(0,0,-.145), Down = 0, Does = 'balance', Time = 1 },
                signalprobe = { Item = 'sd_tool_signalprobe', Model = 'sd_bomb_tool_signalprobe', Fits = 'rail', Tip = vec3(0,0,-.137), Down = 0, Does = 'signal', Time = 1 },
                driver = { Item = 'sd_tool_driver', Model = 'sd_bomb_tool_driver', Fits = 'shelf', Yaw = 180.0, Tip = vec3(0.1805, 0.0, 0.205), Down = 90.0, Does = 'screws', Time = 0.46 },
                screwdriver = { Item = 'sd_tool_screwdriver', Model = 'sd_bomb_tool_screwdriver', Fits = 'rail', Tip = vec3(0.0, 0.0, -0.143), Down = 0.0, Does = 'screws', Time = 1.3 },
                hexkey = { Item = 'sd_tool_hexkey', Model = 'sd_bomb_tool_hexkey', Fits = 'rail', Tip = vec3(0.0, 0.0, -0.1475), Down = 0.0, Does = 'bolts', Time = 0.9 },
                knife = { Item = 'sd_tool_knife', Model = 'sd_bomb_tool_knife', Fits = 'sheath', Tip = vec3(0.004, 0.0, -0.102), Down = 0.0, Does = 'cut', Time = 0.9 },
                pliers = { Item = 'sd_tool_pliers', Model = 'sd_bomb_tool_pliers', Fits = 'loop', Tip = vec3(0.0, -0.0062, 0.05), Down = 180.0, Does = 'crimp', Time = 0.7 },
                tensioner = { Item = 'sd_tool_tensioner', Model = 'sd_bomb_tool_tensioner', Fits = 'loop', Tip = vec3(0.0, -0.008, 0.052), Down = 180.0, Does = 'strap', Time = 0.6 },
                cutters = { Item = 'sd_tool_cutters', Model = 'sd_bomb_tool_cutters', Fits = 'loop', Tip = vec3(0.0, -0.0062, 0.03), Down = 180.0, Does = 'snip', Time = 0.5 },
                strippers = { Item = 'sd_tool_strippers', Model = 'sd_bomb_tool_strippers', Fits = 'loop', Tip = vec3(0.0, -0.004, 0.062), Down = 180.0, Does = 'strip', Time = 0.8 },
                multimeter = { Item = 'sd_tool_multimeter', Model = 'sd_bomb_tool_multimeter', Fits = 'cradle', Tip = vec3(0.0, -0.03, 0.0), Down = 0.0, Does = 'test', Time = 1.4 },
            },
            -- The bench's own lamp, while somebody works at it: a spot light out of the shade.
            Light = { Enabled = true, Color = { 255, 222, 176 }, Distance = 3.2, Brightness = 9.0, Radius = 42.0, Falloff = 14.0 },
            -- How the work feels (metres / seconds).
            Feel = { CarryHeight = 0.27, HoverHeight = 0.055, Follow = 15.0, DropTime = 0.2, ScrewTime = 0.46, ScrewOut = 0.022, FlipTime = 0.75, FlipLift = 0.28,
                Parallax = 0.012 }, -- metres the view leans after the cursor: the bench is looked AT, not photographed
            -- true = a kit can only be opened AT a bench (using the item tells you to find one).
            -- As long as the server has no bench at all, the item opens the builder on the spot anyway.
            Required = true,
            Reach = 3.0, -- metres from a bench it can be used from
            SpawnDistance = 60.0, -- metres at which the bench prop spawns
            Locations = {}, -- fixed benches: { vec4(x, y, z, heading), ... } - z = the ground
            Command = 'bomb_bench', -- /bomb_bench places one in front of you, /bomb_bench remove takes the nearest away (saved to data/benches.json); false = no command
            Restricted = 'group.admin',
            -- The bench as an ITEM, for players: use it and stand the bench wherever you like with the placement gizmo (the
            -- item is taken once it stands; the bench stays across restarts). Whoever stood it can pack it up again - empty,
            -- and nobody working at it - and gets the item back. Icon: blender/icons/sd_workbench.png. false = no item
            -- (benches come only from Locations, the command and the admin panel).
            --   Limit   benches one character may have standing at once (0 = no limit)
            --   Reach   metres from the player the bench may be stood
            --   PackUp  false = a bench stays where it was stood (only an admin takes it away)
            Item = { Name = 'sd_workbench', Limit = 2, Reach = 8.0, PackUp = true },
            -- The move onto the bench. With the World bench, Eye and Look are points in the bench's
            -- space; with another prop (World = false) Height/Back are metres above its top / out
            -- towards the player's side of it.
            Camera = { Eye = vec3(0.02, -0.97, 2.2), Look = vec3(0.02, -0.03, 0.965), Height = 1.15, Back = 0.35, Fov = 49.0, InMs = 1500, OutMs = 900,
                -- The camera works with you (World bench only). Click the work order and it comes down
                -- over the sheet to read it; drive a screw and it closes in on that module, stays while
                -- you work on it and draws back 'Hold' seconds after the last screw. Back / Up = where it
                -- hangs, in metres from what it looks at; Spring = how briskly it moves (higher = snappier).
                Focus = { Sheet = { Back = -0.12, Up = 0.4, Fov = 42.0 }, Bay = { Back = 0.19, Up = 0.4, Fov = 40.0 }, Hold = 1.1, Spring = 6.5,
                    Unpack = { Eye = vec3(0.02, -1.52, 2.30), Look = vec3(0.0, -0.24, 0.95), Fov = 51.0 }, -- drawer, carton and bin together during unpacking
                    Book = { Back = 0.34, Up = 0.0, Fov = 40.0 }, -- square on to the open handbook (Layout.Book): Back = metres out from its pages
                    Wire = { Back = 0.2, Up = 0.62, Fov = 40.0, Close = 0.7, Meter = 1.24 }, -- the view down into the open case while the pliers cap its blocks; Close = how much nearer it comes to the block being wired, Meter = how much further off it stands with the multimeter laid out before the case

                    Prep = { Back = 0.34, Up = 0.42, Fov = 40.0 }, -- over the mat's front edge while a length of wire is cut to length and stripped (Steps.Prep)
                    Board = { Eye = vec3(0.4, -0.62, 1.46), Look = vec3(0.36, 0.41, 1.3), Fov = 50.0 }, -- the view of the pegboard, to hang a tool up or take one down
                    Shelf = { Eye = vec3(0.0, -1.75, 0.8), Look = vec3(0.0, -0.22, 0.27), Fov = 46.0 } } }, -- the view under the bench, of the shelf things are stored on (Layout.Shelf)
        },
        -- One item per module. Rename an item here and in your inventory's item list; remove a
        -- line and that module cannot be built with (the random bombs still deal it).
        Modules = {
            wires = 'sd_mod_wires',
            button = 'sd_mod_button',
            keypads = 'sd_mod_keypads',
            simon = 'sd_mod_simon',
            whosOnFirst = 'sd_mod_whos_on_first',
            memory = 'sd_mod_memory',
            morse = 'sd_mod_morse',
            complicatedWires = 'sd_mod_complicated_wires',
            wireSequences = 'sd_mod_wire_sequences',
            mazes = 'sd_mod_mazes',
            passwords = 'sd_mod_passwords',
            ventingGas = 'sd_mod_venting_gas',
            capacitor = 'sd_mod_capacitor',
            knobs = 'sd_mod_knobs',
        },
    },

    -- Planting: the player AIMS first - a see-through casing shows how the bomb would sit on what
    -- they are looking at (standing on a floor, flat against a wall, under a ceiling); left click
    -- or E settles it, right click or Backspace backs out, the wheel turns a standing bomb. Then
    -- a cancellable progress bar, and the bomb is armed right there. The bomb it deals uses
    -- config.Bomb.Defaults (random seed), exactly like a bare /bomb_plant.
    Plant = {
        Aim = true, -- false = the old way: no aiming, the bomb is set on the ground in front of you
        Reach = 2.5, -- metres from you a spot may be (the server allows a little more for lag, no further)
        FloorNormal = 0.7, -- how level a surface must be to STAND a bomb on it (its normal's z); steeper = hung on it like a wall
        Standoff = 0.035, -- metres left between a wall and the casing's back, for the modules on its back face
        TurnStep = 15.0, -- degrees per wheel click
        Surfaces = { floor = true, wall = true, ceiling = true }, -- set one to false to refuse it
        -- true = while a player aims, every wall near enough shows what THEIR bomb would do to it: the hole it would
        -- open (the wall shown breached, right where the blast would put it), or why not - too tough for its charge,
        -- no room for another hole, out of reach. false = they find out when it goes off. (Admins always see it.)
        Preview = true,
        Hud = 'own', -- 'own' = the placing panel at the bottom of the screen (the same look as the admin placer); 'textui' = ox_lib's text UI
        -- Planting as a moment (client/bomb/plantfx.lua + aim.lua). Any animation the game does not have is skipped.
        Fx = {
            Glide = 18.0, -- how quickly the aimed bomb glides after the aim (0 = it jumps there)
            Outline = { breach = { 72, 214, 128 }, widen = { 72, 214, 128 }, tough = { 229, 163, 59 }, full = { 229, 163, 59 }, none = { 190, 205, 230 } }, -- by what it would do to the wall; false = none
            Carry = {
                Enabled = true, -- false = it is armed where it was aimed, the player just turning to it
                Hold = { dict = 'anim@heists@box_carry@', clip = 'idle', flag = 49 }, -- carried in front, both hands, walked up to the spot
                Grip = vec3(-0.105, 0.27, 0.03), -- the middle of the palms in that pose (the ped's space, measured): the bomb's middle, its face to the player
                WalkIf = 0.2, WalkTime = 4000, -- metres off where to stand before walking there; ms at most spent walking
                LowWall = 1.0, -- metres up a wall below which it is crouched to (wallLow) instead of pushed on at chest height
                -- Setting it on, by surface. Measured from each animation: where the hands' middle is at the moment they meet the
                -- surface (reach forward / side, metres in the ped's space as it started - where the player is stood so they
                -- land on it), when that is (contact, s), and when the gesture is over (stopAt, s) and the arming pose takes over.
                Place = {
                    -- arm = the pose held while it is armed (the progress bar), armReach = metres from the player to it for that pose.
                    -- (stopAt: once the gesture has stepped the player back - the push and the set-down both lean them in 0.3 m.)
                    wall = { dict = 'anim@heists@load_box', clip = 'load_box_1', reach = 1.1, ahead = true, side = -0.05, contact = 1.45, stopAt = 3.2, -- both arms, pushed on
                        arms = false }, -- (then stood back from it, hands off: the camera does the work - an arm = { dict, clip } here would play instead)
                    wallLow = { dict = 'weapons@projectile@sticky_bomb', clip = 'plant_vertical', reach = 0.36, side = -0.09, contact = 0.85, stopAt = 1.35,
                        arm = { dict = 'amb@medic@standing@kneel@base', clip = 'base', flag = 1 }, armReach = 0.45, armSide = 0.42 },
                    floor = { dict = 'amb@medic@standing@kneel@enter', clip = 'enter', reach = 0.5, side = 0.25, contact = 2.3, stopAt = 3.2, flag = 2, -- kneeling down to set it by them
                        arm = { dict = 'amb@medic@standing@kneel@base', clip = 'base', flag = 1 }, armReach = 0.23, armSide = 0.25 }, -- (where the kneel ends: no step)
                    ceiling = { dict = 'amb@prop_human_movie_bulb@base', clip = 'base', reach = 0.24, side = 0.03, contact = 0.5, stopAt = 1.2,
                        arm = { dict = 'amb@prop_human_movie_bulb@base', clip = 'base', flag = 1 }, armReach = 0.24 },
                },
                BlendIn = 250, -- ms the bomb takes from where it was carried to between the hands, as the animation starts
                PressTime = 260, -- ms from the hands onto the spot
                Shake = true, ShakeAmount = 0.03, -- a small jolt as it lands
            },
            Arm = { FadeIn = 0.06, Boot = 0.38, Decode = 0.16, ArmedAt = 0.82, BeepSlow = 520, BeepFast = 130 }, -- (fractions of Duration: the screen fades up, its boot bar fills to Boot, the countdown decodes in over Decode, it arms at ArmedAt; ms between beeps)
            -- The arming, directed (client/bomb/plantfx.lua > direct), in cinema bars (Letterbox: of the screen's height) with the
            -- HUD hidden, a shallow focus (Dof) and a handheld sway (Handheld). false = no camera move at all. Each shot is set
            -- out from the display in its own frame - Out of its face, Side across it, Rise up it (metres) - and kept within
            -- ~20 degrees of straight on: the display sits at the bottom of a well in its lid, and past that the digits are cut off.
            --   Open    from the start: over the player's shoulder, high, pushing in (Push)
            --   Close   at CloseAt (0-1 through the arming): straight at the display as its boot bar fills and it counts, from
            --           Out pushing in to OutTo
            --   Reveal  just before it arms: a quick pull back, wide, with a jolt, settling back (Drift)
            --   By      per way it went on (wall, wallLow, floor, ceiling), over the three above
            Camera = { Push = 0.15, EaseIn = 900, EaseOut = 800, Letterbox = 0.1, Dof = true, DofStrength = 1.0, Handheld = 0.18,
                Open = { Out = 1.5, Side = -0.45, Rise = 0.5, Fov = 38.0 },
                CloseAt = 0.18, Close = { Out = 0.42, OutTo = 0.3, Side = 0.02, Rise = 0.03, Fov = 30.0, Ease = 650 },
                RevealLead = 0.03, Reveal = { Out = 1.8, Side = -0.6, Rise = 0.5, Fov = 40.0, Ease = 420, Jolt = 0.08, Drift = 0.25 },
                By = { floor = { Open = { Out = 1.2, Side = -0.7, Rise = 0.55 }, Reveal = { Out = 1.7, Side = -0.75, Rise = 0.7 } },
                    wallLow = { Open = { Out = 1.2, Side = -0.7, Rise = 0.5 }, Reveal = { Out = 1.7, Side = -0.75, Rise = 0.6 } } } },
            Sounds = { -- { name, sound set } (frontend / from the bomb); remove one to silence it
                Snap = { 'CLICK_BACK', 'WEB_NAVIGATION_SOUNDS_PHONE' },
                Thunk = { 'Drill_Pin_Break', 'DLC_HEIST_FLEECA_SOUNDSET' },
                Beep = { 'Beep_Red', 'DLC_HEIST_HACKING_SNAKE_SOUNDS' },
                Armed = { 'Beep_Green', 'DLC_HEIST_HACKING_SNAKE_SOUNDS' },
            },
        },
        Duration = 4500, -- ms on the progress bar
        -- Held while arming it: kneeling for a bomb on the floor, hands up to it for one on a wall
        -- or overhead. false plants standing still.
        Anim = { dict = 'amb@medic@standing@kneel@base', clip = 'base', flag = 1 },
        AnimMounted = { dict = 'mini@repair', clip = 'fixing_a_player', flag = 17 },
    },

    -- Pose held while a UI is open, so other players see what you are up to (you only see the
    -- blurred world behind the page). Set either to false for no pose.
    Pose = {
        -- Reading the manual: clipboard in the left hand. bone 36029 = left hand; offset/rot are
        -- tuned for this dict + prop - re-tune if you swap either.
        Manual = {
            dict = 'missfam4', clip = 'base', flag = 49,
            prop = 'p_amb_clipboard_01', bone = 36029,
            offset = vec3(0.16, 0.08, 0.1), rot = vec3(-130.0, -50.0, 0.0),
        },
        -- Defusing: kneeling over the bomb - or standing at it, when it hangs on a wall or overhead.
        Defuse = { dict = 'amb@medic@standing@kneel@base', clip = 'base', flag = 1 },
        DefuseMounted = { dict = 'mini@repair', clip = 'fixing_a_player', flag = 17 },
        -- Building: kneeling over the open casing (the kit opened on the spot), or standing at a workbench.
        Build = { dict = 'amb@medic@standing@kneel@base', clip = 'base', flag = 1 },
        BuildAtBench = { dict = 'mini@repair', clip = 'fixing_a_player', flag = 1 },
    },
}