Installation — ns-illegal
1. Files and start order
Put the ns-illegal folder into your resources (e.g. resources/[nullsect]/ns-illegal) and start it after
oxmysql, your framework, your inventory and (optionally) ox_lib / ox_target:
ensure oxmysql
ensure ox_lib
ensure qbx_core # or qb-core / es_extended
ensure ox_inventory # or your framework inventory
ensure ox_target # optional
ensure ns-illegal
add_ace group.admin ns-illegal.admin allow
ns-illegal does not need ns-core at runtime: the ns-core modules are vendored in core/.
2. Database
Config.Database.autoMigrate = true (default) creates the tables on the first start (the DB user needs CREATE /
ALTER). Otherwise import sql/ns-illegal.sql once. Tables: ns_illegal_meta, _profiles, _stats, _ledger,
_unlocks, _objectives, _contracts, _contract_progress, _grow, _audit, _points (positions edited with
/illegal locations, 1.2.0). The SQL never drops anything.
3. Items
The Dark Tablet is a real item. Without an inventory (Inventory = 'none') the tablet cannot open.
Every item below is used by the default configuration; rename them in config.lua / data/*.lua if your server
already has equivalents (the console lists configured items your inventory does not know at start).
| Item | Used by |
|---|---|
dark_tablet | the tablet (not consumed) |
lockpick | vehicle theft, house robbery (lockpickItem = false to disable) |
stolen_jewels, stolen_electronics, stolen_watch | house robbery loot, pawn fence |
car_door, car_wheel, car_engine_part, car_radio | dismantling, parts buyer |
weed_seed, fertilizer, weed_bag | cultivation, supplier, distributors |
gold_bar, stolen_art | Kingpin buyer (sourced from your other resources / contracts) |
black_money | only when Config.Economy.payout.method = 'item' |
ox_inventory (ox_inventory/data/items.lua)
['dark_tablet'] = {
label = 'Dark Tablet', weight = 600, stack = false, close = true, consume = 0,
description = 'An encrypted tablet for the underground network.',
server = { export = 'ns-illegal.useDarkTablet' },
},
['stolen_jewels'] = { label = 'Stolen Jewels', weight = 150 },
['stolen_electronics'] = { label = 'Stolen Electronics', weight = 800 },
['stolen_watch'] = { label = 'Stolen Watch', weight = 100 },
['car_door'] = { label = 'Car Door', weight = 9000 },
['car_wheel'] = { label = 'Car Wheel', weight = 6000 },
['car_engine_part'] = { label = 'Engine Part', weight = 7000 },
['car_radio'] = { label = 'Car Radio', weight = 1500 },
['weed_seed'] = { label = 'Weed Seed', weight = 10 },
['fertilizer'] = { label = 'Fertilizer', weight = 1000 },
['weed_bag'] = { label = 'Bag of Weed', weight = 50 },
['gold_bar'] = { label = 'Gold Bar', weight = 1000 },
['stolen_art'] = { label = 'Stolen Art', weight = 3000 },
-- 'lockpick' and 'black_money' usually exist already
With the server.export line, set Config.Tablet.registerUsable = false (the export opens the tablet; it only
accepts calls coming from ox_inventory and cancels the generic "use" so the tablet is never consumed).
QBCore (qb-core/shared/items.lua)
dark_tablet = { name = 'dark_tablet', label = 'Dark Tablet', weight = 600, type = 'item', image = 'dark_tablet.png',
unique = true, useable = true, shouldClose = true, description = 'An encrypted tablet for the underground network.' },
-- add the other items from the table above the same way (useable = false)
Keep Config.Tablet.registerUsable = true: ns-illegal registers the item through QBCore.Functions.CreateUseableItem
(the core bridge). Qbox with CreateUseableItem works the same way.
ESX
Insert the items into the items table (name, label, weight) and keep registerUsable = true
(ESX.RegisterUsableItem).
4. Police arrests (required for the reputation penalty)
The workspace this resource was built in had no police resource, so the arrest hook is an adapter that your police resource must call. Call it only server-side, at the point where an arrest is confirmed (sentence / booking), never from a client event and not for routine stops:
-- in your police resource, server side, after it validated the officer and the target
exports['ns-illegal']:ReportArrest(targetServerId, incidentId, officerServerId)
-- offline / by character id:
exports['ns-illegal']:ReportArrestByIdentifier(citizenId, incidentId)
- The calling resource must be listed in
Config.Arrest.allowedResources(checked withGetInvokingResource()). incidentIdshould be the police resource's own unique id of this arrest (booking / case / jail record id). Retries with the same id are ignored. Without an id, a second report for the same character insideConfig.Arrest.dedupeSecondsis ignored.officerServerIdis optional; when given, that player must have a job fromConfig.Police.jobs.- The character is resolved immediately: a player who disconnects right after the arrest still gets the penalty.
Example for qb-policejob (verify the event in your version — this was not tested against a real copy):
in qb-policejob/server/main.lua, inside RegisterNetEvent('police:server:JailPlayer', function(playerId, time) ... end),
after the existing officer / distance checks and after the target was jailed, add:
exports['ns-illegal']:ReportArrest(playerId, ('jail:%s:%d'):format(playerId, os.time()), src)
5. Coordinates and content
All shipped coordinates are marked -- example. The easy way is in game: /illegal locations (admins) shows every
position on a map and a list. Go to a spot and press E to move or add one, switch the examples off, done; it applies
at once and is stored in the database (ns_illegal_points). The files stay the defaults: data/locations.lua
(contacts), data/activities.lua (theft spots / drop-offs, houses / interiors) and config.lua
(Config.Contracts.dropPoints). Reset in the editor goes back to the file value. data/*.lua is a
server-only script, so hidden coordinates are never downloaded by clients.
With ns-locksmith installed, the vehicle spots recorded there with /lslocations are also used as theft parking spots
(Config.VehicleTheft.locksmithSpots = 'add'; 'replace' uses only them, false none). ns-illegal only reads its
data/locations.json, it never writes it.
6. Verify
| Check | Expected |
|---|---|
| Server console | [ns-illegal:SV] [INFO] ready (framework=qbox, inventory=ox, core=1.0.0) and Contracts loaded: N active. |
| Config errors | Config: ... lines name the exact key; the tablet answers "network is starting" until they are fixed (/illegal validate) |
| Using the Dark Tablet | registration screen on first use, dashboard afterwards |
| Without the item | "You need your Dark Tablet for that." |
/illegal help | admin command list (normal players: refused and logged) |
7. In-game test checklist
The offline tests (tests/run_*.lua) cover the server rules with a simulated runtime. These need a real server:
- Use the item → register → reconnect → still registered. Two players try the same name at the same time.
- Drop the tablet while it is open → it closes within
watchIntervalMs, actions are refused. - Vehicle theft end to end (spawn, lockpick, keys hook, drop-off payout) with OneSync; restart the resource mid-job → vehicle removed, no penalty.
- House robbery: two players in different houses never see each other (routing buckets); disconnect inside → back at the door after reconnecting.
- Arrest through your police resource → HUD shows the reputation loss, arrest count +1; repeat the same incident → no second penalty.
- Buy a clue twice quickly (double click / two clients) → charged once; GPS works only after buying.
- Two players finish the same contract within the same second → one winner, one reward.
- NUI at 1920x1080 and 1280x720, EN and TR; ESC always frees the mouse.
/illegal setxp <id> 7000→ level 10, Tier 2 contacts appear in Locations.- Location editor (
/illegal locations), 1.2.0 (confirmed in game 2026-10-22):- the panel opens with every point; map drag / wheel zoom; search; ESC frees the mouse;
- Move a contact: walk, press E → its ped and target zone move for every player without a restart;
- Add a parking spot from a car (heading = the car) and switch the examples off → the next theft contract spawns there; a car parked on a spot makes the job pick another one;
- Add a contract drop point (name form) → it is offered in the tablet's contract form;
- in an interior (teleport to its entry): add / move search spots, move the exit; a running robbery keeps the old positions;
- ns-locksmith spots are listed with the
ns-locksmithtag; restart ns-locksmith → still there; restart ns-illegal→ every change is still there; world view ghosts and blips are removed.
8. With ns-carthief and ns-houserobbery (optional)
Nothing to configure: start them in any order (ensure [nullsect] is fine). While they run:
- ns-carthief: a theft contract vehicle is opened with the ns-carthief lockpick and hotwire instead of the progress
bar; the contract moves on to the drop-off when the player who took it hotwires it. Every other hotwired vehicle is a
street theft worth career XP (
vehicle_hotwire, 20 per day; stealing is never blocked). Keep the contract models (Config.VehicleTheft.models) out of ns-carthief's blacklist, or they cannot be opened. - ns-houserobbery: house contacts (
services.house) open the ns-houserobbery contract offers; its own informant keeps working. Finished contracts give career XP to every member who went inside (house_robberyby house tier) and to the safe cracker (house_safe). The career level unlocks the house tiers (mid at level 5, high at 10 by default:Config.Tiers[x].illegalMinLevelin ns-houserobbery). - Both use the career level for their minigames. Players without a Dark Tablet username keep stealing, but get no career XP and only the cheapest houses.
Switch a link off with Config.Integrations.carthief = false / houserobbery = false here, or
Config.Integrations.illegal = false in the other resource.
In-game checks (not tested in game yet):
- Theft contract with ns-carthief: no progress-bar option at the car; lockpick + hotwire → drop-off blip; delivery → XP.
- Hotwire a street car → tablet HUD shows XP; after 20 in a day the car still starts, no XP.
house_tipsterat night → ns-houserobbery offers; mid locked below career level 5.restart ns-illegalduring a house contract → the contract finishes, no errors in either console.
Troubleshooting
oxmysql is not started→ start oxmysql before ns-illegal.Migration v1 failed→ DB permissions, or importsql/ns-illegal.sql.No inventory detected→ start ox_inventory / your framework inventory before ns-illegal.Could not register "dark_tablet" as a usable item→ use the ox_inventoryserver.exportline above.Items used by the configuration but unknown to your inventory→ add them (section 3) or change the names.- Arrests do nothing → the calling resource is not in
Config.Arrest.allowedResources, or the call runs on the client.