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:

  1. Edit the value in its config file and save the file.
  2. Restart the whole server.
  3. Open /pestcontrol settings and press Reset to default on the setting that holds it (for example Wasps → Wasp nests for anything in Config.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

FilePurpose
shared/config.luaCore toggles, menu mode, summary mode, depot slots, testing aid
shared/config/job.luaCrew, payment, complaints, timer, call dispatch, level unlocks
shared/config/levels.luaLevel thresholds and rewards
shared/config/locations.luaThe depot
shared/config/properties.luaThe houses, restaurants and warehouses calls go to
shared/config/sites.luaHow signs are laid out, how many finish the inspection, and the pet bowl and vegetable bed props
shared/config/vehicles.luaThe company van
shared/config/visuals.luaThe depot clerk, map blips, waypoints
shared/config/gear.luaThe torch and backpack, the UV inspection, where the stage card and findings card sit
shared/config/treatment.luaThe backpack sprayer, bait stations, glue traps, report rules
shared/config/wasp.luaThe wasp nest takedown
shared/config/outfits.luaOptional on-duty uniform (off by default)
shared/config/models.luaModel replacements for older game builds
shared/config/tutorials.luaTutorial 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

SettingTypeDefaultDescription
Config.DebugbooleanfalsePrints extra detail to the server console and the F8 console. Leave off in production (Advanced tab)
Config.usePhonebooleantrueSends 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.shiftSummarystring'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.menuStylestring'nui''nui' for the full shift menu, 'menu' for a simple context menu
Config.MaxSlotsPerLocationnumber3How many technicians can be clocked in at one depot at the same time (1 to 20). A crew partner takes a slot too
Config.DebugForceSpeciesstring'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

SettingTypeDefaultDescription
Config.Crew.maxSizenumber21 for solo-only shifts, 2 to allow a partner. Crews are pairs, so higher values are lowered to 2
Config.Crew.inviteTimeoutnumber60Seconds a crew invite stays open
Config.Crew.coopBonusPercentnumber0.15Extra 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

SettingTypeDefaultDescription
Config.Payment.basePaytableroach 90, rat 100, wasp 130Base pay for each kind of call
Config.Payment.kindMultipliertablehouse 1.0, restaurant 1.2, warehouse 1.4Base pay is multiplied by this for each kind of property
Config.Payment.distanceBonusPerUnitnumber0.25Extra pay per metre, in a straight line from where the call was accepted to the property
Config.Payment.speedBonusAmountnumber25Bonus for reporting a call before its timer runs out
Config.Payment.fullFindBonusnumber40Bonus 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.glueTrapBonusnumber8Per glue trap on a cockroach trail, up to three. 0 turns it off and hides its line on the stage card
Config.Payment.wasteCostPerUnitnumber2Charged per unit of chemical wasted beyond the level's allowance
Config.Payment.exclusionPenaltynumber40Charged for spraying a pet bowl or vegetable bed
Config.Payment.stingPenaltynumber20Charged each time the wasps sting
Config.Payment.wrongTreatmentFactornumber0.4Unfinished 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.missedNestFactornumber0.7Call pay multiplier when the treatment was finished but the nest was left untreated
Config.Payment.vehicleDestroyFinenumber200Fine charged on the spot when the van is destroyed
Config.Payment.streakBonusPerDaynumber10Extra pay per call for each day of the daily streak
Config.Payment.maxStreakDaysnumber7The streak stops growing at this many days
Config.Payment.gasFeePercentnumber0.05Share of gross shift pay deducted as fuel costs (0.05 = 5%)
Config.Payment.taxPercentnumber0.10Share of gross shift pay deducted as tax (0.10 = 10%)
Config.Payment.payoutAccountstring'bank'Account net shift pay is deposited to ('bank', 'cash', and so on)
Config.Payment.fineAccountstring'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)

SettingTypeDefaultDescription
Config.Complaints.maxComplaintsnumber3Complaints before the technician is suspended from the job
Config.Complaints.timeoutMinutesnumber30How long the suspension lasts, in minutes
Config.Complaints.callbackPenaltynumber1Complaints added to each crew member when the customer calls back (treatment not finished or nest not treated)
Config.Complaints.exclusionPenaltynumber1Complaints added for spraying a pet bowl or vegetable bed
Config.Complaints.vehicleDestroyPenaltynumber1Complaints added to the crew leader when the van is destroyed
Config.Complaints.deathPenaltynumber1Complaints added when the technician dies or goes down on shift
Config.Complaints.decayPerJobnumber1Complaints removed for each clean call (no callback)
Config.Complaints.callbackDelaySecondsnumber90How 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

SettingTypeDefaultDescription
Config.Timer.baseTimenumber420Seconds given for every call, before distance (7 minutes)
Config.Timer.timePerDistanceUnitnumber0.20Extra seconds per metre from where the call was accepted to the property
Config.Timer.expiredPenaltynumber0.85Pay 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

SettingTypeDefaultDescription
Config.Calls.minIntervalSecondsnumber15Shortest wait between calls
Config.Calls.maxIntervalSecondsnumber45Longest wait between calls
Config.Calls.responseTimeSecondsnumber30How long an incoming call rings before it's missed
Config.Calls.maxMissedCallsnumber3Missed or rejected calls before the shift ends automatically
Config.Calls.cancelCooldownSecondsnumber60Wait before the next call after cancelling one. 0 = no extra wait
Config.Calls.arrivalMarginnumber15.0Metres beyond a property's radius that count as arrived
Config.Calls.maxTravelSpeednumber50.0Fastest 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.speciesWeightstableroach 45, rat 35, wasp 20Relative 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.

SettingTypeDefaultDescription
Config.Unlocks.restaurantnumber2Calls can go to restaurants
Config.Unlocks.warehousenumber3Calls can go to warehouses
Config.Unlocks.exclusionsnumber2Pet bowls and vegetable beds appear on site and must not be sprayed
Config.Unlocks.waspnumber3Wasp calls start. A technician also needs this level to climb to a nest
Config.Unlocks.speciesHintnumber3The 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.ladderFallnumber4A wasp sting knocks the technician off the ladder
Config.Unlocks.fullFindBonusnumber5Finding 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:

FieldTypeDescription
titlestringThe level's name, as a key from locales/en.json (for example 'level.trainee')
requiredJobsnumberCalls reported in total before reaching this level
payMultipliernumberAll call pay is multiplied by this at this level
tipChancenumberChance the customer tips (0.25 = 25%)
tipRangetableSmallest and largest possible tip, { min, max }
pingRadiusnumberWhile the torch is out, the nearest unfound sign within this many metres gets a marker with its distance and a sound. 0 = no pings
wasteAllowancenumberFree 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.

FieldTypeRequiredWhat it does
namestringYesThe 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
coordsvector3YesWhere the clerk stands (Clerk spot in the editor). Players walk up to this spot to clock in and out
headingnumberYesWhich way the clerk faces, in degrees (0 to 360) (Facing)
vehicleSpawnvec4YesWhere 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
menuCameratableNoMenu 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

  1. Open /pestcontrol settings and go to World → Locations.
  2. Add a row. Give it a name.
  3. Use Place in world on Clerk spot: it previews the clerk and also sets Facing.
  4. Use Place in world on Van spawn: it previews the van.
  5. 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.

FieldTypeRequiredWhat it does
labelstringYesThe property's name (Name in the editor), shown on the call card and the map
kindstringYes'house', 'restaurant' or 'warehouse'. Each kind opens at a level (see Unlocks)
centervector3YesThe middle of the building, at ground height. The call blip goes here
radiusnumberYesMetres from the centre that count as on site (5 to 150 in the editor). Devices must be placed inside it
segmentstableYesThe 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
wasptableNoWasp 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
exclusionstableNoNo-spray spots. Each has a kind ('petbowl' or 'vegbed') and coords (x, y, z, heading) by the foundation line
spotstableNoSign 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:

  1. Open /pestcontrol settings and go to World → Properties.
  2. Add a row. Type a Name and choose a Kind.
  3. 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.
  4. 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.
  5. 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.
  6. No-spray spots (optional): choose Pet bowl or Vegetable bed and place the Spot by the foundation line.
  7. 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.

SettingTypeDefaultDescription
Config.Sites.signstablehouse 7, restaurant 9, warehouse 11Signs per call by kind of property, besides the nest
Config.Sites.requiredtablehouse 4, restaurant 5, warehouse 6Signs 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.spacingnumber1.6Minimum metres between two signs
Config.Sites.nestPullnumber8.0Signs thin out over about this many metres from the nest. Smaller = a tighter cluster
Config.Sites.straysnumber0.08How likely a sign lands far from the nest (0 = never)
Config.Sites.spotStepnumber1.5Metres between possible sign spots along the foundation line
Config.Sites.mixtablesee belowRelative chance of each sign type for each pest
Config.Sites.renderDistancenumber80.0Pet 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.

SettingTypeDefaultDescription
Config.Exclusions.petbowl.modelstring'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.radiusnumber0.9How close the spray may come before it counts as sprayed
Config.Exclusions.vegbed.modelstring'prop_veg_crop_02'The vegetable bed prop
Config.Exclusions.vegbed.radiusnumber1.6How 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

SettingTypeDefaultDescription
Config.Vehicle.modelstring'burrito2'The van spawned at clock-in (the base-game Bugstars van)
Config.Vehicle.platestring'BUGSTARS'Number plate text, up to 8 characters
Config.Vehicle.healthCheckIntervalnumber1000Milliseconds between van health checks
Config.Vehicle.destroyedThresholdnumber150If the van's engine health drops below this, it counts as destroyed
Config.Vehicle.damageChargeRatenumber0.50Charged per point of body health lost below the threshold, at clock-out
Config.Vehicle.damageChargeThresholdnumber950Only charge below this body health, so minor scratches are free
Config.Vehicle.liverynumber-1Livery number for models that have several. -1 = the model's default
Config.Vehicle.extrastable or nilnilForce 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.doorslist{ 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.offsetnumber-3.4Spot 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.radiusnumber1.5Spot size. How close to that spot the technician must stand
Config.Vehicle.rear.durationMsnumber1500Reach 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.animtablesee codeReach 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

SettingTypeDefaultDescription
Config.NpcModelstring's_m_y_pestcont_01'The clerk standing at each depot
Config.NpcScenariostring'WORLD_HUMAN_CLIPBOARD'What the clerk is doing (Advanced tab)
Config.Blip.enabledbooleantrueShow the depot on the map
Config.Blip.spritenumber273Depot blip icon
Config.Blip.colornumber2Depot blip colour
Config.Blip.scalenumber0.8Depot blip size
Config.EventBlip.spritenumber463Icon of the call's map marker (Advanced tab)
Config.EventBlip.colornumber1Colour of the call's map marker and route
Config.EventBlip.scalenumber1.2Size of the call's map marker
Config.Waypoints.heightOffsetnumber5.0Metres above the property the floating marker sits (Advanced tab)
Config.Waypoints.updateIntervalnumber0Milliseconds between marker updates (0 = every frame)
Config.Waypoints.scaleMinnumber0.5Marker size when far away
Config.Waypoints.scaleMaxnumber1.0Marker size when close
Config.Waypoints.scaleNearDistnumber30.0Distance at which the marker is largest
Config.Waypoints.scaleFarDistnumber300.0Distance 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).

SettingTypeDefaultDescription
Config.Kit.renderDistancenumber60.0Other players' tanks, torches and foam cans are drawn within this range
Config.Kit.tank.male / femalestring'oxide_pest_sprayer_tank_m' / 'oxide_pest_sprayer_tank_f'The backpack tank model for each body
Config.Kit.tank.bonenumber24818Bone the tank hangs from (upper spine)
Config.Kit.tank.outlettablesee codeWhere the hose leaves each tank, in the tank's own space
Config.Kit.tank.hideInVehiclesbooleantrueHide the tank while seated in a vehicle
Config.Kit.torch.modelstring'prop_scn_police_torch'The UV torch prop: the torch the game's security guards carry
Config.Kit.torch.bonenumber60309Bone the torch attaches to (left hand)
Config.Kit.torch.offset / rotationvector3vec3(0.0, 0.0, 0.0)How the torch sits in the hand. Zero, the way the game itself places it
Config.Kit.torch.bulbvector3vec3(0.219, 0.0, 0.0)Bulb in the editor. Where the beam starts, in the torch's own space
Config.Kit.torch.animtablesee codeThe 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

SettingTypeDefaultDescription
Config.Inspection.claimRangenumber2.6How close a sign must be to log it
Config.Inspection.claimAnglenumber20.0How near the middle of the beam it must be, in degrees
Config.Inspection.claimHoldMsnumber500Milliseconds the sign must stay in the beam
Config.Inspection.serverRangenumber3.5The server accepts a log within this distance of the technician (Advanced)
Config.Inspection.serverAnglenumber75.0The technician must face the sign within this many degrees (Advanced)
Config.Inspection.minGapMsnumber600Milliseconds between two logs by one technician (Advanced)
Config.Inspection.minSecondsOnSitenumber2Seconds after arriving before a log counts (Advanced)
Config.Inspection.beamPitchnumber-15.0How far other players' torch beams point down, in degrees
Config.Inspection.cone.rangenumber9.0How far the beam reveals signs
Config.Inspection.cone.anglenumber26.0Half-angle of the beam. Signs fade out toward its edge
Config.Inspection.lighttablesee codeThe 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 / bnumber170 / 110 / 255Sign 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.lightRangenumber1.4How far that light reaches, in metres
Config.Inspection.glow.lightIntensitynumber3.0How bright that light is. Logged signs get about half. 0 turns the light off; the sign's glowing model still shows
Config.Inspection.spotstablesee codeThe 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.groundtablesee codeHow 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.pingIntervalMsnumber2000How 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.

SettingTypeDefaultDescription
Config.HudLayout.topLeft.topnumber30Stage card → From the top. Percent of the screen height from the top (0 to 95)
Config.HudLayout.topLeft.sidenumber3Stage card → From the left. Percent of the screen height in from the left edge (0 to 80)
Config.HudLayout.topRight.topnumber30Findings card → From the top. Percent of the screen height from the top (0 to 95)
Config.HudLayout.topRight.sidenumber3Findings card → From the right. Percent of the screen height in from the right edge (0 to 80)
Config.UseHudAccentbooleantrueFollow 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 = true

Treatment

File: shared/config/treatment.lua

Config.Spray (Backpack sprayer)

In the editor: Gameplay → Treatment → Backpack sprayer

SettingTypeDefaultDescription
Config.Spray.tankUnitsnumber60.0Chemical units the backpack holds. Refilled at the van
Config.Spray.needPerMetrenumber0.8Units a metre of foundation takes to fill
Config.Spray.minCoveragenumber0.85Share of the foundation a cockroach treatment must fill (0.85 = 85%)
Config.Spray.reachnumber4.0A run only fills while the technician is this close to it
Config.Spray.hitWidthnumber1.75Spray landing this close to the foundation line counts for that run
Config.Spray.harborageNeednumber2.0Units on the cockroach harbourage to treat the nest
Config.Spray.harborageRadiusnumber1.2How close to the harbourage the spray must land
Config.Spray.exclusionUnitsnumber0.8Units on a pet bowl or vegetable bed before it counts as sprayed
Config.Spray.exclusionAnglenumber25.0Spraying toward a pet bowl or vegetable bed within this many degrees also counts
Config.Spray.patternstablefan and streamThe spray patterns, switched with the mouse wheel. Each has a name, flow (units per second), maxReach (metres), speed and splashRadius

The two shipped patterns:

PatternFlowReachSpeedSplash radius
fan1.0 units/s3.2 m5.0 m/s0.8 m
stream0.7 units/s5.5 m8.0 m/s0.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.

SettingDefaultWhat it does
coverageUpdateMs500How often the crew's coverage display refreshes, in milliseconds
length, warnAt, leashSlack, teleport3.0, 0.95, 3.0, 30.0Hose 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
maxTargets4Most runs and spots the spray is shared between at once
renderDistance60.0Other players' sprayers are drawn within this range
fxDistance30.0The spray effect is drawn within this range
maxRopes6Most hoses drawn at once
tickMs1000How often the server drains the tank while spraying, in milliseconds
hitsInterval800How often the spray reports where it lands, in milliseconds
ropesee codeThe hose's look
pressure.minRangeScale0.8Spray reach when the tank is almost empty, as a share of full reach
pressure.sputterBelow0.05The spray sputters below this much chemical (5%)
pitchmin -50.0, max 15.0How far down and up the wand can aim, in degrees
patterns[].gravity, patterns[].fxsee codeHow each pattern falls and its effect
splashsee codeThe effect where the spray lands
models.nozzle'oxide_pest_sprayer_wand'The wand model
hand, socketssee codeHow 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
animsee codeThe aiming animations
keysspray = 24, patternUp = 241, patternDown = 242, putBack = 194Game 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

SettingTypeDefaultDescription
Config.Devices.bait.modelstring'oxide_pest_bait_station'The bait station model
Config.Devices.bait.maxnumber4Bait stations per call
Config.Devices.glue.modelstring'oxide_pest_glue_trap'The glue trap model
Config.Devices.glue.maxnumber4Glue traps per call
Config.Devices.placeRangenumber5.0How far from the technician a device may be put down
Config.Devices.spacingnumber1.5Minimum metres between two devices
Config.Devices.placeAnimtablekneelThe animation while placing
Config.Devices.placeDurationnumber1800Milliseconds 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.

SettingTypeDefaultDescription
Config.Treatment.runwayDistancenumber1.2A bait station within this of the foundation line can be on a runway...
Config.Treatment.runwaySignRangenumber4.5...when a rat sign is within this of it
Config.Treatment.minStationsnumber2Bait stations on runways a rat treatment needs
Config.Treatment.burrowRangenumber4.0A bait station this close to the burrow treats the rats' nest
Config.Treatment.glueRangenumber2.5A 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.

SettingTypeDefaultDescription
Config.Report.durationMsnumber4000Paperwork time. How long filing takes, in milliseconds
Config.Report.animtablesee codeClipboard animation: the game's own clipboard pose
Config.Report.proptablesee codeClipboard: 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

SettingTypeDefaultDescription
Config.Wasp.ladderRangenumber2.5How close to the ladder spot to set it up or climb it
Config.Wasp.killSecondsnumber6.0Seconds of foam on the nest that kill it. A perfect aim is never faster
Config.Wasp.nestRadiusnumber0.17Foam this close to the middle of the nest counts as on target
Config.Wasp.agitation.basenumber0.04Swarm agitation added per second, always. The swarm stings at 1
Config.Wasp.agitation.missnumber0.22Added per second while the foam is off the nest
Config.Wasp.agitation.calmnumber0.10Taken away per second while the foam is on the nest
Config.Wasp.push.strengthnumber0.10How hard the swarm pushes the aim (a fraction of the screen per second, growing with agitation)
Config.Wasp.push.ratenumber0.8How quickly the push changes direction
Config.Wasp.keepOnStingnumber0.5Share of the foam progress kept after a sting (0.5 = half)
Config.Wasp.sting.damagenumber10Damage from a sting
Config.Wasp.sting.fallDamagenumber15Damage when the sting knocks the technician off the ladder (see Config.Unlocks.ladderFall)
Config.Wasp.scrape.residuenumber100.0How much of the foamed nest there is to scrape
Config.Wasp.scrape.perStrokenumber6.0Residue each scraper stroke removes
Config.Wasp.scrape.minStrokeMsnumber120Strokes faster than this many milliseconds are ignored

File only

SettingDefaultWhat it does
nestModel, foamedModel'oxide_pest_wasp_nest', 'oxide_pest_wasp_nest_foamed'The nest models
ladderModel'prop_paint_stepl01'The stepladder
nestHeight0.322How far the nest hangs below its eave point
swarmFx, foamFxsee codeThe swarm and foam effects
cansee codeThe foam can (prop_cs_spray_can) and how it sits in the hand
climbsee codeHow the technician stands on the ladder, its treads and the climbing animation
cameraout = 1.55, down = 0.85, fov = 50.0The close-up view of the nest
sampleMs, minSampleMs, maxAimSpeed, idleTimeoutMs100, 70, 4.0, 5000How the aim is checked on the server. Leave these alone
sting.shake, sting.shakeAmplitude'jolt_SHAKE', 0.8The camera shake on a sting
scrape.model'prop_cs_trowel'The scraper
scrape.strokeLength, scrape.reach, scrape.hand0.05, 0.22, see codeHow 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.

SettingTypeDefaultDescription
Config.uniformsEnabledbooleanfalseMaster switch. Turn it on only after filling in the outfits. Applies at the next clock-in
Config.PestControlOutfitMaletable{ components = {}, props = {} }The outfit for male characters (Advanced tab)
Config.PestControlOutfitFemaletable{ 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), a drawable (which item) and a texture (which colour or pattern of that item).
  • props: accessories. Each row has a prop_id (the accessory slot), a drawable and a texture.

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 slotHoldsProp slotHolds
1Mask0Hat
2Hair1Glasses
3Arms2Ears
4Pants6Watch
5Bag7Bracelet
6Shoes
7Accessory
8Undershirt (t-shirt)
9Vest
10Decals
11Top

Step 1: find your numbers

  1. Join your server on a male character.
  2. Open your server's clothing menu (for example a clothing store) and put together the work outfit you want.
  3. For each piece, write down its slot, the item number (drawable) and the colour or texture number your clothing menu shows.
  4. 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):

  1. Type /pestcontrol settings and go to Presentation → Uniforms.
  2. Switch to the Advanced tab.
  3. 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.
  4. Open Female outfit and do the same with your female numbers.
  5. 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

  1. In /pestcontrol settings, go to Presentation → Uniforms on the Basic tab.
  2. Turn on Uniforms (Config.uniformsEnabled).
  3. Press Save Changes.

Step 4: test it

  1. Clock in at the depot.
  2. Talk to the clerk again and press Put On Uniform. You should change into the outfit and see "Work uniform equipped."
  3. 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:

SettingTypeDefaultDescription
Config.ModelSwapstable{} (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:

ModelUsed for
burrito2The company van
s_m_y_pestcont_01The depot clerk
prop_scn_police_torchThe UV torch
p_cs_clipboardThe clipboard while filing the report
prop_cs_spray_canThe foam can
prop_cs_trowelThe scraper
prop_paint_stepl01The stepladder
prop_veg_crop_02The vegetable bed
m25_1_prop_m51_dog_bowl_fullThe pet bowl (needs a recent game build)
prop_hose_nozzleThe 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:

FieldDescription
iconFont Awesome icon classes, for example 'fa-solid fa-circle-play'
titleKey of the card title in locales/en.json
bodyKey of the card text in locales/en.json
positionOptional screen position: 'top-right' (default), 'top-left', 'bottom-right', 'bottom-left', 'center-top', 'center' or 'center-bottom'
durationOptional 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