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. 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. Set your own in data/locations.lua (contacts), data/activities.lua
(theft spots / drop-offs, houses / interiors) and config.lua (Config.Contracts.dropPoints). data/*.lua is a
server-only script, so hidden coordinates are never downloaded by clients.
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.
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.