Skip to content

Pay 'n' Spray and mod shops

The game's Pay 'n' Sprays and mod shops run on the server and work as they do in the game. Both are on by default. Players' games run no garage logic: in single player it was the game's own code and mission script, for one player.

Turn either off to build your own from garages and menus, or keep them and change how they work with configure.

server.payNSpray.configure({ price: 250 });          // dearer resprays
server.modShops.configure({ priceScale: 1.5 });      // every part 50% more
server.modShops.enabled = false;                      // no mod shops at all

Pay 'n' Spray

server.payNSpray (PayNSpray).

  1. A player drives in. A police car (or the like) is turned away ("Whoa! I don't touch nothing THAT hot!"), an RC toy too ("I don't do toys!"), and so is anyone without the price ("No more freebies!"). Each gets the game's sound.
  2. Once the car is wholly inside, the door shuts and holds them still while the spray hisses.
  3. The car is repaired and resprayed in another colour, and the price is taken, and it comes out clean (vehicle.dirt 0). A car modified in a mod shop (vehicle.modded) keeps its paint: it's only repaired and cleaned.
  4. Their wanted level is cleared on parole: the old stars flash for 20 seconds, and a crime brings them back.
  5. The door opens with the game's notice, and a cloud of dust in the new colour rolls out.

It fires vehicleRespray.

enabled

enabled: boolean

Whether they work. Default true. Turned off, their doors stay as they are: open or shut them yourself as you need.

configure(options)

configure(options: PayNSprayOptions): PayNSpray

Changes how they work. Only the options given change.

Option Type Default Description
price number 100 What a respray costs.
clearWanted boolean true Clear their wanted level, on parole.
repair boolean true Repair the car.
respray boolean true New colours, never the one it had.
dust boolean true The cloud of dust from the door.
blips boolean true The game's spray can icon on everyone's radar, near each one.
hotModels number[] the game's police vehicles Models turned away as too hot.
onEnter (player, vehicle, garage) => boolean \| string \| number \| void Asked as they drive in, after the game's own checks.

onEnter can:

  • return false to turn them away (tell them why yourself);
  • return a text to turn them away with that notice;
  • return a number to use it as the price this time.
server.payNSpray.configure({
  onEnter: (player, vehicle) => {
    if (player.data.banned) return "~r~We don't serve your kind.";
    if (player.data.vip) return 0;   // free for VIPs
  },
});

garages

readonly garages: Garage[]

The Pay 'n' Spray garages.


Mod shops

server.modShops (ModShops).

There are five: TransFender in Temple, Doherty and Come-A-Lot, Loco Low Co in Willowfield, and Wheel Arch Angels in Ocean Flats.

Each shop works on its own cars (ModShops.shopFor(model)):

  • Loco Low Co: lowriders;
  • Wheel Arch Angels: street racers;
  • TransFender: other cars.

  • A player drives in through the door. A car the shop doesn't work on is turned away ("You cannot mod this vehicle here.").

  • At the game's spot just inside, the door shuts behind them and the screen fades. The car is put on the shop's own floor, the game's interior for it, in a world of its own, so others can use the shop at the same time. Passengers come along.
  • A menu like the game's, under the shop's sign on every level (TransFender's and Wheel Arch Angels' own, from the game; Loco Low Co has none, so its name), lists the areas the car has parts for: Colors, Spoilers, Hood, Exhausts, Wheels, Paint Jobs, and so on. The keys for it show along the bottom left. The radar and chat are hidden meanwhile, and come back as the gamemode had them.
  • Each area lists its parts with prices. The car shows each part as it's chosen, and the camera turns to that part of the car. Colours are the game's grid of 64, 8 across, the first chosen (and shown on the car) to begin with. New paint repairs and cleans the car, as the game's script does. With plates on (the default), a Licence Plate area lets them type a plate of their own. Under the grid: the car's colour now, and the chosen one's price.
  • Enter buys, and Backspace (or F, as in the game) goes back. Something already on the car, or too dear, gets the game's notice and sound.
  • Backing out of the areas puts the car back at the spot inside the door, facing in, as the game does: they reverse out.

