Configuration Reference
Every setting in oxide-pestcontroljob — how the /pestcontrol settings editor and factory defaults work, plus crew, pay, dispatch, levels, depots, properties, the van, the inspection, treatment, wasps, uniforms and model replacements.
Every setting in oxide-pestcontroljob, what it does, and its factory default.
How Settings Work
Settings are edited in-game with the /pestcontrol settings command (admins only). Changes save to your database and apply instantly, with no restart needed.
The editor groups settings into six categories (Core, Economy, Vehicles, World, Gameplay, Presentation) and has a search box. Everyday settings are on the Basic tab; finer tuning is on the Advanced tab. Every setting has a Reset to default button.
The config files in the shared/ folder are factory defaults. The first time the resource starts, every value is imported from the files into the database (the oxide_settings table). From then on, the database is the source of truth and editing the files has no effect on settings that are already stored. Only brand-new settings added by a future update are imported from the files.
This applies to everything, including the depot and the property list. Once the resource has started even once, /pestcontrol settings is the way to change a setting on a running server.
Other resources can read and change these settings too, through the GetSetting and SetSetting server exports. See the API Reference.
Settings marked "File only"
A few settings are not shown in the editor (they're marked File only below). They are still stored in the database. To change one after the first start:
- Edit the value in its config file and save the file.
- Restart the whole server.
- Open
/pestcontrol settingsand press Reset to default on the setting that holds it (for example Wasps → Wasp nests for anything inConfig.Wasp).
Reset to default copies the whole setting from the file as it was when the server started. Any part of that setting you had changed in the editor goes back to the file's value too, so re-enter those changes afterwards.
When changes take effect
Most changes apply immediately. A few apply later:
- Levels, the van's model and appearance, and uniforms apply at the next clock-in.
- Properties, site layout and the call timer apply to new calls. A call in progress keeps the property and layout it started with.
The tables below document every setting and its factory default.
Config File Map
| File | Purpose |
|---|---|
shared/config.lua | Core toggles, menu mode, summary mode, depot slots, testing aid |
shared/config/job.lua | Crew, payment, complaints, timer, call dispatch, level unlocks |
shared/config/levels.lua | Level thresholds and rewards |
shared/config/locations.lua | The depot |
shared/config/properties.lua | The houses, restaurants and warehouses calls go to |
shared/config/sites.lua | How signs are laid out, how many finish the inspection, and the pet bowl and vegetable bed props |
shared/config/vehicles.lua | The company van |
shared/config/visuals.lua | The depot clerk, map blips, waypoints |
shared/config/gear.lua | The torch and backpack, the UV inspection, where the stage card and findings card sit |
shared/config/treatment.lua | The backpack sprayer, bait stations, glue traps, report rules |
shared/config/wasp.lua | The wasp nest takedown |
shared/config/outfits.lua | Optional on-duty uniform (off by default) |
shared/config/models.lua | Model replacements for older game builds |
shared/config/tutorials.lua | Tutorial definitions |
Distances are in metres, times in seconds, and amounts in dollars, unless noted.
General Settings
File: shared/config.lua | In the editor: Core → General
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Debug | boolean | false | Prints extra detail to the server console and the F8 console. Leave off in production (Advanced tab) |
Config.usePhone | boolean | true | Sends shift messages (welcome, customer callbacks, a destroyed van, a cancelled shift and, with Config.shiftSummary = 'text', the pay summary) to the player's phone, and makes the phone ring for each incoming call (crew leader only; the call is still accepted or rejected on screen). Messages arrive as texts on phones that support them and as mail on the rest. Texts and the call ring need o-link 1.11.0 or newer. When false, nothing is sent to the phone; the on-screen notifications still show |
Config.shiftSummary | string | 'text' | 'nui' for a popup summary on clock-out (with either menu style), 'text' to send the full pay summary to the phone (or a net-pay notification when Config.usePhone = false). With 'nui', a shift that ends any other way (going down, missed calls, the complaint limit, a destroyed van) still sends the summary as a text |
Config.menuStyle | string | 'nui' | 'nui' for the full shift menu, 'menu' for a simple context menu |
Config.MaxSlotsPerLocation | number | 3 | How many technicians can be clocked in at one depot at the same time (1 to 20). A crew partner takes a slot too |
Config.DebugForceSpecies | string | 'off' | Testing aid: 'roach', 'rat' or 'wasp' sends only that pest. 'off' is the normal mix. A forced pest ignores the level it normally opens at, but climbing to a wasp nest still needs the wasp level (Advanced tab) |
Config.Debug = false
Config.usePhone = true
Config.shiftSummary = 'text' -- 'nui' or 'text'
Config.menuStyle = 'nui' -- 'nui' or 'menu'
Config.MaxSlotsPerLocation = 3
Config.DebugForceSpecies = 'off' -- 'off', 'roach', 'rat' or 'wasp'Below these settings, the same file lists fixed names the job uses internally (shift states, pests, property kinds and call stages). Don't change those.
Job Settings
File: shared/config/job.lua
Crew
In the editor: Core → Crew
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Crew.maxSize | number | 2 | 1 for solo-only shifts, 2 to allow a partner. Crews are pairs, so higher values are lowered to 2 |
Config.Crew.inviteTimeout | number | 60 | Seconds a crew invite stays open |
Config.Crew.coopBonusPercent | number | 0.15 | Extra pay on each call for working as a crew (0.15 = 15%) |
Config.Crew = {
maxSize = 2,
inviteTimeout = 60,
coopBonusPercent = 0.15,
}Payment
In the editor: Economy → Payment
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Payment.basePay | table | roach 90, rat 100, wasp 130 | Base pay for each kind of call |
Config.Payment.kindMultiplier | table | house 1.0, restaurant 1.2, warehouse 1.4 | Base pay is multiplied by this for each kind of property |
Config.Payment.distanceBonusPerUnit | number | 0.25 | Extra pay per metre, in a straight line from where the call was accepted to the property |
Config.Payment.speedBonusAmount | number | 25 | Bonus for reporting a call before its timer runs out |
Config.Payment.fullFindBonus | number | 40 | Bonus for finding every sign on a call, paid to each crew member at or above the level in Config.Unlocks.fullFindBonus. 0 turns it off and hides its line on the stage card |
Config.Payment.glueTrapBonus | number | 8 | Per glue trap on a cockroach trail, up to three. 0 turns it off and hides its line on the stage card |
Config.Payment.wasteCostPerUnit | number | 2 | Charged per unit of chemical wasted beyond the level's allowance |
Config.Payment.exclusionPenalty | number | 40 | Charged for spraying a pet bowl or vegetable bed |
Config.Payment.stingPenalty | number | 20 | Charged each time the wasps sting |
Config.Payment.wrongTreatmentFactor | number | 0.4 | Unfinished treatment in the editor. Call pay multiplier when the call is reported before the treatment is finished: a cockroach call with glue traps down but less than 85% of the foundation sprayed, or a rat call with fewer than 2 bait stations on runways (0.4 = 60% less) |
Config.Payment.missedNestFactor | number | 0.7 | Call pay multiplier when the treatment was finished but the nest was left untreated |
Config.Payment.vehicleDestroyFine | number | 200 | Fine charged on the spot when the van is destroyed |
Config.Payment.streakBonusPerDay | number | 10 | Extra pay per call for each day of the daily streak |
Config.Payment.maxStreakDays | number | 7 | The streak stops growing at this many days |
Config.Payment.gasFeePercent | number | 0.05 | Share of gross shift pay deducted as fuel costs (0.05 = 5%) |
Config.Payment.taxPercent | number | 0.10 | Share of gross shift pay deducted as tax (0.10 = 10%) |
Config.Payment.payoutAccount | string | 'bank' | Account net shift pay is deposited to ('bank', 'cash', and so on) |
Config.Payment.fineAccount | string | 'cash' | Account the van destruction fine is taken from |
The waste, pet bowl, vegetable bed and sting charges are treatment charges: they come off the shift pay at clock-out. See Payment for how a call's pay is worked out.
Config.Payment = {
basePay = {
roach = 90,
rat = 100,
wasp = 130,
},
kindMultiplier = {
house = 1.0,
restaurant = 1.2,
warehouse = 1.4,
},
distanceBonusPerUnit = 0.25,
speedBonusAmount = 25,
fullFindBonus = 40,
glueTrapBonus = 8,
wasteCostPerUnit = 2,
exclusionPenalty = 40,
stingPenalty = 20,
wrongTreatmentFactor = 0.4,
missedNestFactor = 0.7,
vehicleDestroyFine = 200,
streakBonusPerDay = 10,
maxStreakDays = 7,
gasFeePercent = 0.05,
taxPercent = 0.10,
payoutAccount = 'bank',
fineAccount = 'cash',
}Complaints
In the editor: Core → Complaints (Advanced tab)
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Complaints.maxComplaints | number | 3 | Complaints before the technician is suspended from the job |
Config.Complaints.timeoutMinutes | number | 30 | How long the suspension lasts, in minutes |
Config.Complaints.callbackPenalty | number | 1 | Complaints added to each crew member when the customer calls back (treatment not finished or nest not treated) |
Config.Complaints.exclusionPenalty | number | 1 | Complaints added for spraying a pet bowl or vegetable bed |
Config.Complaints.vehicleDestroyPenalty | number | 1 | Complaints added to the crew leader when the van is destroyed |
Config.Complaints.deathPenalty | number | 1 | Complaints added when the technician dies or goes down on shift |
Config.Complaints.decayPerJob | number | 1 | Complaints removed for each clean call (no callback) |
Config.Complaints.callbackDelaySeconds | number | 90 | How long after the report the customer's callback message arrives. The complaint itself counts straight away |
Config.Complaints = {
maxComplaints = 3,
timeoutMinutes = 30,
callbackPenalty = 1,
exclusionPenalty = 1,
vehicleDestroyPenalty = 1,
deathPenalty = 1,
decayPerJob = 1,
callbackDelaySeconds = 90,
}Timer
In the editor: Economy → Calls & Dispatch → Call timer
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Timer.baseTime | number | 420 | Seconds given for every call, before distance (7 minutes) |
Config.Timer.timePerDistanceUnit | number | 0.20 | Extra seconds per metre from where the call was accepted to the property |
Config.Timer.expiredPenalty | number | 0.85 | Pay multiplier when the timer runs out (0.85 = 15% less) |
Config.Timer = {
baseTime = 420,
timePerDistanceUnit = 0.20,
expiredPenalty = 0.85,
}
-- timeLimit = baseTime + math.floor(distance * timePerDistanceUnit)Calls
In the editor: Economy → Calls & Dispatch → Call dispatch
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Calls.minIntervalSeconds | number | 15 | Shortest wait between calls |
Config.Calls.maxIntervalSeconds | number | 45 | Longest wait between calls |
Config.Calls.responseTimeSeconds | number | 30 | How long an incoming call rings before it's missed |
Config.Calls.maxMissedCalls | number | 3 | Missed or rejected calls before the shift ends automatically |
Config.Calls.cancelCooldownSeconds | number | 60 | Wait before the next call after cancelling one. 0 = no extra wait |
Config.Calls.arrivalMargin | number | 15.0 | Metres beyond a property's radius that count as arrived |
Config.Calls.maxTravelSpeed | number | 50.0 | Fastest believable trip in the editor, in metres per second along a straight line. A crew that reaches the property faster than this from where they accepted the call (only possible by teleporting) gets no distance bonus and no speed bonus for that call. Raise it if your server has very fast vehicles |
Config.Calls.speciesWeights | table | roach 45, rat 35, wasp 20 | Relative chance of each pest. Wasps only come once the level allows them. Set a pest to 0 to stop sending it |
The cancel cooldown is a floor, not an extra delay on top: after a cancel, the next call arrives once both the normal 15 to 45 second gap and the cooldown have passed.
Config.Calls = {
minIntervalSeconds = 15,
maxIntervalSeconds = 45,
responseTimeSeconds = 30,
maxMissedCalls = 3,
cancelCooldownSeconds = 60,
arrivalMargin = 15.0,
maxTravelSpeed = 50.0,
speciesWeights = {
roach = 45,
rat = 35,
wasp = 20,
},
}Unlocks
In the editor: Economy → Progression → Unlocks
The level each part of the job opens at.
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Unlocks.restaurant | number | 2 | Calls can go to restaurants |
Config.Unlocks.warehouse | number | 3 | Calls can go to warehouses |
Config.Unlocks.exclusions | number | 2 | Pet bowls and vegetable beds appear on site and must not be sprayed |
Config.Unlocks.wasp | number | 3 | Wasp calls start. A technician also needs this level to climb to a nest |
Config.Unlocks.speciesHint | number | 3 | The stage card names the pest as soon as the first sign is logged. Below this level the pest is named when the inspection is done (see Config.Sites.required) |
Config.Unlocks.ladderFall | number | 4 | A wasp sting knocks the technician off the ladder |
Config.Unlocks.fullFindBonus | number | 5 | Finding every sign pays Config.Payment.fullFindBonus |
Houses are always open.
Config.Unlocks = {
restaurant = 2,
warehouse = 3,
exclusions = 2,
wasp = 3,
speciesHint = 3,
ladderFall = 4,
fullFindBonus = 5,
}Levels
File: shared/config/levels.lua | In the editor: Economy → Progression → Levels
Config.Levels is the list of levels, lowest first (row 1 is level 1). Each level has:
| Field | Type | Description |
|---|---|---|
title | string | The level's name, as a key from locales/en.json (for example 'level.trainee') |
requiredJobs | number | Calls reported in total before reaching this level |
payMultiplier | number | All call pay is multiplied by this at this level |
tipChance | number | Chance the customer tips (0.25 = 25%) |
tipRange | table | Smallest and largest possible tip, { min, max } |
pingRadius | number | While the torch is out, the nearest unfound sign within this many metres gets a marker with its distance and a sound. 0 = no pings |
wasteAllowance | number | Free over-spray, as a share of the whole foundation's chemical need (0.3 = 30%) |
Keep requiredJobs increasing from row to row. There must be at least one level.
Config.Levels = {
[1] = { title = 'level.trainee', requiredJobs = 0, payMultiplier = 1.0, tipChance = 0.10, tipRange = { 5, 15 }, pingRadius = 12.0, wasteAllowance = 0.6 },
[2] = { title = 'level.technician', requiredJobs = 15, payMultiplier = 1.1, tipChance = 0.15, tipRange = { 5, 20 }, pingRadius = 9.0, wasteAllowance = 0.45 },
[3] = { title = 'level.senior_technician', requiredJobs = 40, payMultiplier = 1.2, tipChance = 0.25, tipRange = { 10, 30 }, pingRadius = 6.0, wasteAllowance = 0.3 },
[4] = { title = 'level.field_specialist', requiredJobs = 80, payMultiplier = 1.35, tipChance = 0.30, tipRange = { 10, 40 }, pingRadius = 4.0, wasteAllowance = 0.2 },
[5] = { title = 'level.master_exterminator', requiredJobs = 150, payMultiplier = 1.5, tipChance = 0.40, tipRange = { 15, 50 }, pingRadius = 0.0, wasteAllowance = 0.15 },
}The titles show as Trainee Technician, Technician, Senior Technician, Field Specialist and Master Exterminator.
Locations
File: shared/config/locations.lua | In the editor: World → Locations
Config.Locations is the list of depots. The job ships with one, Bugstars Pest Control. Each entry is a complete, independent depot with its own clerk, map blip, van spot and technician slots.
| Field | Type | Required | What it does |
|---|---|---|---|
name | string | Yes | The depot's name. It shows on the map blip, in the shift menu and in the sender name of phone messages ("Bugstars - " followed by the name). Ships as a translation key ('location.depot', from locales/en.json). A key that isn't in the locale file is shown exactly as typed, so plain text like 'Bugstars Sandy Shores' also works |
coords | vector3 | Yes | Where the clerk stands (Clerk spot in the editor). Players walk up to this spot to clock in and out |
heading | number | Yes | Which way the clerk faces, in degrees (0 to 360) (Facing) |
vehicleSpawn | vec4 | Yes | Where the van appears at clock-in: x, y, z and the direction it faces (Van spawn). Pick a clear patch of road or lot. If a car or another player is within 5 metres of it, clock-in is refused |
menuCamera | table | No | Menu camera. The shot behind the full shift menu, measured from the clerk so it moves with the clerk: distance (metres from the clerk's chest, 1.2 to 12), angle (degrees round from the way the clerk faces, positive to the clerk's right), elevation (degrees above the clerk's chest, -10 to 80) and fov (field of view, 20 to 90). Set up in world lets you frame it with the mouse (see Admin Tools). Missing: 3.2 m, 12°, 6°, field of view 45 |
Config.Locations = {
{
name = 'location.depot',
coords = vector3(870.00, -2170.45, 30.50),
heading = 175.0,
vehicleSpawn = vec4(874.00, -2177.50, 30.00, 265.0),
menuCamera = { distance = 3.2, angle = 12.0, elevation = 6.0, fov = 45.0 },
},
}Adding a second depot
- Open
/pestcontrol settingsand go to World → Locations. - Add a row. Give it a name.
- Use Place in world on Clerk spot: it previews the clerk and also sets Facing.
- Use Place in world on Van spawn: it previews the van.
- Press Save Changes. The new clerk and blip appear right away, facing the way you set.
The shift menu's camera follows the clerk on its own. To change the shot, use Set up in world on Menu camera and press Save Changes.
Each depot gets its own technician slots (Config.MaxSlotsPerLocation, 3 by default, so two depots means six technicians in total). A shift belongs to the depot the player clocked in at: that's where their van spawns.
You can edit, reorder or delete depots while technicians are on shift. A shift keeps its depot as long as the depot keeps either its name or its clerk spot. If you delete a depot (or change its name and its clerk spot in the same save), the shifts there end straight away and are paid, and the players are told their depot was removed.
Calls are shared, not per depot. The property list is one list for the whole job, and dispatch picks from all of it no matter which depot a player clocked in at. A second depot does not need properties of its own. If it's far from the shipped properties, you may want to add a few near it (see Properties), so its technicians aren't always driving across the map. Pay and the timer both scale with distance, so far-away calls aren't unfair, just slow.
Properties
File: shared/config/properties.lua | In the editor: World → Properties
Config.Properties is every place a call can be sent. The job ships with 12 (see Properties for the list). The list stays on the server: players' games only receive the property their call is at.
| Field | Type | Required | What it does |
|---|---|---|---|
label | string | Yes | The property's name (Name in the editor), shown on the call card and the map |
kind | string | Yes | 'house', 'restaurant' or 'warehouse'. Each kind opens at a level (see Unlocks) |
center | vector3 | Yes | The middle of the building, at ground height. The call blip goes here |
radius | number | Yes | Metres from the centre that count as on site (5 to 150 in the editor). Devices must be placed inside it |
segments | table | Yes | The foundation line: one row per wall run, from a to b, on the ground about 0.4 metres out from the wall. Signs, nests and the spray line are built from these. At least one run, each at least 0.5 metres long |
wasp | table | No | Wasp nest spots. nest is the underside of the eave (x, y, z and a heading facing the wall), ladder is where the ladder stands, facing the wall. The nest must be reachable from the stepladder (see step 5 of Adding a property). Leave empty for no wasp calls here |
exclusions | table | No | No-spray spots. Each has a kind ('petbowl' or 'vegbed') and coords (x, y, z, heading) by the foundation line |
spots | table | No | Sign spots. Where the signs of a cockroach or rat call can appear: each has a pos (x, y, z) and nest (true where the burrow or harbourage may go). Empty or left out: one spot every Spot step along the foundation line (see Site Layout), as the shipped properties use. When some spots have nest on, the nest only goes on those; when none do, it can go on any spot. Each spot faces the wall of its nearest foundation run |
Config.Properties = {
{
label = 'Forum Drive (Strawberry)',
kind = 'house',
center = vector3(-15.90, -1430.82, 29.67),
radius = 21.29,
segments = {
{ a = vector3(-20.22, -1443.39, 29.62), b = vector3(-7.05, -1443.39, 31.17) },
-- ...one row per wall run, all the way round
},
wasp = {
{ nest = vector4(-14.20, -1443.24, 32.61, 0.00), ladder = vector4(-14.20, -1443.94, 29.82, 0.00) },
},
exclusions = {
{ kind = 'petbowl', coords = vector4(-21.22, -1434.77, 29.65, 180.00) },
{ kind = 'vegbed', coords = vector4(-13.49, -1423.93, 29.69, 180.00) },
},
},
-- ...the other 11 properties
}Adding a property
The easiest way is in the world: in /pestcontrol settings → World → Properties, press Add in world under the list, or Edit in world on a property, and fly round it. Everything below is drawn where it is, and the editor says what's wrong before you save. See Admin Tools.
To do it with the form instead:
- Open
/pestcontrol settingsand go to World → Properties. - Add a row. Type a Name and choose a Kind.
- Center: use Place in world and pick the middle of the building at ground height. Set a Radius that covers the building and its yard.
- Foundation line: add one row per stretch of outside wall. For each row, place From and To on the ground about 0.4 metres out from the wall. Go all the way round the building, corner to corner.
- Wasp nest spots (optional): place Nest under an eave, with its heading facing the wall. Place Ladder on the ground below it, facing the wall. The eave must be about 2.3 to 4.0 metres above the ladder spot, and the ladder spot within 1.5 metres of the point under the nest. The editor refuses a spot the stepladder can't reach and says which property and spot it is, and by how much it is too high, too low or too far.
- No-spray spots (optional): choose Pet bowl or Vegetable bed and place the Spot by the foundation line.
- Press Save Changes. The property can be picked for new calls straight away.
The editor refuses a property that's missing a name, a kind, a centre, a radius or a foundation line, that has a run shorter than 0.5 metres or a spot with no position, or that has a wasp nest spot the stepladder can't reach. The message says which property (and which wasp spot) needs fixing. Use Teleport here on any point to go and check it.
Site Layout and No-Spray Spots
File: shared/config/sites.lua | In the editor: World → Properties (Advanced tab)
Config.Sites (Site layout)
How signs are laid out around a property when the crew arrives, and how many finish the inspection. Applies to new calls.
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Sites.signs | table | house 7, restaurant 9, warehouse 11 | Signs per call by kind of property, besides the nest |
Config.Sites.required | table | house 4, restaurant 5, warehouse 6 | Signs to finish in the editor. Signs the crew must log before the inspection is done: then the pest is named, the findings card appears and the van offers the treatment gear. Never more than the signs on site. 1 to 40 |
Config.Sites.spacing | number | 1.6 | Minimum metres between two signs |
Config.Sites.nestPull | number | 8.0 | Signs thin out over about this many metres from the nest. Smaller = a tighter cluster |
Config.Sites.strays | number | 0.08 | How likely a sign lands far from the nest (0 = never) |
Config.Sites.spotStep | number | 1.5 | Metres between possible sign spots along the foundation line |
Config.Sites.mix | table | see below | Relative chance of each sign type for each pest |
Config.Sites.renderDistance | number | 80.0 | Pet bowls, vegetable beds, ladders, nests and devices are drawn within this range |
Config.Sites = {
signs = { house = 7, restaurant = 9, warehouse = 11 }, -- besides the nest
required = { house = 4, restaurant = 5, warehouse = 6 }, -- signs to log before the inspection is done
spacing = 1.6,
nestPull = 8.0,
strays = 0.08,
spotStep = 1.5,
mix = {
rat = {
{ kind = 'droppings_rodent', weight = 4 },
{ kind = 'gnaw', weight = 3 },
{ kind = 'grease', weight = 3 },
},
roach = {
{ kind = 'droppings_roach', weight = 6 },
{ kind = 'grease', weight = 4 },
},
},
renderDistance = 80.0,
}Config.Exclusions (No-spray spots)
What stands on a no-spray spot, and how close the spray may come.
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Exclusions.petbowl.model | string | 'm25_1_prop_m51_dog_bowl_full' | The pet bowl prop. It needs a recent game build; replace it in Model Compatibility if yours lacks it |
Config.Exclusions.petbowl.radius | number | 0.9 | How close the spray may come before it counts as sprayed |
Config.Exclusions.vegbed.model | string | 'prop_veg_crop_02' | The vegetable bed prop |
Config.Exclusions.vegbed.radius | number | 1.6 | How close the spray may come before it counts as sprayed |
Config.Exclusions = {
petbowl = { model = 'm25_1_prop_m51_dog_bowl_full', radius = 0.9 },
vegbed = { model = 'prop_veg_crop_02', radius = 1.6 },
}Vehicle
File: shared/config/vehicles.lua | In the editor: Vehicles → Vehicle
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Vehicle.model | string | 'burrito2' | The van spawned at clock-in (the base-game Bugstars van) |
Config.Vehicle.plate | string | 'BUGSTARS' | Number plate text, up to 8 characters |
Config.Vehicle.healthCheckInterval | number | 1000 | Milliseconds between van health checks |
Config.Vehicle.destroyedThreshold | number | 150 | If the van's engine health drops below this, it counts as destroyed |
Config.Vehicle.damageChargeRate | number | 0.50 | Charged per point of body health lost below the threshold, at clock-out |
Config.Vehicle.damageChargeThreshold | number | 950 | Only charge below this body health, so minor scratches are free |
Config.Vehicle.livery | number | -1 | Livery number for models that have several. -1 = the model's default |
Config.Vehicle.extras | table or nil | nil | Force vehicle extras on or off by ID, as { [extraId] = true/false }. nil (empty in the editor) leaves the model as it is |
Config.Vehicle.rear.doors | list | { 2, 3 } | Rear doors in the editor. The doors that open while a technician takes or puts back gear. 2 and 3 are a van's two rear doors |
Config.Vehicle.rear.offset | number | -3.4 | Spot behind the van. Where the technician must stand, in metres along the van from its middle. Negative is behind it. -3.4 is just behind the burrito2's rear bumper |
Config.Vehicle.rear.radius | number | 1.5 | Spot size. How close to that spot the technician must stand |
Config.Vehicle.rear.durationMs | number | 1500 | Reach time. How long taking or putting back gear takes, with a short progress bar. The server hands the gear over only after this long |
Config.Vehicle.rear.anim | table | see code | Reach animation: the technician leaning into the back of the van |
Model and appearance changes apply at the next clock-in.
Every gear option on the van (take the torch, the sprayer, bait stations, glue traps, the ladder or the foam can, refill the sprayer, return the sprayer) only shows to a technician standing behind the van. Both rear doors open, a short progress bar plays while they reach in, and the doors shut again afterwards. If the crew partner is still reaching in, the doors stay open until they are done. If you change the van's model, check rear.doors and rear.offset fit the new one.
Config.Vehicle = {
model = 'burrito2',
plate = 'BUGSTARS',
healthCheckInterval = 1000,
destroyedThreshold = 150,
damageChargeRate = 0.50,
damageChargeThreshold = 950,
livery = -1,
extras = nil,
rear = {
doors = { 2, 3 },
offset = -3.4,
radius = 1.5,
durationMs = 1500,
anim = { dict = 'mini@repair', clip = 'fixing_a_player', flag = 1 },
},
}Clerk, Blips and Waypoints
File: shared/config/visuals.lua | In the editor: World → NPCs & Blips
| Setting | Type | Default | Description |
|---|---|---|---|
Config.NpcModel | string | 's_m_y_pestcont_01' | The clerk standing at each depot |
Config.NpcScenario | string | 'WORLD_HUMAN_CLIPBOARD' | What the clerk is doing (Advanced tab) |
Config.Blip.enabled | boolean | true | Show the depot on the map |
Config.Blip.sprite | number | 273 | Depot blip icon |
Config.Blip.color | number | 2 | Depot blip colour |
Config.Blip.scale | number | 0.8 | Depot blip size |
Config.EventBlip.sprite | number | 463 | Icon of the call's map marker (Advanced tab) |
Config.EventBlip.color | number | 1 | Colour of the call's map marker and route |
Config.EventBlip.scale | number | 1.2 | Size of the call's map marker |
Config.Waypoints.heightOffset | number | 5.0 | Metres above the property the floating marker sits (Advanced tab) |
Config.Waypoints.updateInterval | number | 0 | Milliseconds between marker updates (0 = every frame) |
Config.Waypoints.scaleMin | number | 0.5 | Marker size when far away |
Config.Waypoints.scaleMax | number | 1.0 | Marker size when close |
Config.Waypoints.scaleNearDist | number | 30.0 | Distance at which the marker is largest |
Config.Waypoints.scaleFarDist | number | 300.0 | Distance at which the marker is smallest |
Config.NpcModel = 's_m_y_pestcont_01'
Config.NpcScenario = 'WORLD_HUMAN_CLIPBOARD'
Config.Blip = { enabled = true, sprite = 273, color = 2, scale = 0.8 }
Config.EventBlip = { sprite = 463, color = 1, scale = 1.2 }
Config.Waypoints = {
heightOffset = 5.0,
updateInterval = 0,
scaleMin = 0.5,
scaleMax = 1.0,
scaleNearDist = 30.0,
scaleFarDist = 300.0,
}Gear, Inspection and HUD
File: shared/config/gear.lua
Config.Kit (Gear)
In the editor: Gameplay → Gear (Advanced tab)
The torch and the backpack tank. Where technicians take them from is set under Vehicle (Config.Vehicle.rear).
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Kit.renderDistance | number | 60.0 | Other players' tanks, torches and foam cans are drawn within this range |
Config.Kit.tank.male / female | string | 'oxide_pest_sprayer_tank_m' / 'oxide_pest_sprayer_tank_f' | The backpack tank model for each body |
Config.Kit.tank.bone | number | 24818 | Bone the tank hangs from (upper spine) |
Config.Kit.tank.outlet | table | see code | Where the hose leaves each tank, in the tank's own space |
Config.Kit.tank.hideInVehicles | boolean | true | Hide the tank while seated in a vehicle |
Config.Kit.torch.model | string | 'prop_scn_police_torch' | The UV torch prop: the torch the game's security guards carry |
Config.Kit.torch.bone | number | 60309 | Bone the torch attaches to (left hand) |
Config.Kit.torch.offset / rotation | vector3 | vec3(0.0, 0.0, 0.0) | How the torch sits in the hand. Zero, the way the game itself places it |
Config.Kit.torch.bulb | vector3 | vec3(0.219, 0.0, 0.0) | Bulb in the editor. Where the beam starts, in the torch's own space |
Config.Kit.torch.anim | table | see code | The torch-holding animation: the security guard's "shine torch" pose |
Config.Kit = {
renderDistance = 60.0,
tank = {
male = 'oxide_pest_sprayer_tank_m',
female = 'oxide_pest_sprayer_tank_f',
bone = 24818,
outlet = {
male = vec3(-0.3776, -0.2304, -0.1698),
female = vec3(-0.3746, -0.2151, -0.1698),
},
hideInVehicles = true,
},
torch = {
model = 'prop_scn_police_torch',
bone = 60309, -- left hand
offset = vec3(0.0, 0.0, 0.0),
rotation = vec3(0.0, 0.0, 0.0),
bulb = vec3(0.219, 0.0, 0.0), -- the beam starts here
anim = { dict = 'amb@world_human_security_shine_torch@male@base', clip = 'base', flag = 49 },
},
}Config.Inspection (UV inspection)
In the editor: Gameplay → Inspection
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Inspection.claimRange | number | 2.6 | How close a sign must be to log it |
Config.Inspection.claimAngle | number | 20.0 | How near the middle of the beam it must be, in degrees |
Config.Inspection.claimHoldMs | number | 500 | Milliseconds the sign must stay in the beam |
Config.Inspection.serverRange | number | 3.5 | The server accepts a log within this distance of the technician (Advanced) |
Config.Inspection.serverAngle | number | 75.0 | The technician must face the sign within this many degrees (Advanced) |
Config.Inspection.minGapMs | number | 600 | Milliseconds between two logs by one technician (Advanced) |
Config.Inspection.minSecondsOnSite | number | 2 | Seconds after arriving before a log counts (Advanced) |
Config.Inspection.beamPitch | number | -15.0 | How far other players' torch beams point down, in degrees |
Config.Inspection.cone.range | number | 9.0 | How far the beam reveals signs |
Config.Inspection.cone.angle | number | 26.0 | Half-angle of the beam. Signs fade out toward its edge |
Config.Inspection.light | table | see code | The torch's violet beam, which every nearby player sees: colour (r, g, b), distance, brightness, hardness, radius, falloff. This beam and the glowing signs make the UV look |
Config.Inspection.glow.r / g / b | number | 170 / 110 / 255 | Sign glow in the editor. The colour of the soft light at a sign while it glows in the beam (0 to 255 each). The default is violet |
Config.Inspection.glow.lightRange | number | 1.4 | How far that light reaches, in metres |
Config.Inspection.glow.lightIntensity | number | 3.0 | How bright that light is. Logged signs get about half. 0 turns the light off; the sign's glowing model still shows |
Config.Inspection.spots | table | see code | The model for each kind of sign. Not in the editor. Each kind has a wall model, a floor model or both: model is the sign as it looks in daylight, uv its glowing twin, and centre the point on the model the beam aims at. A kind with both uses the wall model against a wall and the floor model in open ground. sink is the most that kind of sign may go into the ground to close a gap on uneven ground, in metres. Leave this alone: to use your own sign models, add them under Model replacements instead |
Config.Inspection.ground | table | see code | How each sign sits on the ground. Not in the editor. The ground is checked under the sign itself, and the sign is tilted to match its slope. sink: how far every sign goes into the ground so no gap shows (up to its kind's own sink). maxTilt: steeper ground is treated as this steep, in degrees. wallGap: a sign against a wall tips away from the wall only until its top is this far off it. step: ground more than this much higher or lower than the rest under a sign (a kerb or a step) is ignored |
Config.Inspection.pingIntervalMs | number | 2000 | How often the nearest unfound sign is pinged (marker, distance and a sound), for levels with a ping radius |
The signs only show for the crew working the call. Each sign is a model that shows in any light, and its glowing twin takes its place while the technician's own beam is on it. Signs at a wall sit at the foot of the wall, on the ground in front of it, and follow the ground's slope.
Config.Inspection = {
claimRange = 2.6,
claimAngle = 20.0,
claimHoldMs = 500,
serverRange = 3.5,
serverAngle = 75.0,
minGapMs = 600,
minSecondsOnSite = 2,
beamPitch = -15.0,
cone = {
range = 9.0,
angle = 26.0,
},
light = {
r = 150, g = 90, b = 255,
distance = 12.0,
brightness = 8.0,
hardness = 0.0,
radius = 18.0,
falloff = 30.0,
},
glow = {
r = 170, g = 110, b = 255,
lightRange = 1.4,
lightIntensity = 3.0,
},
spots = {
droppings_rodent = { floor = { model = 'oxide_pest_sign_droppings_rodent', uv = 'oxide_pest_sign_droppings_rodent_uv', centre = vec3(0.0, 0.0, 0.004), sink = 0.001 } },
droppings_roach = { floor = { model = 'oxide_pest_sign_droppings_roach', uv = 'oxide_pest_sign_droppings_roach_uv', centre = vec3(0.0, 0.0, 0.003), sink = 0.001 } },
gnaw = { wall = { model = 'oxide_pest_sign_gnaw', uv = 'oxide_pest_sign_gnaw_uv', centre = vec3(0.0, 0.002, 0.0234), sink = 0.004 } },
grease = { wall = { model = 'oxide_pest_sign_grease', uv = 'oxide_pest_sign_grease_uv', centre = vec3(0.0, 0.003, 0.0585), sink = 0.004 } },
nest_rat = { -- the burrow
wall = { model = 'oxide_pest_sign_burrow', uv = 'oxide_pest_sign_burrow_uv', centre = vec3(0.0, 0.06, 0.02), sink = 0.015 },
floor = { model = 'oxide_pest_sign_burrow_ground', uv = 'oxide_pest_sign_burrow_ground_uv', centre = vec3(0.0, 0.0, 0.02), sink = 0.015 },
},
nest_roach = { wall = { model = 'oxide_pest_sign_harborage', uv = 'oxide_pest_sign_harborage_uv', centre = vec3(0.0, 0.02, 0.01), sink = 0.004 } },
},
ground = {
sink = 0.004,
maxTilt = 30.0,
wallGap = 0.01,
step = 0.12,
},
pingIntervalMs = 2000,
}Card positions
In the editor: Presentation → HUD → Card positions
Where the stage card (top left) and the findings card (top right) sit when no server HUD places them. Each card has its own position.
| Setting | Type | Default | Description |
|---|---|---|---|
Config.HudLayout.topLeft.top | number | 30 | Stage card → From the top. Percent of the screen height from the top (0 to 95) |
Config.HudLayout.topLeft.side | number | 3 | Stage card → From the left. Percent of the screen height in from the left edge (0 to 80) |
Config.HudLayout.topRight.top | number | 30 | Findings card → From the top. Percent of the screen height from the top (0 to 95) |
Config.HudLayout.topRight.side | number | 3 | Findings card → From the right. Percent of the screen height in from the right edge (0 to 80) |
Config.UseHudAccent | boolean | true | Follow the HUD accent. Use Oxide HUD's accent colour for both cards when it has one |
Most servers keep their HUD in the top corners, so both cards start lower. When Oxide HUD runs (with o-link 1.12.1 or newer), it places both cards itself and Config.HudLayout isn't used. See On-screen cards and Oxide HUD.
Config.HudLayout = {
topLeft = { top = 30, side = 3 }, -- the stage card
topRight = { top = 30, side = 3 }, -- the findings card
}
Config.UseHudAccent = trueTreatment
File: shared/config/treatment.lua
Config.Spray (Backpack sprayer)
In the editor: Gameplay → Treatment → Backpack sprayer
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Spray.tankUnits | number | 60.0 | Chemical units the backpack holds. Refilled at the van |
Config.Spray.needPerMetre | number | 0.8 | Units a metre of foundation takes to fill |
Config.Spray.minCoverage | number | 0.85 | Share of the foundation a cockroach treatment must fill (0.85 = 85%) |
Config.Spray.reach | number | 4.0 | A run only fills while the technician is this close to it |
Config.Spray.hitWidth | number | 1.75 | Spray landing this close to the foundation line counts for that run |
Config.Spray.harborageNeed | number | 2.0 | Units on the cockroach harbourage to treat the nest |
Config.Spray.harborageRadius | number | 1.2 | How close to the harbourage the spray must land |
Config.Spray.exclusionUnits | number | 0.8 | Units on a pet bowl or vegetable bed before it counts as sprayed |
Config.Spray.exclusionAngle | number | 25.0 | Spraying toward a pet bowl or vegetable bed within this many degrees also counts |
Config.Spray.patterns | table | fan and stream | The spray patterns, switched with the mouse wheel. Each has a name, flow (units per second), maxReach (metres), speed and splashRadius |
The two shipped patterns:
| Pattern | Flow | Reach | Speed | Splash radius |
|---|---|---|---|---|
fan | 1.0 units/s | 3.2 m | 5.0 m/s | 0.8 m |
stream | 0.7 units/s | 5.5 m | 8.0 m/s | 0.4 m |
File only
These control how the hose, the wand and the spray are drawn and animated. They aren't in the editor. Leave them alone unless you are fitting your own wand model.
| Setting | Default | What it does |
|---|---|---|
coverageUpdateMs | 500 | How often the crew's coverage display refreshes, in milliseconds |
length, warnAt, leashSlack, teleport | 3.0, 0.95, 3.0, 30.0 | Hose length and stretch limits from the shared hose system. The hose runs from the backpack to the wearer's own hand, so these rarely come into play |
maxTargets | 4 | Most runs and spots the spray is shared between at once |
renderDistance | 60.0 | Other players' sprayers are drawn within this range |
fxDistance | 30.0 | The spray effect is drawn within this range |
maxRopes | 6 | Most hoses drawn at once |
tickMs | 1000 | How often the server drains the tank while spraying, in milliseconds |
hitsInterval | 800 | How often the spray reports where it lands, in milliseconds |
rope | see code | The hose's look |
pressure.minRangeScale | 0.8 | Spray reach when the tank is almost empty, as a share of full reach |
pressure.sputterBelow | 0.05 | The spray sputters below this much chemical (5%) |
pitch | min -50.0, max 15.0 | How far down and up the wand can aim, in degrees |
patterns[].gravity, patterns[].fx | see code | How each pattern falls and its effect |
splash | see code | The effect where the spray lands |
models.nozzle | 'oxide_pest_sprayer_wand' | The wand model |
hand, sockets | see code | How the wand sits in the hand, and where the spray and the hose meet it |
nozzleFallback | 'prop_hose_nozzle' | The base-game nozzle used if the wand model can't load |
anim | see code | The aiming animations |
keys | spray = 24, patternUp = 241, patternDown = 242, putBack = 194 | Game control numbers: spray (left mouse button), pattern (mouse wheel), holster the wand (Backspace). The key that takes a holstered wand back out is a FiveM key binding, G by default, which players change in GTA Settings → Key Bindings → FiveM (Pest Control: take out the sprayer wand) |
Config.Spray = {
tankUnits = 60.0,
needPerMetre = 0.8,
minCoverage = 0.85,
reach = 4.0,
hitWidth = 1.75,
harborageNeed = 2.0,
harborageRadius = 1.2,
exclusionUnits = 0.8,
exclusionAngle = 25.0,
coverageUpdateMs = 500,
length = 3.0,
warnAt = 0.95,
maxTargets = 4,
renderDistance = 60.0,
fxDistance = 30.0,
maxRopes = 6,
tickMs = 1000,
leashSlack = 3.0,
teleport = 30.0,
hitsInterval = 800,
rope = { radius = 0.0075, sections = 6, uvScale = { 1.0, 20.0 }, type = 4, slack = 0.25 },
pressure = { minRangeScale = 0.8, sputterBelow = 0.05 },
pitch = { min = -50.0, max = 15.0, offset = 0.0 },
patterns = {
{
name = 'fan',
speed = 5.0, gravity = 9.81, maxReach = 3.2, splashRadius = 0.8,
flow = 1.0,
fx = { asset = 'core', name = 'water_cannon_spray', scale = 0.18 },
},
{
name = 'stream',
speed = 8.0, gravity = 9.81, maxReach = 5.5, splashRadius = 0.4,
flow = 0.7,
fx = { asset = 'core', name = 'water_cannon_jet', scale = 0.05 },
},
},
splash = { asset = 'core', name = 'ent_sht_water', scale = 0.3, interval = 300 },
models = { nozzle = 'oxide_pest_sprayer_wand' },
hand = { bone = 28422, offset = vec3(0.0, 0.0, 0.0), rotation = vec3(0.0, 0.0, 0.0) },
sockets = {
tip = vec3(0.7403, -0.0396, 0.0845),
inlet = vec3(-0.1549, -0.0098, -0.0177),
},
nozzleFallback = {
model = 'prop_hose_nozzle',
hand = { bone = 28422, offset = vec3(0.3879, -0.0207, 0.0444), rotation = vec3(-6.52, 61.71, 86.95) },
sockets = { tip = vec3(0.0, -0.165, 0.0), inlet = vec3(0.0, 0.165, 0.0) },
},
anim = {
dict = 'anim@weapons@heavy@space_cannon', clip = 'aim_low_loop', flag = 49,
aims = {
{ clip = 'aim_low_loop', pitch = -35.0 },
{ clip = 'aim_med_loop', pitch = 4.0 },
{ clip = 'aim_high_loop', pitch = 49.0 },
},
},
keys = { spray = 24, patternUp = 241, patternDown = 242, putBack = 194 },
}Config.Devices (Bait stations and glue traps)
In the editor: Gameplay → Treatment → Bait stations & glue traps
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Devices.bait.model | string | 'oxide_pest_bait_station' | The bait station model |
Config.Devices.bait.max | number | 4 | Bait stations per call |
Config.Devices.glue.model | string | 'oxide_pest_glue_trap' | The glue trap model |
Config.Devices.glue.max | number | 4 | Glue traps per call |
Config.Devices.placeRange | number | 5.0 | How far from the technician a device may be put down |
Config.Devices.spacing | number | 1.5 | Minimum metres between two devices |
Config.Devices.placeAnim | table | kneel | The animation while placing |
Config.Devices.placeDuration | number | 1800 | Milliseconds placing takes |
Config.Devices = {
bait = { model = 'oxide_pest_bait_station', max = 4 },
glue = { model = 'oxide_pest_glue_trap', max = 4 },
placeRange = 5.0,
spacing = 1.5,
placeAnim = { dict = 'amb@medic@standing@kneel@base', clip = 'base', flag = 1 },
placeDuration = 1800,
}Config.Treatment (Report rules)
In the editor: Gameplay → Treatment → Report rules (Advanced tab)
What a report counts as the right treatment and a treated nest.
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Treatment.runwayDistance | number | 1.2 | A bait station within this of the foundation line can be on a runway... |
Config.Treatment.runwaySignRange | number | 4.5 | ...when a rat sign is within this of it |
Config.Treatment.minStations | number | 2 | Bait stations on runways a rat treatment needs |
Config.Treatment.burrowRange | number | 4.0 | A bait station this close to the burrow treats the rats' nest |
Config.Treatment.glueRange | number | 2.5 | A glue trap this close to a cockroach sign is on its trail |
Config.Treatment = {
runwayDistance = 1.2,
runwaySignRange = 4.5,
minStations = 2,
burrowRange = 4.0,
glueRange = 2.5,
}Config.Report (Filing the report)
In the editor: Gameplay → Treatment → Filing the report
Filing the report at the van takes a few seconds of paperwork: the technician holds a clipboard and writes on it, with a progress bar, and other players see the clipboard and the animation. Cancelling the bar files nothing. The server pays the call only once the paperwork has had its full time.
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Report.durationMs | number | 4000 | Paperwork time. How long filing takes, in milliseconds |
Config.Report.anim | table | see code | Clipboard animation: the game's own clipboard pose |
Config.Report.prop | table | see code | Clipboard: the model, the bone it attaches to (60309 = left hand) and its position and rotation in the hand. Zero, the way the game itself places it |
Other players see the clipboard with every progress bar that shows a held prop: ox_lib, oxide-progressbar, QBCore's progressbar and zsxui. esx_progressbar, lation_ui, keep-progressbar and wasabi_uikit show no props, so with those the animation plays without the clipboard.
Config.Report = {
durationMs = 4000,
anim = { dict = 'amb@world_human_clipboard@male@base', clip = 'base', flag = 49 },
prop = { model = 'p_cs_clipboard', bone = 60309, offset = vec3(0.0, 0.0, 0.0), rotation = vec3(0.0, 0.0, 0.0) },
}Wasps
File: shared/config/wasp.lua | In the editor: Gameplay → Wasps → Wasp nests
| Setting | Type | Default | Description |
|---|---|---|---|
Config.Wasp.ladderRange | number | 2.5 | How close to the ladder spot to set it up or climb it |
Config.Wasp.killSeconds | number | 6.0 | Seconds of foam on the nest that kill it. A perfect aim is never faster |
Config.Wasp.nestRadius | number | 0.17 | Foam this close to the middle of the nest counts as on target |
Config.Wasp.agitation.base | number | 0.04 | Swarm agitation added per second, always. The swarm stings at 1 |
Config.Wasp.agitation.miss | number | 0.22 | Added per second while the foam is off the nest |
Config.Wasp.agitation.calm | number | 0.10 | Taken away per second while the foam is on the nest |
Config.Wasp.push.strength | number | 0.10 | How hard the swarm pushes the aim (a fraction of the screen per second, growing with agitation) |
Config.Wasp.push.rate | number | 0.8 | How quickly the push changes direction |
Config.Wasp.keepOnSting | number | 0.5 | Share of the foam progress kept after a sting (0.5 = half) |
Config.Wasp.sting.damage | number | 10 | Damage from a sting |
Config.Wasp.sting.fallDamage | number | 15 | Damage when the sting knocks the technician off the ladder (see Config.Unlocks.ladderFall) |
Config.Wasp.scrape.residue | number | 100.0 | How much of the foamed nest there is to scrape |
Config.Wasp.scrape.perStroke | number | 6.0 | Residue each scraper stroke removes |
Config.Wasp.scrape.minStrokeMs | number | 120 | Strokes faster than this many milliseconds are ignored |
File only
| Setting | Default | What it does |
|---|---|---|
nestModel, foamedModel | 'oxide_pest_wasp_nest', 'oxide_pest_wasp_nest_foamed' | The nest models |
ladderModel | 'prop_paint_stepl01' | The stepladder |
nestHeight | 0.322 | How far the nest hangs below its eave point |
swarmFx, foamFx | see code | The swarm and foam effects |
can | see code | The foam can (prop_cs_spray_can) and how it sits in the hand |
climb | see code | How the technician stands on the ladder, its treads and the climbing animation |
camera | out = 1.55, down = 0.85, fov = 50.0 | The close-up view of the nest |
sampleMs, minSampleMs, maxAimSpeed, idleTimeoutMs | 100, 70, 4.0, 5000 | How the aim is checked on the server. Leave these alone |
sting.shake, sting.shakeAmplitude | 'jolt_SHAKE', 0.8 | The camera shake on a sting |
scrape.model | 'prop_cs_trowel' | The scraper |
scrape.strokeLength, scrape.reach, scrape.hand | 0.05, 0.22, see code | How far one stroke travels, how far the scraper can move around the nest, and how others see it held |
Config.Wasp = {
nestModel = 'oxide_pest_wasp_nest',
foamedModel = 'oxide_pest_wasp_nest_foamed',
ladderModel = 'prop_paint_stepl01',
nestHeight = 0.322,
swarmFx = { asset = 'core', name = 'ent_amb_insect_swarm', scale = 1.0 },
can = {
model = 'prop_cs_spray_can',
bone = 28422,
offset = vec3(0.0, 0.0, -0.03),
rotation = vec3(0.0, 0.0, 0.0),
nozzle = vec3(0.0, 0.0, 0.14),
},
foamFx = { asset = 'core', name = 'weap_extinguisher', scale = 0.45 },
ladderRange = 2.5,
climb = {
reach = 2.1,
tolerance = 0.25,
maxSpotOffset = 1.5,
gap = 0.05,
backLegs = 0.19,
standBack = 0.08,
steps = {
{ height = 0.28, depth = 0.54 },
{ height = 0.58, depth = 0.49 },
{ height = 0.87, depth = 0.43 },
{ height = 1.17, depth = 0.37 },
{ height = 1.49, depth = 0.20 },
},
anim = { dict = 'amb@prop_human_movie_bulb@base', clip = 'base', flag = 1 },
},
camera = {
out = 1.55,
down = 0.85,
fov = 50.0,
},
killSeconds = 6.0,
nestRadius = 0.17,
agitation = {
base = 0.04,
miss = 0.22,
calm = 0.10,
},
push = {
strength = 0.10,
rate = 0.8,
},
sampleMs = 100,
minSampleMs = 70,
maxAimSpeed = 4.0,
keepOnSting = 0.5,
idleTimeoutMs = 5000,
sting = {
damage = 10,
fallDamage = 15,
shake = 'jolt_SHAKE',
shakeAmplitude = 0.8,
},
scrape = {
model = 'prop_cs_trowel',
residue = 100.0,
perStroke = 6.0,
strokeLength = 0.05,
minStrokeMs = 120,
reach = 0.22,
hand = { bone = 28422, offset = vec3(0.0, 0.0, 0.0), rotation = vec3(0.0, 0.0, 0.0) },
},
}Uniforms
File: shared/config/outfits.lua | In the editor: Presentation → Uniforms
An optional work uniform. When it's on, a Put On Uniform button appears in the shift menu while on shift. The player's own clothes come back when they press Remove Uniform or when the shift ends for any reason.
| Setting | Type | Default | Description |
|---|---|---|---|
Config.uniformsEnabled | boolean | false | Master switch. Turn it on only after filling in the outfits. Applies at the next clock-in |
Config.PestControlOutfitMale | table | { components = {}, props = {} } | The outfit for male characters (Advanced tab) |
Config.PestControlOutfitFemale | table | { components = {}, props = {} } | The outfit for female characters (Advanced tab) |
Config.uniformsEnabled = false
Config.PestControlOutfitMale = {
components = {},
props = {},
}
Config.PestControlOutfitFemale = {
components = {},
props = {},
}The outfits ship empty on purpose. The numbers for a piece of clothing depend on the clothing packs your server runs, so a number that gives a work shirt on one server can give something else, or nothing, on another. You need to find your own.
How an outfit is written
Each outfit has two lists:
- components: clothing. Each row has a
component_id(the clothing slot), adrawable(which item) and atexture(which colour or pattern of that item). - props: accessories. Each row has a
prop_id(the accessory slot), adrawableand atexture.
List only the slots you want to change. Slots you leave out keep the player's own clothes.
The slot numbers are fixed in GTA:
| Component slot | Holds | Prop slot | Holds | |
|---|---|---|---|---|
| 1 | Mask | 0 | Hat | |
| 2 | Hair | 1 | Glasses | |
| 3 | Arms | 2 | Ears | |
| 4 | Pants | 6 | Watch | |
| 5 | Bag | 7 | Bracelet | |
| 6 | Shoes | |||
| 7 | Accessory | |||
| 8 | Undershirt (t-shirt) | |||
| 9 | Vest | |||
| 10 | Decals | |||
| 11 | Top |
Step 1: find your numbers
- Join your server on a male character.
- Open your server's clothing menu (for example a clothing store) and put together the work outfit you want.
- For each piece, write down its slot, the item number (drawable) and the colour or texture number your clothing menu shows.
- Do the same on a female character. Male and female clothing use different numbers, so you need both.
Step 2: enter the outfits
In the settings editor (works on a running server):
- Type
/pestcontrol settingsand go to Presentation → Uniforms. - Switch to the Advanced tab.
- Open Male outfit. Under Components, press Add for each clothing piece and fill in Component (the slot), Drawable and Texture. Under Props, do the same for any hat, glasses or other accessory, using Prop, Drawable and Texture.
- Open Female outfit and do the same with your female numbers.
- Press Save Changes.
Or in the file (best before the job's first start):
Open shared/config/outfits.lua and add one line per piece, inside the right list, then save:
Config.PestControlOutfitMale = {
components = {
-- one line per clothing piece, with your own numbers:
-- { component_id = SLOT, drawable = ITEM, texture = COLOUR },
},
props = {
-- one line per accessory, with your own numbers:
-- { prop_id = SLOT, drawable = ITEM, texture = COLOUR },
},
}Replace SLOT, ITEM and COLOUR with your numbers and remove the -- at the start of each line you use. After the job has started once, a file change only applies after you restart the server and press Reset to default on Male outfit or Female outfit in the editor (see How Settings Work), so the editor is easier.
Step 3: turn uniforms on
- In
/pestcontrol settings, go to Presentation → Uniforms on the Basic tab. - Turn on Uniforms (
Config.uniformsEnabled). - Press Save Changes.
Step 4: test it
- Clock in at the depot.
- Talk to the clerk again and press Put On Uniform. You should change into the outfit and see "Work uniform equipped."
- Press Remove Uniform or clock out. Your own clothes come back.
If a piece looks wrong or invisible, its numbers don't exist in your clothing packs for that body. Check them in your clothing menu and correct them in the editor.
Model Compatibility
File: shared/config/models.lua | In the editor: Presentation → Compatibility → Model replacements
Every model the job spawns is checked before it loads. If one doesn't exist on a player's game build (older builds lack the newer props), it's skipped with a warning instead of freezing, and online admins get a notification naming it, once:
[oxide-pestcontroljob] model "m25_1_prop_m51_dog_bowl_full" is not on this game build. Set a replacement in /pestcontrol settings.Nothing is replaced automatically. You choose the replacement:
| Setting | Type | Default | Description |
|---|---|---|---|
Config.ModelSwaps | table | {} (empty) | Rows of { from = 'shipped model', to = 'your model' }. Replaces that model everywhere the job uses it |
Config.ModelSwaps = {
{ from = 'm25_1_prop_m51_dog_bowl_full', to = 'prop_cs_bowl_01' }, -- example: a different pet bowl
}In the editor, add a row under Model replacements: Replace is the model name exactly as the warning shows it, With is a model your game build has.
Base-game models the job uses, all replaceable:
| Model | Used for |
|---|---|
burrito2 | The company van |
s_m_y_pestcont_01 | The depot clerk |
prop_scn_police_torch | The UV torch |
p_cs_clipboard | The clipboard while filing the report |
prop_cs_spray_can | The foam can |
prop_cs_trowel | The scraper |
prop_paint_stepl01 | The stepladder |
prop_veg_crop_02 | The vegetable bed |
m25_1_prop_m51_dog_bowl_full | The pet bowl (needs a recent game build) |
prop_hose_nozzle | The wand, only if the job's own wand model can't load |
The job's own models (oxide_pest_sprayer_tank_m, oxide_pest_sprayer_tank_f, oxide_pest_sprayer_wand, oxide_pest_bait_station, oxide_pest_glue_trap, oxide_pest_wasp_nest, oxide_pest_wasp_nest_foamed and the oxide_pest_sign_* signs with their _uv twins) ship in the resource's stream/ folder. If one of those is reported missing, the warning says the install is incomplete: check the stream/ folder is there and restart the whole server.
Config.ModelHints and Config.SettingsCommand in the same file only set the wording of these warnings. Leave them alone.
Tutorials
File: shared/config/tutorials.lua | In the editor: Presentation → Tutorials (Advanced tab)
Config.Tutorials holds 19 first-time tips, keyed by moment (first_clockin, first_arrival, first_inspection, first_signs, first_findings, first_spray, first_wasp, first_climb, first_scrape, first_callback and so on). Each one has:
| Field | Description |
|---|---|
icon | Font Awesome icon classes, for example 'fa-solid fa-circle-play' |
title | Key of the card title in locales/en.json |
body | Key of the card text in locales/en.json |
position | Optional screen position: 'top-right' (default), 'top-left', 'bottom-right', 'bottom-left', 'center-top', 'center' or 'center-bottom' |
duration | Optional seconds on screen (default 10) |
The tip text itself lives in locales/en.json, so it can be translated.
Text and Translations
The text players see, including notifications, menus, the stage card, the findings card, the call card, phone messages, tutorials, the property editor's card and the settings editor's buttons, lives in locales/en.json. The names and help text of the settings themselves, the property names and the server console messages are not in the locale file. See Translations for how to add a language.
Server Rate Limits
The server has built-in protection against spammed actions (clocking in and out, accepting calls, logging signs, placing devices, filing reports and so on). Each action has a short cooldown so a player, or a cheat tool, can't fire it repeatedly. These limits are built in and need no configuration. If a player triggers one by clicking too fast, the action is ignored, sometimes with a "Please wait" message. They can simply try again a moment later.
Next Steps
Installation Guide
Step-by-step setup for oxide-pestcontroljob — required resources, server.cfg load order, the database, verification, phone messages, Oxide HUD, uniforms, admin permission and translations.
Feature Guide
How oxide-pestcontroljob plays — shifts, dispatch, properties, gear at the van, the UV inspection, spraying, bait stations, wasp nests, the report, callbacks, progression, pay, crews and the on-screen guidance.