Anything chosen but not bought is taken off again.

All of this is the game's own: part names and prices from its shopping.dat, and from its mod shop script (carmod1.scm) the spots, the cameras, the headings, the colour and paint job prices, the sounds and the notices.

It fires modShop on the way in and out (with the shop's id), vehicleLivery, and vehicleMod, vehiclePaintjob and vehicleRespray for each purchase.

enabled

enabled: boolean

Whether they work. Default true. On, their doors are open, as the game's script opened them. Off, they're shut, as the game starts them.

configure(options)

configure(options: ModShopOptions): ModShops

Changes how every shop works. Only the options given change. Each shop can have its own options on top (shop(id)), and so can each player (forPlayer).

Option Type Default Description
enabled boolean true Open. A closed game shop's door is shut.
shops { transFender?, locoLowCo?, wheelArchAngels? } all true Which of the game's shops are open.
title string the shop's name The menu's title (text, or gxt()).
logo string \| null the shop's sign An image over the menu: a client file, or a game texture ("game:<txd>/<texture>"). null shows the title alone.
accepts (model) => boolean the game's rule Which cars the shop works on. The game's shops take their own (lowriders, street racers, the rest); a shop you make takes any car with parts.
sells number its own Whose parts it sells: ModShop values added together. A shop you make sells all three.
areas { [area]: boolean } all on Each area on or off: colors, finishes, liveries, paintjobs, plate, spoilers, hood, vents, sideSkirts, exhausts, nitro, frontBumper, rearBumper, lights, roof, wheels, frontBullbars, rearBullbars, misc, stereo, hydraulics. An area also hides when there's nothing in it for the car, and paintjobs defaults to off at TransFender, as in the game.
parts { [part]: number \| false } Each part by id (MOD_SHOP_PARTS; each wheel is its own part): a price (used as is, without priceScale), or false to not sell it.
priceScale number 1 Every other price is multiplied by this.
price (part, price, vehicle, player) => number \| null \| void A part's price for this car, after the rest. Return a number to change it, or null to not sell it.
colors number[] the game's 64, then yours Exactly which colour ids the colour grid offers, 8 to a row.
gameColors boolean true The game's 64 colours in the grid (when colors isn't given). false leaves only your own (server.vehicleColors).
hideColors number[] Colours left out of the grid.
colorPrice number 150 (the game's) A colour's price.
paintjobPrice number 500 (the game's) One of the game's paint jobs.
finishes ModShopFinish[] DEFAULT_MOD_SHOP_FINISHES The Paint Finish area's list (below).
finishPrice number 1000 A finish's price, where it has none of its own.
liveries ModShopLivery[] none The Liveries area's list (below). With none, the area is hidden.
liveryPrice number 2000 A livery's price, where it has none of its own.
platePrice number 250 A plate's price.
blips boolean true The game's mod garage icon on everyone's radar, near each open shop.
onEnter (player, vehicle, shop, garage) => boolean \| string \| void Asked as they drive in, after the shop's own checks. Return false to turn them away (tell them why yourself), or a text to turn them away with that notice.
forPlayer (player, vehicle, shop) => ModShopOptions \| void Options for this player and car, over the rest (see Per player).
server.modShops.configure({
  gameColors: false,                                       // only your own colours
  parts: { 1010: false, 1080: 2500 },                      // no 10x nitro; the Switch wheels at $2500
  areas: { hydraulics: false },
  onEnter: (player) => (player.data.level < 5 ? "~r~Come back at level 5." : undefined),
});

Finishes

interface ModShopFinish {
  name: string;                       // as the menu shows it
  finish: PaintFinish | null;         // null: standard paint
  price?: number;
  available?: (player, vehicle) => boolean | string | void;
}

The list starts as DEFAULT_MOD_SHOP_FINISHES (Standard, Metallic, Pearl, Satin, Matte, Colour Shift). Give your own list to reorder it, remove finishes or add your own paint finish shaders.

available decides who may buy one: - true (or nothing): everyone; - false: hidden from them; - a text: shown, locked, with the text in place of the price.

import { DEFAULT_MOD_SHOP_FINISHES } from "@sagl/server";

server.modShops.configure({
  finishes: [
    ...DEFAULT_MOD_SHOP_FINISHES,
    { name: "Candy", finish: { shader: "shaders/candy.hlsl", params: { depth: 0.6 } }, price: 5000,
      available: (player) => player.data.donor ? true : "~r~Donors only" },
  ],
});

Liveries

interface ModShopLivery {
  name: string;
  livery: Livery | null;              // null: none (to take one off)
  price?: number;
  models?: number[];                  // only these models (a livery drawn for one layout)
  available?: (player, vehicle) => boolean | string | void;
}

Liveries (paint jobs of your own) shown on the car as they're chosen.

server.modShops.configure({
  liveries: [
    { name: "None", livery: null, price: 0 },
    { name: "Sticker Bomb", livery: { texture: "liveries/stickerbomb.jpg" } },
    { name: "Flames", livery: { texture: "game:savanna1/savanna92body256a", mapping: "uv", blend: "multiply" }, models: [567] },
  ],
});

Per player

forPlayer gives options for one player and car, on top of everything else, when they drive in.

server.modShops.configure({
  forPlayer: (player) => {
    if (player.data.staff) return { priceScale: 0 };                            // free for staff
    if (!player.data.donor) return { finishes: DEFAULT_MOD_SHOP_FINISHES.slice(0, 2) };  // Standard, Metallic
  },
});

shop(id), shops

shop(id: string): ModShopSite | undefined
readonly shops: ModShopSite[]

One shop, and every shop. The game's are "transfender-ls", "transfender-sf", "transfender-lv", "locolow" and "waa"; yours have the ids you gave them. A shop's own options go over everyone's.

server.modShops.shop("waa").configure({ priceScale: 1.5, areas: { plate: false } });
server.modShops.shop("transfender-lv").configure({ enabled: false });
ModShopSite
id Its id.
shop The game's shop it is (ModShop), or 0 for one you made.
floor Whose shop floor it uses (ModShop).
garage Its garage, or null.
position Where a car starts it.
configure(options) Its own options.
remove() Takes away a shop you made.

create(spec)

create(spec: ModShopCreate): ModShopSite

Makes a mod shop of your own. A car driving onto at goes to one of the game's shop floors, with any options you give.

Field Type Default Description
id string "custom-1", ... Its id, for shop(id).
at Vector3 Where a car starts it: the middle of the spot it drives onto.
size Vector3 4 x 4 x 2 How far from at (m, each way) counts.
exitHeading number as it came in Which way the car faces when it's put back at at.
floor number ModShop.TransFender Whose shop floor it uses: its interior and cameras.
garage Garage none A garage whose door shuts behind them.
...options Any of configure's.
server.modShops.create({
  id: "docks", at: { x: 2770, y: -2400, z: 13.6 }, floor: ModShop.WheelArchAngels,
  title: "Docks Tuning", logo: "ui/docks.png", priceScale: 0.8,
});

garages

readonly garages: { garage: Garage; shop: number }[]

The mod shop garages, and which shop each is (ModShop).

isShopping(player), kickOut(player)

isShopping(player: Player): boolean
kickOut(player: Player): Promise<void>

Whether a player is in a shop now, and sending them out of it as if they'd left.

ModShops.shopFor(model)

static shopFor(model: number): number

Which shop (ModShop) works on a vehicle model, or 0 for none.

ModShop

Name Value
TransFender 1
LocoLowCo 2
WheelArchAngels 4

Data

MOD_SHOP_PARTS lists every part the shops sell. Each entry has:

  • part: the component id;
  • gxt: its name, as a GXT key for gxt();
  • price: the game's price;
  • shops: the ModShops that sell it, added together;
  • cat: its area, the n of the game's CARMn text;
  • pair: for left and right halves sold as one, the other half.

CAR_COLORS lists the game's car colours, 0-127, as 0xRRGGBB, and MOD_SHOP_COLORS the 64 in the shops' grid (colour ids, row by row).

MOD_SHOP_SITES holds each shop's spots, floor, cameras and exit heading, from its mission script.

All three come from the game's own files (scripts/gen-modshop-data.py).