Repository: overextended/ox_core Branch: main Commit: d20de0b00419 Files: 102 Total size: 454.8 KB Directory structure: gitextract_tzxqlagq/ ├── .editorconfig ├── .github/ │ ├── actions/ │ │ └── bump-package-version.js │ └── workflows/ │ └── release.yml ├── .gitignore ├── .npmignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── biome.json ├── build.js ├── client/ │ ├── config.ts │ ├── death.ts │ ├── index.ts │ ├── player/ │ │ ├── index.ts │ │ └── status.ts │ ├── spawn.ts │ ├── tsconfig.json │ ├── utils.ts │ └── vehicle/ │ ├── index.ts │ └── parser.ts ├── common/ │ ├── config.ts │ ├── data/ │ │ ├── hospitals.json │ │ ├── vehicleStats.json │ │ └── vehicles.json │ ├── index.ts │ ├── locales.ts │ ├── tsconfig.json │ └── vehicles.ts ├── lib/ │ ├── client/ │ │ ├── index.ts │ │ ├── init.lua │ │ ├── player.lua │ │ └── player.ts │ ├── index.ts │ ├── init.lua │ ├── server/ │ │ ├── account.lua │ │ ├── account.ts │ │ ├── index.ts │ │ ├── init.lua │ │ ├── player.lua │ │ ├── player.ts │ │ ├── vehicle.lua │ │ └── vehicle.ts │ └── tsconfig.json ├── locales/ │ ├── ar.json │ ├── bg.json │ ├── cs.json │ ├── da.json │ ├── de.json │ ├── en.json │ ├── es.json │ ├── et.json │ ├── fr.json │ ├── hu.json │ ├── it.json │ ├── jp.json │ ├── lt.json │ ├── nl.json │ ├── no.json │ ├── pl.json │ ├── ro.json │ ├── ru.json │ ├── sk.json │ ├── tr.json │ ├── zh-cn.json │ └── zh-tw.json ├── package.json ├── server/ │ ├── accounts/ │ │ ├── class.ts │ │ ├── db.ts │ │ ├── index.ts │ │ └── roles.ts │ ├── bridge/ │ │ ├── index.ts │ │ ├── npwd.ts │ │ └── ox_inventory.ts │ ├── classInterface.ts │ ├── commands.ts │ ├── config.ts │ ├── db/ │ │ ├── config.ts │ │ ├── index.ts │ │ ├── pool.ts │ │ └── schema.ts │ ├── groups/ │ │ ├── db.ts │ │ └── index.ts │ ├── index.ts │ ├── player/ │ │ ├── class.ts │ │ ├── commands.ts │ │ ├── db.ts │ │ ├── events.ts │ │ ├── index.ts │ │ ├── license.ts │ │ ├── loading.ts │ │ └── status.ts │ ├── tsconfig.json │ ├── utils.ts │ └── vehicle/ │ ├── class.ts │ ├── commands.ts │ ├── db.ts │ ├── events.ts │ ├── index.ts │ └── parser.ts ├── sql/ │ └── install.sql ├── tsconfig.json └── types/ └── index.ts ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ root = true [*] indent_style = space indent_size = 2 end_of_line = crlf charset = utf-8 insert_final_newline = true max_line_length = 120 [*.lua] indent_size = 4 max_line_length = 160 ================================================ FILE: .github/actions/bump-package-version.js ================================================ const packageJson = await Bun.file('./package.json').json(); const newVersion = process.env.TGT_RELEASE_VERSION; packageJson.version = newVersion.replace('v', ''); await Bun.write('./package.json', JSON.stringify(packageJson, null, 2)); ================================================ FILE: .github/workflows/release.yml ================================================ name: Create release on: push: tags: - "v*.*.*" permissions: id-token: write # Required for OIDC contents: write jobs: create-release: if: github.actor_id != 278903378 runs-on: ubuntu-latest steps: - name: Install zip run: sudo apt install zip - name: Install Bun uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Generate GitHub App token id: app_token uses: tibdex/github-app-token@v2 with: app_id: ${{ secrets.APP_ID }} private_key: ${{ secrets.APP_PRIVATE_KEY }} - name: Get latest code uses: actions/checkout@v4 with: fetch-depth: 0 ref: ${{ github.event.repository.default_branch }} token: ${{ steps.app_token.outputs.token }} - name: Setup Node.js uses: actions/setup-node@v6 with: node-version: "24" - name: Install Bun uses: oven-sh/setup-bun@v2 with: bun-version: latest - name: Bump package version run: bun run .github/actions/bump-package-version.js env: TGT_RELEASE_VERSION: ${{ github.ref_name }} - name: Push version bump change uses: EndBug/add-and-commit@v9 with: add: package.json push: true default_author: github_actions message: "chore: bump version to ${{ github.ref_name }}" - name: Build run: | bun install --frozen-lockfile bun run build env: TGT_RELEASE_VERSION: ${{ github.ref_name }} - name: Bundle files run: | mkdir -p ./temp/ox_core/common cp ./{LICENSE,README.md,fxmanifest.lua} ./temp/ox_core cp -r ./{lib,locales,sql,dist} ./temp/ox_core cp -r ./common/data ./temp/ox_core/common/data cd ./temp && zip -r ../ox_core.zip ./ox_core - name: Publish package to npm registry run: npm publish --access public - name: Create release uses: "marvinpinto/action-automatic-releases@v1.2.1" with: repo_token: ${{ secrets.GITHUB_TOKEN }} prerelease: false files: ox_core.zip - name: Update tag uses: EndBug/latest-tag@v1 with: ref: ${{ github.ref_name }} ================================================ FILE: .gitignore ================================================ # ignore node_modules dist fxmanifest.lua .yarn.installed *.zip **.d.ts *.tgz *.tsbuildinfo /package/ /temp/ # keep !./build.js ================================================ FILE: .npmignore ================================================ !/package/ !**.d.ts !**.js ================================================ FILE: CONTRIBUTING.md ================================================ ## Found a bug? - Check if the bug has already been reported under under [Issues](https://github.com/overextended/ox_core/issues). - If an **active** issue matches your own, provide additional information on the existing issue. - If there is no **open** issue related to the bug, create a new issue. Include a **descriptive title and clear description** with as much relevant information as possible, and include **code samples** or **reproduction steps**. - Use the relevant bug report template when creating an issue. ## Patched a bug? - Open a new pull request including **only** the related changes. - Clearly describe the problem being fixed, and the solution. If the patch resolves any issues, mention them in the description. ## Want to share an improvement or add a new feature? - Create an issue discussing the change and wait for feedback. - If you've already worked on the change you can submit a **draft** pull request for feedback and review. - Not all features and changes are desired! Changes may be messy, poorly-planned, incomplete, or simply incompatible with our design philosophy. ## Is your change cosmetic (e.g. formatting)? - We will not accept pull requests that do not make substantial changes to the stability or functionality of the resource. ## Submitting pull requests - Fork the repo and create a new branch. - If relevant, include example code to demonstrate your changes. - If you have modified or changed APIs, submit the changes to our [documentation](https://github.com/overextended/docs). - Ensure your coding style is consistent with the project. ================================================ FILE: LICENSE ================================================ GNU LESSER GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. 0. Additional Definitions. As used herein, "this License" refers to version 3 of the GNU Lesser General Public License, and the "GNU GPL" refers to version 3 of the GNU General Public License. "The Library" refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An "Application" is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A "Combined Work" is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the "Linked Version". The "Minimal Corresponding Source" for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The "Corresponding Application Code" for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. 1. Exception to Section 3 of the GNU GPL. You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. 2. Conveying Modified Versions. If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: a) under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or b) under the GNU GPL, with none of the additional permissions of this License applicable to that copy. 3. Object Code Incorporating Material from Library Header Files. The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: a) Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the object code with a copy of the GNU GPL and this license document. 4. Combined Works. You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: a) Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. b) Accompany the Combined Work with a copy of the GNU GPL and this license document. c) For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. d) Do one of the following: 0) Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. 1) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (a) uses at run time a copy of the Library already present on the user's computer system, and (b) will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. e) Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option 4d0, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option 4d1, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) 5. Combined Libraries. You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. b) Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. 6. Revised Versions of the GNU Lesser General Public License. The Free Software Foundation may publish revised and/or new versions of the GNU Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. ================================================ FILE: README.md ================================================ # ox_core A modern FiveM framework. ![](https://img.shields.io/github/downloads/overextended/ox_core/total?logo=github) ![](https://img.shields.io/github/downloads/overextended/ox_core/latest/total?logo=github) ![](https://img.shields.io/github/contributors/overextended/ox_core?logo=github) ![](https://img.shields.io/github/v/release/overextended/ox_core?logo=github) ## 🔗 Links - 📚 [Documentation](https://overextended.dev/ox_core) - For installation, setup, and everything else. - 🧾 [txAdmin recipe](https://github.com/overextended/txAdminRecipe) - Install and configure ox_core in minutes. - 📦 [npm](https://www.npmjs.com/package/@overextended/ox_core) - Use our npm package to create JavaScript resources using ox_core. - 🛤️ [Cfx.re](https://forum.cfx.re/t/pre-release-ox-core-player-and-vehicle-management/5253275) - See our release thread for discussions or other information. ## Third-party resources When releasing a resource using the this framework _do not use the ox prefix_. This creates confusion about the creator of the resource, and causes conflicts between similarly named resources. ## Copyright Copyright © 2024 Overextended This program is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this program. If not, see . ================================================ FILE: biome.json ================================================ { "files": { "include": ["./client/*.ts", "./common/*.ts", "./server/*.ts"], "ignore": ["**/*.d.ts"] }, "linter": { "enabled": false, "rules": { "suspicious": { "noConfusingLabels": "off", "noAssignInExpressions": "off", "noExplicitAny": "off", "noFallthroughSwitchClause": "off", "noGlobalIsNan": "off", "noControlCharactersInRegex": "off" }, "correctness": { "noUnusedLabels": "off", "noVoidTypeReturn": "off" }, "style": { "useEnumInitializers": "off", "noNonNullAssertion": "off", "noParameterAssign": "off", "noArguments": "off", "noUnusedTemplateLiteral": { "fix": "safe" } }, "performance": { "noDelete": "off" }, "complexity": { "noForEach": "off", "noUselessConstructor": "off", "noStaticOnlyClass": "off", "noThisInStatic": "off" } } }, "formatter": { "enabled": true, "formatWithErrors": false, "ignore": [], "attributePosition": "auto", "indentStyle": "space", "indentWidth": 2, "lineWidth": 120, "lineEnding": "lf" }, "javascript": { "formatter": { "arrowParentheses": "always", "bracketSameLine": false, "bracketSpacing": true, "jsxQuoteStyle": "single", "quoteProperties": "asNeeded", "quoteStyle": "single", "semicolons": "always", "trailingCommas": "all" } }, "json": { "formatter": { "trailingCommas": "none" } } } ================================================ FILE: build.js ================================================ //@ts-check import { createBuilder, createFxmanifest } from "@overextended/fx-utils"; import { spawn } from "child_process"; const watch = process.argv.includes("--watch"); function exec(command) { return new Promise((resolve) => { const child = spawn(command, { stdio: "inherit", shell: true }); child.on("exit", (code) => { resolve(code === 0); }); }); } if (!watch) { const tsc = await exec(`tsc --build ${watch ? "--watch --preserveWatchOutput" : ""} && tsc-alias`); if (!tsc) process.exit(0); } createBuilder( watch, { dropLabels: !watch ? ["DEV"] : undefined, }, [ { name: "server", options: { platform: "node", target: ["node22"], format: "cjs", entryPoints: [`./server/index.ts`], }, }, { name: "client", options: { platform: "browser", target: ["es2023"], format: "iife", entryPoints: [`./client/index.ts`], }, }, ], async (files) => { await createFxmanifest({ client_scripts: [files.client], server_scripts: [files.server], files: ["lib/init.lua", "lib/client/**.lua", "locales/*.json", "common/data/*.json"], dependencies: ["/server:12913", "/onesync"], metadata: { node_version: "22" }, }); }, ); ================================================ FILE: client/config.ts ================================================ export * from '../common/config'; export const DEATH_SYSTEM = GetConvarInt('ox:deathSystem', 1) === 1; export const CHARACTER_SELECT = GetConvarInt('ox:characterSelect', 1) === 1; export const SPAWN_LOCATION = JSON.parse(GetConvar('ox:spawnLocation', '[-258.211, -293.077, 21.6132, 206.0]')); export const HOSPITAL_BLIPS = GetConvarInt('ox:hospitalBlips', 1) === 1; ================================================ FILE: client/death.ts ================================================ import { cache, requestAnimDict, sleep } from "@overextended/ox_lib/client"; import { Vector3, Vector4 } from "@nativewrappers/fivem"; import { OxPlayer } from "player"; import { DEATH_SYSTEM, DEBUG, HOSPITAL_BLIPS } from "config"; import { LoadDataFile } from "../common"; const hospitals: Vector4[] = LoadDataFile("hospitals").map((vec: number[]) => { const hospital = Vector4.fromArray(vec); if (HOSPITAL_BLIPS) { const blip = AddBlipForCoord(hospital.x, hospital.y, hospital.z); SetBlipSprite(blip, 61); SetBlipDisplay(blip, 8); SetBlipScale(blip, 0.8); SetBlipColour(blip, 35); SetBlipAsShortRange(blip, true); } return hospital; }); const anims = [ ["missfinale_c1@", "lying_dead_player0"], ["veh@low@front_ps@idle_duck", "sit"], ["dead", "dead_a"], ]; let playerIsDead = false; async function ClearDeath(tickId: number, bleedOut: boolean) { const anim = cache.vehicle ? anims[1] : anims[0]; clearTick(tickId); if (bleedOut) { const coords = Vector3.fromArray(GetEntityCoords(cache.ped, true)); let distance = 1000; const hospital = hospitals.reduce((closest, hospital) => { const hospitalDistance = coords.distance(hospital); if (hospitalDistance > distance) return closest; distance = hospitalDistance; return hospital; }); DoScreenFadeOut(500); RequestCollisionAtCoord(hospital.x, hospital.y, hospital.z); while (!IsScreenFadedOut()) await sleep(0); StopAnimTask(cache.ped, anim[0], anim[1], 8.0); SetEntityCoordsNoOffset(cache.ped, hospital.x, hospital.y, hospital.z, false, false, false); SetEntityHeading(cache.ped, hospital.w); SetGameplayCamRelativeHeading(0); await sleep(500); DoScreenFadeIn(500); while (!IsScreenFadedIn()) await sleep(0); } else { StopAnimTask(cache.ped, anim[0], anim[1], 8.0); } ClearPedBloodDamage(cache.ped); SetPlayerControl(cache.playerId, false, 0); SetEveryoneIgnorePlayer(cache.playerId, false); SetPlayerControl(cache.playerId, true, 0); SetPlayerInvincible(cache.playerId, false); for (let index = 0; index < anims.length; index++) RemoveAnimDict(anims[index][0]); emit("ox:playerRevived"); } const bleedOutTime = DEBUG ? 100 : 1000; async function OnPlayerDeath() { OxPlayer.state.set("isDead", true, true); emit("ox_inventory:disarm"); emit("ox:playerDeath"); if (!DEATH_SYSTEM) return; for (let index = 0; index < anims.length; index++) await requestAnimDict(anims[index][0]); ShakeGameplayCam("DEATH_FAIL_IN_EFFECT_SHAKE", 1.0); let bleedOut = 0; const tickId = setTick(() => { const anim = cache.vehicle ? anims[1] : anims[0]; if (!IsEntityPlayingAnim(cache.ped, anim[0], anim[1], 3)) TaskPlayAnim(cache.ped, anim[0], anim[1], 50.0, 8.0, -1, 1, 1.0, false, false, false); DisableFirstPersonCamThisFrame(); bleedOut++; if (bleedOut > bleedOutTime) ClearDeath(tickId, true); }); const coords = GetEntityCoords(cache.ped, true); const health = Math.floor(Math.max(100, GetEntityMaxHealth(cache.ped) * 0.8)); NetworkResurrectLocalPlayer(coords[0], coords[1], coords[2], GetEntityHeading(cache.ped), 0, false); if (cache.vehicle) SetPedIntoVehicle(cache.ped, cache.vehicle, cache.seat as number); SetEntityInvincible(cache.ped, true); SetEntityHealth(cache.ped, health); SetEveryoneIgnorePlayer(cache.playerId, true); } AddStateBagChangeHandler("isDead", `player:${cache.serverId}`, async (_bagName: string, _key: string, value: any) => { playerIsDead = value; }); function ResetDeathState() { OxPlayer.state.set("isDead", false, true); } on("ox:playerLogout", ResetDeathState); on("ox:playerRevived", ResetDeathState); on("ox:playerLoaded", () => { const id: CitizenTimer = setInterval(() => { if (!OxPlayer.isLoaded) return clearInterval(id); if (!playerIsDead && IsPedDeadOrDying(PlayerPedId(), true)) OnPlayerDeath(); }, 200); }); ================================================ FILE: client/index.ts ================================================ export * from '../common'; import { PLATE_PATTERN } from 'config'; import 'player'; import 'spawn'; import 'death'; import 'vehicle'; for (let i = 0; i < GetNumberOfVehicleNumberPlates(); i++) { SetDefaultVehicleNumberPlateTextPattern(i, PLATE_PATTERN); } ================================================ FILE: client/player/index.ts ================================================ import { netEvent } from 'utils'; import type { Character, Dict, OxGroup, OxStatus, PlayerMetadata } from 'types'; import { GetGroupPermissions } from '../../common'; export const Statuses: Dict = {}; const callableMethods: Dict = {}; class PlayerSingleton { userId: number; charId?: number; stateId?: string; #isLoaded: boolean; #groups: Dict; #statuses: Dict; #metadata: Dict; #state: StateBagInterface; constructor() { this.#isLoaded = false; this.#groups = {}; this.#statuses = {}; this.#metadata = {}; this.#state = LocalPlayer.state; Object.entries(Object.getOwnPropertyDescriptors(this.constructor.prototype)).reduce( (methods: { [key: string]: true }, [name, desc]) => { if (name !== 'constructor' && desc.writable && typeof desc.value === 'function') methods[name] = true; return methods; }, callableMethods, ); netEvent('ox:startCharacterSelect', (userId: number) => { this.userId = userId; for (const key in this.#groups) delete this.#groups[key]; for (const key in this.#metadata) delete this.#metadata[key]; }); netEvent('ox:setActiveCharacter', async (character: Character, groups: Record) => { OxPlayer.charId = character.charId; OxPlayer.stateId = character.stateId; for (const key in groups) this.#groups[key] = groups[key]; DEV: { console.log(this); console.log(this.#groups); console.log(this.#statuses); } }); netEvent('ox:setPlayerData', (key: string, value: any) => { if (!this.charId) return; this.#metadata[key] = value; emit(`ox:player:${key}`, value); }); netEvent('ox:setPlayerStatus', (key: string, value: number, set?: boolean) => { if (set) { Statuses[key] = GlobalState[`status.${key}`]; } this.#statuses[key] = value; }); netEvent('ox:setGroup', (name: string, grade: number) => { this.#groups[name] = grade; }); exports('GetPlayer', () => this); exports('GetPlayerCalls', () => callableMethods); exports('CallPlayer', (method: string, ...args: any[]) => { const fn = (this as any)[method]; if (!fn) return console.error(`cannot call method ${method} (method does not exist)`); if (!callableMethods[method]) return console.error(`cannot call method ${method} (method is not exported)`); return fn.bind(this)(...args); // why :\ }); } get isLoaded() { return this.#isLoaded; } set isLoaded(state: boolean) { this.#isLoaded = state; } get state() { return this.#state; } get(key: K | keyof PlayerMetadata): K extends keyof PlayerMetadata ? PlayerMetadata[K] : any; get(key?: string) { if (!key) return OxPlayer; return this.#metadata[key]; } getGroup(filter: string): number; getGroup(filter: string[] | Record): [string, number] | []; getGroup(filter: string | string[] | Record) { if (typeof filter === 'string') { return this.#groups[filter]; } if (Array.isArray(filter)) { for (const name of filter) { const grade = this.#groups[name]; if (grade) return [name, grade]; } } else if (typeof filter === 'object') { for (const [name, requiredGrade] of Object.entries(filter)) { const grade = this.#groups[name]; if (grade && (requiredGrade as number) <= grade) { return [name, grade]; } } } } getGroupByType(type: string) { const groupNames: string[] = GlobalState.groups; const groups = groupNames.reduce((acc, groupName) => { const group: OxGroup = GlobalState[`group.${groupName}`]; if (group.type === type) acc.push(groupName); return acc; }, [] as string[]); return this.getGroup(groups); } getGroups() { return this.#groups; } getStatus(name: string) { return this.#statuses[name]; } getStatuses() { return this.#statuses; } setStatus(name: string, value: number) { if (this.#statuses[name] === undefined) return false; this.#statuses[name] = value < 0 ? 0 : value > 100 ? 100 : Number.parseFloat((value).toPrecision(8)); return true; } addStatus(name: string, value: number) { if (this.#statuses[name] === undefined) return false; const newValue = this.#statuses[name] + value; this.#statuses[name] = newValue < 0 ? 0 : newValue > 100 ? 100 : Number.parseFloat((newValue).toPrecision(8)); return true; } removeStatus(name: string, value: number) { if (this.#statuses[name] === undefined) return false; const newValue = this.#statuses[name] - value; this.#statuses[name] = newValue < 0 ? 0 : newValue > 100 ? 100 : Number.parseFloat((newValue).toPrecision(8)); return true; } hasPermission(permission: string): boolean { const matchResult = permission.match(/^group\.([^.]+)\.(.*)/); const groupName = matchResult?.[1]; permission = matchResult?.[2] ?? permission; if (groupName) { const grade = this.#groups[groupName]; if (!grade) return false; const permissions = GetGroupPermissions(groupName); for (let g = grade; g > 0; g--) { const value = permissions[g] && permissions[g][permission]; if (value !== undefined) return value; } } return false; } } export const OxPlayer = new PlayerSingleton(); import './status'; ================================================ FILE: client/player/status.ts ================================================ import { OxPlayer, Statuses } from 'player'; function UpdateStatuses() { for (const name in Statuses) { const status = Statuses[name]; if (!status?.onTick) continue; const curValue = OxPlayer.getStatus(name) ?? status.default; const newValue = curValue + status.onTick; OxPlayer.setStatus( name, newValue < 0 ? 0 : newValue > 100 ? 100 : Number.parseFloat((newValue).toPrecision(8)), ); } emit('ox:statusTick', OxPlayer.getStatuses()); emitNet('ox:updateStatuses', OxPlayer.getStatuses()); } on('ox:playerLoaded', () => { const id: CitizenTimer = setInterval(() => { if (!OxPlayer.isLoaded) return clearInterval(id); UpdateStatuses(); }, 1000); }); ================================================ FILE: client/spawn.ts ================================================ import { sleep, waitFor } from "@overextended/ox_lib"; import { cache, inputDialog } from "@overextended/ox_lib/client"; import { OxPlayer } from "./player"; import { netEvent } from "utils"; import { CHARACTER_SELECT, SPAWN_LOCATION } from "config"; import locale from "../common/locales"; import type { Character, NewCharacter } from "types"; DoScreenFadeOut(0); NetworkStartSoloTutorialSession(); setTimeout(() => emitNet("ox:playerJoined")); async function StartSession() { if (IsPlayerSwitchInProgress()) { StopPlayerSwitch(); } if (GetIsLoadingScreenActive()) { SendLoadingScreenMessage('{"fullyLoaded": true}'); ShutdownLoadingScreenNui(); } NetworkStartSoloTutorialSession(); DoScreenFadeOut(0); ShutdownLoadingScreen(); SetPlayerControl(cache.playerId, false, 0); SetPlayerInvincible(cache.playerId, true); while (!OxPlayer.isLoaded) { DisableAllControlActions(0); ThefeedHideThisFrame(); HideHudAndRadarThisFrame(); await sleep(0); } NetworkEndTutorialSession(); SetPlayerControl(cache.playerId, true, 0); SetPlayerInvincible(cache.playerId, false); SetMaxWantedLevel(0); NetworkSetFriendlyFireOption(true); SetPlayerHealthRechargeMultiplier(cache.playerId, 0.0); } netEvent("ox:startCharacterSelect", async (_userId: number, characters: Character[]) => { if (OxPlayer.isLoaded) { OxPlayer.isLoaded = false; emit("ox:playerLogout"); } StartSession(); if (!CHARACTER_SELECT) return; const character = characters[0]; const [x, y, z] = [ character?.x || SPAWN_LOCATION[0], character?.y || SPAWN_LOCATION[1], character?.z || SPAWN_LOCATION[2], ]; const heading = character?.heading || SPAWN_LOCATION[3]; RequestCollisionAtCoord(x, y, z); FreezeEntityPosition(cache.ped, true); SetEntityCoordsNoOffset(cache.ped, x, y, z, true, true, false); SetEntityHeading(cache.ped, heading); SwitchOutPlayer(cache.ped, 1 | 8192, 1); while (GetPlayerSwitchState() !== 5) await sleep(0); DoScreenFadeIn(200); if (character) { return emitNet("ox:setActiveCharacter", character.charId); } const input = await inputDialog( locale("create_character"), [ { type: "input", required: true, icon: "user-pen", label: locale("firstname"), placeholder: "John", }, { type: "input", required: true, icon: "user-pen", label: locale("lastname"), placeholder: "Smith", }, { type: "select", required: true, icon: "circle-user", label: locale("gender"), options: [ { label: locale("male"), value: "male", }, { label: locale("female"), value: "female", }, { label: locale("non_binary"), value: "non_binary", }, ], }, { type: "date", required: true, icon: "calendar-days", label: locale("date_of_birth"), format: "YYYY-MM-DD", min: "1900-01-01", max: "2006-01-01", default: "2006-01-01", }, ], { allowCancel: false, }, ); if (!input) return; emitNet("ox:setActiveCharacter", { firstName: input[0] as string, lastName: input[1] as string, gender: input[2] as string, date: input[3] as number, }); }); netEvent("ox:setActiveCharacter", async (character: Character) => { if (CHARACTER_SELECT) { SwitchInPlayer(PlayerPedId()); SetGameplayCamRelativeHeading(0); } await waitFor(() => (IsScreenFadedIn() && !IsPlayerSwitchInProgress() ? true : undefined), "", 0); SetEntityHealth(cache.ped, character.health ?? GetEntityMaxHealth(cache.ped)); SetPedArmour(cache.ped, character.armour ?? 0); FreezeEntityPosition(cache.ped, false); OxPlayer.isLoaded = true; emit("playerSpawned"); emit("ox:playerLoaded", OxPlayer, character.isNew); }); ================================================ FILE: client/tsconfig.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "baseUrl": ".", "types": ["@citizenfx/client"], "composite": true, "paths": { "types": ["../types"] } }, "include": ["./", "../types", "../common/", "../locales/en.json"] } ================================================ FILE: client/utils.ts ================================================ export function netEvent(event: string, fn: Function) { onNet(event, (...args: any[]) => { if ((source as any) !== '') fn(...args); }); } ================================================ FILE: client/vehicle/index.ts ================================================ import { cache, onServerCallback, waitFor } from "@overextended/ox_lib/client"; import { Vector3 } from "@nativewrappers/fivem"; import { DEBUG } from "../config"; if (DEBUG) import("./parser"); onServerCallback("ox:getNearbyVehicles", (radius: number) => { const nearbyEntities: number[] = []; const playerCoords = Vector3.fromArray(GetEntityCoords(cache.ped, true)); (GetGamePool("CVehicle") as number[]).forEach((entityId) => { const coords = Vector3.fromArray(GetEntityCoords(entityId, true)); const distance = coords.distance(playerCoords); if (distance <= (radius ?? 2) && NetworkGetEntityIsNetworked(entityId)) nearbyEntities.push(VehToNet(entityId)); }); return nearbyEntities; }); AddStateBagChangeHandler("initVehicle", "", async (bagName: string, key: string, value: any) => { if (!value) return; await waitFor(() => (!NetworkIsInTutorialSession() ? true : undefined), "", 0); const entity = await waitFor( async () => { const entity = GetEntityFromStateBagName(bagName); if (entity) return entity; }, `failed to get entity from statebag name ${bagName}`, 10000, ); if (!entity) return; await waitFor(async () => { if (!IsEntityWaitingForWorldCollision(entity)) return true; }); if (NetworkGetEntityOwner(entity) !== cache.playerId) return; SetVehicleOnGroundProperly(entity); setTimeout(() => Entity(entity).state.set(key, null, true)); }); ================================================ FILE: client/vehicle/parser.ts ================================================ import { cache, notify, onServerCallback, requestModel, sleep } from "@overextended/ox_lib/client"; import { GetTopVehicleStats, GetVehicleData } from "../../common/vehicles"; import type { VehicleData, VehicleTypes, VehicleCategories } from "types"; const PRICE_WEIGHTS: Record = { automobile: 1600, bicycle: 150, bike: 500, boat: 6000, heli: 90000, plane: 16000, quadbike: 1100, train: 6000, submarinecar: 26000, submarine: 22000, blimp: 14000, trailer: 10000, amphibious_automobile: 6400, amphibious_quadbike: 4600, }; const BATCH_SIZE = 10; const vehicles = GetVehicleData(); function GetVehicleModels(parseAll: boolean): string[] { return GetAllVehicleModels() .filter((vehicle: string) => parseAll || !vehicles[vehicle]) .sort(); } async function IsModelValid(hash: number): Promise { try { await requestModel(hash, 10000); return true; } catch { return false; } } function SpawnVehicle(hash: number, coords: [number, number, number]): number { const entity = CreateVehicle(hash, ...coords, 0, false, false); SetPedIntoVehicle(cache.ped, entity, -1); return entity; } function GetVehicleTypeEx(entity: number): VehicleTypes { switch (GetVehicleTypeRaw(entity)) { case 0: default: return "automobile"; case 1: return "plane"; case 2: return "trailer"; case 3: return "quadbike"; case 5: return "submarinecar"; case 6: return "amphibious_automobile"; case 7: return "amphibious_quadbike"; case 8: return "heli"; case 9: return "blimp"; case 11: return "bike"; case 12: return "bicycle"; case 13: return "boat"; case 14: return "train"; case 15: return "submarine"; } } function ParseVehicleData(entity: number, hash: number, model: string): VehicleData { let make = GetMakeNameFromVehicleModel(hash); if (!make) make = GetMakeNameFromVehicleModel(model.replace(/\W/g, "")) || ""; const vehicleType = GetVehicleTypeEx(entity); const vehicleCategory: VehicleCategories = vehicleType === "heli" || vehicleType === "plane" || vehicleType === "blimp" ? "air" : vehicleType === "boat" || vehicleType === "submarine" ? "sea" : "land"; const data: VehicleData = { acceleration: +GetVehicleModelAcceleration(hash).toFixed(4), braking: +GetVehicleModelMaxBraking(hash).toFixed(4), handling: +GetVehicleModelEstimatedAgility(hash).toFixed(4), speed: +GetVehicleModelEstimatedMaxSpeed(hash).toFixed(4), traction: +GetVehicleModelMaxTraction(hash).toFixed(4), name: GetLabelText(GetDisplayNameFromVehicleModel(hash)), make: make ? GetLabelText(make) : "", class: GetVehicleClass(entity), seats: GetVehicleModelNumberOfSeats(hash), doors: GetNumberOfVehicleDoors(entity), type: vehicleType, price: 0, category: vehicleCategory, }; if (DoesVehicleHaveWeapons(entity)) data.weapons = true; CalculateVehiclePrice(data, entity); console.log(`^5Parsed valid model ${model} (${data.make || "?"} ${data.name})^0`); return data; } function CalculateVehiclePrice(data: VehicleData, entity: number) { let price = data.braking + data.acceleration + data.handling + data.speed; if (GetVehicleHasKers(entity)) price *= 2; if (GetHasRocketBoost(entity)) price *= 3; if (GetCanVehicleJump(entity)) price *= 1.5; if (GetVehicleHasParachute(entity)) price *= 1.5; if (data.weapons) price *= 5; data.price = Math.floor(price * (PRICE_WEIGHTS[data.type] ?? 1)); } function CleanupVehicle(entity: number, coords: [number, number, number]) { SetVehicleAsNoLongerNeeded(entity); SetModelAsNoLongerNeeded(GetEntityModel(entity)); DeleteEntity(entity); SetEntityCoordsNoOffset(cache.ped, ...coords, false, false, false); } /** * An event only registered when DEBUG is enabled. * Allows external scripts to freely modify vehicle data. */ on("ox:setVehicleData", (model: string, data: Record) => { if (!vehicles[model]) console.error(`Cannot set vehicle data for ${model} (invalid model)`); Object.assign(vehicles[model], data); }); onServerCallback("ox:generateVehicleData", async (parseAll: boolean) => { const coords = GetEntityCoords(cache.ped, true) as [number, number, number]; const invalidVehicles: string[] = []; const vehicleModels = GetVehicleModels(parseAll); SetPlayerControl(cache.playerId, false, 1 << 8); FreezeEntityPosition(cache.ped, true); notify({ title: "Generating vehicle data", description: `${vehicleModels.length} models loaded.`, type: "inform" }); let parsed = 0; for (let i = 0; i < vehicleModels.length; i += BATCH_SIZE) { await Promise.all( vehicleModels.slice(i, i + BATCH_SIZE).map(async (model) => { model = model.toLowerCase(); const hash = GetHashKey(model); const isValid = await IsModelValid(hash); if (!isValid) return invalidVehicles.push(model); try { const entity = SpawnVehicle(hash, coords); vehicles[model] = ParseVehicleData(entity, hash, model); emit(`ox:parsedVehicle`, model, entity); ++parsed; CleanupVehicle(entity, coords); } catch { invalidVehicles.push(model); } }), ); } SetPlayerControl(cache.playerId, true, 0); FreezeEntityPosition(cache.ped, false); notify({ title: "Generated vehicle data", description: `Generated data for ${parsed}/${vehicleModels.length} models.`, type: "success", }); console.log(`^5Generated data for ${parsed}/${vehicleModels.length} models.^0`); if (invalidVehicles.length) console.log( `^3Failed to parse data for ${invalidVehicles.length} invalid vehicles.\n${JSON.stringify(invalidVehicles, null, 2)}^0`, ); await sleep(5000); return [vehicles, GetTopVehicleStats(), invalidVehicles]; }); ================================================ FILE: common/config.ts ================================================ export const SV_LAN = GetConvarInt('sv_lan', 0) === 1; export const CHARACTER_SLOTS = GetConvarInt('ox:characterSlots', 1); export const PLATE_PATTERN = GetConvar('ox:plateFormat', '........').toUpperCase(); export const DEFAULT_VEHICLE_STORE = GetConvar('ox:defaultVehicleStore', 'impound'); export const DEBUG = (() => { DEV: return true; //@ts-ignore return SV_LAN || GetConvarInt('ox:debug', 0) === 1; })(); ================================================ FILE: common/data/hospitals.json ================================================ [ [340.5, -1396.8, 32.5, 60.1], [-449.3, -340.2, 34.5, 76.2], [295.6, -583.9, 43.2, 79.5], [1840.1, 3670.7, 33.9, 207.6], [1153.2, -1526.4, 34.8, 352.4], [-244.7, 6328.3, 32.4, 242.1] ] ================================================ FILE: common/data/vehicleStats.json ================================================ { "air": { "acceleration": 21.56, "braking": 22.417, "handling": 21.56, "speed": 109.7643, "traction": 2.15 }, "land": { "acceleration": 0.82, "braking": 3.1, "handling": 1.2, "speed": 53.8674, "traction": 3.2925 }, "sea": { "acceleration": 18, "braking": 0.4, "handling": 18.38, "speed": 46.6667, "traction": 0 } } ================================================ FILE: common/data/vehicles.json ================================================ { "adder": { "acceleration": 0.32, "braking": 1, "handling": 0.7, "speed": 51.771, "traction": 2.5, "name": "Adder", "make": "Truffade", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 86065, "category": "land" }, "airbus": { "acceleration": 0.12, "braking": 0.25, "handling": 0.5, "speed": 25.5809, "traction": 1.45, "name": "Airport Bus", "make": "", "class": 17, "seats": 16, "doors": 5, "type": "automobile", "price": 42321, "category": "land" }, "airtug": { "acceleration": 0.06, "braking": 0.3, "handling": 0.44, "speed": 10.9417, "traction": 1.15, "name": "Airtug", "make": "", "class": 11, "seats": 2, "doors": 1, "type": "automobile", "price": 18786, "category": "land" }, "akula": { "acceleration": 5.88, "braking": 3.592, "handling": 5.88, "speed": 61.0891, "traction": 1.3, "name": "Akula", "make": "", "class": 15, "seats": 4, "doors": 4, "type": "heli", "price": 34398495, "category": "air", "weapons": true }, "akuma": { "acceleration": 0.4, "braking": 1.2, "handling": 0.78, "speed": 48.3333, "traction": 2.15, "name": "Akuma", "make": "Dinka", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 25356, "category": "land" }, "aleutian": { "acceleration": 0.2795, "braking": 0.75, "handling": 0.6595, "speed": 39.6667, "traction": 2.098, "name": "Aleutian", "make": "Vapid", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 66169, "category": "land" }, "alkonost": { "acceleration": 13.524, "braking": 11.1734, "handling": 13.524, "speed": 82.6192, "traction": 1.85, "name": "RO-86 Alkonost", "make": "", "class": 16, "seats": 10, "doors": 2, "type": "plane", "price": 1933449, "category": "air" }, "alpha": { "acceleration": 0.33, "braking": 1, "handling": 0.71, "speed": 49.5388, "traction": 2.5, "name": "Alpha", "make": "Albany", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 82526, "category": "land" }, "alphaz1": { "acceleration": 20.58, "braking": 20.58, "handling": 20.58, "speed": 100, "traction": 1.15, "name": "Alpha-Z1", "make": "Buckingham", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 2587840, "category": "air" }, "ambulance": { "acceleration": 0.18, "braking": 0.6, "handling": 0.56, "speed": 39.9117, "traction": 1.95, "name": "Ambulance", "make": "", "class": 18, "seats": 4, "doors": 5, "type": "automobile", "price": 66002, "category": "land" }, "annihilator": { "acceleration": 4.606, "braking": 2.1766, "handling": 4.606, "speed": 47.2565, "traction": 1.3, "name": "Annihilator", "make": "", "class": 15, "seats": 6, "doors": 2, "type": "heli", "price": 26390295, "category": "air", "weapons": true }, "annihilator2": { "acceleration": 5.292, "braking": 2.7894, "handling": 5.292, "speed": 52.7096, "traction": 1.3, "name": "Annihilator Stealth", "make": "", "class": 15, "seats": 6, "doors": 4, "type": "heli", "price": 29737349, "category": "air", "weapons": true }, "apc": { "acceleration": 0.21, "braking": 0.2, "handling": 0.59, "speed": 23.0063, "traction": 2.4, "name": "APC", "make": "HVY", "class": 19, "seats": 4, "doors": 4, "type": "amphibious_automobile", "price": 768201, "category": "land", "weapons": true }, "ardent": { "acceleration": 0.285, "braking": 0.9, "handling": 0.665, "speed": 47.791, "traction": 2.65, "name": "Ardent", "make": "Ocelot", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 397128, "category": "land", "weapons": true }, "armytanker": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5051, "traction": 1.8, "name": "Army Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5749, "category": "land" }, "armytrailer": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3, "name": "Army Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "armytrailer2": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3, "name": "Army Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "asbo": { "acceleration": 0.234, "braking": 0.47, "handling": 0.614, "speed": 38.0701, "traction": 1.92, "name": "Asbo", "make": "Maxwell", "class": 0, "seats": 2, "doors": 5, "type": "automobile", "price": 63020, "category": "land" }, "asea": { "acceleration": 0.2, "braking": 0.4, "handling": 0.58, "speed": 38.4289, "traction": 2.05, "name": "Asea", "make": "Declasse", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 63374, "category": "land" }, "asea2": { "acceleration": 0.2, "braking": 0.4, "handling": 0.58, "speed": 38.4289, "traction": 2.05, "name": "Asea", "make": "Declasse", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 63374, "category": "land" }, "asterope": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 38.4289, "traction": 2.5, "name": "Asterope", "make": "Karin", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 64174, "category": "land" }, "asterope2": { "acceleration": 0.238, "braking": 0.9, "handling": 0.618, "speed": 42.8105, "traction": 2.335, "name": "Asterope GZ", "make": "Karin", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 71306, "category": "land" }, "astron": { "acceleration": 0.324, "braking": 0.48, "handling": 0.704, "speed": 49.5667, "traction": 2.185, "name": "Astron", "make": "Pfister", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 81719, "category": "land" }, "autarch": { "acceleration": 0.377, "braking": 1.2, "handling": 0.757, "speed": 51.2899, "traction": 2.705, "name": "Autarch", "make": "Overflod", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 85798, "category": "land" }, "avarus": { "acceleration": 0.27, "braking": 1, "handling": 0.65, "speed": 44.1369, "traction": 1.85, "name": "Avarus", "make": "LCC", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 23028, "category": "land" }, "avenger": { "acceleration": 8.82, "braking": 8.4657, "handling": 8.82, "speed": 95.9834, "traction": 2.15, "name": "Avenger", "make": "Mammoth", "class": 16, "seats": 3, "doors": 3, "type": "plane", "price": 9767128, "category": "air", "weapons": true }, "avenger2": { "acceleration": 8.82, "braking": 8.4657, "handling": 8.82, "speed": 95.9834, "traction": 2.15, "name": "Avenger", "make": "Mammoth", "class": 16, "seats": 3, "doors": 3, "type": "plane", "price": 9767128, "category": "air", "weapons": true }, "avenger3": { "acceleration": 8.82, "braking": 8.4657, "handling": 8.82, "speed": 95.9834, "traction": 2.15, "name": "Avenger", "make": "Mammoth", "class": 16, "seats": 3, "doors": 3, "type": "plane", "price": 9767128, "category": "air", "weapons": true }, "avenger4": { "acceleration": 8.82, "braking": 8.4657, "handling": 8.82, "speed": 95.9834, "traction": 2.15, "name": "Avenger", "make": "Mammoth", "class": 16, "seats": 3, "doors": 3, "type": "plane", "price": 9767128, "category": "air", "weapons": true }, "avisa": { "acceleration": 10.5, "braking": 0.4, "handling": 10.88, "speed": 25, "traction": 0, "name": "Avisa", "make": "Kraken", "class": 14, "seats": 4, "doors": 4, "type": "submarine", "price": 1029160, "category": "sea" }, "bagger": { "acceleration": 0.21, "braking": 1.2, "handling": 0.59, "speed": 37.8686, "traction": 1.65, "name": "Bagger", "make": "Western", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 19934, "category": "land" }, "baletrailer": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Baletrailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "baller": { "acceleration": 0.21, "braking": 0.6, "handling": 0.59, "speed": 39.6177, "traction": 1.9, "name": "Baller", "make": "Gallivanter", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 65628, "category": "land" }, "baller2": { "acceleration": 0.27, "braking": 0.6, "handling": 0.65, "speed": 45, "traction": 2, "name": "Baller", "make": "Gallivanter", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 74432, "category": "land" }, "baller3": { "acceleration": 0.275, "braking": 0.6, "handling": 0.655, "speed": 45, "traction": 2, "name": "Baller LE", "make": "Gallivanter", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 74448, "category": "land" }, "baller4": { "acceleration": 0.27, "braking": 0.57, "handling": 0.65, "speed": 45, "traction": 2, "name": "Baller LE LWB", "make": "Gallivanter", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 74384, "category": "land" }, "baller5": { "acceleration": 0.27, "braking": 0.58, "handling": 0.65, "speed": 45, "traction": 2, "name": "Baller LE (Armored)", "make": "Gallivanter", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 74400, "category": "land" }, "baller6": { "acceleration": 0.265, "braking": 0.55, "handling": 0.645, "speed": 45, "traction": 2, "name": "Baller LE LWB (Armored)", "make": "Gallivanter", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 74336, "category": "land" }, "baller7": { "acceleration": 0.3025, "braking": 0.7, "handling": 0.6825, "speed": 46.5833, "traction": 2.2, "name": "Baller ST", "make": "Gallivanter", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 77229, "category": "land" }, "baller8": { "acceleration": 0.295, "braking": 0.9225, "handling": 0.675, "speed": 46.6667, "traction": 2.283, "name": "Baller ST-D", "make": "Gallivanter", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 77694, "category": "land" }, "banshee": { "acceleration": 0.34, "braking": 1, "handling": 0.82, "speed": 47.5245, "traction": 2.42, "name": "Banshee", "make": "Bravado", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 79495, "category": "land" }, "banshee2": { "acceleration": 0.3475, "braking": 1, "handling": 0.7275, "speed": 44.7295, "traction": 2.5, "name": "Banshee 900R", "make": "Bravado", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 74887, "category": "land" }, "banshee3": { "acceleration": 0.3878, "braking": 0.72, "handling": 0.8478, "speed": 53.8674, "traction": 2.6065, "name": "Banshee GTS", "make": "Bravado", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 89316, "category": "land" }, "barracks": { "acceleration": 0.11, "braking": 0.3, "handling": 0.49, "speed": 32.4021, "traction": 1.65, "name": "Barracks", "make": "", "class": 19, "seats": 10, "doors": 4, "type": "automobile", "price": 53283, "category": "land" }, "barracks2": { "acceleration": 0.23, "braking": 1, "handling": 0.61, "speed": 35.7058, "traction": 1.55, "name": "Barracks Semi", "make": "HVY", "class": 19, "seats": 2, "doors": 3, "type": "automobile", "price": 60073, "category": "land" }, "barracks3": { "acceleration": 0.11, "braking": 0.3, "handling": 0.49, "speed": 32.4021, "traction": 1.65, "name": "Barracks", "make": "", "class": 19, "seats": 10, "doors": 4, "type": "automobile", "price": 53283, "category": "land" }, "barrage": { "acceleration": 0.2225, "braking": 0.85, "handling": 0.6025, "speed": 39.938, "traction": 1.75, "name": "Barrage", "make": "", "class": 19, "seats": 4, "doors": 4, "type": "automobile", "price": 332904, "category": "land", "weapons": true }, "bati": { "acceleration": 0.3, "braking": 1.4, "handling": 0.68, "speed": 49.296, "traction": 2.32, "name": "Bati 801", "make": "Pegassi", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 25838, "category": "land" }, "bati2": { "acceleration": 0.3, "braking": 1.4, "handling": 0.68, "speed": 49.296, "traction": 2.32, "name": "Bati 801RR", "make": "Pegassi", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 25838, "category": "land" }, "benson": { "acceleration": 0.16, "braking": 0.25, "handling": 0.54, "speed": 36.9472, "traction": 1.75, "name": "Benson", "make": "Vapid", "class": 20, "seats": 2, "doors": 4, "type": "automobile", "price": 60635, "category": "land" }, "benson2": { "acceleration": 0.16, "braking": 0.25, "handling": 0.54, "speed": 36.9472, "traction": 1.75, "name": "Benson (Cluckin' Bell)", "make": "Vapid", "class": 20, "seats": 2, "doors": 4, "type": "automobile", "price": 60635, "category": "land" }, "besra": { "acceleration": 21.07, "braking": 18.4796, "handling": 21.07, "speed": 87.7058, "traction": 2.15, "name": "Besra", "make": "Western", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 2373206, "category": "air" }, "bestiagts": { "acceleration": 0.32, "braking": 1, "handling": 0.7, "speed": 47.7946, "traction": 2.42, "name": "Bestia GTS", "make": "Grotti", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 79703, "category": "land" }, "bf400": { "acceleration": 0.29, "braking": 1.1, "handling": 0.67, "speed": 44.1479, "traction": 2.15, "name": "BF400", "make": "Nagasaki", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 23103, "category": "land" }, "bfinjection": { "acceleration": 0.22, "braking": 0.62, "handling": 0.6, "speed": 40.7799, "traction": 1.85, "name": "Injection", "make": "BF", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 67551, "category": "land" }, "biff": { "acceleration": 0.12, "braking": 0.3, "handling": 0.5, "speed": 34.4979, "traction": 1.65, "name": "Biff", "make": "HVY", "class": 20, "seats": 2, "doors": 3, "type": "automobile", "price": 56668, "category": "land" }, "bifta": { "acceleration": 0.26, "braking": 0.7, "handling": 0.64, "speed": 45.1953, "traction": 2.05, "name": "Bifta", "make": "BF", "class": 9, "seats": 2, "doors": 0, "type": "automobile", "price": 74872, "category": "land" }, "bison": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 37.5559, "traction": 2.05, "name": "Bison", "make": "Bravado", "class": 12, "seats": 6, "doors": 6, "type": "automobile", "price": 62297, "category": "land" }, "bison2": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 37.5559, "traction": 2.05, "name": "Bison", "make": "Bravado", "class": 12, "seats": 4, "doors": 6, "type": "automobile", "price": 62297, "category": "land" }, "bison3": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 37.5559, "traction": 2.05, "name": "Bison", "make": "Bravado", "class": 12, "seats": 4, "doors": 6, "type": "automobile", "price": 62297, "category": "land" }, "bjxl": { "acceleration": 0.19, "braking": 0.8, "handling": 0.57, "speed": 36.3729, "traction": 2.05, "name": "BeeJay XL", "make": "Karin", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 60692, "category": "land" }, "blade": { "acceleration": 0.324, "braking": 0.8, "handling": 0.704, "speed": 42.2471, "traction": 2.23, "name": "Blade", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 70520, "category": "land" }, "blazer": { "acceleration": 0.2, "braking": 1, "handling": 0.58, "speed": 33.9831, "traction": 2.6, "name": "Blazer", "make": "Nagasaki", "class": 9, "seats": 1, "doors": 0, "type": "quadbike", "price": 39339, "category": "land" }, "blazer2": { "acceleration": 0.12, "braking": 0.8, "handling": 0.5, "speed": 24.7121, "traction": 2, "name": "Blazer Lifeguard", "make": "Nagasaki", "class": 9, "seats": 1, "doors": 0, "type": "quadbike", "price": 28745, "category": "land" }, "blazer3": { "acceleration": 0.2, "braking": 1, "handling": 0.58, "speed": 33.9831, "traction": 2.6, "name": "Hot Rod Blazer", "make": "Nagasaki", "class": 9, "seats": 1, "doors": 0, "type": "quadbike", "price": 39339, "category": "land" }, "blazer4": { "acceleration": 0.25, "braking": 1, "handling": 0.63, "speed": 38.876, "traction": 2.7, "name": "Street Blazer", "make": "Nagasaki", "class": 9, "seats": 1, "doors": 0, "type": "quadbike", "price": 44831, "category": "land" }, "blazer5": { "acceleration": 0.272, "braking": 1, "handling": 0.652, "speed": 40.8773, "traction": 2.7, "name": "Blazer Aqua", "make": "Nagasaki", "class": 9, "seats": 1, "doors": 0, "type": "amphibious_quadbike", "price": 984429, "category": "land", "weapons": true }, "blimp": { "acceleration": 5.684, "braking": 3.9788, "handling": 5.684, "speed": 70, "traction": 0.65, "name": "Atomic Blimp", "make": "", "class": 16, "seats": 4, "doors": 4, "type": "blimp", "price": 1194855, "category": "air" }, "blimp2": { "acceleration": 6.076, "braking": 4.2532, "handling": 6.076, "speed": 70, "traction": 0.65, "name": "Xero Blimp", "make": "", "class": 16, "seats": 4, "doors": 4, "type": "blimp", "price": 1209672, "category": "air" }, "blimp3": { "acceleration": 5.684, "braking": 3.9788, "handling": 5.684, "speed": 70, "traction": 0.65, "name": "Blimp", "make": "", "class": 16, "seats": 4, "doors": 4, "type": "blimp", "price": 1194855, "category": "air" }, "blista": { "acceleration": 0.23, "braking": 0.6, "handling": 0.61, "speed": 41.9174, "traction": 2.05, "name": "Blista", "make": "Dinka", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 69371, "category": "land" }, "blista2": { "acceleration": 0.23, "braking": 0.55, "handling": 0.61, "speed": 41.9174, "traction": 2.1, "name": "Blista Compact", "make": "Dinka", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 69291, "category": "land" }, "blista3": { "acceleration": 0.23, "braking": 0.55, "handling": 0.61, "speed": 41.9174, "traction": 2.1, "name": "Go Go Monkey Blista", "make": "Dinka", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 69291, "category": "land" }, "bmx": { "acceleration": 0.16, "braking": 3, "handling": 0.54, "speed": 14.5335, "traction": 1.85, "name": "BMX", "make": "", "class": 13, "seats": 1, "doors": 1, "type": "bicycle", "price": 2735, "category": "land" }, "boattrailer": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Boat Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "boattrailer2": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Boat Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "boattrailer3": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Boat Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "bobcatxl": { "acceleration": 0.18, "braking": 0.8, "handling": 0.56, "speed": 35.1601, "traction": 1.95, "name": "Bobcat XL", "make": "Vapid", "class": 12, "seats": 2, "doors": 4, "type": "automobile", "price": 58720, "category": "land" }, "bodhi2": { "acceleration": 0.215, "braking": 1.1, "handling": 0.595, "speed": 35.5074, "traction": 2.25, "name": "Bodhi", "make": "Canis", "class": 9, "seats": 4, "doors": 4, "type": "automobile", "price": 59867, "category": "land" }, "bombushka": { "acceleration": 5.39, "braking": 1.9682, "handling": 5.39, "speed": 36.5148, "traction": 0.85, "name": "RM-10 Bombushka", "make": "", "class": 16, "seats": 6, "doors": 3, "type": "plane", "price": 3941040, "category": "air", "weapons": true }, "boor": { "acceleration": 0.267, "braking": 0.68, "handling": 0.647, "speed": 40.4296, "traction": 2.065, "name": "Boor", "make": "Karin", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 67237, "category": "land" }, "boxville": { "acceleration": 0.11, "braking": 0.25, "handling": 0.49, "speed": 28.6961, "traction": 1.55, "name": "Boxville", "make": "Brute", "class": 12, "seats": 6, "doors": 5, "type": "automobile", "price": 47273, "category": "land" }, "boxville2": { "acceleration": 0.11, "braking": 0.25, "handling": 0.49, "speed": 28.6961, "traction": 1.55, "name": "Boxville", "make": "", "class": 12, "seats": 6, "doors": 5, "type": "automobile", "price": 47273, "category": "land" }, "boxville3": { "acceleration": 0.11, "braking": 0.25, "handling": 0.49, "speed": 28.6961, "traction": 1.55, "name": "Boxville", "make": "Brute", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 47273, "category": "land" }, "boxville4": { "acceleration": 0.11, "braking": 0.25, "handling": 0.49, "speed": 28.6961, "traction": 1.55, "name": "Boxville", "make": "Brute", "class": 12, "seats": 6, "doors": 5, "type": "automobile", "price": 47273, "category": "land" }, "boxville5": { "acceleration": 0.32, "braking": 0.35, "handling": 0.7, "speed": 39.4684, "traction": 2.175, "name": "Armored Boxville", "make": "", "class": 12, "seats": 5, "doors": 5, "type": "automobile", "price": 326707, "category": "land", "weapons": true }, "boxville6": { "acceleration": 0.11, "braking": 0.25, "handling": 0.49, "speed": 28.6961, "traction": 1.55, "name": "Boxville (LSDS)", "make": "Brute", "class": 12, "seats": 6, "doors": 5, "type": "automobile", "price": 47273, "category": "land" }, "brawler": { "acceleration": 0.28, "braking": 0.88, "handling": 0.66, "speed": 46.6667, "traction": 1.92, "name": "Brawler", "make": "Coil", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 77578, "category": "land" }, "brickade": { "acceleration": 0.2, "braking": 0.4, "handling": 0.58, "speed": 32.8303, "traction": 2, "name": "Brickade", "make": "MTL", "class": 17, "seats": 6, "doors": 2, "type": "automobile", "price": 54416, "category": "land" }, "brickade2": { "acceleration": 0.2, "braking": 0.4, "handling": 0.58, "speed": 32.8303, "traction": 2, "name": "Brickade 6x6", "make": "MTL", "class": 17, "seats": 6, "doors": 2, "type": "automobile", "price": 54416, "category": "land" }, "brigham": { "acceleration": 0.21, "braking": 0.38, "handling": 0.59, "speed": 38.7112, "traction": 1.905, "name": "Brigham", "make": "Albany", "class": 4, "seats": 4, "doors": 6, "type": "automobile", "price": 63825, "category": "land" }, "brioso": { "acceleration": 0.29, "braking": 0.6, "handling": 0.77, "speed": 41.536, "traction": 2.3, "name": "Brioso R/A", "make": "Grotti", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 69113, "category": "land" }, "brioso2": { "acceleration": 0.179, "braking": 0.25, "handling": 0.559, "speed": 32.9442, "traction": 1.885, "name": "Brioso 300", "make": "Grotti", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 54291, "category": "land" }, "brioso3": { "acceleration": 0.216, "braking": 0.25, "handling": 0.596, "speed": 37.2714, "traction": 1.995, "name": "Brioso 300 Widebody", "make": "Grotti", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 61333, "category": "land" }, "broadway": { "acceleration": 0.195, "braking": 0.32, "handling": 0.575, "speed": 37.4739, "traction": 1.995, "name": "Broadway", "make": "Classique", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 61702, "category": "land" }, "bruiser": { "acceleration": 0.26, "braking": 0.5, "handling": 0.64, "speed": 38.4047, "traction": 1.75, "name": "Apocalypse Bruiser", "make": "Benefactor", "class": 9, "seats": 4, "doors": 5, "type": "automobile", "price": 63687, "category": "land" }, "bruiser2": { "acceleration": 0.26, "braking": 0.5, "handling": 0.64, "speed": 38.4047, "traction": 1.75, "name": "Future Shock Bruiser", "make": "Benefactor", "class": 9, "seats": 4, "doors": 5, "type": "automobile", "price": 63687, "category": "land" }, "bruiser3": { "acceleration": 0.26, "braking": 0.5, "handling": 0.64, "speed": 38.4047, "traction": 1.75, "name": "Nightmare Bruiser", "make": "Benefactor", "class": 9, "seats": 4, "doors": 5, "type": "automobile", "price": 63687, "category": "land" }, "brutus": { "acceleration": 0.27, "braking": 0.6, "handling": 0.65, "speed": 42.3068, "traction": 2, "name": "Apocalypse Brutus", "make": "Declasse", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 70122, "category": "land" }, "brutus2": { "acceleration": 0.27, "braking": 0.6, "handling": 0.65, "speed": 42.3068, "traction": 2, "name": "Future Shock Brutus", "make": "Declasse", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 70122, "category": "land" }, "brutus3": { "acceleration": 0.27, "braking": 0.6, "handling": 0.65, "speed": 42.3068, "traction": 2, "name": "Nightmare Brutus", "make": "Declasse", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 70122, "category": "land" }, "btype": { "acceleration": 0.27, "braking": 0.55, "handling": 0.65, "speed": 41.6667, "traction": 2.1, "name": "Roosevelt", "make": "Albany", "class": 5, "seats": 6, "doors": 5, "type": "automobile", "price": 69018, "category": "land" }, "btype2": { "acceleration": 0.355, "braking": 0.55, "handling": 0.735, "speed": 45, "traction": 1.94, "name": "Fränken Stange", "make": "Albany", "class": 5, "seats": 4, "doors": 4, "type": "automobile", "price": 74624, "category": "land" }, "btype3": { "acceleration": 0.27, "braking": 0.55, "handling": 0.65, "speed": 41.6667, "traction": 2.1, "name": "Roosevelt Valor", "make": "Albany", "class": 5, "seats": 6, "doors": 5, "type": "automobile", "price": 69018, "category": "land" }, "buccaneer": { "acceleration": 0.28, "braking": 0.8, "handling": 0.66, "speed": 46.1566, "traction": 2.15, "name": "Buccaneer", "make": "Albany", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 76634, "category": "land" }, "buccaneer2": { "acceleration": 0.28, "braking": 0.8, "handling": 0.66, "speed": 46.1566, "traction": 2.15, "name": "Buccaneer Custom", "make": "Albany", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 76634, "category": "land" }, "buffalo": { "acceleration": 0.27, "braking": 0.9, "handling": 0.65, "speed": 45.1527, "traction": 2.45, "name": "Buffalo", "make": "Bravado", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 75156, "category": "land" }, "buffalo2": { "acceleration": 0.29, "braking": 0.9, "handling": 0.67, "speed": 46.2816, "traction": 2.45, "name": "Buffalo S", "make": "Bravado", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 77026, "category": "land" }, "buffalo3": { "acceleration": 0.31, "braking": 1, "handling": 0.69, "speed": 48.1652, "traction": 2.45, "name": "Sprunk Buffalo", "make": "Bravado", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 80264, "category": "land" }, "buffalo4": { "acceleration": 0.3425, "braking": 0.95, "handling": 0.7225, "speed": 48.308, "traction": 2.58, "name": "Buffalo STX", "make": "Bravado", "class": 4, "seats": 4, "doors": 6, "type": "automobile", "price": 80516, "category": "land" }, "buffalo5": { "acceleration": 0.382, "braking": 0.985, "handling": 0.842, "speed": 49.8737, "traction": 2.42, "name": "Buffalo EVX", "make": "Bravado", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 83332, "category": "land" }, "bulldozer": { "acceleration": 0.14, "braking": 0.2, "handling": 0.52, "speed": 5, "traction": 1.1, "name": "Dozer", "make": "HVY", "class": 10, "seats": 1, "doors": 2, "type": "automobile", "price": 9376, "category": "land" }, "bullet": { "acceleration": 0.33, "braking": 0.8, "handling": 0.71, "speed": 49.7632, "traction": 2.55, "name": "Bullet", "make": "Vapid", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 82565, "category": "land" }, "burrito": { "acceleration": 0.16, "braking": 0.6, "handling": 0.54, "speed": 36.9472, "traction": 1.95, "name": "Burrito", "make": "Declasse", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 61195, "category": "land" }, "burrito2": { "acceleration": 0.16, "braking": 0.6, "handling": 0.54, "speed": 36.9472, "traction": 1.95, "name": "Bugstars Burrito", "make": "Declasse", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 61195, "category": "land" }, "burrito3": { "acceleration": 0.16, "braking": 0.6, "handling": 0.54, "speed": 36.9472, "traction": 1.95, "name": "Burrito", "make": "Declasse", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 61195, "category": "land" }, "burrito4": { "acceleration": 0.16, "braking": 0.6, "handling": 0.54, "speed": 36.9472, "traction": 1.95, "name": "Burrito", "make": "Declasse", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 61195, "category": "land" }, "burrito5": { "acceleration": 0.16, "braking": 0.6, "handling": 0.54, "speed": 36.9472, "traction": 1.95, "name": "Burrito", "make": "Declasse", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 61195, "category": "land" }, "bus": { "acceleration": 0.12, "braking": 0.35, "handling": 0.5, "speed": 25.5809, "traction": 1.45, "name": "Bus", "make": "", "class": 17, "seats": 16, "doors": 5, "type": "automobile", "price": 42481, "category": "land" }, "buzzard": { "acceleration": 5.39, "braking": 3.1212, "handling": 5.39, "speed": 57.9072, "traction": 1.3, "name": "Buzzard Attack Chopper", "make": "", "class": 15, "seats": 4, "doors": 2, "type": "heli", "price": 32313780, "category": "air", "weapons": true }, "buzzard2": { "acceleration": 5.39, "braking": 3.1212, "handling": 5.39, "speed": 57.9072, "traction": 1.3, "name": "Buzzard", "make": "", "class": 15, "seats": 4, "doors": 2, "type": "heli", "price": 32313780, "category": "air", "weapons": true }, "cablecar": { "acceleration": 0.2, "braking": 5, "handling": 0.58, "speed": 26.6667, "traction": 2.5, "name": "Cable Car", "make": "", "class": 21, "seats": 4, "doors": 2, "type": "train", "price": 194680, "category": "land" }, "caddy": { "acceleration": 0.3, "braking": 0.2, "handling": 0.68, "speed": 21.6667, "traction": 1.5, "name": "Caddy", "make": "", "class": 11, "seats": 2, "doors": 0, "type": "automobile", "price": 36554, "category": "land" }, "caddy2": { "acceleration": 0.3, "braking": 0.2, "handling": 0.68, "speed": 21.6667, "traction": 1.45, "name": "Caddy", "make": "", "class": 11, "seats": 2, "doors": 0, "type": "automobile", "price": 36554, "category": "land" }, "caddy3": { "acceleration": 0.3, "braking": 0.2, "handling": 0.68, "speed": 21.3342, "traction": 1.45, "name": "Caddy", "make": "", "class": 11, "seats": 2, "doors": 0, "type": "automobile", "price": 36022, "category": "land" }, "calico": { "acceleration": 0.3385, "braking": 0.825, "handling": 0.7185, "speed": 49.4621, "traction": 2.435, "name": "Calico GTF", "make": "Karin", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 82150, "category": "land" }, "camper": { "acceleration": 0.12, "braking": 0.25, "handling": 0.5, "speed": 30.462, "traction": 1.5, "name": "Camper", "make": "Brute", "class": 12, "seats": 2, "doors": 4, "type": "automobile", "price": 50131, "category": "land" }, "caracara": { "acceleration": 0.27, "braking": 0.27, "handling": 0.65, "speed": 38.6133, "traction": 2.25, "name": "Caracara", "make": "Vapid", "class": 9, "seats": 5, "doors": 5, "type": "automobile", "price": 318426, "category": "land", "weapons": true }, "caracara2": { "acceleration": 0.27, "braking": 0.3, "handling": 0.65, "speed": 39.2704, "traction": 2.05, "name": "Caracara 4x4", "make": "Vapid", "class": 9, "seats": 4, "doors": 6, "type": "automobile", "price": 64784, "category": "land" }, "carbonizzare": { "acceleration": 0.35, "braking": 0.8, "handling": 0.73, "speed": 48.3368, "traction": 2.38, "name": "Carbonizzare", "make": "Grotti", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 80346, "category": "land" }, "carbonrs": { "acceleration": 0.3, "braking": 1.3, "handling": 0.68, "speed": 47.0182, "traction": 2.15, "name": "Carbon RS", "make": "Nagasaki", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 24649, "category": "land" }, "cargobob": { "acceleration": 4.998, "braking": 2.495, "handling": 4.998, "speed": 49.92, "traction": 1.3, "name": "Cargobob", "make": "", "class": 15, "seats": 10, "doors": 3, "type": "heli", "price": 5616990, "category": "air" }, "cargobob2": { "acceleration": 4.998, "braking": 2.495, "handling": 4.998, "speed": 49.92, "traction": 1.3, "name": "Cargobob", "make": "", "class": 15, "seats": 10, "doors": 3, "type": "heli", "price": 5616990, "category": "air" }, "cargobob3": { "acceleration": 4.998, "braking": 2.495, "handling": 4.998, "speed": 49.92, "traction": 1.3, "name": "Cargobob", "make": "", "class": 15, "seats": 10, "doors": 3, "type": "heli", "price": 5616990, "category": "air" }, "cargobob4": { "acceleration": 4.998, "braking": 2.495, "handling": 4.998, "speed": 49.92, "traction": 1.3, "name": "Cargobob", "make": "", "class": 15, "seats": 2, "doors": 3, "type": "heli", "price": 5616990, "category": "air" }, "cargobob5": { "acceleration": 5.88, "braking": 3.524, "handling": 5.88, "speed": 59.9315, "traction": 1.3, "name": "DH-7 Iron Mule", "make": "Buckingham", "class": 15, "seats": 16, "doors": 6, "type": "heli", "price": 6769394, "category": "air" }, "cargoplane": { "acceleration": 6.174, "braking": 4.8714, "handling": 6.174, "speed": 78.9023, "traction": 0.85, "name": "Cargo Plane", "make": "", "class": 16, "seats": 2, "doors": 4, "type": "plane", "price": 1537947, "category": "air" }, "cargoplane2": { "acceleration": 6.174, "braking": 4.8714, "handling": 6.174, "speed": 78.9023, "traction": 0.85, "name": "Cargo Plane", "make": "", "class": 16, "seats": 5, "doors": 4, "type": "plane", "price": 1537947, "category": "air" }, "casco": { "acceleration": 0.32, "braking": 0.6, "handling": 0.7, "speed": 50.3333, "traction": 2.3, "name": "Casco", "make": "Lampadati", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 83125, "category": "land" }, "castigator": { "acceleration": 0.2905, "braking": 0.76, "handling": 0.6705, "speed": 49.2117, "traction": 2.264, "name": "Castigator", "make": "Canis", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 81492, "category": "land" }, "cavalcade": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 38.4289, "traction": 1.9, "name": "Cavalcade", "make": "Albany", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 63694, "category": "land" }, "cavalcade2": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 38.4289, "traction": 1.9, "name": "Cavalcade", "make": "Albany", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 63694, "category": "land" }, "cavalcade3": { "acceleration": 0.2765, "braking": 0.875, "handling": 0.6565, "speed": 40.6667, "traction": 2.225, "name": "Cavalcade XL", "make": "Albany", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 67959, "category": "land" }, "cerberus": { "acceleration": 0.19, "braking": 0.25, "handling": 0.57, "speed": 37.2117, "traction": 1.7, "name": "Apocalypse Cerberus", "make": "MTL", "class": 20, "seats": 2, "doors": 2, "type": "automobile", "price": 305773, "category": "land", "weapons": true }, "cerberus2": { "acceleration": 0.19, "braking": 0.25, "handling": 0.57, "speed": 37.2117, "traction": 1.7, "name": "Future Shock Cerberus", "make": "MTL", "class": 20, "seats": 2, "doors": 2, "type": "automobile", "price": 305773, "category": "land", "weapons": true }, "cerberus3": { "acceleration": 0.19, "braking": 0.25, "handling": 0.57, "speed": 37.2117, "traction": 1.7, "name": "Nightmare Cerberus", "make": "MTL", "class": 20, "seats": 2, "doors": 2, "type": "automobile", "price": 305773, "category": "land", "weapons": true }, "champion": { "acceleration": 0.359, "braking": 1.15, "handling": 0.739, "speed": 51.8663, "traction": 2.5662, "name": "Champion", "make": "Dewbauchee", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 86582, "category": "land" }, "chavosv6": { "acceleration": 0.274, "braking": 0.69, "handling": 0.654, "speed": 46.6629, "traction": 2.554, "name": "Chavos V6", "make": "Dinka", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 77249, "category": "land" }, "cheburek": { "acceleration": 0.265, "braking": 0.8, "handling": 0.645, "speed": 44.644, "traction": 2.25, "name": "Cheburek", "make": "RUNE", "class": 5, "seats": 4, "doors": 6, "type": "automobile", "price": 74166, "category": "land" }, "cheetah": { "acceleration": 0.32, "braking": 0.8, "handling": 0.7, "speed": 50.0095, "traction": 2.65, "name": "Cheetah", "make": "Grotti", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 82927, "category": "land" }, "cheetah2": { "acceleration": 0.3, "braking": 0.8, "handling": 0.68, "speed": 49.923, "traction": 2.65, "name": "Cheetah Classic", "make": "Grotti", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 82724, "category": "land" }, "chernobog": { "acceleration": 0.12, "braking": 0.1, "handling": 0.5, "speed": 22.5872, "traction": 2, "name": "Chernobog", "make": "", "class": 19, "seats": 2, "doors": 5, "type": "automobile", "price": 186457, "category": "land", "weapons": true }, "chimera": { "acceleration": 0.275, "braking": 1, "handling": 0.655, "speed": 35.4249, "traction": 2.1, "name": "Chimera", "make": "Nagasaki", "class": 8, "seats": 1, "doors": 0, "type": "quadbike", "price": 41090, "category": "land" }, "chino": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 35.2769, "traction": 2.05, "name": "Chino", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 58651, "category": "land" }, "chino2": { "acceleration": 0.21, "braking": 0.6, "handling": 0.59, "speed": 36.3465, "traction": 2.07, "name": "Chino Custom", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 60394, "category": "land" }, "cinquemila": { "acceleration": 0.3535, "braking": 0.975, "handling": 0.7335, "speed": 48.236, "traction": 2.63, "name": "Cinquemila", "make": "Lampadati", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 80476, "category": "land" }, "cliffhanger": { "acceleration": 0.318, "braking": 1.1, "handling": 0.698, "speed": 49.1271, "traction": 2.25, "name": "Cliffhanger", "make": "Western", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 25621, "category": "land" }, "clique": { "acceleration": 0.3, "braking": 0.85, "handling": 0.68, "speed": 45.9981, "traction": 2.35, "name": "Clique", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 76524, "category": "land" }, "clique2": { "acceleration": 0.198, "braking": 0.35, "handling": 0.578, "speed": 33.1878, "traction": 2.08, "name": "Clique Wagon", "make": "Vapid", "class": 4, "seats": 2, "doors": 5, "type": "automobile", "price": 54902, "category": "land" }, "club": { "acceleration": 0.2375, "braking": 0.72, "handling": 0.6175, "speed": 41.7599, "traction": 2.05, "name": "Club", "make": "BF", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 69335, "category": "land" }, "coach": { "acceleration": 0.12, "braking": 0.25, "handling": 0.5, "speed": 25.5809, "traction": 1.45, "name": "Dashound", "make": "", "class": 17, "seats": 10, "doors": 2, "type": "automobile", "price": 42321, "category": "land" }, "cog55": { "acceleration": 0.265, "braking": 0.57, "handling": 0.645, "speed": 46.2961, "traction": 2.2, "name": "Cognoscenti 55", "make": "Enus", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 76441, "category": "land" }, "cog552": { "acceleration": 0.26, "braking": 0.55, "handling": 0.64, "speed": 45.7596, "traction": 2.2, "name": "Cognoscenti 55 (Armored)", "make": "Enus", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 75535, "category": "land" }, "cogcabrio": { "acceleration": 0.26, "braking": 0.6, "handling": 0.64, "speed": 45.1953, "traction": 2.3, "name": "Cognoscenti Cabrio", "make": "Enus", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 74712, "category": "land" }, "cognoscenti": { "acceleration": 0.26, "braking": 0.55, "handling": 0.64, "speed": 45.7596, "traction": 2.1, "name": "Cognoscenti", "make": "Enus", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 75535, "category": "land" }, "cognoscenti2": { "acceleration": 0.255, "braking": 0.52, "handling": 0.635, "speed": 45.2183, "traction": 2.1, "name": "Cognoscenti (Armored)", "make": "Enus", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 74605, "category": "land" }, "comet2": { "acceleration": 0.34, "braking": 0.8, "handling": 0.72, "speed": 50.1961, "traction": 2.6, "name": "Comet", "make": "Pfister", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 83289, "category": "land" }, "comet3": { "acceleration": 0.34, "braking": 0.8, "handling": 0.72, "speed": 50.1737, "traction": 2.8, "name": "Comet Retro Custom", "make": "Pfister", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 83253, "category": "land" }, "comet4": { "acceleration": 0.2925, "braking": 0.8, "handling": 0.6725, "speed": 47.166, "traction": 2.1, "name": "Comet Safari", "make": "Pfister", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 78289, "category": "land" }, "comet5": { "acceleration": 0.32, "braking": 1.2, "handling": 0.7, "speed": 46.8946, "traction": 2.7, "name": "Comet SR", "make": "Pfister", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 78583, "category": "land" }, "comet6": { "acceleration": 0.346, "braking": 0.88, "handling": 0.726, "speed": 50.1596, "traction": 2.655, "name": "Comet S2", "make": "Pfister", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 83378, "category": "land" }, "comet7": { "acceleration": 0.3495, "braking": 0.89, "handling": 0.7295, "speed": 50.3806, "traction": 2.655, "name": "Comet S2 Cabrio", "make": "Pfister", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 83759, "category": "land" }, "conada": { "acceleration": 5.6644, "braking": 3.5215, "handling": 5.6644, "speed": 62.169, "traction": 1.3, "name": "Conada", "make": "Buckingham", "class": 15, "seats": 4, "doors": 2, "type": "heli", "price": 6931737, "category": "air" }, "conada2": { "acceleration": 5.684, "braking": 3.4855, "handling": 5.684, "speed": 61.3214, "traction": 1.3, "name": "Weaponized Conada", "make": "Buckingham", "class": 15, "seats": 2, "doors": 2, "type": "heli", "price": 34278704, "category": "air", "weapons": true }, "contender": { "acceleration": 0.26, "braking": 0.6, "handling": 0.64, "speed": 41.3615, "traction": 2.1, "name": "Contender", "make": "Vapid", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 68578, "category": "land" }, "coquette": { "acceleration": 0.33, "braking": 0.8, "handling": 0.71, "speed": 50.4563, "traction": 2.55, "name": "Coquette", "make": "Invetero", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 83674, "category": "land" }, "coquette2": { "acceleration": 0.34, "braking": 0.5, "handling": 0.72, "speed": 49.5374, "traction": 2.3, "name": "Coquette Classic", "make": "Invetero", "class": 5, "seats": 2, "doors": 3, "type": "automobile", "price": 81755, "category": "land" }, "coquette3": { "acceleration": 0.29, "braking": 0.6, "handling": 0.67, "speed": 45.0781, "traction": 2.25, "name": "Coquette BlackFin", "make": "Invetero", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 74620, "category": "land" }, "coquette4": { "acceleration": 0.32, "braking": 0.6, "handling": 0.7, "speed": 48.8621, "traction": 2.6, "name": "Coquette D10", "make": "Invetero", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 80771, "category": "land" }, "coquette5": { "acceleration": 0.278, "braking": 0.6, "handling": 0.658, "speed": 47.3099, "traction": 2.325, "name": "Coquette D1", "make": "Invetero", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 78153, "category": "land" }, "coquette6": { "acceleration": 0.325, "braking": 0.7, "handling": 0.705, "speed": 49.3143, "traction": 2.637, "name": "Coquette D5", "make": "Invetero", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 81670, "category": "land" }, "corsita": { "acceleration": 0.4, "braking": 1.3, "handling": 0.78, "speed": 52.2801, "traction": 2.726, "name": "Corsita", "make": "Lampadati", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 87616, "category": "land" }, "coureur": { "acceleration": 0.37, "braking": 0.8, "handling": 0.81, "speed": 46.6167, "traction": 2.435, "name": "La Coureuse", "make": "Penaud", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 77754, "category": "land" }, "cruiser": { "acceleration": 0.08, "braking": 2.8, "handling": 0.46, "speed": 15, "traction": 1.8, "name": "Cruiser", "make": "", "class": 13, "seats": 1, "doors": 1, "type": "bicycle", "price": 2751, "category": "land" }, "crusader": { "acceleration": 0.18, "braking": 0.3, "handling": 0.56, "speed": 35.9639, "traction": 1.9, "name": "Crusader", "make": "Canis", "class": 19, "seats": 4, "doors": 6, "type": "automobile", "price": 59206, "category": "land" }, "cuban800": { "acceleration": 5.88, "braking": 4.5156, "handling": 5.88, "speed": 76.7957, "traction": 2.15, "name": "Cuban 800", "make": "", "class": 16, "seats": 2, "doors": 2, "type": "plane", "price": 1489140, "category": "air" }, "cutter": { "acceleration": 0.16, "braking": 0.3, "handling": 0.54, "speed": 12.7612, "traction": 1.62, "name": "Cutter", "make": "HVY", "class": 10, "seats": 1, "doors": 1, "type": "automobile", "price": 22017, "category": "land" }, "cyclone": { "acceleration": 0.2725, "braking": 1.2, "handling": 0.6525, "speed": 40.9219, "traction": 2.25, "name": "Cyclone", "make": "Coil", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 68875, "category": "land" }, "cypher": { "acceleration": 0.326, "braking": 0.7, "handling": 0.706, "speed": 45.5658, "traction": 2.59, "name": "Cypher", "make": "Ubermacht", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 75676, "category": "land" }, "daemon": { "acceleration": 0.26, "braking": 0.6, "handling": 0.64, "speed": 43.1425, "traction": 1.85, "name": "Daemon", "make": "Western", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 22321, "category": "land" }, "daemon2": { "acceleration": 0.262, "braking": 0.6, "handling": 0.642, "speed": 43.3429, "traction": 1.85, "name": "Daemon", "make": "Western", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 22423, "category": "land" }, "deathbike": { "acceleration": 0.3125, "braking": 1.1, "handling": 0.6925, "speed": 48.2865, "traction": 2.05, "name": "Apocalypse Deathbike", "make": "Western", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 25195, "category": "land" }, "deathbike2": { "acceleration": 0.3125, "braking": 1.1, "handling": 0.6925, "speed": 48.2865, "traction": 2.05, "name": "Future Shock Deathbike", "make": "Western", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 25195, "category": "land" }, "deathbike3": { "acceleration": 0.3125, "braking": 1.1, "handling": 0.6925, "speed": 48.2865, "traction": 2.05, "name": "Nightmare Deathbike", "make": "Western", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 25195, "category": "land" }, "defiler": { "acceleration": 0.405, "braking": 1.2, "handling": 0.785, "speed": 48.8436, "traction": 2.15, "name": "Defiler", "make": "Shitzu", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 25616, "category": "land" }, "deity": { "acceleration": 0.2925, "braking": 0.65, "handling": 0.6725, "speed": 44.3737, "traction": 2.15, "name": "Deity", "make": "Enus", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 73581, "category": "land" }, "deluxo": { "acceleration": 0.2275, "braking": 0.7, "handling": 0.6075, "speed": 42.6766, "traction": 2.05, "name": "Deluxo", "make": "Imponte", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 70738, "category": "land" }, "deveste": { "acceleration": 0.42, "braking": 1, "handling": 0.85, "speed": 52.6778, "traction": 2.725, "name": "Deveste Eight", "make": "Principe", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 87916, "category": "land" }, "deviant": { "acceleration": 0.29, "braking": 0.5, "handling": 0.67, "speed": 42.457, "traction": 2.25, "name": "Deviant", "make": "Schyster", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 70267, "category": "land" }, "diablous": { "acceleration": 0.312, "braking": 1.2, "handling": 0.692, "speed": 47.2867, "traction": 1.95, "name": "Diabolus", "make": "Principe", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 24745, "category": "land" }, "diablous2": { "acceleration": 0.32, "braking": 1.25, "handling": 0.7, "speed": 47.5333, "traction": 2, "name": "Diabolus Custom", "make": "Principe", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 24901, "category": "land" }, "dilettante": { "acceleration": 0.1, "braking": 0.6, "handling": 0.48, "speed": 24.0066, "traction": 1.76, "name": "Dilettante", "make": "Karin", "class": 0, "seats": 4, "doors": 6, "type": "automobile", "price": 40298, "category": "land" }, "dilettante2": { "acceleration": 0.1, "braking": 0.6, "handling": 0.48, "speed": 24.0066, "traction": 1.76, "name": "Dilettante", "make": "Karin", "class": 0, "seats": 4, "doors": 6, "type": "automobile", "price": 40298, "category": "land" }, "dinghy": { "acceleration": 16, "braking": 0.4, "handling": 16.38, "speed": 41.6667, "traction": 0, "name": "Dinghy", "make": "Nagasaki", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 2233400, "category": "sea", "weapons": true }, "dinghy2": { "acceleration": 16, "braking": 0.4, "handling": 16.38, "speed": 41.6667, "traction": 0, "name": "Dinghy", "make": "Nagasaki", "class": 14, "seats": 2, "doors": 0, "type": "boat", "price": 446680, "category": "sea" }, "dinghy3": { "acceleration": 16, "braking": 0.4, "handling": 16.38, "speed": 41.6667, "traction": 0, "name": "Dinghy", "make": "Nagasaki", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 2233400, "category": "sea", "weapons": true }, "dinghy4": { "acceleration": 16, "braking": 0.4, "handling": 16.38, "speed": 41.6667, "traction": 0, "name": "Dinghy", "make": "Nagasaki", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 2233400, "category": "sea", "weapons": true }, "dinghy5": { "acceleration": 16, "braking": 0.4, "handling": 16.38, "speed": 41.6667, "traction": 0, "name": "Weaponized Dinghy", "make": "Nagasaki", "class": 14, "seats": 5, "doors": 0, "type": "boat", "price": 2233400, "category": "sea", "weapons": true }, "dloader": { "acceleration": 0.17, "braking": 0.6, "handling": 0.55, "speed": 33.1998, "traction": 1.7, "name": "Duneloader", "make": "Bravado", "class": 9, "seats": 2, "doors": 3, "type": "automobile", "price": 55231, "category": "land" }, "docktrailer": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "NULL", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "docktug": { "acceleration": 0.2, "braking": 0.5, "handling": 0.58, "speed": 24.6406, "traction": 1.4, "name": "Docktug", "make": "", "class": 11, "seats": 1, "doors": 1, "type": "automobile", "price": 41472, "category": "land" }, "dodo": { "acceleration": 4.9, "braking": 3.399, "handling": 4.9, "speed": 69.3676, "traction": 2.15, "name": "Dodo", "make": "Mammoth", "class": 16, "seats": 4, "doors": 4, "type": "plane", "price": 1321065, "category": "air" }, "dominator": { "acceleration": 0.29, "braking": 0.8, "handling": 0.67, "speed": 48.3333, "traction": 2.25, "name": "Dominator", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 80149, "category": "land" }, "dominator10": { "acceleration": 0.3, "braking": 0.69, "handling": 0.68, "speed": 49.296, "traction": 2.383, "name": "Dominator FX", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 81545, "category": "land" }, "dominator2": { "acceleration": 0.31, "braking": 0.9, "handling": 0.69, "speed": 49, "traction": 2.3, "name": "Pisswasser Dominator", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 81440, "category": "land" }, "dominator3": { "acceleration": 0.335, "braking": 0.5, "handling": 0.715, "speed": 47.114, "traction": 2.57, "name": "Dominator GTX", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 77862, "category": "land" }, "dominator4": { "acceleration": 0.375, "braking": 0.8, "handling": 0.755, "speed": 48.4233, "traction": 2.35, "name": "Apocalypse Dominator", "make": "Vapid", "class": 4, "seats": 2, "doors": 2, "type": "automobile", "price": 80565, "category": "land" }, "dominator5": { "acceleration": 0.375, "braking": 0.8, "handling": 0.755, "speed": 48.4233, "traction": 2.35, "name": "Future Shock Dominator", "make": "Vapid", "class": 4, "seats": 2, "doors": 2, "type": "automobile", "price": 80565, "category": "land" }, "dominator6": { "acceleration": 0.375, "braking": 0.8, "handling": 0.755, "speed": 48.4233, "traction": 2.35, "name": "Nightmare Dominator", "make": "Vapid", "class": 4, "seats": 2, "doors": 2, "type": "automobile", "price": 80565, "category": "land" }, "dominator7": { "acceleration": 0.342, "braking": 0.92, "handling": 0.722, "speed": 50.0913, "traction": 2.535, "name": "Dominator ASP", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 83320, "category": "land" }, "dominator8": { "acceleration": 0.3027, "braking": 0.75, "handling": 0.6827, "speed": 45.9496, "traction": 2.5, "name": "Dominator GTT", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 76295, "category": "land" }, "dominator9": { "acceleration": 0.3448, "braking": 0.93, "handling": 0.7248, "speed": 49.6897, "traction": 2.545, "name": "Dominator GT", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 82702, "category": "land" }, "dorado": { "acceleration": 0.2168, "braking": 0.5, "handling": 0.5968, "speed": 42.173, "traction": 1.845, "name": "Dorado", "make": "Bravado", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 69578, "category": "land" }, "double": { "acceleration": 0.31, "braking": 1.4, "handling": 0.69, "speed": 47.9473, "traction": 2.18, "name": "Double-T", "make": "Dinka", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 25173, "category": "land" }, "drafter": { "acceleration": 0.342, "braking": 1, "handling": 0.722, "speed": 47.8784, "traction": 2.695, "name": "8F Drafter", "make": "Obey", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 79907, "category": "land" }, "draugur": { "acceleration": 0.3425, "braking": 0.484, "handling": 0.7225, "speed": 43.84, "traction": 2.485, "name": "Draugur", "make": "Declasse", "class": 9, "seats": 4, "doors": 6, "type": "automobile", "price": 72622, "category": "land" }, "driftcheburek": { "acceleration": 0.7075, "braking": 0.59, "handling": 1.0875, "speed": 44.4167, "traction": 1.53, "name": "Cheburek", "make": "RUNE", "class": 5, "seats": 4, "doors": 6, "type": "automobile", "price": 74882, "category": "land" }, "driftcypher": { "acceleration": 0.7075, "braking": 0.83, "handling": 1.0875, "speed": 44.4167, "traction": 1.53, "name": "Cypher", "make": "Ubermacht", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 75266, "category": "land" }, "drifteuros": { "acceleration": 0.711, "braking": 0.85, "handling": 1.091, "speed": 44.95, "traction": 1.53, "name": "Euros", "make": "Annis", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 76163, "category": "land" }, "driftfr36": { "acceleration": 0.7125, "braking": 0.765, "handling": 1.0925, "speed": 44.3333, "traction": 1.53, "name": "FR36", "make": "Fathom", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 75045, "category": "land" }, "driftfuto": { "acceleration": 0.71, "braking": 0.765, "handling": 1.09, "speed": 44.3333, "traction": 1.53, "name": "Futo GTX", "make": "Karin", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 75037, "category": "land" }, "driftfuto2": { "acceleration": 0.71, "braking": 0.66, "handling": 1.09, "speed": 44.3333, "traction": 1.53, "name": "Futo", "make": "Karin", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 74869, "category": "land" }, "driftjester": { "acceleration": 0.706, "braking": 0.875, "handling": 1.086, "speed": 44.1667, "traction": 1.53, "name": "Jester RR", "make": "Dinka", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 74933, "category": "land" }, "driftjester3": { "acceleration": 0.711, "braking": 0.65, "handling": 1.091, "speed": 44.6667, "traction": 1.53, "name": "Jester Classic", "make": "Dinka", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 75389, "category": "land" }, "driftnebula": { "acceleration": 0.725, "braking": 0.74, "handling": 1.105, "speed": 45.2667, "traction": 1.53, "name": "Nebula Turbo", "make": "Vulcar", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 76538, "category": "land" }, "driftremus": { "acceleration": 0.712, "braking": 0.87, "handling": 1.092, "speed": 46.2667, "traction": 1.53, "name": "Remus", "make": "Annis", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 78305, "category": "land" }, "driftsentinel": { "acceleration": 0.716, "braking": 0.85, "handling": 1.096, "speed": 44.6667, "traction": 1.53, "name": "Sentinel Classic Widebody", "make": "Ubermacht", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 75725, "category": "land" }, "drifttampa": { "acceleration": 0.712, "braking": 0.8, "handling": 1.092, "speed": 46.3333, "traction": 1.53, "name": "Drift Tampa", "make": "Declasse", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 78299, "category": "land" }, "driftvorschlag": { "acceleration": 0.7085, "braking": 0.725, "handling": 1.0885, "speed": 45.0667, "traction": 1.53, "name": "Vorschlaghammer", "make": "Benefactor", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 76141, "category": "land" }, "driftyosemite": { "acceleration": 0.718, "braking": 0.82, "handling": 1.098, "speed": 41.6667, "traction": 1.53, "name": "Drift Yosemite", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 70884, "category": "land" }, "driftzr350": { "acceleration": 0.708, "braking": 0.85, "handling": 1.088, "speed": 44.2667, "traction": 1.53, "name": "ZR350", "make": "Annis", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 75060, "category": "land" }, "dubsta": { "acceleration": 0.2, "braking": 0.8, "handling": 0.58, "speed": 37.5559, "traction": 2.15, "name": "Dubsta", "make": "Benefactor", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 62617, "category": "land" }, "dubsta2": { "acceleration": 0.2, "braking": 0.8, "handling": 0.58, "speed": 37.5559, "traction": 2.15, "name": "Dubsta", "make": "Benefactor", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 62617, "category": "land" }, "dubsta3": { "acceleration": 0.28, "braking": 0.6, "handling": 0.66, "speed": 38.8077, "traction": 2, "name": "Dubsta 6x6", "make": "Benefactor", "class": 9, "seats": 6, "doors": 6, "type": "automobile", "price": 64556, "category": "land" }, "dukes": { "acceleration": 0.32, "braking": 0.8, "handling": 0.7, "speed": 47.7946, "traction": 2.25, "name": "Dukes", "make": "Imponte", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 79383, "category": "land" }, "dukes2": { "acceleration": 0.35, "braking": 0.9, "handling": 0.73, "speed": 46.5269, "traction": 2.26, "name": "Duke O'Death", "make": "Imponte", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 77611, "category": "land" }, "dukes3": { "acceleration": 0.315, "braking": 0.75, "handling": 0.695, "speed": 47.3507, "traction": 2.2, "name": "Beater Dukes", "make": "Imponte", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 78577, "category": "land" }, "dump": { "acceleration": 0.19, "braking": 0.3, "handling": 0.57, "speed": 14.3333, "traction": 1.45, "name": "Dump", "make": "HVY", "class": 10, "seats": 1, "doors": 1, "type": "automobile", "price": 24629, "category": "land" }, "dune": { "acceleration": 0.25, "braking": 0.63, "handling": 0.63, "speed": 38.876, "traction": 2.2, "name": "Dune Buggy", "make": "BF", "class": 9, "seats": 2, "doors": 0, "type": "automobile", "price": 64617, "category": "land" }, "dune2": { "acceleration": 0.24, "braking": 0.63, "handling": 0.62, "speed": 37.9382, "traction": 2, "name": "Space Docker", "make": "", "class": 9, "seats": 2, "doors": 0, "type": "automobile", "price": 63085, "category": "land" }, "dune3": { "acceleration": 0.25, "braking": 0.63, "handling": 0.63, "speed": 38.876, "traction": 2.2, "name": "Dune FAV", "make": "BF", "class": 9, "seats": 2, "doors": 0, "type": "automobile", "price": 323087, "category": "land", "weapons": true }, "dune4": { "acceleration": 0.324, "braking": 1, "handling": 0.704, "speed": 49.2242, "traction": 2.65, "name": "Ramp Buggy", "make": "", "class": 9, "seats": 2, "doors": 0, "type": "automobile", "price": 82003, "category": "land" }, "dune5": { "acceleration": 0.32, "braking": 1, "handling": 0.7, "speed": 48.84, "traction": 2.65, "name": "Ramp Buggy", "make": "", "class": 9, "seats": 2, "doors": 0, "type": "automobile", "price": 81376, "category": "land" }, "duster": { "acceleration": 4.9, "braking": 3.399, "handling": 4.9, "speed": 69.3676, "traction": 2.15, "name": "Duster", "make": "", "class": 16, "seats": 2, "doors": 0, "type": "plane", "price": 1321065, "category": "air" }, "duster2": { "acceleration": 6.566, "braking": 5.9026, "handling": 6.566, "speed": 89.897, "traction": 2.15, "name": "Duster 300-H", "make": "Western", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 1742905, "category": "air" }, "dynasty": { "acceleration": 0.18, "braking": 0.4, "handling": 0.56, "speed": 35.6353, "traction": 1.7, "name": "Dynasty", "make": "Weeny", "class": 5, "seats": 4, "doors": 6, "type": "automobile", "price": 58840, "category": "land" }, "elegy": { "acceleration": 0.33, "braking": 1, "handling": 0.71, "speed": 47.1697, "traction": 2.7, "name": "Elegy Retro Custom", "make": "Annis", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 78735, "category": "land" }, "elegy2": { "acceleration": 0.33, "braking": 0.5, "handling": 0.71, "speed": 48.6724, "traction": 2.7, "name": "Elegy RH8", "make": "Annis", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 80339, "category": "land" }, "ellie": { "acceleration": 0.325, "braking": 0.5, "handling": 0.705, "speed": 45.8341, "traction": 2.55, "name": "Ellie", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 75782, "category": "land" }, "emerus": { "acceleration": 0.378, "braking": 1.2, "handling": 0.758, "speed": 50.8594, "traction": 2.78, "name": "Emerus", "make": "Progen", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 85112, "category": "land" }, "emperor": { "acceleration": 0.14, "braking": 0.6, "handling": 0.52, "speed": 33.8088, "traction": 1.9, "name": "Emperor", "make": "Albany", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 56110, "category": "land" }, "emperor2": { "acceleration": 0.14, "braking": 0.6, "handling": 0.52, "speed": 33.8088, "traction": 1.9, "name": "Emperor", "make": "Albany", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 56110, "category": "land" }, "emperor3": { "acceleration": 0.14, "braking": 0.6, "handling": 0.52, "speed": 33.8088, "traction": 1.9, "name": "Emperor", "make": "Albany", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 56110, "category": "land" }, "enduro": { "acceleration": 0.3, "braking": 1.05, "handling": 0.68, "speed": 39.6667, "traction": 2.16, "name": "Enduro", "make": "Dinka", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 20848, "category": "land" }, "entity2": { "acceleration": 0.355, "braking": 1, "handling": 0.735, "speed": 53.7141, "traction": 2.77, "name": "Entity XXR", "make": "Overflod", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 89286, "category": "land" }, "entity3": { "acceleration": 0.3545, "braking": 1, "handling": 0.7845, "speed": 53.6165, "traction": 2.782, "name": "Entity MT", "make": "Overflod", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 89208, "category": "land" }, "entityxf": { "acceleration": 0.33, "braking": 0.9, "handling": 0.71, "speed": 50.9358, "traction": 2.75, "name": "Entity XF", "make": "Overflod", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 84601, "category": "land" }, "envisage": { "acceleration": 0.175, "braking": 0.82, "handling": 0.555, "speed": 33.3943, "traction": 2.72, "name": "Envisage", "make": "Bollokan", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 55910, "category": "land" }, "esskey": { "acceleration": 0.295, "braking": 1.2, "handling": 0.675, "speed": 44.1064, "traction": 2.15, "name": "Esskey", "make": "Pegassi", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 23138, "category": "land" }, "eudora": { "acceleration": 0.195, "braking": 0.265, "handling": 0.575, "speed": 40.7273, "traction": 1.89, "name": "Eudora", "make": "Willard", "class": 4, "seats": 4, "doors": 6, "type": "automobile", "price": 66819, "category": "land" }, "euros": { "acceleration": 0.324, "braking": 0.9, "handling": 0.704, "speed": 47.3475, "traction": 2.5615, "name": "Euros", "make": "Annis", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 78840, "category": "land" }, "eurosx32": { "acceleration": 0.28, "braking": 0.8, "handling": 0.71, "speed": 44.1427, "traction": 2.287, "name": "Euros X32", "make": "Annis", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 73492, "category": "land" }, "everon": { "acceleration": 0.295, "braking": 0.3, "handling": 0.675, "speed": 41.7322, "traction": 2.05, "name": "Everon", "make": "Karin", "class": 9, "seats": 4, "doors": 6, "type": "automobile", "price": 68803, "category": "land" }, "everon2": { "acceleration": 0.3395, "braking": 0.685, "handling": 0.7195, "speed": 48.1577, "traction": 2.5445, "name": "Hotring Everon", "make": "Karin", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 79842, "category": "land" }, "exemplar": { "acceleration": 0.26, "braking": 0.9, "handling": 0.64, "speed": 48.1321, "traction": 2.6, "name": "Exemplar", "make": "Dewbauchee", "class": 3, "seats": 4, "doors": 6, "type": "automobile", "price": 79891, "category": "land" }, "f620": { "acceleration": 0.24, "braking": 0.9, "handling": 0.62, "speed": 47.9948, "traction": 2.5, "name": "F620", "make": "Ocelot", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 79607, "category": "land" }, "faction": { "acceleration": 0.28, "braking": 0.8, "handling": 0.66, "speed": 46.6667, "traction": 2.25, "name": "Faction", "make": "Willard", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 77450, "category": "land" }, "faction2": { "acceleration": 0.28, "braking": 0.8, "handling": 0.66, "speed": 46.6667, "traction": 2.25, "name": "Faction Custom", "make": "Willard", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 77450, "category": "land" }, "faction3": { "acceleration": 0.2, "braking": 0.8, "handling": 0.58, "speed": 35.2769, "traction": 2.35, "name": "Faction Custom Donk", "make": "Willard", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 58971, "category": "land" }, "fagaloa": { "acceleration": 0.21, "braking": 0.775, "handling": 0.59, "speed": 34.3913, "traction": 2.375, "name": "Fagaloa", "make": "Vulcar", "class": 5, "seats": 2, "doors": 5, "type": "automobile", "price": 57546, "category": "land" }, "faggio": { "acceleration": 0.1975, "braking": 0.4, "handling": 0.5775, "speed": 28.2827, "traction": 1.7, "name": "Faggio Sport", "make": "Pegassi", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 14728, "category": "land" }, "faggio2": { "acceleration": 0.1, "braking": 0.4, "handling": 0.48, "speed": 23.5477, "traction": 1.6, "name": "Faggio", "make": "Pegassi", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 12263, "category": "land" }, "faggio3": { "acceleration": 0.195, "braking": 0.4, "handling": 0.575, "speed": 27.4138, "traction": 1.7, "name": "Faggio Mod", "make": "Pegassi", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 14291, "category": "land" }, "fbi": { "acceleration": 0.28, "braking": 0.9, "handling": 0.66, "speed": 46.1566, "traction": 2.45, "name": "FIB", "make": "", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 76794, "category": "land" }, "fbi2": { "acceleration": 0.2, "braking": 0.8, "handling": 0.58, "speed": 37.5559, "traction": 2.15, "name": "FIB", "make": "", "class": 18, "seats": 8, "doors": 6, "type": "automobile", "price": 62617, "category": "land" }, "fcr": { "acceleration": 0.305, "braking": 1.2, "handling": 0.685, "speed": 46.4526, "traction": 2.1, "name": "FCR 1000", "make": "Pegassi", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 24321, "category": "land" }, "fcr2": { "acceleration": 0.31, "braking": 1.25, "handling": 0.69, "speed": 46.7333, "traction": 2.15, "name": "FCR 1000 Custom", "make": "Pegassi", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 24491, "category": "land" }, "felon": { "acceleration": 0.24, "braking": 0.9, "handling": 0.62, "speed": 45.7952, "traction": 2.55, "name": "Felon", "make": "Lampadati", "class": 3, "seats": 4, "doors": 6, "type": "automobile", "price": 76088, "category": "land" }, "felon2": { "acceleration": 0.24, "braking": 0.9, "handling": 0.62, "speed": 43.455, "traction": 2.5, "name": "Felon GT", "make": "Lampadati", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 72344, "category": "land" }, "feltzer2": { "acceleration": 0.34, "braking": 0.8, "handling": 0.72, "speed": 49.5374, "traction": 2.65, "name": "Feltzer", "make": "Benefactor", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 82235, "category": "land" }, "feltzer3": { "acceleration": 0.3, "braking": 0.8, "handling": 0.68, "speed": 45.9981, "traction": 2.35, "name": "Stirling GT", "make": "Benefactor", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 76444, "category": "land" }, "firebolt": { "acceleration": 0.2962, "braking": 0.39, "handling": 0.7362, "speed": 46.6612, "traction": 2.34, "name": "Firebolt ASP", "make": "Vapid", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 76933, "category": "land" }, "firetruk": { "acceleration": 0.16, "braking": 0.5, "handling": 0.54, "speed": 39.2959, "traction": 1.7, "name": "Fire Truck", "make": "MTL", "class": 18, "seats": 8, "doors": 4, "type": "automobile", "price": 323967, "category": "land", "weapons": true }, "fixter": { "acceleration": 0.135, "braking": 0.4, "handling": 0.515, "speed": 17.851, "traction": 1.85, "name": "Fixter", "make": "", "class": 13, "seats": 1, "doors": 1, "type": "bicycle", "price": 2835, "category": "land" }, "flashgt": { "acceleration": 0.32, "braking": 1, "handling": 0.7, "speed": 44.1615, "traction": 2.45, "name": "Flash GT", "make": "Vapid", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 73890, "category": "land" }, "flatbed": { "acceleration": 0.14, "braking": 0.25, "handling": 0.52, "speed": 28.2361, "traction": 1.65, "name": "Flatbed", "make": "MTL", "class": 10, "seats": 2, "doors": 3, "type": "automobile", "price": 46633, "category": "land" }, "fmj": { "acceleration": 0.3655, "braking": 1.1, "handling": 0.7455, "speed": 52.8, "traction": 2.7, "name": "FMJ", "make": "Vapid", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 88017, "category": "land" }, "forklift": { "acceleration": 0.18, "braking": 0.3, "handling": 0.56, "speed": 10, "traction": 1.15, "name": "Forklift", "make": "HVY", "class": 11, "seats": 1, "doors": 0, "type": "automobile", "price": 17664, "category": "land" }, "formula": { "acceleration": 0.75, "braking": 1.25, "handling": 1.13, "speed": 52.5647, "traction": 3.25, "name": "PR4", "make": "Progen", "class": 22, "seats": 1, "doors": 0, "type": "automobile", "price": 178223, "category": "land" }, "formula2": { "acceleration": 0.745, "braking": 1.25, "handling": 1.125, "speed": 52.3861, "traction": 3.1325, "name": "R88", "make": "Ocelot", "class": 22, "seats": 1, "doors": 0, "type": "automobile", "price": 177619, "category": "land" }, "fq2": { "acceleration": 0.18, "braking": 0.25, "handling": 0.56, "speed": 39.9117, "traction": 2, "name": "FQ 2", "make": "Fathom", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 65442, "category": "land" }, "fr36": { "acceleration": 0.301, "braking": 0.86, "handling": 0.681, "speed": 47.3251, "traction": 2.383, "name": "FR36", "make": "Fathom", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 78667, "category": "land" }, "freecrawler": { "acceleration": 0.24, "braking": 0.3, "handling": 0.62, "speed": 36.6234, "traction": 2.05, "name": "Freecrawler", "make": "Canis", "class": 9, "seats": 4, "doors": 7, "type": "automobile", "price": 60453, "category": "land" }, "freight": { "acceleration": 0.2, "braking": 5, "handling": 0.58, "speed": 26.6667, "traction": 2.5, "name": "Freight Train", "make": "", "class": 21, "seats": 2, "doors": 1, "type": "train", "price": 194680, "category": "land" }, "freight2": { "acceleration": 0.2, "braking": 5, "handling": 0.58, "speed": 26.6667, "traction": 2.5, "name": "Freight Train", "make": "", "class": 21, "seats": 2, "doors": 0, "type": "train", "price": 194680, "category": "land" }, "freightcar": { "acceleration": 0.2, "braking": 5, "handling": 0.58, "speed": 26.6667, "traction": 2.5, "name": "Freight Train", "make": "", "class": 21, "seats": 0, "doors": 0, "type": "train", "price": 194680, "category": "land" }, "freightcar2": { "acceleration": 0.2, "braking": 5, "handling": 0.58, "speed": 26.6667, "traction": 2.5, "name": "Freight Train", "make": "", "class": 21, "seats": 0, "doors": 0, "type": "train", "price": 194680, "category": "land" }, "freightcar3": { "acceleration": 0.2, "braking": 5, "handling": 0.58, "speed": 26.6667, "traction": 2.5, "name": "Freight Train", "make": "", "class": 21, "seats": 0, "doors": 0, "type": "train", "price": 194680, "category": "land" }, "freightcont1": { "acceleration": 0.2, "braking": 5, "handling": 0.58, "speed": 26.6667, "traction": 2.5, "name": "Freight Train", "make": "", "class": 21, "seats": 2, "doors": 2, "type": "train", "price": 194680, "category": "land" }, "freightcont2": { "acceleration": 0.2, "braking": 5, "handling": 0.58, "speed": 26.6667, "traction": 2.5, "name": "Freight Train", "make": "", "class": 21, "seats": 2, "doors": 0, "type": "train", "price": 194680, "category": "land" }, "freightgrain": { "acceleration": 0.2, "braking": 5, "handling": 0.58, "speed": 26.6667, "traction": 2.5, "name": "Freight Train", "make": "", "class": 21, "seats": 2, "doors": 1, "type": "train", "price": 194680, "category": "land" }, "freighttrailer": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3, "name": "NULL", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "frogger": { "acceleration": 5.488, "braking": 3.1099, "handling": 5.488, "speed": 56.6681, "traction": 1.3, "name": "Frogger", "make": "", "class": 15, "seats": 4, "doors": 2, "type": "heli", "price": 31839300, "category": "air", "weapons": true }, "frogger2": { "acceleration": 5.488, "braking": 3.1099, "handling": 5.488, "speed": 56.6681, "traction": 1.3, "name": "Frogger", "make": "", "class": 15, "seats": 4, "doors": 2, "type": "heli", "price": 31839300, "category": "air", "weapons": true }, "fugitive": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 40.3933, "traction": 2.5, "name": "Fugitive", "make": "Cheval", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 67317, "category": "land" }, "furia": { "acceleration": 0.365, "braking": 1, "handling": 0.745, "speed": 49.8338, "traction": 2.7, "name": "Furia", "make": "Grotti", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 83110, "category": "land" }, "furoregt": { "acceleration": 0.335, "braking": 1, "handling": 0.715, "speed": 50.6667, "traction": 2.56, "name": "Furore GT", "make": "Lampadati", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 84346, "category": "land" }, "fusilade": { "acceleration": 0.32, "braking": 0.9, "handling": 0.7, "speed": 48.8621, "traction": 2.45, "name": "Fusilade", "make": "Schyster", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 81251, "category": "land" }, "futo": { "acceleration": 0.29, "braking": 0.5, "handling": 0.67, "speed": 45, "traction": 2.05, "name": "Futo", "make": "Karin", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 74336, "category": "land" }, "futo2": { "acceleration": 0.2965, "braking": 0.525, "handling": 0.6765, "speed": 45.6667, "traction": 2.095, "name": "Futo GTX", "make": "Karin", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 75463, "category": "land" }, "gargoyle": { "acceleration": 0.3125, "braking": 1.1, "handling": 0.6925, "speed": 48.2865, "traction": 2.25, "name": "Gargoyle", "make": "Western", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 25195, "category": "land" }, "gauntlet": { "acceleration": 0.3, "braking": 0.9, "handling": 0.68, "speed": 47.0182, "traction": 2.5, "name": "Gauntlet", "make": "Bravado", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 78237, "category": "land" }, "gauntlet2": { "acceleration": 0.315, "braking": 0.9, "handling": 0.695, "speed": 48.4065, "traction": 2.51, "name": "Redwood Gauntlet", "make": "Bravado", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 80506, "category": "land" }, "gauntlet3": { "acceleration": 0.28, "braking": 0.9, "handling": 0.66, "speed": 44.1427, "traction": 2.5, "name": "Gauntlet Classic", "make": "Bravado", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 73572, "category": "land" }, "gauntlet4": { "acceleration": 0.36, "braking": 0.9, "handling": 0.74, "speed": 48.6553, "traction": 2.35, "name": "Gauntlet Hellfire", "make": "Bravado", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 81048, "category": "land" }, "gauntlet5": { "acceleration": 0.29, "braking": 0.9, "handling": 0.67, "speed": 48.2968, "traction": 2.25, "name": "Gauntlet Classic Custom", "make": "Bravado", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 80250, "category": "land" }, "gauntlet6": { "acceleration": 0.3668, "braking": 0.935, "handling": 0.7467, "speed": 48.9926, "traction": 2.482, "name": "Hotring Hellfire", "make": "Bravado", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 81665, "category": "land" }, "gb200": { "acceleration": 0.315, "braking": 1, "handling": 0.695, "speed": 44.574, "traction": 2.3, "name": "GB200", "make": "Vapid", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 74534, "category": "land" }, "gburrito": { "acceleration": 0.16, "braking": 0.6, "handling": 0.54, "speed": 36.9472, "traction": 1.95, "name": "Gang Burrito", "make": "Declasse", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 61195, "category": "land" }, "gburrito2": { "acceleration": 0.18, "braking": 0.7, "handling": 0.56, "speed": 39.9117, "traction": 2.05, "name": "Gang Burrito", "make": "Declasse", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 66162, "category": "land" }, "glendale": { "acceleration": 0.235, "braking": 0.65, "handling": 0.615, "speed": 39.718, "traction": 2.05, "name": "Glendale", "make": "Benefactor", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 65948, "category": "land" }, "glendale2": { "acceleration": 0.233, "braking": 0.625, "handling": 0.613, "speed": 39.8451, "traction": 2.25, "name": "Glendale Custom", "make": "Benefactor", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 66105, "category": "land" }, "gp1": { "acceleration": 0.37, "braking": 1.2, "handling": 0.75, "speed": 50.3181, "traction": 2.68, "name": "GP1", "make": "Progen", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 84220, "category": "land" }, "graintrailer": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "NULL", "make": "", "class": 11, "seats": 0, "doors": 1, "type": "trailer", "price": 5668, "category": "land" }, "granger": { "acceleration": 0.19, "braking": 0.8, "handling": 0.57, "speed": 36.3729, "traction": 2.15, "name": "Granger", "make": "Declasse", "class": 2, "seats": 8, "doors": 6, "type": "automobile", "price": 60692, "category": "land" }, "granger2": { "acceleration": 0.2075, "braking": 0.84, "handling": 0.5875, "speed": 34.0257, "traction": 2.18, "name": "Granger 3600LX", "make": "Declasse", "class": 2, "seats": 8, "doors": 6, "type": "automobile", "price": 57057, "category": "land" }, "greenwood": { "acceleration": 0.282, "braking": 0.7, "handling": 0.662, "speed": 47.6667, "traction": 2.3, "name": "Greenwood", "make": "Bravado", "class": 4, "seats": 4, "doors": 6, "type": "automobile", "price": 78897, "category": "land" }, "gresley": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 38.4289, "traction": 1.9, "name": "Gresley", "make": "Bravado", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 63694, "category": "land" }, "growler": { "acceleration": 0.3338, "braking": 0.84, "handling": 0.7138, "speed": 49.7596, "traction": 2.63, "name": "Growler", "make": "Pfister", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 82635, "category": "land" }, "gt500": { "acceleration": 0.29, "braking": 0.77, "handling": 0.67, "speed": 45.8691, "traction": 2.2, "name": "GT500", "make": "Grotti", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 76158, "category": "land" }, "guardian": { "acceleration": 0.21, "braking": 0.6, "handling": 0.59, "speed": 39.6177, "traction": 2, "name": "Guardian", "make": "Vapid", "class": 10, "seats": 6, "doors": 6, "type": "automobile", "price": 65628, "category": "land" }, "habanero": { "acceleration": 0.18, "braking": 0.25, "handling": 0.56, "speed": 39.9117, "traction": 2.1, "name": "Habanero", "make": "Emperor", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 65442, "category": "land" }, "hakuchou": { "acceleration": 0.315, "braking": 1.4, "handling": 0.695, "speed": 49.5412, "traction": 2.3, "name": "Hakuchou", "make": "Shitzu", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 25975, "category": "land" }, "hakuchou2": { "acceleration": 0.425, "braking": 1.4, "handling": 0.885, "speed": 52.0387, "traction": 2.9, "name": "Hakuchou Drag", "make": "Shitzu", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 27374, "category": "land" }, "halftrack": { "acceleration": 0.16, "braking": 0.2, "handling": 0.54, "speed": 20.9333, "traction": 2.3, "name": "Half-track", "make": "Bravado", "class": 19, "seats": 3, "doors": 3, "type": "automobile", "price": 174666, "category": "land", "weapons": true }, "handler": { "acceleration": 0.14, "braking": 0.1, "handling": 0.52, "speed": 8.3333, "traction": 1.85, "name": "Dock Handler", "make": "", "class": 10, "seats": 1, "doors": 1, "type": "automobile", "price": 14549, "category": "land" }, "hauler": { "acceleration": 0.16, "braking": 0.8, "handling": 0.54, "speed": 28.6501, "traction": 1.55, "name": "Hauler", "make": "JoBuilt", "class": 20, "seats": 2, "doors": 2, "type": "automobile", "price": 48240, "category": "land" }, "hauler2": { "acceleration": 0.32, "braking": 0.85, "handling": 0.7, "speed": 41.9442, "traction": 1.95, "name": "Hauler Custom", "make": "JoBuilt", "class": 20, "seats": 2, "doors": 2, "type": "automobile", "price": 70102, "category": "land" }, "havok": { "acceleration": 5.39, "braking": 3.1212, "handling": 5.39, "speed": 57.9072, "traction": 1.3, "name": "Havok", "make": "Nagasaki", "class": 15, "seats": 1, "doors": 1, "type": "heli", "price": 6462756, "category": "air" }, "hellion": { "acceleration": 0.25, "braking": 0.3, "handling": 0.63, "speed": 40.3984, "traction": 2, "name": "Hellion", "make": "Annis", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 66525, "category": "land" }, "hermes": { "acceleration": 0.285, "braking": 0.775, "handling": 0.665, "speed": 41.2605, "traction": 2.375, "name": "Hermes", "make": "Albany", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 68776, "category": "land" }, "hexer": { "acceleration": 0.26, "braking": 1, "handling": 0.64, "speed": 43.1425, "traction": 1.85, "name": "Hexer", "make": "LCC", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 22521, "category": "land" }, "hotknife": { "acceleration": 0.3, "braking": 0.43, "handling": 0.68, "speed": 45.0454, "traction": 1.85, "name": "Hotknife", "make": "Vapid", "class": 4, "seats": 2, "doors": 3, "type": "automobile", "price": 74328, "category": "land" }, "hotring": { "acceleration": 0.335, "braking": 0.7, "handling": 0.715, "speed": 48.0775, "traction": 2.55, "name": "Hotring Sabre", "make": "Declasse", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 79724, "category": "land" }, "howard": { "acceleration": 20.58, "braking": 20.58, "handling": 20.58, "speed": 100, "traction": 1.15, "name": "Howard NX-25", "make": "Buckingham", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 2587840, "category": "air" }, "hunter": { "acceleration": 5.488, "braking": 3.2135, "handling": 5.488, "speed": 58.5543, "traction": 1.3, "name": "FH-1 Hunter", "make": "", "class": 15, "seats": 2, "doors": 4, "type": "heli", "price": 32734709, "category": "air", "weapons": true }, "huntley": { "acceleration": 0.265, "braking": 0.55, "handling": 0.645, "speed": 45.3333, "traction": 2.1, "name": "Huntley S", "make": "Enus", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 74869, "category": "land" }, "hustler": { "acceleration": 0.3, "braking": 0.43, "handling": 0.68, "speed": 44.1528, "traction": 1.95, "name": "Hustler", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 72900, "category": "land" }, "hydra": { "acceleration": 8.82, "braking": 9.6812, "handling": 8.82, "speed": 109.7643, "traction": 1.15, "name": "Hydra", "make": "Mammoth", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 10966840, "category": "air", "weapons": true }, "ignus": { "acceleration": 0.3835, "braking": 1, "handling": 0.7635, "speed": 52.3653, "traction": 2.7675, "name": "Ignus", "make": "Pegassi", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 87219, "category": "land" }, "imorgon": { "acceleration": 0.66, "braking": 0.835, "handling": 1.04, "speed": 45.499, "traction": 2.598, "name": "Imorgon", "make": "Overflod", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 76854, "category": "land" }, "impaler": { "acceleration": 0.31, "braking": 0.6, "handling": 0.69, "speed": 45.0153, "traction": 2.35, "name": "Impaler", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 74584, "category": "land" }, "impaler2": { "acceleration": 0.38, "braking": 0.7, "handling": 0.76, "speed": 50.7078, "traction": 2.4, "name": "Apocalypse Impaler", "make": "Declasse", "class": 4, "seats": 2, "doors": 2, "type": "automobile", "price": 84076, "category": "land" }, "impaler3": { "acceleration": 0.38, "braking": 0.7, "handling": 0.76, "speed": 50.7078, "traction": 2.4, "name": "Future Shock Impaler", "make": "Declasse", "class": 4, "seats": 2, "doors": 2, "type": "automobile", "price": 84076, "category": "land" }, "impaler4": { "acceleration": 0.38, "braking": 0.7, "handling": 0.76, "speed": 50.7078, "traction": 2.4, "name": "Nightmare Impaler", "make": "Declasse", "class": 4, "seats": 2, "doors": 2, "type": "automobile", "price": 84076, "category": "land" }, "impaler5": { "acceleration": 0.2537, "braking": 0.865, "handling": 0.6338, "speed": 47.5449, "traction": 2.3, "name": "Impaler SZ", "make": "Declasse", "class": 4, "seats": 4, "doors": 6, "type": "automobile", "price": 78875, "category": "land" }, "impaler6": { "acceleration": 0.3, "braking": 0.7, "handling": 0.68, "speed": 47.0182, "traction": 2.135, "name": "Impaler LX", "make": "Declasse", "class": 4, "seats": 4, "doors": 6, "type": "automobile", "price": 77917, "category": "land" }, "imperator": { "acceleration": 0.365, "braking": 0.5, "handling": 0.745, "speed": 48.5751, "traction": 2.25, "name": "Apocalypse Imperator", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 80296, "category": "land" }, "imperator2": { "acceleration": 0.365, "braking": 0.5, "handling": 0.745, "speed": 48.5751, "traction": 2.25, "name": "Future Shock Imperator", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 80296, "category": "land" }, "imperator3": { "acceleration": 0.365, "braking": 0.5, "handling": 0.745, "speed": 48.5751, "traction": 2.25, "name": "Nightmare Imperator", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 80296, "category": "land" }, "inductor": { "acceleration": 0.112, "braking": 3.1, "handling": 0.492, "speed": 10.7824, "traction": 2.05, "name": "Inductor", "make": "", "class": 13, "seats": 1, "doors": 1, "type": "bicycle", "price": 4345, "category": "land" }, "inductor2": { "acceleration": 0.112, "braking": 3.1, "handling": 0.492, "speed": 10.7824, "traction": 2.05, "name": "Junk Energy Inductor", "make": "", "class": 13, "seats": 1, "doors": 1, "type": "bicycle", "price": 4345, "category": "land" }, "infernus": { "acceleration": 0.34, "braking": 0.5, "handling": 0.72, "speed": 49.1131, "traction": 2.65, "name": "Infernus", "make": "Pegassi", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 81076, "category": "land" }, "infernus2": { "acceleration": 0.33, "braking": 0.5, "handling": 0.71, "speed": 48.0533, "traction": 2.635, "name": "Infernus Classic", "make": "Pegassi", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 79349, "category": "land" }, "ingot": { "acceleration": 0.14, "braking": 0.6, "handling": 0.52, "speed": 32.0802, "traction": 1.95, "name": "Ingot", "make": "Vulcar", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 53344, "category": "land" }, "innovation": { "acceleration": 0.32, "braking": 1, "handling": 0.7, "speed": 45, "traction": 1.9, "name": "Innovation", "make": "LCC", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 23510, "category": "land" }, "insurgent": { "acceleration": 0.24, "braking": 0.6, "handling": 0.62, "speed": 35.4436, "traction": 2, "name": "Insurgent Pick-Up", "make": "HVY", "class": 9, "seats": 9, "doors": 5, "type": "automobile", "price": 295228, "category": "land", "weapons": true }, "insurgent2": { "acceleration": 0.24, "braking": 0.6, "handling": 0.62, "speed": 35.4436, "traction": 2, "name": "Insurgent", "make": "HVY", "class": 9, "seats": 6, "doors": 5, "type": "automobile", "price": 59045, "category": "land" }, "insurgent3": { "acceleration": 0.24, "braking": 0.6, "handling": 0.62, "speed": 35.4436, "traction": 2, "name": "Insurgent Pick-Up Custom", "make": "HVY", "class": 9, "seats": 9, "doors": 5, "type": "automobile", "price": 295228, "category": "land", "weapons": true }, "intruder": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 39.3713, "traction": 2.35, "name": "Intruder", "make": "Karin", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 65682, "category": "land" }, "issi2": { "acceleration": 0.23, "braking": 0.6, "handling": 0.61, "speed": 41.9174, "traction": 2.05, "name": "Issi", "make": "Weeny", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 69371, "category": "land" }, "issi3": { "acceleration": 0.26, "braking": 0.3, "handling": 0.64, "speed": 39.7959, "traction": 2, "name": "Issi Classic", "make": "Weeny", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 65593, "category": "land" }, "issi4": { "acceleration": 0.34, "braking": 0.4, "handling": 0.72, "speed": 44.9375, "traction": 2, "name": "Apocalypse Issi", "make": "Weeny", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 74236, "category": "land" }, "issi5": { "acceleration": 0.34, "braking": 0.4, "handling": 0.72, "speed": 44.9375, "traction": 2, "name": "Future Shock Issi", "make": "Weeny", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 74236, "category": "land" }, "issi6": { "acceleration": 0.34, "braking": 0.4, "handling": 0.72, "speed": 44.9375, "traction": 2, "name": "Nightmare Issi", "make": "Weeny", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 74236, "category": "land" }, "issi7": { "acceleration": 0.305, "braking": 1, "handling": 0.685, "speed": 42.0375, "traction": 2.55, "name": "Issi Sport", "make": "Weeny", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 70444, "category": "land" }, "issi8": { "acceleration": 0.3215, "braking": 0.548, "handling": 0.7415, "speed": 48.2089, "traction": 2.1495, "name": "Issi Rally", "make": "Weeny", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 79711, "category": "land" }, "italigtb": { "acceleration": 0.3365, "braking": 1.1, "handling": 0.7165, "speed": 52.1595, "traction": 2.5, "name": "Itali GTB", "make": "Progen", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 86900, "category": "land" }, "italigtb2": { "acceleration": 0.34, "braking": 1.2, "handling": 0.72, "speed": 52.4821, "traction": 2.55, "name": "Itali GTB Custom", "make": "Progen", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 87587, "category": "land" }, "italigto": { "acceleration": 0.4, "braking": 1.1, "handling": 0.78, "speed": 52.3922, "traction": 2.62, "name": "Itali GTO", "make": "Grotti", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 87475, "category": "land" }, "italirsx": { "acceleration": 0.4013, "braking": 1.35, "handling": 0.7813, "speed": 52.2692, "traction": 2.737, "name": "Itali RSX", "make": "Grotti", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 87682, "category": "land" }, "iwagen": { "acceleration": 0.2315, "braking": 0.775, "handling": 0.6115, "speed": 33.3125, "traction": 2.365, "name": "I-Wagen", "make": "Obey", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 55888, "category": "land" }, "jackal": { "acceleration": 0.22, "braking": 0.9, "handling": 0.6, "speed": 45.9825, "traction": 2.6, "name": "Jackal", "make": "Ocelot", "class": 3, "seats": 4, "doors": 6, "type": "automobile", "price": 76324, "category": "land" }, "jb700": { "acceleration": 0.26, "braking": 0.6, "handling": 0.64, "speed": 50, "traction": 2.15, "name": "JB 700", "make": "Dewbauchee", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 82400, "category": "land" }, "jb7002": { "acceleration": 0.26, "braking": 0.6, "handling": 0.64, "speed": 50, "traction": 2.15, "name": "JB 700W", "make": "Dewbauchee", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 82400, "category": "land" }, "jester": { "acceleration": 0.3, "braking": 0.95, "handling": 0.68, "speed": 45.9981, "traction": 2.55, "name": "Jester", "make": "Dinka", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 76684, "category": "land" }, "jester2": { "acceleration": 0.31, "braking": 0.95, "handling": 0.69, "speed": 46.9034, "traction": 2.57, "name": "Jester (Racecar)", "make": "Dinka", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 78165, "category": "land" }, "jester3": { "acceleration": 0.32, "braking": 0.95, "handling": 0.7, "speed": 48.319, "traction": 2.575, "name": "Jester Classic", "make": "Dinka", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 80462, "category": "land" }, "jester4": { "acceleration": 0.3291, "braking": 0.85, "handling": 0.7091, "speed": 48.0367, "traction": 2.59, "name": "Jester RR", "make": "Dinka", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 79879, "category": "land" }, "jester5": { "acceleration": 0.3291, "braking": 0.85, "handling": 0.7091, "speed": 46.905, "traction": 2.725, "name": "Jester RR Widebody", "make": "Dinka", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 78069, "category": "land" }, "jet": { "acceleration": 6.174, "braking": 4.8714, "handling": 6.174, "speed": 78.9023, "traction": 1.6, "name": "Jet", "make": "", "class": 16, "seats": 2, "doors": 0, "type": "plane", "price": 1537947, "category": "air" }, "jetmax": { "acceleration": 17, "braking": 0.4, "handling": 17.38, "speed": 45, "traction": 0, "name": "Jetmax", "make": "Shitzu", "class": 14, "seats": 2, "doors": 0, "type": "boat", "price": 478680, "category": "sea" }, "journey": { "acceleration": 0.13, "braking": 0.25, "handling": 0.51, "speed": 32.1641, "traction": 1.4, "name": "Journey", "make": "Zirconium", "class": 12, "seats": 6, "doors": 3, "type": "automobile", "price": 52886, "category": "land" }, "journey2": { "acceleration": 0.13, "braking": 0.25, "handling": 0.51, "speed": 32.1641, "traction": 1.4, "name": "Journey II", "make": "Zirconium", "class": 12, "seats": 6, "doors": 3, "type": "automobile", "price": 52886, "category": "land" }, "jubilee": { "acceleration": 0.2975, "braking": 0.75, "handling": 0.6775, "speed": 42.7814, "traction": 2.285, "name": "Jubilee", "make": "Enus", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 71210, "category": "land" }, "jugular": { "acceleration": 0.378, "braking": 1.1, "handling": 0.758, "speed": 48.6466, "traction": 2.62, "name": "Jugular", "make": "Ocelot", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 81412, "category": "land" }, "kalahari": { "acceleration": 0.26, "braking": 1, "handling": 0.64, "speed": 35.004, "traction": 1.78, "name": "Kalahari", "make": "Canis", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 59046, "category": "land" }, "kamacho": { "acceleration": 0.255, "braking": 0.3, "handling": 0.635, "speed": 40.8822, "traction": 2, "name": "Kamacho", "make": "Canis", "class": 9, "seats": 4, "doors": 6, "type": "automobile", "price": 67315, "category": "land" }, "kanjo": { "acceleration": 0.32, "braking": 0.5, "handling": 0.7, "speed": 44.4042, "traction": 1.97, "name": "Blista Kanjo", "make": "Dinka", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 73478, "category": "land" }, "kanjosj": { "acceleration": 0.315, "braking": 0.45, "handling": 0.695, "speed": 44.6584, "traction": 1.995, "name": "Kanjo SJ", "make": "Dinka", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 73789, "category": "land" }, "khamelion": { "acceleration": 0.15, "braking": 0.9, "handling": 0.53, "speed": 31.7276, "traction": 2.6, "name": "Khamelion", "make": "Hijak", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 53292, "category": "land" }, "khanjali": { "acceleration": 0.1, "braking": 0.2, "handling": 0.48, "speed": 18.1221, "traction": 2.55, "name": "TM-02 Khanjali", "make": "", "class": 19, "seats": 4, "doors": 4, "type": "automobile", "price": 151216, "category": "land", "weapons": true }, "komoda": { "acceleration": 0.367, "braking": 0.95, "handling": 0.747, "speed": 49.8918, "traction": 2.69, "name": "Komoda", "make": "Lampadati", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 83129, "category": "land" }, "kosatka": { "acceleration": 5.25, "braking": 0.4, "handling": 5.63, "speed": 25, "traction": 0, "name": "Kosatka", "make": "RUNE", "class": 14, "seats": 1, "doors": 3, "type": "submarine", "price": 3990800, "category": "sea", "weapons": true }, "krieger": { "acceleration": 0.374, "braking": 1.12, "handling": 0.754, "speed": 52.5741, "traction": 2.782, "name": "Krieger", "make": "Benefactor", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 87715, "category": "land" }, "kuruma": { "acceleration": 0.31, "braking": 0.5, "handling": 0.69, "speed": 46.9034, "traction": 2.45, "name": "Kuruma", "make": "Karin", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 77445, "category": "land" }, "kuruma2": { "acceleration": 0.31, "braking": 0.5, "handling": 0.69, "speed": 45.9285, "traction": 2.25, "name": "Kuruma (Armored)", "make": "Karin", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 75885, "category": "land" }, "l35": { "acceleration": 0.236, "braking": 0.32, "handling": 0.616, "speed": 39.0963, "traction": 2.038, "name": "Walton L35", "make": "Declasse", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 64429, "category": "land" }, "landstalker": { "acceleration": 0.18, "braking": 0.8, "handling": 0.56, "speed": 36.8308, "traction": 2.1, "name": "Landstalker", "make": "Dundreary", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 61393, "category": "land" }, "landstalker2": { "acceleration": 0.19, "braking": 0.8, "handling": 0.57, "speed": 37.6554, "traction": 2.15, "name": "Landstalker XL", "make": "Dundreary", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 62744, "category": "land" }, "lazer": { "acceleration": 19.6, "braking": 17.8923, "handling": 19.6, "speed": 91.2871, "traction": 2.15, "name": "P-996 LAZER", "make": "", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 11870352, "category": "air", "weapons": true }, "le7b": { "acceleration": 0.371, "braking": 1.1, "handling": 0.751, "speed": 50.3563, "traction": 2.68, "name": "RE-7B", "make": "Annis", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 84125, "category": "land" }, "lectro": { "acceleration": 0.28, "braking": 1.2, "handling": 0.66, "speed": 43.2353, "traction": 1.95, "name": "Lectro", "make": "Principe", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 45375, "category": "land" }, "lguard": { "acceleration": 0.2, "braking": 0.8, "handling": 0.58, "speed": 37.5559, "traction": 2.05, "name": "Lifeguard", "make": "Declasse", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 62617, "category": "land" }, "limo2": { "acceleration": 0.27, "braking": 0.8, "handling": 0.65, "speed": 39.2704, "traction": 2.175, "name": "Turreted Limo", "make": "Benefactor", "class": 1, "seats": 5, "doors": 6, "type": "automobile", "price": 327923, "category": "land", "weapons": true }, "lm87": { "acceleration": 0.3765, "braking": 1.35, "handling": 0.7565, "speed": 50.3353, "traction": 2.77, "name": "LM87", "make": "Benefactor", "class": 7, "seats": 1, "doors": 4, "type": "automobile", "price": 84509, "category": "land" }, "locust": { "acceleration": 0.334, "braking": 1, "handling": 0.714, "speed": 48.8089, "traction": 2.685, "name": "Locust", "make": "Ocelot", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 81371, "category": "land" }, "longfin": { "acceleration": 18, "braking": 0.4, "handling": 18.38, "speed": 46.6667, "traction": 0, "name": "Longfin", "make": "Shitzu", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 500680, "category": "sea" }, "lurcher": { "acceleration": 0.29, "braking": 0.8, "handling": 0.67, "speed": 47.1435, "traction": 2.15, "name": "Lurcher", "make": "Albany", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 78245, "category": "land" }, "luxor": { "acceleration": 7.938, "braking": 7.193, "handling": 7.938, "speed": 90.6145, "traction": 1.15, "name": "Luxor", "make": "Buckingham", "class": 16, "seats": 10, "doors": 1, "type": "plane", "price": 1818936, "category": "air" }, "luxor2": { "acceleration": 8.036, "braking": 7.3309, "handling": 8.036, "speed": 91.2252, "traction": 1.15, "name": "Luxor Deluxe", "make": "Buckingham", "class": 16, "seats": 8, "doors": 1, "type": "plane", "price": 1834049, "category": "air" }, "lynx": { "acceleration": 0.315, "braking": 1, "handling": 0.695, "speed": 49.0772, "traction": 2.56, "name": "Lynx", "make": "Ocelot", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 81739, "category": "land" }, "mamba": { "acceleration": 0.34, "braking": 0.5, "handling": 0.72, "speed": 49.3333, "traction": 2.5, "name": "Mamba", "make": "Declasse", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 81429, "category": "land" }, "mammatus": { "acceleration": 4.9, "braking": 3.399, "handling": 4.9, "speed": 69.3676, "traction": 2.15, "name": "Mammatus", "make": "", "class": 16, "seats": 4, "doors": 3, "type": "plane", "price": 1321065, "category": "air" }, "manana": { "acceleration": 0.16, "braking": 0.25, "handling": 0.54, "speed": 36.9472, "traction": 1.95, "name": "Manana", "make": "Albany", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 60635, "category": "land" }, "manana2": { "acceleration": 0.24, "braking": 0.275, "handling": 0.62, "speed": 43.563, "traction": 1.98, "name": "Manana Custom", "make": "Albany", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 71516, "category": "land" }, "manchez": { "acceleration": 0.295, "braking": 1.2, "handling": 0.675, "speed": 44.1064, "traction": 2.15, "name": "Manchez", "make": "Maibatsu", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 23138, "category": "land" }, "manchez2": { "acceleration": 0.265, "braking": 0.8, "handling": 0.645, "speed": 41.3801, "traction": 2.12, "name": "Manchez Scout", "make": "Maibatsu", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 21545, "category": "land" }, "manchez3": { "acceleration": 0.272, "braking": 0.8, "handling": 0.652, "speed": 41.5401, "traction": 2.11, "name": "Manchez Scout C", "make": "Maibatsu", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 21632, "category": "land" }, "marquis": { "acceleration": 2.5, "braking": 0.4, "handling": 2.88, "speed": 10, "traction": 0, "name": "Marquis", "make": "Dinka", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 94680, "category": "sea" }, "marshall": { "acceleration": 0.4, "braking": 0.65, "handling": 0.78, "speed": 33.9677, "traction": 2.25, "name": "Marshall", "make": "Cheval", "class": 9, "seats": 2, "doors": 3, "type": "automobile", "price": 57276, "category": "land" }, "massacro": { "acceleration": 0.364, "braking": 0.9, "handling": 0.744, "speed": 50.4748, "traction": 2.42, "name": "Massacro", "make": "Dewbauchee", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 83972, "category": "land" }, "massacro2": { "acceleration": 0.364, "braking": 0.9, "handling": 0.744, "speed": 50.4748, "traction": 2.43, "name": "Massacro (Racecar)", "make": "Dewbauchee", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 83972, "category": "land" }, "maverick": { "acceleration": 5.096, "braking": 2.7553, "handling": 5.096, "speed": 54.0675, "traction": 1.3, "name": "Maverick", "make": "", "class": 15, "seats": 4, "doors": 2, "type": "heli", "price": 30156660, "category": "air", "weapons": true }, "menacer": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 31.794, "traction": 2, "name": "Menacer", "make": "HVY", "class": 9, "seats": 5, "doors": 5, "type": "automobile", "price": 265392, "category": "land", "weapons": true }, "mesa": { "acceleration": 0.17, "braking": 0.3, "handling": 0.55, "speed": 34.6832, "traction": 2, "name": "Mesa", "make": "Canis", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 57125, "category": "land" }, "mesa2": { "acceleration": 0.17, "braking": 0.3, "handling": 0.55, "speed": 34.6832, "traction": 2, "name": "Mesa", "make": "Canis", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 57125, "category": "land" }, "mesa3": { "acceleration": 0.17, "braking": 0.3, "handling": 0.55, "speed": 34.6832, "traction": 2, "name": "Mesa", "make": "Canis", "class": 9, "seats": 4, "doors": 6, "type": "automobile", "price": 57125, "category": "land" }, "metrotrain": { "acceleration": 0.2, "braking": 5, "handling": 0.58, "speed": 26.6667, "traction": 2.5, "name": "Freight Train", "make": "", "class": 21, "seats": 4, "doors": 4, "type": "train", "price": 194680, "category": "land" }, "michelli": { "acceleration": 0.2825, "braking": 0.75, "handling": 0.6625, "speed": 44.5681, "traction": 2.3, "name": "Michelli GT", "make": "Lampadati", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 74020, "category": "land" }, "microlight": { "acceleration": 13.72, "braking": 4.1367, "handling": 13.72, "speed": 30.1511, "traction": 2.15, "name": "Ultralight", "make": "Nagasaki", "class": 16, "seats": 1, "doors": 0, "type": "plane", "price": 987644, "category": "air" }, "miljet": { "acceleration": 8.134, "braking": 7.4696, "handling": 8.134, "speed": 91.8322, "traction": 1.5, "name": "Miljet", "make": "Buckingham", "class": 16, "seats": 16, "doors": 1, "type": "plane", "price": 1849116, "category": "air" }, "minitank": { "acceleration": 0.06, "braking": 0.15, "handling": 0.44, "speed": 9.7297, "traction": 2, "name": "Invade and Persuade Tank", "make": "", "class": 19, "seats": 1, "doors": 0, "type": "automobile", "price": 124556, "category": "land", "weapons": true }, "minivan": { "acceleration": 0.15, "braking": 0.4, "handling": 0.53, "speed": 35.4017, "traction": 1.9, "name": "Minivan", "make": "Vapid", "class": 12, "seats": 4, "doors": 6, "type": "automobile", "price": 58370, "category": "land" }, "minivan2": { "acceleration": 0.15, "braking": 0.45, "handling": 0.53, "speed": 35.4017, "traction": 1.925, "name": "Minivan Custom", "make": "Vapid", "class": 12, "seats": 4, "doors": 6, "type": "automobile", "price": 58450, "category": "land" }, "mixer": { "acceleration": 0.11, "braking": 0.3, "handling": 0.49, "speed": 32.4021, "traction": 1.6, "name": "Mixer", "make": "HVY", "class": 10, "seats": 2, "doors": 3, "type": "automobile", "price": 53283, "category": "land" }, "mixer2": { "acceleration": 0.11, "braking": 0.3, "handling": 0.49, "speed": 32.4021, "traction": 1.6, "name": "Mixer", "make": "HVY", "class": 10, "seats": 2, "doors": 3, "type": "automobile", "price": 53283, "category": "land" }, "mogul": { "acceleration": 5.88, "braking": 4.5156, "handling": 5.88, "speed": 76.7957, "traction": 2.15, "name": "Mogul", "make": "Mammoth", "class": 16, "seats": 3, "doors": 3, "type": "plane", "price": 7445704, "category": "air", "weapons": true }, "molotok": { "acceleration": 14.7, "braking": 13.4192, "handling": 14.7, "speed": 91.2871, "traction": 2.15, "name": "V-65 Molotok", "make": "", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 10728503, "category": "air", "weapons": true }, "monroe": { "acceleration": 0.28, "braking": 0.65, "handling": 0.66, "speed": 50, "traction": 2.2, "name": "Monroe", "make": "Pegassi", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 82544, "category": "land" }, "monster": { "acceleration": 0.4, "braking": 0.65, "handling": 0.78, "speed": 33.9677, "traction": 2.25, "name": "Liberator", "make": "Vapid", "class": 9, "seats": 2, "doors": 3, "type": "automobile", "price": 57276, "category": "land" }, "monster3": { "acceleration": 0.41, "braking": 0.65, "handling": 0.79, "speed": 38.3787, "traction": 2.275, "name": "Apocalypse Sasquatch", "make": "Bravado", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 64365, "category": "land" }, "monster4": { "acceleration": 0.41, "braking": 0.65, "handling": 0.79, "speed": 38.3787, "traction": 2.275, "name": "Future Shock Sasquatch", "make": "Bravado", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 64365, "category": "land" }, "monster5": { "acceleration": 0.41, "braking": 0.65, "handling": 0.79, "speed": 38.3787, "traction": 2.275, "name": "Nightmare Sasquatch", "make": "Bravado", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 64365, "category": "land" }, "monstrociti": { "acceleration": 0.2735, "braking": 0.369, "handling": 0.7135, "speed": 41.3981, "traction": 2.16, "name": "MonstroCiti", "make": "Maibatsu", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 68406, "category": "land" }, "moonbeam": { "acceleration": 0.21, "braking": 0.4, "handling": 0.59, "speed": 41.6667, "traction": 2, "name": "Moonbeam", "make": "Declasse", "class": 4, "seats": 4, "doors": 6, "type": "automobile", "price": 68586, "category": "land" }, "moonbeam2": { "acceleration": 0.21, "braking": 0.4, "handling": 0.59, "speed": 41.6667, "traction": 2, "name": "Moonbeam Custom", "make": "Declasse", "class": 4, "seats": 4, "doors": 6, "type": "automobile", "price": 68586, "category": "land" }, "mower": { "acceleration": 0.05, "braking": 0.5, "handling": 0.43, "speed": 6.6667, "traction": 1.35, "name": "Lawn Mower", "make": "", "class": 11, "seats": 1, "doors": 1, "type": "automobile", "price": 12234, "category": "land" }, "mule": { "acceleration": 0.11, "braking": 0.25, "handling": 0.49, "speed": 28.6961, "traction": 1.5, "name": "Mule", "make": "Maibatsu", "class": 20, "seats": 6, "doors": 4, "type": "automobile", "price": 47273, "category": "land" }, "mule2": { "acceleration": 0.11, "braking": 0.25, "handling": 0.49, "speed": 28.6961, "traction": 1.5, "name": "Mule", "make": "Maibatsu", "class": 20, "seats": 2, "doors": 3, "type": "automobile", "price": 47273, "category": "land" }, "mule3": { "acceleration": 0.17, "braking": 0.25, "handling": 0.55, "speed": 31.9047, "traction": 1.5, "name": "Mule", "make": "Maibatsu", "class": 20, "seats": 6, "doors": 4, "type": "automobile", "price": 52599, "category": "land" }, "mule4": { "acceleration": 0.11, "braking": 0.25, "handling": 0.49, "speed": 28.6961, "traction": 1.5, "name": "Mule Custom", "make": "Maibatsu", "class": 20, "seats": 4, "doors": 4, "type": "automobile", "price": 47273, "category": "land" }, "mule5": { "acceleration": 0.17, "braking": 0.25, "handling": 0.55, "speed": 31.9047, "traction": 1.5, "name": "Mule", "make": "Maibatsu", "class": 20, "seats": 6, "doors": 4, "type": "automobile", "price": 52599, "category": "land" }, "nebula": { "acceleration": 0.24, "braking": 0.5, "handling": 0.62, "speed": 37.9382, "traction": 1.95, "name": "Nebula Turbo", "make": "Vulcar", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 62877, "category": "land" }, "nemesis": { "acceleration": 0.3, "braking": 1.2, "handling": 0.68, "speed": 45.0454, "traction": 1.95, "name": "Nemesis", "make": "Principe", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 23612, "category": "land" }, "neo": { "acceleration": 0.387, "braking": 1.2, "handling": 0.767, "speed": 51.2066, "traction": 2.62, "name": "Neo", "make": "Vysser", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 85696, "category": "land" }, "neon": { "acceleration": 0.2505, "braking": 1.3, "handling": 0.6305, "speed": 38.9224, "traction": 2.275, "name": "Neon", "make": "Pfister", "class": 6, "seats": 4, "doors": 4, "type": "automobile", "price": 65765, "category": "land" }, "nero": { "acceleration": 0.3375, "braking": 1, "handling": 0.7175, "speed": 52.642, "traction": 2.65, "name": "Nero", "make": "Truffade", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 87515, "category": "land" }, "nero2": { "acceleration": 0.3401, "braking": 1.1, "handling": 0.7201, "speed": 52.879, "traction": 2.675, "name": "Nero Custom", "make": "Truffade", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 88062, "category": "land" }, "nightblade": { "acceleration": 0.31, "braking": 1.2, "handling": 0.69, "speed": 46.9034, "traction": 1.95, "name": "Nightblade", "make": "Western", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 24551, "category": "land" }, "nightshade": { "acceleration": 0.25, "braking": 0.6, "handling": 0.63, "speed": 40.3984, "traction": 2.25, "name": "Nightshade", "make": "Imponte", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 67005, "category": "land" }, "nightshark": { "acceleration": 0.27, "braking": 0.75, "handling": 0.65, "speed": 36.833, "traction": 2.1, "name": "Nightshark", "make": "HVY", "class": 9, "seats": 4, "doors": 4, "type": "automobile", "price": 308024, "category": "land", "weapons": true }, "nimbus": { "acceleration": 8.134, "braking": 7.6912, "handling": 8.134, "speed": 94.5562, "traction": 1.15, "name": "Nimbus", "make": "Buckingham", "class": 16, "seats": 8, "doors": 1, "type": "plane", "price": 1896246, "category": "air" }, "ninef": { "acceleration": 0.33, "braking": 1, "handling": 0.71, "speed": 48.6724, "traction": 2.55, "name": "9F", "make": "Obey", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 81139, "category": "land" }, "ninef2": { "acceleration": 0.33, "braking": 1, "handling": 0.71, "speed": 48.6724, "traction": 2.55, "name": "9F Cabrio", "make": "Obey", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 81139, "category": "land" }, "niobe": { "acceleration": 0.3805, "braking": 0.76, "handling": 0.8405, "speed": 50.7465, "traction": 2.605, "name": "Niobe", "make": "Ubermacht", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 84364, "category": "land" }, "nokota": { "acceleration": 14.7, "braking": 12.8928, "handling": 14.7, "speed": 87.7058, "traction": 2.15, "name": "P-45 Nokota", "make": "", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 10399888, "category": "air", "weapons": true }, "novak": { "acceleration": 0.3, "braking": 0.8, "handling": 0.68, "speed": 47.0182, "traction": 2.3, "name": "Novak", "make": "Lampadati", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 78077, "category": "land" }, "omnis": { "acceleration": 0.305, "braking": 1, "handling": 0.685, "speed": 43.7374, "traction": 2, "name": "Omnis", "make": "Obey", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 73163, "category": "land" }, "omnisegt": { "acceleration": 0.58, "braking": 1, "handling": 0.96, "speed": 47.6944, "traction": 2.595, "name": "Omnis e-GT", "make": "Obey", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 80375, "category": "land" }, "openwheel1": { "acceleration": 0.76, "braking": 1.3, "handling": 1.14, "speed": 52.5277, "traction": 3.2925, "name": "BR8", "make": "Benefactor", "class": 22, "seats": 1, "doors": 0, "type": "automobile", "price": 178328, "category": "land" }, "openwheel2": { "acceleration": 0.655, "braking": 1.15, "handling": 1.035, "speed": 53.0537, "traction": 3.08, "name": "DR1", "make": "Declasse", "class": 22, "seats": 1, "doors": 0, "type": "automobile", "price": 178859, "category": "land" }, "oppressor": { "acceleration": 0.4, "braking": 1.2, "handling": 0.78, "speed": 48.4978, "traction": 2.125, "name": "Oppressor", "make": "Pegassi", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 381583, "category": "land", "weapons": true }, "oppressor2": { "acceleration": 0.38, "braking": 1.2, "handling": 0.76, "speed": 47.0927, "traction": 2.1, "name": "Oppressor Mk II", "make": "Pegassi", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 370745, "category": "land", "weapons": true }, "oracle": { "acceleration": 0.26, "braking": 0.9, "handling": 0.64, "speed": 45.1953, "traction": 2.25, "name": "Oracle XS", "make": "Ubermacht", "class": 3, "seats": 4, "doors": 6, "type": "automobile", "price": 75192, "category": "land" }, "oracle2": { "acceleration": 0.27, "braking": 0.9, "handling": 0.65, "speed": 46.2473, "traction": 2.25, "name": "Oracle", "make": "Ubermacht", "class": 3, "seats": 4, "doors": 6, "type": "automobile", "price": 76907, "category": "land" }, "osiris": { "acceleration": 0.36, "braking": 1.05, "handling": 0.74, "speed": 49.6559, "traction": 2.66, "name": "Osiris", "make": "Pegassi", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 82889, "category": "land" }, "outlaw": { "acceleration": 0.475, "braking": 0.6, "handling": 0.855, "speed": 36.1565, "traction": 2.025, "name": "Outlaw", "make": "Nagasaki", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 60938, "category": "land" }, "packer": { "acceleration": 0.21, "braking": 0.8, "handling": 0.59, "speed": 36.3465, "traction": 1.55, "name": "Packer", "make": "MTL", "class": 20, "seats": 2, "doors": 3, "type": "automobile", "price": 60714, "category": "land" }, "panthere": { "acceleration": 0.3462, "braking": 0.84, "handling": 0.7262, "speed": 49.5323, "traction": 2.5814, "name": "Panthere", "make": "Toundra", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 82311, "category": "land" }, "panto": { "acceleration": 0.27, "braking": 0.6, "handling": 0.65, "speed": 40.6987, "traction": 1.97, "name": "Panto", "make": "Benefactor", "class": 0, "seats": 2, "doors": 3, "type": "automobile", "price": 67549, "category": "land" }, "paradise": { "acceleration": 0.17, "braking": 0.4, "handling": 0.55, "speed": 38.4494, "traction": 1.95, "name": "Paradise", "make": "Bravado", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 63311, "category": "land" }, "paragon": { "acceleration": 0.329, "braking": 1, "handling": 0.709, "speed": 44.997, "traction": 2.675, "name": "Paragon R", "make": "Enus", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 75256, "category": "land" }, "paragon2": { "acceleration": 0.3275, "braking": 0.95, "handling": 0.7075, "speed": 44.828, "traction": 2.675, "name": "Paragon R (Armored)", "make": "Enus", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 374504, "category": "land", "weapons": true }, "paragon3": { "acceleration": 0.33, "braking": 1.1, "handling": 0.71, "speed": 47.4585, "traction": 2.515, "name": "Paragon S", "make": "Enus", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 79357, "category": "land" }, "pariah": { "acceleration": 0.321, "braking": 1, "handling": 0.701, "speed": 48.7328, "traction": 2.625, "name": "Pariah", "make": "Ocelot", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 81207, "category": "land" }, "patriot": { "acceleration": 0.2, "braking": 0.32, "handling": 0.58, "speed": 38.4289, "traction": 1.7, "name": "Patriot", "make": "Mammoth", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 63246, "category": "land" }, "patriot2": { "acceleration": 0.18, "braking": 0.32, "handling": 0.56, "speed": 35.9639, "traction": 1.6, "name": "Patriot Stretch", "make": "Mammoth", "class": 2, "seats": 6, "doors": 6, "type": "automobile", "price": 59238, "category": "land" }, "patriot3": { "acceleration": 0.23, "braking": 0.485, "handling": 0.61, "speed": 37.6763, "traction": 1.985, "name": "Patriot Mil-Spec", "make": "Mammoth", "class": 9, "seats": 4, "doors": 6, "type": "automobile", "price": 62402, "category": "land" }, "patrolboat": { "acceleration": 15.5, "braking": 0.4, "handling": 15.88, "speed": 40, "traction": 0, "name": "Kurtz 31 Patrol Boat", "make": "", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 2153400, "category": "sea", "weapons": true }, "pbus": { "acceleration": 0.14, "braking": 0.25, "handling": 0.52, "speed": 28.2361, "traction": 1.35, "name": "Prison Bus", "make": "", "class": 18, "seats": 11, "doors": 4, "type": "automobile", "price": 46633, "category": "land" }, "pbus2": { "acceleration": 0.13, "braking": 0.25, "handling": 0.51, "speed": 23.1177, "traction": 1.15, "name": "Festival Bus", "make": "", "class": 17, "seats": 11, "doors": 4, "type": "automobile", "price": 38412, "category": "land" }, "pcj": { "acceleration": 0.26, "braking": 1.3, "handling": 0.64, "speed": 39.7959, "traction": 2.05, "name": "PCJ 600", "make": "Shitzu", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 20997, "category": "land" }, "penetrator": { "acceleration": 0.3, "braking": 0.8, "handling": 0.68, "speed": 49.4194, "traction": 2.58, "name": "Penetrator", "make": "Ocelot", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 81919, "category": "land" }, "penumbra": { "acceleration": 0.22, "braking": 0.8, "handling": 0.6, "speed": 40.7799, "traction": 2.25, "name": "Penumbra", "make": "Maibatsu", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 67839, "category": "land" }, "penumbra2": { "acceleration": 0.3, "braking": 0.8, "handling": 0.68, "speed": 45.0454, "traction": 2.35, "name": "Penumbra FF", "make": "Maibatsu", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 74920, "category": "land" }, "peyote": { "acceleration": 0.16, "braking": 0.25, "handling": 0.54, "speed": 36.9472, "traction": 1.85, "name": "Peyote", "make": "Vapid", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 60635, "category": "land" }, "peyote2": { "acceleration": 0.3445, "braking": 0.9, "handling": 0.7245, "speed": 46.7033, "traction": 2.26, "name": "Peyote Gasser", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 77875, "category": "land" }, "peyote3": { "acceleration": 0.205, "braking": 0.28, "handling": 0.585, "speed": 42.7741, "traction": 1.98, "name": "Peyote Custom", "make": "Vapid", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 70150, "category": "land" }, "pfister811": { "acceleration": 0.356, "braking": 1.12, "handling": 0.736, "speed": 53.1, "traction": 2.688, "name": "811", "make": "Pfister", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 88499, "category": "land" }, "phantom": { "acceleration": 0.21, "braking": 0.8, "handling": 0.59, "speed": 33.8106, "traction": 1.65, "name": "Phantom", "make": "JoBuilt", "class": 20, "seats": 2, "doors": 3, "type": "automobile", "price": 56656, "category": "land" }, "phantom2": { "acceleration": 0.3, "braking": 0.85, "handling": 0.68, "speed": 43.3141, "traction": 1.95, "name": "Phantom Wedge", "make": "JoBuilt", "class": 20, "seats": 5, "doors": 2, "type": "automobile", "price": 72230, "category": "land" }, "phantom3": { "acceleration": 0.3, "braking": 0.85, "handling": 0.68, "speed": 41.7777, "traction": 2.05, "name": "Phantom Custom", "make": "JoBuilt", "class": 20, "seats": 5, "doors": 3, "type": "automobile", "price": 69772, "category": "land" }, "phantom4": { "acceleration": 0.21, "braking": 0.8, "handling": 0.59, "speed": 33.8106, "traction": 1.65, "name": "Phantom", "make": "JoBuilt", "class": 20, "seats": 2, "doors": 3, "type": "automobile", "price": 56656, "category": "land" }, "phoenix": { "acceleration": 0.28, "braking": 0.8, "handling": 0.66, "speed": 47.2809, "traction": 2.15, "name": "Phoenix", "make": "Imponte", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 78433, "category": "land" }, "picador": { "acceleration": 0.22, "braking": 0.8, "handling": 0.6, "speed": 40.7799, "traction": 2.05, "name": "Picador", "make": "Cheval", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 67839, "category": "land" }, "pigalle": { "acceleration": 0.265, "braking": 0.85, "handling": 0.645, "speed": 49.6667, "traction": 2.36, "name": "Pigalle", "make": "Lampadati", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 82282, "category": "land" }, "pipistrello": { "acceleration": 0.224, "braking": 0.92, "handling": 0.604, "speed": 35.3852, "traction": 2.75, "name": "Pipistrello", "make": "Overflod", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 59413, "category": "land" }, "pizzaboy": { "acceleration": 0.12, "braking": 0.33, "handling": 0.5, "speed": 27.152, "traction": 1.66, "name": "Pizza Boy", "make": "Pegassi", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 14051, "category": "land" }, "polcaracara": { "acceleration": 0.27, "braking": 0.3, "handling": 0.65, "speed": 39.2704, "traction": 2.05, "name": "Caracara Pursuit", "make": "Vapid", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 64784, "category": "land" }, "polcoquette4": { "acceleration": 0.32, "braking": 0.6, "handling": 0.7, "speed": 48.8621, "traction": 2.6, "name": "Coquette D10 Pursuit", "make": "Invetero", "class": 18, "seats": 2, "doors": 4, "type": "automobile", "price": 80771, "category": "land" }, "poldominator10": { "acceleration": 0.3, "braking": 0.69, "handling": 0.68, "speed": 49.296, "traction": 2.383, "name": "Dominator FX Interceptor", "make": "Vapid", "class": 18, "seats": 2, "doors": 4, "type": "automobile", "price": 81545, "category": "land" }, "poldorado": { "acceleration": 0.2168, "braking": 0.5, "handling": 0.5968, "speed": 42.173, "traction": 1.845, "name": "Dorado Cruiser", "make": "Bravado", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 69578, "category": "land" }, "polfaction2": { "acceleration": 0.28, "braking": 0.8, "handling": 0.66, "speed": 46.6667, "traction": 2.25, "name": "Outreach Faction", "make": "Willard", "class": 18, "seats": 2, "doors": 4, "type": "automobile", "price": 77450, "category": "land" }, "polgauntlet": { "acceleration": 0.362, "braking": 0.9, "handling": 0.742, "speed": 49.1011, "traction": 2.35, "name": "Gauntlet Interceptor", "make": "Bravado", "class": 18, "seats": 2, "doors": 4, "type": "automobile", "price": 81768, "category": "land" }, "polgreenwood": { "acceleration": 0.282, "braking": 0.7, "handling": 0.662, "speed": 47.6667, "traction": 2.3, "name": "Greenwood Cruiser", "make": "Bravado", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 78897, "category": "land" }, "police": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 42.7284, "traction": 2.55, "name": "Police Cruiser", "make": "", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 71053, "category": "land" }, "police2": { "acceleration": 0.28, "braking": 0.9, "handling": 0.66, "speed": 46.1566, "traction": 2.4, "name": "Police Cruiser", "make": "", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 76794, "category": "land" }, "police3": { "acceleration": 0.3, "braking": 1.2, "handling": 0.68, "speed": 48.1141, "traction": 2.57, "name": "Police Cruiser", "make": "", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 80470, "category": "land" }, "police4": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 42.7284, "traction": 2.55, "name": "Unmarked Cruiser", "make": "Vapid", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 71053, "category": "land" }, "police5": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 42.7284, "traction": 2.55, "name": "Stanier LE Cruiser", "make": "Vapid", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 71053, "category": "land" }, "policeb": { "acceleration": 0.27, "braking": 1.1, "handling": 0.65, "speed": 44.1369, "traction": 1.9, "name": "Police Bike", "make": "", "class": 18, "seats": 1, "doors": 1, "type": "bike", "price": 23078, "category": "land" }, "policeold1": { "acceleration": 0.18, "braking": 0.8, "handling": 0.56, "speed": 35.1601, "traction": 1.95, "name": "Police Rancher", "make": "", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 58720, "category": "land" }, "policeold2": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 42.7284, "traction": 2.15, "name": "Police Roadcruiser", "make": "", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 71053, "category": "land" }, "policet": { "acceleration": 0.16, "braking": 0.6, "handling": 0.54, "speed": 36.9472, "traction": 1.95, "name": "Police Transporter", "make": "", "class": 18, "seats": 4, "doors": 5, "type": "automobile", "price": 61195, "category": "land" }, "policet3": { "acceleration": 0.195, "braking": 0.6, "handling": 0.575, "speed": 42.0369, "traction": 2, "name": "Burrito (Bail Enforcement)", "make": "", "class": 18, "seats": 6, "doors": 5, "type": "automobile", "price": 69451, "category": "land" }, "polimpaler5": { "acceleration": 0.2537, "braking": 0.865, "handling": 0.6338, "speed": 47.5449, "traction": 2.3, "name": "Impaler SZ Cruiser", "make": "Declasse", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 78875, "category": "land" }, "polimpaler6": { "acceleration": 0.3, "braking": 0.7, "handling": 0.68, "speed": 47.0182, "traction": 2.135, "name": "Impaler LX Cruiser", "make": "Declasse", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 77917, "category": "land" }, "polmav": { "acceleration": 5.194, "braking": 2.9396, "handling": 5.194, "speed": 56.5962, "traction": 1.3, "name": "Police Maverick", "make": "", "class": 15, "seats": 4, "doors": 2, "type": "heli", "price": 31465710, "category": "air", "weapons": true }, "polterminus": { "acceleration": 0.269, "braking": 0.55, "handling": 0.649, "speed": 43.2138, "traction": 2.1875, "name": "Terminus Patrol", "make": "Canis", "class": 18, "seats": 4, "doors": 7, "type": "automobile", "price": 71490, "category": "land" }, "pony": { "acceleration": 0.16, "braking": 0.6, "handling": 0.54, "speed": 36.9472, "traction": 1.95, "name": "Pony", "make": "Brute", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 61195, "category": "land" }, "pony2": { "acceleration": 0.16, "braking": 0.6, "handling": 0.54, "speed": 36.9472, "traction": 1.95, "name": "Pony", "make": "Brute", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 61195, "category": "land" }, "postlude": { "acceleration": 0.295, "braking": 0.4, "handling": 0.675, "speed": 42.1823, "traction": 1.985, "name": "Postlude", "make": "Dinka", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 69683, "category": "land" }, "pounder": { "acceleration": 0.14, "braking": 0.25, "handling": 0.52, "speed": 33.8088, "traction": 1.65, "name": "Pounder", "make": "MTL", "class": 20, "seats": 2, "doors": 5, "type": "automobile", "price": 55550, "category": "land" }, "pounder2": { "acceleration": 0.16, "braking": 0.25, "handling": 0.54, "speed": 36.9472, "traction": 1.5, "name": "Pounder Custom", "make": "MTL", "class": 20, "seats": 8, "doors": 4, "type": "automobile", "price": 60635, "category": "land" }, "powersurge": { "acceleration": 0.82, "braking": 1.3, "handling": 1.2, "speed": 42.7333, "traction": 2.365, "name": "Powersurge", "make": "Western", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 23026, "category": "land" }, "prairie": { "acceleration": 0.22, "braking": 0.6, "handling": 0.6, "speed": 40.7799, "traction": 1.95, "name": "Prairie", "make": "Bollokan", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 67519, "category": "land" }, "pranger": { "acceleration": 0.2, "braking": 0.8, "handling": 0.58, "speed": 37.5559, "traction": 2.15, "name": "Park Ranger", "make": "Declasse", "class": 18, "seats": 8, "doors": 6, "type": "automobile", "price": 62617, "category": "land" }, "predator": { "acceleration": 14, "braking": 0.4, "handling": 14.38, "speed": 40, "traction": 0, "name": "Police Predator", "make": "", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 2063399, "category": "sea", "weapons": true }, "premier": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 38.4289, "traction": 2.1, "name": "Premier", "make": "Declasse", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 63694, "category": "land" }, "previon": { "acceleration": 0.3185, "braking": 0.85, "handling": 0.6985, "speed": 47.0085, "traction": 2.4865, "name": "Previon", "make": "Karin", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 78200, "category": "land" }, "primo": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 38.4289, "traction": 2.35, "name": "Primo", "make": "Albany", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 64174, "category": "land" }, "primo2": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 38.4289, "traction": 2.35, "name": "Primo Custom", "make": "Albany", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 64174, "category": "land" }, "proptrailer": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "NULL", "make": "", "class": 11, "seats": 2, "doors": 2, "type": "trailer", "price": 5668, "category": "land" }, "prototipo": { "acceleration": 0.375, "braking": 1.1, "handling": 0.755, "speed": 53.0565, "traction": 2.7, "name": "X80 Proto", "make": "Grotti", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 88458, "category": "land" }, "pyro": { "acceleration": 11.27, "braking": 11.27, "handling": 11.27, "speed": 100, "traction": 2.15, "name": "Pyro", "make": "Buckingham", "class": 16, "seats": 2, "doors": 1, "type": "plane", "price": 10704800, "category": "air", "weapons": true }, "r300": { "acceleration": 0.3288, "braking": 0.78, "handling": 0.7088, "speed": 47.8497, "traction": 2.495, "name": "300R", "make": "Annis", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 79467, "category": "land" }, "radi": { "acceleration": 0.2, "braking": 0.8, "handling": 0.58, "speed": 39.3713, "traction": 2.25, "name": "Radius", "make": "Vapid", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 65522, "category": "land" }, "raiden": { "acceleration": 0.245, "braking": 1.3, "handling": 0.625, "speed": 38.0616, "traction": 2.15, "name": "Raiden", "make": "Coil", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 64370, "category": "land" }, "raiju": { "acceleration": 21.56, "braking": 22.417, "handling": 21.56, "speed": 103.975, "traction": 1.15, "name": "F-160 Raiju", "make": "", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 13560960, "category": "air", "weapons": true }, "raketrailer": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 1.3, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "rallytruck": { "acceleration": 0.33, "braking": 0.6, "handling": 0.71, "speed": 40.7586, "traction": 2.3, "name": "Dune", "make": "MTL", "class": 17, "seats": 2, "doors": 2, "type": "automobile", "price": 67837, "category": "land" }, "rancherxl": { "acceleration": 0.18, "braking": 0.8, "handling": 0.56, "speed": 35.1601, "traction": 1.95, "name": "Rancher XL", "make": "Declasse", "class": 9, "seats": 4, "doors": 6, "type": "automobile", "price": 58720, "category": "land" }, "rancherxl2": { "acceleration": 0.18, "braking": 0.8, "handling": 0.56, "speed": 35.1601, "traction": 1.95, "name": "Rancher XL", "make": "Declasse", "class": 9, "seats": 4, "doors": 6, "type": "automobile", "price": 58720, "category": "land" }, "rapidgt": { "acceleration": 0.36, "braking": 1, "handling": 0.74, "speed": 50.1498, "traction": 2.45, "name": "Rapid GT", "make": "Dewbauchee", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 83599, "category": "land" }, "rapidgt2": { "acceleration": 0.36, "braking": 1, "handling": 0.74, "speed": 50.1498, "traction": 2.45, "name": "Rapid GT", "make": "Dewbauchee", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 83599, "category": "land" }, "rapidgt3": { "acceleration": 0.3, "braking": 0.5, "handling": 0.68, "speed": 47.1242, "traction": 2.6, "name": "Rapid GT Classic", "make": "Dewbauchee", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 77766, "category": "land" }, "raptor": { "acceleration": 0.295, "braking": 1.2, "handling": 0.675, "speed": 42.8873, "traction": 2.6, "name": "Raptor", "make": "BF", "class": 6, "seats": 2, "doors": 0, "type": "automobile", "price": 72091, "category": "land" }, "ratbike": { "acceleration": 0.215, "braking": 1.2, "handling": 0.595, "speed": 36.8722, "traction": 1.65, "name": "Rat Bike", "make": "Western", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 19441, "category": "land" }, "ratel": { "acceleration": 0.342, "braking": 0.32, "handling": 0.722, "speed": 43.8756, "traction": 2.098, "name": "Ratel", "make": "Vapid", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 72415, "category": "land" }, "ratloader": { "acceleration": 0.22, "braking": 0.4, "handling": 0.6, "speed": 37.392, "traction": 1.65, "name": "Rat-Loader", "make": "", "class": 4, "seats": 2, "doors": 3, "type": "automobile", "price": 61779, "category": "land" }, "ratloader2": { "acceleration": 0.24, "braking": 0.7, "handling": 0.62, "speed": 39.4166, "traction": 1.75, "name": "Rat-Truck", "make": "Bravado", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 65562, "category": "land" }, "rcbandito": { "acceleration": 0.14, "braking": 0.8, "handling": 0.52, "speed": 26.3718, "traction": 2.95, "name": "RC Bandito", "make": "", "class": 9, "seats": 1, "doors": 0, "type": "automobile", "price": 44530, "category": "land" }, "reaper": { "acceleration": 0.365, "braking": 1.1, "handling": 0.745, "speed": 49.6593, "traction": 2.67, "name": "Reaper", "make": "Pegassi", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 82990, "category": "land" }, "rebel": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 38.4289, "traction": 2.05, "name": "Rusty Rebel", "make": "Karin", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 63694, "category": "land" }, "rebel2": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 38.4289, "traction": 2.05, "name": "Rebel", "make": "Karin", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 63694, "category": "land" }, "rebla": { "acceleration": 0.335, "braking": 0.85, "handling": 0.715, "speed": 46.5645, "traction": 2.195, "name": "Rebla GTS", "make": "Ubermacht", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 77543, "category": "land" }, "reever": { "acceleration": 0.4085, "braking": 1.3, "handling": 0.7885, "speed": 50.0416, "traction": 2.2, "name": "Reever", "make": "Western", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 26269, "category": "land" }, "regina": { "acceleration": 0.14, "braking": 0.6, "handling": 0.52, "speed": 30.6132, "traction": 1.9, "name": "Regina", "make": "Dundreary", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 50997, "category": "land" }, "remus": { "acceleration": 0.327, "braking": 0.9, "handling": 0.707, "speed": 47.0123, "traction": 2.685, "name": "Remus", "make": "Annis", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 78314, "category": "land" }, "rentalbus": { "acceleration": 0.12, "braking": 0.25, "handling": 0.5, "speed": 25.5809, "traction": 1.55, "name": "Rental Shuttle Bus", "make": "", "class": 17, "seats": 10, "doors": 5, "type": "automobile", "price": 42321, "category": "land" }, "retinue": { "acceleration": 0.2275, "braking": 0.7, "handling": 0.6075, "speed": 42.6766, "traction": 2.05, "name": "Retinue", "make": "Vapid", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 70738, "category": "land" }, "retinue2": { "acceleration": 0.2775, "braking": 0.72, "handling": 0.6575, "speed": 45.9073, "traction": 2.05, "name": "Retinue Mk II", "make": "Vapid", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 76099, "category": "land" }, "revolter": { "acceleration": 0.35, "braking": 0.8, "handling": 0.73, "speed": 44.915, "traction": 2.25, "name": "Revolter", "make": "Ubermacht", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 74872, "category": "land" }, "rhapsody": { "acceleration": 0.23, "braking": 0.6, "handling": 0.61, "speed": 41.3257, "traction": 2.05, "name": "Rhapsody", "make": "Declasse", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 68425, "category": "land" }, "rhinehart": { "acceleration": 0.339, "braking": 0.885, "handling": 0.719, "speed": 46.9796, "traction": 2.285, "name": "Rhinehart", "make": "Ubermacht", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 78276, "category": "land" }, "rhino": { "acceleration": 0.11, "braking": 0.2, "handling": 0.49, "speed": 18.3333, "traction": 2.5, "name": "Rhino Tank", "make": "", "class": 19, "seats": 2, "doors": 1, "type": "automobile", "price": 153066, "category": "land", "weapons": true }, "riata": { "acceleration": 0.25, "braking": 0.3, "handling": 0.63, "speed": 40.3984, "traction": 2.05, "name": "Riata", "make": "Vapid", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 66525, "category": "land" }, "riot": { "acceleration": 0.12, "braking": 0.25, "handling": 0.5, "speed": 34.4979, "traction": 1.65, "name": "Police Riot", "make": "", "class": 18, "seats": 8, "doors": 4, "type": "automobile", "price": 56588, "category": "land" }, "riot2": { "acceleration": 0.16, "braking": 0.3, "handling": 0.54, "speed": 35.9341, "traction": 1.75, "name": "RCV", "make": "", "class": 18, "seats": 6, "doors": 4, "type": "automobile", "price": 295472, "category": "land", "weapons": true }, "ripley": { "acceleration": 0.16, "braking": 0.2, "handling": 0.54, "speed": 23.3333, "traction": 1.35, "name": "Ripley", "make": "", "class": 11, "seats": 2, "doors": 2, "type": "automobile", "price": 38773, "category": "land" }, "rocoto": { "acceleration": 0.19, "braking": 0.25, "handling": 0.57, "speed": 41.3372, "traction": 2.1, "name": "Rocoto", "make": "Obey", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 67755, "category": "land" }, "rogue": { "acceleration": 9.8, "braking": 9.8, "handling": 9.8, "speed": 100, "traction": 2.15, "name": "Rogue", "make": "Western", "class": 16, "seats": 2, "doors": 2, "type": "plane", "price": 2070400, "category": "air" }, "romero": { "acceleration": 0.15, "braking": 0.5, "handling": 0.53, "speed": 32.0112, "traction": 1.95, "name": "Romero Hearse", "make": "Chariot", "class": 1, "seats": 2, "doors": 4, "type": "automobile", "price": 53105, "category": "land" }, "rrocket": { "acceleration": 0.382, "braking": 1, "handling": 0.762, "speed": 41.6183, "traction": 2.1, "name": "Rampant Rocket", "make": "Western", "class": 8, "seats": 1, "doors": 0, "type": "quadbike", "price": 48138, "category": "land" }, "rt3000": { "acceleration": 0.312, "braking": 1, "handling": 0.692, "speed": 48.1314, "traction": 2.58, "name": "RT3000", "make": "Dinka", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 80216, "category": "land" }, "rubble": { "acceleration": 0.14, "braking": 0.25, "handling": 0.52, "speed": 33.3333, "traction": 1.6, "name": "Rubble", "make": "JoBuilt", "class": 10, "seats": 2, "doors": 3, "type": "automobile", "price": 54789, "category": "land" }, "ruffian": { "acceleration": 0.34, "braking": 1.1, "handling": 0.72, "speed": 46.6667, "traction": 1.95, "name": "Ruffian", "make": "Pegassi", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 24413, "category": "land" }, "ruiner": { "acceleration": 0.28, "braking": 0.8, "handling": 0.66, "speed": 48.3333, "traction": 2.2, "name": "Ruiner", "make": "Imponte", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 80117, "category": "land" }, "ruiner2": { "acceleration": 0.33, "braking": 1, "handling": 0.71, "speed": 49.7632, "traction": 2.1, "name": "Ruiner 2000", "make": "Imponte", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 932457, "category": "land", "weapons": true }, "ruiner3": { "acceleration": 0.28, "braking": 0.8, "handling": 0.66, "speed": 48.3333, "traction": 2.2, "name": "Ruiner", "make": "Imponte", "class": 4, "seats": 2, "doors": 0, "type": "automobile", "price": 80117, "category": "land" }, "ruiner4": { "acceleration": 0.3085, "braking": 0.82, "handling": 0.6885, "speed": 48.4697, "traction": 2.36, "name": "Ruiner ZZ-8", "make": "Imponte", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 80458, "category": "land" }, "rumpo": { "acceleration": 0.17, "braking": 0.4, "handling": 0.55, "speed": 38.4494, "traction": 1.95, "name": "Rumpo", "make": "Bravado", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 63311, "category": "land" }, "rumpo2": { "acceleration": 0.17, "braking": 0.4, "handling": 0.55, "speed": 38.4494, "traction": 1.95, "name": "Rumpo", "make": "Bravado", "class": 12, "seats": 2, "doors": 5, "type": "automobile", "price": 63311, "category": "land" }, "rumpo3": { "acceleration": 0.18, "braking": 0.3, "handling": 0.56, "speed": 35.9639, "traction": 2, "name": "Rumpo Custom", "make": "Bravado", "class": 12, "seats": 4, "doors": 6, "type": "automobile", "price": 59206, "category": "land" }, "ruston": { "acceleration": 0.325, "braking": 1.2, "handling": 0.705, "speed": 46.7483, "traction": 2.6, "name": "Ruston", "make": "Hijak", "class": 6, "seats": 2, "doors": 2, "type": "automobile", "price": 78365, "category": "land" }, "s80": { "acceleration": 0.3725, "braking": 1.25, "handling": 0.7525, "speed": 49.6302, "traction": 2.77, "name": "S80RR", "make": "Annis", "class": 7, "seats": 1, "doors": 4, "type": "automobile", "price": 83208, "category": "land" }, "sabregt": { "acceleration": 0.28, "braking": 0.8, "handling": 0.66, "speed": 46.6667, "traction": 2.25, "name": "Sabre Turbo", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 77450, "category": "land" }, "sabregt2": { "acceleration": 0.282, "braking": 0.82, "handling": 0.662, "speed": 46.6667, "traction": 2.26, "name": "Sabre Turbo Custom", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 77489, "category": "land" }, "sadler": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 37.5559, "traction": 2.05, "name": "Sadler", "make": "Vapid", "class": 11, "seats": 4, "doors": 6, "type": "automobile", "price": 62297, "category": "land" }, "sadler2": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 37.5559, "traction": 2.05, "name": "Sadler", "make": "Vapid", "class": 11, "seats": 4, "doors": 6, "type": "automobile", "price": 62297, "category": "land" }, "sanchez": { "acceleration": 0.28, "braking": 1.1, "handling": 0.66, "speed": 39.3333, "traction": 2.15, "name": "Sanchez (livery)", "make": "Maibatsu", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 20686, "category": "land" }, "sanchez2": { "acceleration": 0.28, "braking": 1.1, "handling": 0.66, "speed": 39.3333, "traction": 2.15, "name": "Sanchez", "make": "Maibatsu", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 20686, "category": "land" }, "sanctus": { "acceleration": 0.405, "braking": 1, "handling": 0.785, "speed": 46.5123, "traction": 1.95, "name": "Sanctus", "make": "LCC", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 24351, "category": "land" }, "sandking": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 38.4289, "traction": 2, "name": "Sandking XL", "make": "Vapid", "class": 9, "seats": 4, "doors": 6, "type": "automobile", "price": 63694, "category": "land" }, "sandking2": { "acceleration": 0.2, "braking": 0.6, "handling": 0.58, "speed": 38.4289, "traction": 2, "name": "Sandking SWB", "make": "Vapid", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 63694, "category": "land" }, "savage": { "acceleration": 4.5864, "braking": 2.9901, "handling": 4.5864, "speed": 65.1953, "traction": 1.3, "name": "Savage", "make": "", "class": 15, "seats": 4, "doors": 2, "type": "heli", "price": 34811190, "category": "air", "weapons": true }, "savestra": { "acceleration": 0.2375, "braking": 0.7, "handling": 0.6175, "speed": 43.8312, "traction": 2.05, "name": "Savestra", "make": "Annis", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 72617, "category": "land" }, "sc1": { "acceleration": 0.31, "braking": 1.12, "handling": 0.69, "speed": 47.4162, "traction": 2.65, "name": "SC1", "make": "Ubermacht", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 79257, "category": "land" }, "scarab": { "acceleration": 0.1, "braking": 0.7, "handling": 0.48, "speed": 23.5477, "traction": 2.25, "name": "Apocalypse Scarab", "make": "HVY", "class": 19, "seats": 4, "doors": 2, "type": "automobile", "price": 39724, "category": "land" }, "scarab2": { "acceleration": 0.1, "braking": 0.7, "handling": 0.48, "speed": 23.5477, "traction": 2.25, "name": "Future Shock Scarab", "make": "HVY", "class": 19, "seats": 4, "doors": 2, "type": "automobile", "price": 39724, "category": "land" }, "scarab3": { "acceleration": 0.1, "braking": 0.7, "handling": 0.48, "speed": 23.5477, "traction": 2.25, "name": "Nightmare Scarab", "make": "HVY", "class": 19, "seats": 4, "doors": 2, "type": "automobile", "price": 39724, "category": "land" }, "schafter2": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 39.3713, "traction": 2.55, "name": "Schafter", "make": "Benefactor", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 65682, "category": "land" }, "schafter3": { "acceleration": 0.3, "braking": 0.95, "handling": 0.68, "speed": 50, "traction": 2.55, "name": "Schafter V12", "make": "Benefactor", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 83088, "category": "land" }, "schafter4": { "acceleration": 0.2, "braking": 0.85, "handling": 0.58, "speed": 39.3713, "traction": 2.55, "name": "Schafter LWB", "make": "Benefactor", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 65602, "category": "land" }, "schafter5": { "acceleration": 0.29, "braking": 0.92, "handling": 0.67, "speed": 50, "traction": 2.55, "name": "Schafter V12 (Armored)", "make": "Benefactor", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 83008, "category": "land" }, "schafter6": { "acceleration": 0.185, "braking": 0.82, "handling": 0.565, "speed": 37.4779, "traction": 2.55, "name": "Schafter LWB (Armored)", "make": "Benefactor", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 62476, "category": "land" }, "schlagen": { "acceleration": 0.37, "braking": 0.8, "handling": 0.75, "speed": 52.0606, "traction": 2.5, "name": "Schlagen GT", "make": "Benefactor", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 86368, "category": "land" }, "schwarzer": { "acceleration": 0.29, "braking": 0.9, "handling": 0.67, "speed": 48.2968, "traction": 2.3, "name": "Schwartzer", "make": "Benefactor", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 80250, "category": "land" }, "scorcher": { "acceleration": 0.17, "braking": 2.8, "handling": 0.55, "speed": 15.03, "traction": 2.05, "name": "Scorcher", "make": "", "class": 13, "seats": 1, "doors": 1, "type": "bicycle", "price": 2782, "category": "land" }, "scramjet": { "acceleration": 0.4, "braking": 0.95, "handling": 0.78, "speed": 49.3536, "traction": 2.7, "name": "Scramjet", "make": "Declasse", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 1853409, "category": "land", "weapons": true }, "scrap": { "acceleration": 0.13, "braking": 0.25, "handling": 0.51, "speed": 32.1641, "traction": 1.6, "name": "Scrap Truck", "make": "", "class": 11, "seats": 2, "doors": 3, "type": "automobile", "price": 52886, "category": "land" }, "seabreeze": { "acceleration": 20.09, "braking": 16.4034, "handling": 20.09, "speed": 81.6497, "traction": 2.15, "name": "Seabreeze", "make": "Western", "class": 16, "seats": 2, "doors": 1, "type": "plane", "price": 2211729, "category": "air" }, "seashark": { "acceleration": 12.5, "braking": 0.4, "handling": 12.88, "speed": 43.3333, "traction": 0, "name": "Seashark", "make": "Speedophile", "class": 14, "seats": 2, "doors": 0, "type": "boat", "price": 414679, "category": "sea" }, "seashark2": { "acceleration": 12.5, "braking": 0.4, "handling": 12.88, "speed": 43.3333, "traction": 0, "name": "Seashark", "make": "Speedophile", "class": 14, "seats": 2, "doors": 0, "type": "boat", "price": 414679, "category": "sea" }, "seashark3": { "acceleration": 12.5, "braking": 0.4, "handling": 12.88, "speed": 43.3333, "traction": 0, "name": "Seashark", "make": "Speedophile", "class": 14, "seats": 2, "doors": 0, "type": "boat", "price": 414679, "category": "sea" }, "seasparrow": { "acceleration": 5.194, "braking": 2.9396, "handling": 5.194, "speed": 56.5962, "traction": 1.3, "name": "Sea Sparrow", "make": "", "class": 15, "seats": 2, "doors": 2, "type": "heli", "price": 6293142, "category": "air" }, "seasparrow2": { "acceleration": 6.223, "braking": 4.3221, "handling": 6.223, "speed": 69.4544, "traction": 1.3, "name": "Sparrow", "make": "", "class": 15, "seats": 2, "doors": 2, "type": "heli", "price": 7760025, "category": "air" }, "seasparrow3": { "acceleration": 6.223, "braking": 4.3221, "handling": 6.223, "speed": 69.4544, "traction": 1.3, "name": "Sparrow", "make": "", "class": 15, "seats": 2, "doors": 2, "type": "heli", "price": 7760025, "category": "air" }, "seminole": { "acceleration": 0.18, "braking": 0.8, "handling": 0.56, "speed": 36.8308, "traction": 2.05, "name": "Seminole", "make": "Canis", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 61393, "category": "land" }, "seminole2": { "acceleration": 0.2, "braking": 0.8, "handling": 0.58, "speed": 38.9854, "traction": 2, "name": "Seminole Frontier", "make": "Canis", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 64904, "category": "land" }, "sentinel": { "acceleration": 0.21, "braking": 0.9, "handling": 0.64, "speed": 45.4909, "traction": 2.45, "name": "Sentinel XS", "make": "Ubermacht", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 75585, "category": "land" }, "sentinel2": { "acceleration": 0.21, "braking": 0.9, "handling": 0.64, "speed": 45.4909, "traction": 2.45, "name": "Sentinel", "make": "Ubermacht", "class": 3, "seats": 4, "doors": 4, "type": "automobile", "price": 75585, "category": "land" }, "sentinel3": { "acceleration": 0.265, "braking": 0.8, "handling": 0.645, "speed": 44.644, "traction": 2.25, "name": "Sentinel Classic", "make": "Ubermacht", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 74166, "category": "land" }, "sentinel4": { "acceleration": 0.295, "braking": 0.88, "handling": 0.675, "speed": 47.5188, "traction": 2.295, "name": "Sentinel Classic Widebody", "make": "Ubermacht", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 78990, "category": "land" }, "serrano": { "acceleration": 0.2, "braking": 0.4, "handling": 0.58, "speed": 40.3933, "traction": 2.1, "name": "Serrano", "make": "Benefactor", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 66517, "category": "land" }, "seven70": { "acceleration": 0.335, "braking": 1, "handling": 0.715, "speed": 50.9092, "traction": 2.56, "name": "Seven-70", "make": "Dewbauchee", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 84734, "category": "land" }, "shamal": { "acceleration": 7.938, "braking": 7.193, "handling": 7.938, "speed": 90.6145, "traction": 1.15, "name": "Shamal", "make": "Buckingham", "class": 16, "seats": 10, "doors": 1, "type": "plane", "price": 1818936, "category": "air" }, "sheava": { "acceleration": 0.33, "braking": 1, "handling": 0.71, "speed": 48.6724, "traction": 2.65, "name": "ETR1", "make": "Emperor", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 81139, "category": "land" }, "sheriff": { "acceleration": 0.24, "braking": 0.6, "handling": 0.62, "speed": 43.0315, "traction": 2.25, "name": "Sheriff Cruiser", "make": "", "class": 18, "seats": 4, "doors": 6, "type": "automobile", "price": 71186, "category": "land" }, "sheriff2": { "acceleration": 0.2, "braking": 0.8, "handling": 0.58, "speed": 37.5559, "traction": 2.15, "name": "Sheriff SUV", "make": "", "class": 18, "seats": 8, "doors": 6, "type": "automobile", "price": 62617, "category": "land" }, "shinobi": { "acceleration": 0.3625, "braking": 1.25, "handling": 0.7425, "speed": 52.1333, "traction": 2.3, "name": "Shinobi", "make": "Nagasaki", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 27244, "category": "land" }, "shotaro": { "acceleration": 0.4, "braking": 1.4, "handling": 0.78, "speed": 51.2182, "traction": 2.7, "name": "Shotaro", "make": "Nagasaki", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 26899, "category": "land" }, "skylift": { "acceleration": 4.704, "braking": 2.2547, "handling": 4.704, "speed": 47.9315, "traction": 1.3, "name": "Skylift", "make": "", "class": 15, "seats": 2, "doors": 3, "type": "heli", "price": 5363478, "category": "air" }, "slamtruck": { "acceleration": 0.225, "braking": 0.6, "handling": 0.605, "speed": 37.9062, "traction": 1.71, "name": "Slamtruck", "make": "Vapid", "class": 11, "seats": 2, "doors": 3, "type": "automobile", "price": 62937, "category": "land" }, "slamvan": { "acceleration": 0.245, "braking": 0.6, "handling": 0.625, "speed": 39.9099, "traction": 1.65, "name": "Slamvan", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 66207, "category": "land" }, "slamvan2": { "acceleration": 0.25, "braking": 0.7, "handling": 0.63, "speed": 40.3984, "traction": 1.85, "name": "Lost Slamvan", "make": "Vapid", "class": 4, "seats": 4, "doors": 5, "type": "automobile", "price": 67165, "category": "land" }, "slamvan3": { "acceleration": 0.25, "braking": 0.6, "handling": 0.63, "speed": 40.3984, "traction": 2.35, "name": "Slamvan Custom", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 67005, "category": "land" }, "slamvan4": { "acceleration": 0.27, "braking": 0.4, "handling": 0.65, "speed": 41.478, "traction": 1.85, "name": "Apocalypse Slamvan", "make": "Vapid", "class": 4, "seats": 2, "doors": 2, "type": "automobile", "price": 68476, "category": "land" }, "slamvan5": { "acceleration": 0.27, "braking": 0.4, "handling": 0.65, "speed": 41.478, "traction": 1.85, "name": "Future Shock Slamvan", "make": "Vapid", "class": 4, "seats": 2, "doors": 2, "type": "automobile", "price": 68476, "category": "land" }, "slamvan6": { "acceleration": 0.27, "braking": 0.4, "handling": 0.65, "speed": 41.478, "traction": 1.85, "name": "Nightmare Slamvan", "make": "Vapid", "class": 4, "seats": 2, "doors": 2, "type": "automobile", "price": 68476, "category": "land" }, "sm722": { "acceleration": 0.358, "braking": 0.85, "handling": 0.738, "speed": 48.2165, "traction": 2.508, "name": "SM722", "make": "Benefactor", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 80260, "category": "land" }, "sovereign": { "acceleration": 0.27, "braking": 1.1, "handling": 0.65, "speed": 44.1369, "traction": 1.9, "name": "Sovereign", "make": "Western", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 23078, "category": "land" }, "specter": { "acceleration": 0.32, "braking": 1, "handling": 0.7, "speed": 48.6425, "traction": 2.62, "name": "Specter", "make": "Dewbauchee", "class": 6, "seats": 2, "doors": 2, "type": "automobile", "price": 81060, "category": "land" }, "specter2": { "acceleration": 0.33, "braking": 1.1, "handling": 0.71, "speed": 49.3176, "traction": 2.67, "name": "Specter Custom", "make": "Dewbauchee", "class": 6, "seats": 2, "doors": 2, "type": "automobile", "price": 82332, "category": "land" }, "speeder": { "acceleration": 16, "braking": 0.4, "handling": 16.38, "speed": 44.3333, "traction": 0, "name": "Speeder", "make": "Pegassi", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 462679, "category": "sea" }, "speeder2": { "acceleration": 16, "braking": 0.4, "handling": 16.38, "speed": 44.3333, "traction": 0, "name": "Speeder", "make": "Pegassi", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 462679, "category": "sea" }, "speedo": { "acceleration": 0.18, "braking": 0.6, "handling": 0.56, "speed": 39.9117, "traction": 1.8, "name": "Speedo", "make": "Vapid", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 66002, "category": "land" }, "speedo2": { "acceleration": 0.18, "braking": 0.6, "handling": 0.56, "speed": 39.9117, "traction": 1.8, "name": "Clown Van", "make": "Vapid", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 66002, "category": "land" }, "speedo4": { "acceleration": 0.2, "braking": 0.65, "handling": 0.58, "speed": 42.7284, "traction": 1.9, "name": "Speedo Custom", "make": "Vapid", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 70653, "category": "land" }, "speedo5": { "acceleration": 0.2, "braking": 0.65, "handling": 0.58, "speed": 42.7284, "traction": 1.9, "name": "Speedo Custom", "make": "Vapid", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 70653, "category": "land" }, "squaddie": { "acceleration": 0.2, "braking": 0.315, "handling": 0.58, "speed": 38.4289, "traction": 1.9, "name": "Squaddie", "make": "Mammoth", "class": 2, "seats": 4, "doors": 4, "type": "automobile", "price": 63238, "category": "land" }, "squalo": { "acceleration": 11.5, "braking": 0.4, "handling": 11.88, "speed": 36.6667, "traction": 0, "name": "Squalo", "make": "Shitzu", "class": 14, "seats": 2, "doors": 0, "type": "boat", "price": 362680, "category": "sea" }, "stafford": { "acceleration": 0.2, "braking": 0.45, "handling": 0.58, "speed": 38.0719, "traction": 2, "name": "Stafford", "make": "Enus", "class": 1, "seats": 4, "doors": 7, "type": "automobile", "price": 62883, "category": "land" }, "stalion": { "acceleration": 0.29, "braking": 0.7, "handling": 0.67, "speed": 45.0781, "traction": 2.25, "name": "Stallion", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 74780, "category": "land" }, "stalion2": { "acceleration": 0.31, "braking": 0.7, "handling": 0.69, "speed": 47.7326, "traction": 2.25, "name": "Burger Shot Stallion", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 79092, "category": "land" }, "stanier": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 42.7284, "traction": 2.45, "name": "Stanier", "make": "Vapid", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 71053, "category": "land" }, "starling": { "acceleration": 12.25, "braking": 10.7188, "handling": 12.25, "speed": 87.5, "traction": 1.15, "name": "LF-22 Starling", "make": "", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 29452512, "category": "air", "weapons": true }, "stinger": { "acceleration": 0.26, "braking": 0.6, "handling": 0.64, "speed": 45.1953, "traction": 2.15, "name": "Stinger", "make": "Grotti", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 74712, "category": "land" }, "stingergt": { "acceleration": 0.26, "braking": 0.6, "handling": 0.64, "speed": 45.1953, "traction": 2.15, "name": "Stinger GT", "make": "Grotti", "class": 5, "seats": 2, "doors": 3, "type": "automobile", "price": 74712, "category": "land" }, "stingertt": { "acceleration": 0.398, "braking": 0.98, "handling": 0.858, "speed": 52.2722, "traction": 2.6165, "name": "Itali GTO Stinger TT", "make": "Grotti", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 87213, "category": "land" }, "stockade": { "acceleration": 0.12, "braking": 0.25, "handling": 0.5, "speed": 34.4979, "traction": 1.65, "name": "Stockade", "make": "Brute", "class": 20, "seats": 4, "doors": 5, "type": "automobile", "price": 56588, "category": "land" }, "stockade3": { "acceleration": 0.12, "braking": 0.25, "handling": 0.5, "speed": 34.4979, "traction": 1.65, "name": "Stockade", "make": "Brute", "class": 20, "seats": 4, "doors": 5, "type": "automobile", "price": 56588, "category": "land" }, "stratum": { "acceleration": 0.21, "braking": 0.6, "handling": 0.59, "speed": 41.6589, "traction": 2.2, "name": "Stratum", "make": "Zirconium", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 68894, "category": "land" }, "streamer216": { "acceleration": 7.84, "braking": 6.013, "handling": 7.84, "speed": 76.6965, "traction": 2.15, "name": "Streamer216", "make": "Mammoth", "class": 16, "seats": 4, "doors": 3, "type": "plane", "price": 1574232, "category": "air" }, "streiter": { "acceleration": 0.2125, "braking": 0.8, "handling": 0.5925, "speed": 42.8978, "traction": 2.08, "name": "Streiter", "make": "Benefactor", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 71204, "category": "land" }, "stretch": { "acceleration": 0.17, "braking": 0.8, "handling": 0.55, "speed": 38.4494, "traction": 1.85, "name": "Stretch", "make": "Dundreary", "class": 1, "seats": 6, "doors": 6, "type": "automobile", "price": 63951, "category": "land" }, "strikeforce": { "acceleration": 15.68, "braking": 11.0874, "handling": 15.68, "speed": 70.7107, "traction": 2.15, "name": "B-11 Strikeforce", "make": "", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 9052648, "category": "air", "weapons": true }, "stromberg": { "acceleration": 0.29, "braking": 1, "handling": 0.67, "speed": 45.8691, "traction": 2.5, "name": "Stromberg", "make": "Ocelot", "class": 5, "seats": 2, "doors": 2, "type": "submarinecar", "price": 6217783, "category": "land", "weapons": true }, "stryder": { "acceleration": 0.36, "braking": 1.2, "handling": 0.74, "speed": 42.2187, "traction": 2.45, "name": "Stryder", "make": "Nagasaki", "class": 8, "seats": 2, "doors": 0, "type": "quadbike", "price": 48970, "category": "land" }, "stunt": { "acceleration": 9.8, "braking": 7.7476, "handling": 9.8, "speed": 79.0569, "traction": 1.15, "name": "Mallard", "make": "", "class": 16, "seats": 1, "doors": 1, "type": "plane", "price": 1702472, "category": "air" }, "submersible": { "acceleration": 8, "braking": 0.4, "handling": 8.38, "speed": 25, "traction": 0, "name": "Submersible", "make": "", "class": 14, "seats": 1, "doors": 1, "type": "submarine", "price": 919160, "category": "sea" }, "submersible2": { "acceleration": 10, "braking": 0.4, "handling": 10.38, "speed": 25, "traction": 0, "name": "Kraken", "make": "", "class": 14, "seats": 1, "doors": 1, "type": "submarine", "price": 1007160, "category": "sea" }, "sugoi": { "acceleration": 0.31, "braking": 0.85, "handling": 0.69, "speed": 45.1933, "traction": 2.49, "name": "Sugoi", "make": "Dinka", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 75269, "category": "land" }, "sultan": { "acceleration": 0.26, "braking": 0.4, "handling": 0.64, "speed": 45.1953, "traction": 2.35, "name": "Sultan", "make": "Karin", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 74392, "category": "land" }, "sultan2": { "acceleration": 0.33, "braking": 0.5, "handling": 0.71, "speed": 46.7006, "traction": 2.495, "name": "Sultan Classic", "make": "Karin", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 77184, "category": "land" }, "sultan3": { "acceleration": 0.3385, "braking": 0.52, "handling": 0.7185, "speed": 47.2149, "traction": 2.485, "name": "Sultan RS Classic", "make": "Karin", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 78067, "category": "land" }, "sultanrs": { "acceleration": 0.33, "braking": 1, "handling": 0.71, "speed": 49.3333, "traction": 2.5, "name": "Sultan RS", "make": "Karin", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 82197, "category": "land" }, "suntrap": { "acceleration": 11.5, "braking": 0.4, "handling": 11.88, "speed": 36.6667, "traction": 0, "name": "Suntrap", "make": "Shitzu", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 362680, "category": "sea" }, "superd": { "acceleration": 0.26, "braking": 0.6, "handling": 0.64, "speed": 45.1953, "traction": 2.1, "name": "Super Diamond", "make": "Enus", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 74712, "category": "land" }, "supervolito": { "acceleration": 5.635, "braking": 3.4919, "handling": 5.635, "speed": 61.9686, "traction": 1.3, "name": "SuperVolito", "make": "Buckingham", "class": 15, "seats": 4, "doors": 4, "type": "heli", "price": 6905745, "category": "air" }, "supervolito2": { "acceleration": 5.635, "braking": 3.4919, "handling": 5.635, "speed": 61.9686, "traction": 1.3, "name": "SuperVolito Carbon", "make": "Buckingham", "class": 15, "seats": 4, "doors": 4, "type": "heli", "price": 6905745, "category": "air" }, "surano": { "acceleration": 0.34, "braking": 1, "handling": 0.72, "speed": 50.422, "traction": 2.55, "name": "Surano", "make": "Benefactor", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 83971, "category": "land" }, "surfer": { "acceleration": 0.1, "braking": 0.3, "handling": 0.48, "speed": 20.6859, "traction": 1.55, "name": "Surfer", "make": "BF", "class": 12, "seats": 2, "doors": 6, "type": "automobile", "price": 34505, "category": "land" }, "surfer2": { "acceleration": 0.1, "braking": 0.3, "handling": 0.48, "speed": 20.6859, "traction": 1.55, "name": "Surfer", "make": "BF", "class": 12, "seats": 2, "doors": 6, "type": "automobile", "price": 34505, "category": "land" }, "surfer3": { "acceleration": 0.1, "braking": 0.3, "handling": 0.48, "speed": 20.6859, "traction": 1.55, "name": "Surfer Custom", "make": "BF", "class": 12, "seats": 4, "doors": 6, "type": "automobile", "price": 34505, "category": "land" }, "surge": { "acceleration": 0.1, "braking": 0.6, "handling": 0.48, "speed": 24.1987, "traction": 2.15, "name": "Surge", "make": "Cheval", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 40605, "category": "land" }, "swift": { "acceleration": 5.439, "braking": 3.1672, "handling": 5.439, "speed": 58.2314, "traction": 1.3, "name": "Swift", "make": "Buckingham", "class": 15, "seats": 4, "doors": 4, "type": "heli", "price": 6504894, "category": "air" }, "swift2": { "acceleration": 5.537, "braking": 3.2599, "handling": 5.537, "speed": 58.8757, "traction": 1.3, "name": "Swift Deluxe", "make": "Buckingham", "class": 15, "seats": 4, "doors": 4, "type": "heli", "price": 6588863, "category": "air" }, "swinger": { "acceleration": 0.39, "braking": 0.995, "handling": 0.77, "speed": 48.6413, "traction": 2.4, "name": "Swinger", "make": "Ocelot", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 81274, "category": "land" }, "t20": { "acceleration": 0.365, "braking": 1.1, "handling": 0.745, "speed": 49.6793, "traction": 2.68, "name": "T20", "make": "Progen", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 83022, "category": "land" }, "taco": { "acceleration": 0.11, "braking": 0.25, "handling": 0.49, "speed": 28.6961, "traction": 1.55, "name": "Taco Van", "make": "", "class": 12, "seats": 1, "doors": 5, "type": "automobile", "price": 47273, "category": "land" }, "tahoma": { "acceleration": 0.298, "braking": 0.625, "handling": 0.678, "speed": 43.5549, "traction": 2.308, "name": "Tahoma Coupe", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 72249, "category": "land" }, "tailgater": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 38.4289, "traction": 2.55, "name": "Tailgater", "make": "Obey", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 64174, "category": "land" }, "tailgater2": { "acceleration": 0.2775, "braking": 0.88, "handling": 0.6575, "speed": 46.567, "traction": 2.595, "name": "Tailgater S", "make": "Obey", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 77411, "category": "land" }, "taipan": { "acceleration": 0.357, "braking": 1, "handling": 0.737, "speed": 53.8669, "traction": 2.65, "name": "Taipan", "make": "Cheval", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 89537, "category": "land" }, "tampa": { "acceleration": 0.27, "braking": 0.8, "handling": 0.65, "speed": 42.3068, "traction": 2.25, "name": "Tampa", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 70442, "category": "land" }, "tampa2": { "acceleration": 0.33, "braking": 0.5, "handling": 0.71, "speed": 45.8049, "traction": 2.25, "name": "Drift Tampa", "make": "Declasse", "class": 6, "seats": 2, "doors": 3, "type": "automobile", "price": 75751, "category": "land" }, "tampa3": { "acceleration": 0.33, "braking": 0.5, "handling": 0.71, "speed": 45.8049, "traction": 2.25, "name": "Weaponized Tampa", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 378759, "category": "land", "weapons": true }, "tanker": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5051, "traction": 1.8, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5749, "category": "land" }, "tanker2": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5051, "traction": 1.8, "name": "NULL", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5749, "category": "land" }, "tankercar": { "acceleration": 0.2, "braking": 5, "handling": 0.58, "speed": 26.6667, "traction": 2.5, "name": "Freight Train", "make": "", "class": 21, "seats": 2, "doors": 0, "type": "train", "price": 194680, "category": "land" }, "taxi": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 42.7284, "traction": 2.55, "name": "Taxi", "make": "", "class": 17, "seats": 4, "doors": 6, "type": "automobile", "price": 71053, "category": "land" }, "technical": { "acceleration": 0.2, "braking": 0.7, "handling": 0.58, "speed": 38.4289, "traction": 2.13, "name": "Technical", "make": "Karin", "class": 9, "seats": 3, "doors": 3, "type": "automobile", "price": 319271, "category": "land", "weapons": true }, "technical2": { "acceleration": 0.25, "braking": 0.7, "handling": 0.63, "speed": 39.6139, "traction": 2.13, "name": "Technical Aqua", "make": "Karin", "class": 9, "seats": 3, "doors": 3, "type": "amphibious_automobile", "price": 1318204, "category": "land", "weapons": true }, "technical3": { "acceleration": 0.2, "braking": 0.7, "handling": 0.58, "speed": 38.4289, "traction": 2.13, "name": "Technical Custom", "make": "Karin", "class": 9, "seats": 3, "doors": 3, "type": "automobile", "price": 319271, "category": "land", "weapons": true }, "tempesta": { "acceleration": 0.36, "braking": 1, "handling": 0.74, "speed": 49.1378, "traction": 2.65, "name": "Tempesta", "make": "Pegassi", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 81980, "category": "land" }, "tenf": { "acceleration": 0.3545, "braking": 1.1, "handling": 0.7345, "speed": 49.1413, "traction": 2.68, "name": "10F", "make": "Obey", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 82128, "category": "land" }, "tenf2": { "acceleration": 0.356, "braking": 1.15, "handling": 0.736, "speed": 49.4641, "traction": 2.69, "name": "10F Widebody", "make": "Obey", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 82729, "category": "land" }, "terbyte": { "acceleration": 0.16, "braking": 0.25, "handling": 0.54, "speed": 30.7185, "traction": 1.95, "name": "Terrorbyte", "make": "Benefactor", "class": 20, "seats": 2, "doors": 2, "type": "automobile", "price": 253348, "category": "land", "weapons": true }, "terminus": { "acceleration": 0.269, "braking": 0.55, "handling": 0.649, "speed": 43.2138, "traction": 2.1875, "name": "Terminus", "make": "Canis", "class": 9, "seats": 4, "doors": 7, "type": "automobile", "price": 71490, "category": "land" }, "tezeract": { "acceleration": 0.1375, "braking": 1.2, "handling": 0.5175, "speed": 45.8515, "traction": 2.6, "name": "Tezeract", "make": "Pegassi", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 76330, "category": "land" }, "thrax": { "acceleration": 0.34, "braking": 1.2, "handling": 0.72, "speed": 50.1961, "traction": 2.72, "name": "Thrax", "make": "Truffade", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 83929, "category": "land" }, "thrust": { "acceleration": 0.265, "braking": 1.5, "handling": 0.645, "speed": 49.3205, "traction": 1.98, "name": "Thrust", "make": "Dinka", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 25865, "category": "land" }, "thruster": { "acceleration": 6.86, "braking": 3.9472, "handling": 6.86, "speed": 57.5399, "traction": 1.3, "name": "Thruster", "make": "Mammoth", "class": 19, "seats": 1, "doors": 0, "type": "heli", "price": 6768639, "category": "air" }, "tigon": { "acceleration": 0.3795, "braking": 1.12, "handling": 0.7595, "speed": 51.7179, "traction": 2.781, "name": "Tigon", "make": "Lampadati", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 86363, "category": "land" }, "tiptruck": { "acceleration": 0.11, "braking": 0.25, "handling": 0.49, "speed": 28.6961, "traction": 1.6, "name": "Tipper", "make": "Brute", "class": 10, "seats": 2, "doors": 3, "type": "automobile", "price": 47273, "category": "land" }, "tiptruck2": { "acceleration": 0.11, "braking": 0.25, "handling": 0.49, "speed": 28.6961, "traction": 1.6, "name": "Tipper", "make": "", "class": 10, "seats": 2, "doors": 3, "type": "automobile", "price": 47273, "category": "land" }, "titan": { "acceleration": 6.076, "braking": 4.7518, "handling": 6.076, "speed": 78.2058, "traction": 0.85, "name": "Titan", "make": "", "class": 16, "seats": 10, "doors": 2, "type": "plane", "price": 1521753, "category": "air" }, "titan2": { "acceleration": 6.125, "braking": 4.8942, "handling": 6.125, "speed": 79.9059, "traction": 0.85, "name": "Titan 250 D", "make": "Eberhard", "class": 16, "seats": 16, "doors": 4, "type": "plane", "price": 7764008, "category": "air", "weapons": true }, "toreador": { "acceleration": 0.3, "braking": 1, "handling": 0.68, "speed": 46.997, "traction": 2.45, "name": "Toreador", "make": "Pegassi", "class": 5, "seats": 4, "doors": 4, "type": "submarinecar", "price": 19101030, "category": "land", "weapons": true }, "torero": { "acceleration": 0.3, "braking": 0.5, "handling": 0.68, "speed": 47.556, "traction": 2.6, "name": "Torero", "make": "Pegassi", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 78457, "category": "land" }, "torero2": { "acceleration": 0.385, "braking": 1.15, "handling": 0.765, "speed": 52.4177, "traction": 2.7695, "name": "Torero XO", "make": "Pegassi", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 87548, "category": "land" }, "tornado": { "acceleration": 0.16, "braking": 0.25, "handling": 0.54, "speed": 36.9472, "traction": 1.8, "name": "Tornado", "make": "Declasse", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 60635, "category": "land" }, "tornado2": { "acceleration": 0.16, "braking": 0.25, "handling": 0.54, "speed": 36.9472, "traction": 1.8, "name": "Tornado", "make": "Declasse", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 60635, "category": "land" }, "tornado3": { "acceleration": 0.16, "braking": 0.25, "handling": 0.54, "speed": 36.9472, "traction": 1.8, "name": "Tornado", "make": "Declasse", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 60635, "category": "land" }, "tornado4": { "acceleration": 0.16, "braking": 0.25, "handling": 0.54, "speed": 36.9472, "traction": 1.8, "name": "Tornado", "make": "Declasse", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 60635, "category": "land" }, "tornado5": { "acceleration": 0.161, "braking": 0.255, "handling": 0.541, "speed": 37.0993, "traction": 1.85, "name": "Tornado Custom", "make": "Declasse", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 60890, "category": "land" }, "tornado6": { "acceleration": 0.28, "braking": 0.25, "handling": 0.66, "speed": 40.1205, "traction": 2, "name": "Tornado Rat Rod", "make": "Declasse", "class": 5, "seats": 2, "doors": 3, "type": "automobile", "price": 66096, "category": "land" }, "toro": { "acceleration": 18, "braking": 0.4, "handling": 18.38, "speed": 44.3333, "traction": 0, "name": "Toro", "make": "Lampadati", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 486679, "category": "sea" }, "toro2": { "acceleration": 18, "braking": 0.4, "handling": 18.38, "speed": 44.3333, "traction": 0, "name": "Toro", "make": "Lampadati", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 486679, "category": "sea" }, "toros": { "acceleration": 0.32, "braking": 0.8, "handling": 0.7, "speed": 48.8621, "traction": 2.3, "name": "Toros", "make": "Pegassi", "class": 2, "seats": 4, "doors": 5, "type": "automobile", "price": 81091, "category": "land" }, "tourbus": { "acceleration": 0.12, "braking": 0.25, "handling": 0.5, "speed": 25.5809, "traction": 1.55, "name": "Tour Bus", "make": "", "class": 17, "seats": 10, "doors": 5, "type": "automobile", "price": 42321, "category": "land" }, "towtruck": { "acceleration": 0.15, "braking": 0.25, "handling": 0.53, "speed": 35, "traction": 1.6, "name": "Tow Truck", "make": "", "class": 11, "seats": 2, "doors": 3, "type": "automobile", "price": 57488, "category": "land" }, "towtruck2": { "acceleration": 0.15, "braking": 0.25, "handling": 0.53, "speed": 33.3333, "traction": 1.45, "name": "Tow Truck", "make": "", "class": 11, "seats": 2, "doors": 3, "type": "automobile", "price": 54821, "category": "land" }, "towtruck3": { "acceleration": 0.175, "braking": 0.25, "handling": 0.555, "speed": 38.3333, "traction": 1.6, "name": "Tow Truck", "make": "", "class": 11, "seats": 2, "doors": 3, "type": "automobile", "price": 62901, "category": "land" }, "towtruck4": { "acceleration": 0.175, "braking": 0.25, "handling": 0.555, "speed": 38.3333, "traction": 1.6, "name": "Tow Truck", "make": "", "class": 11, "seats": 2, "doors": 3, "type": "automobile", "price": 62901, "category": "land" }, "tr2": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5051, "traction": 1.8, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 2, "type": "trailer", "price": 5749, "category": "land" }, "tr3": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5051, "traction": 1.8, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 1, "type": "trailer", "price": 5749, "category": "land" }, "tr4": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5051, "traction": 1.8, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 2, "type": "trailer", "price": 5749, "category": "land" }, "tractor": { "acceleration": 0.08, "braking": 0.3, "handling": 0.46, "speed": 13.3333, "traction": 1.2, "name": "Tractor", "make": "", "class": 11, "seats": 1, "doors": 0, "type": "automobile", "price": 22677, "category": "land" }, "tractor2": { "acceleration": 0.15, "braking": 0.3, "handling": 0.53, "speed": 14.4594, "traction": 1.7, "name": "Fieldmaster", "make": "Stanley", "class": 11, "seats": 1, "doors": 3, "type": "automobile", "price": 24703, "category": "land" }, "tractor3": { "acceleration": 0.15, "braking": 0.3, "handling": 0.53, "speed": 14.4594, "traction": 1.7, "name": "Fieldmaster", "make": "Stanley", "class": 11, "seats": 1, "doors": 3, "type": "automobile", "price": 24703, "category": "land" }, "trailerlarge": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Mobile Operations Center", "make": "", "class": 11, "seats": 3, "doors": 1, "type": "trailer", "price": 28340, "category": "land", "weapons": true }, "trailerlogs": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5051, "traction": 1.8, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5749, "category": "land" }, "trailers": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "trailers2": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 2, "type": "trailer", "price": 5668, "category": "land" }, "trailers3": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 1, "type": "trailer", "price": 5668, "category": "land" }, "trailers4": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "trailers5": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 2, "type": "trailer", "price": 5668, "category": "land" }, "trailersmall": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 1.8, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "trailersmall2": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.7, "name": "Anti-Aircraft Trailer", "make": "Vom Feuer", "class": 19, "seats": 1, "doors": 0, "type": "trailer", "price": 28340, "category": "land", "weapons": true }, "trash": { "acceleration": 0.13, "braking": 0.3, "handling": 0.51, "speed": 36.5224, "traction": 1.55, "name": "Trashmaster", "make": "", "class": 17, "seats": 4, "doors": 3, "type": "automobile", "price": 59939, "category": "land" }, "trash2": { "acceleration": 0.13, "braking": 0.3, "handling": 0.51, "speed": 36.5224, "traction": 1.55, "name": "Trashmaster", "make": "", "class": 17, "seats": 4, "doors": 3, "type": "automobile", "price": 59939, "category": "land" }, "trflat": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 0, "type": "trailer", "price": 5668, "category": "land" }, "tribike": { "acceleration": 0.135, "braking": 2.5, "handling": 0.515, "speed": 21, "traction": 1.85, "name": "Whippet Race Bike", "make": "", "class": 13, "seats": 1, "doors": 1, "type": "bicycle", "price": 3622, "category": "land" }, "tribike2": { "acceleration": 0.135, "braking": 2.5, "handling": 0.515, "speed": 21, "traction": 1.85, "name": "Endurex Race Bike", "make": "", "class": 13, "seats": 1, "doors": 1, "type": "bicycle", "price": 3622, "category": "land" }, "tribike3": { "acceleration": 0.135, "braking": 2.5, "handling": 0.515, "speed": 21, "traction": 1.85, "name": "Tri-Cycles Race Bike", "make": "", "class": 13, "seats": 1, "doors": 1, "type": "bicycle", "price": 3622, "category": "land" }, "trophytruck": { "acceleration": 0.339, "braking": 0.3, "handling": 0.719, "speed": 43.3667, "traction": 2.5, "name": "Trophy Truck", "make": "Vapid", "class": 9, "seats": 2, "doors": 3, "type": "automobile", "price": 71559, "category": "land" }, "trophytruck2": { "acceleration": 0.339, "braking": 0.3, "handling": 0.719, "speed": 43.3667, "traction": 2.5, "name": "Desert Raid", "make": "Vapid", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 71559, "category": "land" }, "tropic": { "acceleration": 13, "braking": 0.4, "handling": 13.38, "speed": 38.3333, "traction": 0, "name": "Tropic", "make": "Shitzu", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 390679, "category": "sea" }, "tropic2": { "acceleration": 13, "braking": 0.4, "handling": 13.38, "speed": 38.3333, "traction": 0, "name": "Tropic", "make": "Shitzu", "class": 14, "seats": 4, "doors": 0, "type": "boat", "price": 390679, "category": "sea" }, "tropos": { "acceleration": 0.225, "braking": 0.7, "handling": 0.605, "speed": 42.9329, "traction": 2.05, "name": "Tropos Rallye", "make": "Lampadati", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 71140, "category": "land" }, "tug": { "acceleration": 2.2, "braking": 0.4, "handling": 2.58, "speed": 6, "traction": 0, "name": "Tug", "make": "", "class": 14, "seats": 1, "doors": 0, "type": "boat", "price": 67080, "category": "sea" }, "tula": { "acceleration": 4.9, "braking": 3.399, "handling": 4.9, "speed": 69.3676, "traction": 2.15, "name": "Tula", "make": "Mammoth", "class": 16, "seats": 5, "doors": 2, "type": "plane", "price": 6605327, "category": "air", "weapons": true }, "tulip": { "acceleration": 0.32, "braking": 0.5, "handling": 0.7, "speed": 48.8621, "traction": 2.25, "name": "Tulip", "make": "Declasse", "class": 4, "seats": 4, "doors": 6, "type": "automobile", "price": 80611, "category": "land" }, "tulip2": { "acceleration": 0.3153, "braking": 0.68, "handling": 0.6953, "speed": 45.0262, "traction": 2.33, "name": "Tulip M-100", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 74746, "category": "land" }, "turismo2": { "acceleration": 0.34, "braking": 0.5, "handling": 0.77, "speed": 49.5374, "traction": 2.65, "name": "Turismo Classic", "make": "Grotti", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 81835, "category": "land" }, "turismo3": { "acceleration": 0.374, "braking": 1.185, "handling": 0.754, "speed": 49.9028, "traction": 2.764, "name": "Turismo Omaggio", "make": "Grotti", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 83545, "category": "land" }, "turismor": { "acceleration": 0.353, "braking": 1.2, "handling": 0.733, "speed": 51.0911, "traction": 2.64, "name": "Turismo R", "make": "Grotti", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 85403, "category": "land" }, "tvtrailer": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 2, "type": "trailer", "price": 5668, "category": "land" }, "tvtrailer2": { "acceleration": 0, "braking": 0.7, "handling": 0.38, "speed": -0.5132, "traction": 3.3, "name": "Trailer", "make": "", "class": 11, "seats": 0, "doors": 2, "type": "trailer", "price": 5668, "category": "land" }, "tyrant": { "acceleration": 0.34, "braking": 1, "handling": 0.72, "speed": 53.1411, "traction": 2.75, "name": "Tyrant", "make": "Overflod", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 88321, "category": "land" }, "tyrus": { "acceleration": 0.371, "braking": 1.2, "handling": 0.751, "speed": 50.3972, "traction": 2.685, "name": "Tyrus", "make": "Progen", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 84350, "category": "land" }, "uranus": { "acceleration": 0.3245, "braking": 0.48, "handling": 0.7045, "speed": 47.8313, "traction": 2.54, "name": "Uranus LozSpeed", "make": "Vapid", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 78944, "category": "land" }, "utillitruck": { "acceleration": 0.12, "braking": 0.25, "handling": 0.5, "speed": 30.462, "traction": 1.6, "name": "Utility Truck", "make": "", "class": 11, "seats": 2, "doors": 3, "type": "automobile", "price": 50131, "category": "land" }, "utillitruck2": { "acceleration": 0.12, "braking": 0.25, "handling": 0.5, "speed": 30.462, "traction": 1.6, "name": "Utility Truck", "make": "", "class": 11, "seats": 2, "doors": 4, "type": "automobile", "price": 50131, "category": "land" }, "utillitruck3": { "acceleration": 0.12, "braking": 0.25, "handling": 0.5, "speed": 30.462, "traction": 1.6, "name": "Utility Truck", "make": "", "class": 11, "seats": 2, "doors": 4, "type": "automobile", "price": 50131, "category": "land" }, "vacca": { "acceleration": 0.3, "braking": 1, "handling": 0.68, "speed": 50.3117, "traction": 2.6, "name": "Vacca", "make": "Pegassi", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 83666, "category": "land" }, "vader": { "acceleration": 0.27, "braking": 1.1, "handling": 0.65, "speed": 42.3068, "traction": 1.9, "name": "Vader", "make": "Shitzu", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 22163, "category": "land" }, "vagner": { "acceleration": 0.37, "braking": 1.12, "handling": 0.75, "speed": 52.6405, "traction": 2.76, "name": "Vagner", "make": "Dewbauchee", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 87808, "category": "land" }, "vagrant": { "acceleration": 0.3325, "braking": 0.625, "handling": 0.7125, "speed": 46.8151, "traction": 2.35, "name": "Vagrant", "make": "Maxwell", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 77576, "category": "land" }, "valkyrie": { "acceleration": 5.194, "braking": 2.8425, "handling": 5.194, "speed": 54.7262, "traction": 1.6, "name": "Valkyrie", "make": "", "class": 15, "seats": 4, "doors": 2, "type": "heli", "price": 30580515, "category": "air", "weapons": true }, "valkyrie2": { "acceleration": 5.194, "braking": 2.8425, "handling": 5.194, "speed": 54.7262, "traction": 1.6, "name": "Valkyrie MOD.0", "make": "", "class": 15, "seats": 4, "doors": 2, "type": "heli", "price": 30580515, "category": "air", "weapons": true }, "vamos": { "acceleration": 0.33, "braking": 0.5, "handling": 0.71, "speed": 45.8049, "traction": 2.25, "name": "Vamos", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 75751, "category": "land" }, "vectre": { "acceleration": 0.324, "braking": 0.6, "handling": 0.704, "speed": 44.1236, "traction": 2.635, "name": "Vectre", "make": "Emperor", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 73202, "category": "land" }, "velum": { "acceleration": 5.586, "braking": 4.1692, "handling": 5.586, "speed": 74.6362, "traction": 2.15, "name": "Velum", "make": "", "class": 16, "seats": 4, "doors": 2, "type": "plane", "price": 1439638, "category": "air" }, "velum2": { "acceleration": 5.586, "braking": 4.1692, "handling": 5.586, "speed": 74.6362, "traction": 2.15, "name": "Velum 5-Seater", "make": "", "class": 16, "seats": 5, "doors": 3, "type": "plane", "price": 1439638, "category": "air" }, "verlierer2": { "acceleration": 0.335, "braking": 1, "handling": 0.715, "speed": 50, "traction": 2.43, "name": "Verlierer", "make": "Bravado", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 83280, "category": "land" }, "verus": { "acceleration": 0.135, "braking": 0.26, "handling": 0.515, "speed": 26.6342, "traction": 2.1, "name": "Verus", "make": "Dinka", "class": 9, "seats": 2, "doors": 0, "type": "quadbike", "price": 30298, "category": "land" }, "vestra": { "acceleration": 9.016, "braking": 8.7581, "handling": 9.016, "speed": 97.1397, "traction": 1.15, "name": "Vestra", "make": "Buckingham", "class": 16, "seats": 2, "doors": 2, "type": "plane", "price": 1982876, "category": "air" }, "vetir": { "acceleration": 0.11, "braking": 0.3, "handling": 0.49, "speed": 23.3676, "traction": 1.52, "name": "Vetir", "make": "", "class": 19, "seats": 10, "doors": 3, "type": "automobile", "price": 38828, "category": "land" }, "veto": { "acceleration": 0.45, "braking": 0.35, "handling": 0.83, "speed": 25, "traction": 2.85, "name": "Veto Classic", "make": "Dinka", "class": 6, "seats": 1, "doors": 0, "type": "automobile", "price": 42608, "category": "land" }, "veto2": { "acceleration": 0.4275, "braking": 0.4, "handling": 0.8075, "speed": 26.6667, "traction": 2.925, "name": "Veto Modern", "make": "Dinka", "class": 6, "seats": 1, "doors": 0, "type": "automobile", "price": 45282, "category": "land" }, "vigero": { "acceleration": 0.29, "braking": 0.8, "handling": 0.67, "speed": 46.6667, "traction": 2.05, "name": "Vigero", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 77482, "category": "land" }, "vigero2": { "acceleration": 0.3645, "braking": 0.95, "handling": 0.8245, "speed": 49.201, "traction": 2.585, "name": "Vigero ZX", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 82144, "category": "land" }, "vigero3": { "acceleration": 0.3645, "braking": 0.95, "handling": 0.8245, "speed": 49.0273, "traction": 2.585, "name": "Vigero ZX Convertible", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 81866, "category": "land" }, "vigilante": { "acceleration": 0.375, "braking": 1.02, "handling": 0.755, "speed": 52.2429, "traction": 2.775, "name": "Vigilante", "make": "", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 1305429, "category": "land", "weapons": true }, "vindicator": { "acceleration": 0.263, "braking": 1.52, "handling": 0.643, "speed": 49.3333, "traction": 1.98, "name": "Vindicator", "make": "Dinka", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 51759, "category": "land" }, "virgo": { "acceleration": 0.21, "braking": 0.7, "handling": 0.59, "speed": 36.3465, "traction": 2.05, "name": "Virgo", "make": "Albany", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 60554, "category": "land" }, "virgo2": { "acceleration": 0.211, "braking": 0.72, "handling": 0.591, "speed": 36.4521, "traction": 2.1, "name": "Virgo Classic Custom", "make": "Dundreary", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 60758, "category": "land" }, "virgo3": { "acceleration": 0.21, "braking": 0.7, "handling": 0.59, "speed": 36.3465, "traction": 2.05, "name": "Virgo Classic", "make": "Dundreary", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 60554, "category": "land" }, "virtue": { "acceleration": 0.3418, "braking": 1.28, "handling": 0.7218, "speed": 45.3958, "traction": 2.588, "name": "Virtue", "make": "Ocelot", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 76383, "category": "land" }, "viseris": { "acceleration": 0.3, "braking": 0.8, "handling": 0.68, "speed": 47.1242, "traction": 2.3, "name": "Viseris", "make": "Lampadati", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 78246, "category": "land" }, "visione": { "acceleration": 0.355, "braking": 1.02, "handling": 0.735, "speed": 51.3756, "traction": 2.75, "name": "Visione", "make": "Grotti", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 85576, "category": "land" }, "vivanite": { "acceleration": 0.1095, "braking": 0.78, "handling": 0.5895, "speed": 27.48, "traction": 2.1785, "name": "Vivanite", "make": "Karin", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 46334, "category": "land" }, "volatol": { "acceleration": 6.37, "braking": 5.1138, "handling": 6.37, "speed": 80.2793, "traction": 2.15, "name": "Volatol", "make": "", "class": 16, "seats": 4, "doors": 1, "type": "plane", "price": 7850648, "category": "air", "weapons": true }, "volatus": { "acceleration": 5.39, "braking": 3.1931, "handling": 5.39, "speed": 59.2404, "traction": 1.3, "name": "Volatus", "make": "Buckingham", "class": 15, "seats": 4, "doors": 4, "type": "heli", "price": 6589215, "category": "air" }, "voltic": { "acceleration": 0.18, "braking": 1, "handling": 0.56, "speed": 35.9639, "traction": 2.53, "name": "Voltic", "make": "Coil", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 60326, "category": "land" }, "voltic2": { "acceleration": 0.18, "braking": 1, "handling": 0.56, "speed": 35.9639, "traction": 2.53, "name": "Rocket Voltic", "make": "Coil", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 180978, "category": "land" }, "voodoo": { "acceleration": 0.18, "braking": 0.7, "handling": 0.56, "speed": 39.9117, "traction": 1.85, "name": "Voodoo Custom", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 66162, "category": "land" }, "voodoo2": { "acceleration": 0.17, "braking": 0.25, "handling": 0.55, "speed": 38.4494, "traction": 1.85, "name": "Voodoo", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 63071, "category": "land" }, "vorschlaghammer": { "acceleration": 0.265, "braking": 0.625, "handling": 0.645, "speed": 43.4501, "traction": 2.065, "name": "Vorschlaghammer", "make": "Benefactor", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 71976, "category": "land" }, "vortex": { "acceleration": 0.4025, "braking": 1.2, "handling": 0.7825, "speed": 48.8391, "traction": 2.15, "name": "Vortex", "make": "Pegassi", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 25612, "category": "land" }, "vstr": { "acceleration": 0.379, "braking": 0.625, "handling": 0.759, "speed": 48.0182, "traction": 2.58, "name": "V-STR", "make": "Albany", "class": 6, "seats": 4, "doors": 6, "type": "automobile", "price": 79649, "category": "land" }, "warrener": { "acceleration": 0.245, "braking": 0.95, "handling": 0.625, "speed": 38.4094, "traction": 2.16, "name": "Warrener", "make": "Vulcar", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 64367, "category": "land" }, "warrener2": { "acceleration": 0.265, "braking": 0.95, "handling": 0.645, "speed": 40.2494, "traction": 2.16, "name": "Warrener HKR", "make": "Vulcar", "class": 1, "seats": 2, "doors": 4, "type": "automobile", "price": 67375, "category": "land" }, "washington": { "acceleration": 0.2, "braking": 0.9, "handling": 0.58, "speed": 42.7284, "traction": 2.45, "name": "Washington", "make": "Albany", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 71053, "category": "land" }, "wastelander": { "acceleration": 0.33, "braking": 0.6, "handling": 0.71, "speed": 40.7586, "traction": 2.3, "name": "Wastelander", "make": "MTL", "class": 17, "seats": 6, "doors": 0, "type": "automobile", "price": 67837, "category": "land" }, "weevil": { "acceleration": 0.188, "braking": 0.26, "handling": 0.568, "speed": 32.7237, "traction": 1.78, "name": "Weevil", "make": "BF", "class": 0, "seats": 2, "doors": 4, "type": "automobile", "price": 53983, "category": "land" }, "weevil2": { "acceleration": 0.3375, "braking": 0.775, "handling": 0.7175, "speed": 50.5455, "traction": 2.1075, "name": "Weevil Custom", "make": "BF", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 83800, "category": "land" }, "windsor": { "acceleration": 0.28, "braking": 0.7, "handling": 0.66, "speed": 47.9991, "traction": 2.2, "name": "Windsor", "make": "Enus", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 79422, "category": "land" }, "windsor2": { "acceleration": 0.279, "braking": 0.7, "handling": 0.659, "speed": 47.8947, "traction": 2.2, "name": "Windsor Drop", "make": "Enus", "class": 3, "seats": 4, "doors": 6, "type": "automobile", "price": 79252, "category": "land" }, "winky": { "acceleration": 0.15, "braking": 0.26, "handling": 0.53, "speed": 30, "traction": 1.85, "name": "Winky", "make": "Vapid", "class": 9, "seats": 3, "doors": 1, "type": "automobile", "price": 49504, "category": "land" }, "wolfsbane": { "acceleration": 0.215, "braking": 1.2, "handling": 0.595, "speed": 36.8722, "traction": 1.65, "name": "Wolfsbane", "make": "Western", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 19441, "category": "land" }, "xa21": { "acceleration": 0.364, "braking": 1.1, "handling": 0.744, "speed": 49.5999, "traction": 2.68, "name": "XA-21", "make": "Ocelot", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 82892, "category": "land" }, "xls": { "acceleration": 0.26, "braking": 0.58, "handling": 0.64, "speed": 44, "traction": 2, "name": "XLS", "make": "Benefactor", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 72768, "category": "land" }, "xls2": { "acceleration": 0.265, "braking": 0.59, "handling": 0.645, "speed": 44, "traction": 2.05, "name": "XLS (Armored)", "make": "Benefactor", "class": 2, "seats": 4, "doors": 6, "type": "automobile", "price": 72800, "category": "land" }, "yosemite": { "acceleration": 0.285, "braking": 0.75, "handling": 0.665, "speed": 41.6363, "traction": 2.375, "name": "Yosemite", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 69338, "category": "land" }, "yosemite1500": { "acceleration": 0.2335, "braking": 0.53, "handling": 0.6135, "speed": 42.8304, "traction": 2, "name": "Yosemite 1500", "make": "Declasse", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 70731, "category": "land" }, "yosemite2": { "acceleration": 0.395, "braking": 0.85, "handling": 0.775, "speed": 44.3373, "traction": 2.625, "name": "Drift Yosemite", "make": "Declasse", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 74171, "category": "land" }, "yosemite3": { "acceleration": 0.275, "braking": 0.48, "handling": 0.655, "speed": 40.4001, "traction": 2.1, "name": "Yosemite Rancher", "make": "Declasse", "class": 9, "seats": 2, "doors": 4, "type": "automobile", "price": 66896, "category": "land" }, "youga": { "acceleration": 0.14, "braking": 0.3, "handling": 0.52, "speed": 33.8088, "traction": 1.8, "name": "Youga", "make": "Bravado", "class": 12, "seats": 2, "doors": 5, "type": "automobile", "price": 55630, "category": "land" }, "youga2": { "acceleration": 0.14, "braking": 0.3, "handling": 0.52, "speed": 33.8088, "traction": 1.8, "name": "Youga Classic", "make": "Bravado", "class": 12, "seats": 4, "doors": 6, "type": "automobile", "price": 55630, "category": "land" }, "youga3": { "acceleration": 0.17, "braking": 0.3, "handling": 0.55, "speed": 38.4494, "traction": 1.95, "name": "Youga Classic 4x4", "make": "Bravado", "class": 12, "seats": 4, "doors": 5, "type": "automobile", "price": 63151, "category": "land" }, "youga4": { "acceleration": 0.158, "braking": 0.32, "handling": 0.538, "speed": 36.6417, "traction": 1.8, "name": "Youga Custom", "make": "Vapid", "class": 12, "seats": 4, "doors": 6, "type": "automobile", "price": 60252, "category": "land" }, "youga5": { "acceleration": 0.158, "braking": 0.32, "handling": 0.538, "speed": 36.6417, "traction": 1.8, "name": "Youga Custom", "make": "Vapid", "class": 12, "seats": 4, "doors": 6, "type": "automobile", "price": 60252, "category": "land" }, "z190": { "acceleration": 0.27, "braking": 0.95, "handling": 0.65, "speed": 43.9424, "traction": 2.3, "name": "190z", "make": "Karin", "class": 5, "seats": 2, "doors": 6, "type": "automobile", "price": 73299, "category": "land" }, "zeno": { "acceleration": 0.388, "braking": 1.2, "handling": 0.768, "speed": 53.6449, "traction": 2.75, "name": "Zeno", "make": "Overflod", "class": 7, "seats": 2, "doors": 4, "type": "automobile", "price": 89601, "category": "land" }, "zentorno": { "acceleration": 0.354, "braking": 1, "handling": 0.734, "speed": 49.659, "traction": 2.65, "name": "Zentorno", "make": "Pegassi", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 82795, "category": "land" }, "zhaba": { "acceleration": 0.18, "braking": 0.8, "handling": 0.56, "speed": 30.3041, "traction": 1.9, "name": "Zhaba", "make": "RUNE", "class": 9, "seats": 4, "doors": 6, "type": "amphibious_automobile", "price": 203802, "category": "land" }, "zion": { "acceleration": 0.22, "braking": 0.9, "handling": 0.6, "speed": 46.8751, "traction": 2.6, "name": "Zion", "make": "Ubermacht", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 77752, "category": "land" }, "zion2": { "acceleration": 0.22, "braking": 0.9, "handling": 0.6, "speed": 46.8751, "traction": 2.6, "name": "Zion Cabrio", "make": "Ubermacht", "class": 3, "seats": 2, "doors": 4, "type": "automobile", "price": 77752, "category": "land" }, "zion3": { "acceleration": 0.305, "braking": 0.85, "handling": 0.685, "speed": 44.5858, "traction": 2.35, "name": "Zion Classic", "make": "Ubermacht", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 74281, "category": "land" }, "zombiea": { "acceleration": 0.29, "braking": 0.8, "handling": 0.67, "speed": 44.6053, "traction": 1.875, "name": "Zombie Bobber", "make": "Western", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 23182, "category": "land" }, "zombieb": { "acceleration": 0.29, "braking": 0.8, "handling": 0.67, "speed": 44.6053, "traction": 1.875, "name": "Zombie Chopper", "make": "Western", "class": 8, "seats": 2, "doors": 1, "type": "bike", "price": 23182, "category": "land" }, "zorrusso": { "acceleration": 0.3745, "braking": 1.2, "handling": 0.7545, "speed": 50.8958, "traction": 2.728, "name": "Zorrusso", "make": "Pegassi", "class": 7, "seats": 2, "doors": 3, "type": "automobile", "price": 85159, "category": "land" }, "zr350": { "acceleration": 0.33, "braking": 0.85, "handling": 0.71, "speed": 48.0938, "traction": 2.485, "name": "ZR350", "make": "Annis", "class": 6, "seats": 2, "doors": 4, "type": "automobile", "price": 79974, "category": "land" }, "zr380": { "acceleration": 0.35, "braking": 0.8, "handling": 0.73, "speed": 50.39, "traction": 2.5, "name": "Apocalypse ZR380", "make": "Annis", "class": 6, "seats": 2, "doors": 2, "type": "automobile", "price": 83632, "category": "land" }, "zr3802": { "acceleration": 0.35, "braking": 0.8, "handling": 0.73, "speed": 50.39, "traction": 2.5, "name": "Future Shock ZR380", "make": "Annis", "class": 6, "seats": 2, "doors": 2, "type": "automobile", "price": 83632, "category": "land" }, "zr3803": { "acceleration": 0.35, "braking": 0.8, "handling": 0.73, "speed": 50.39, "traction": 2.5, "name": "Nightmare ZR380", "make": "Annis", "class": 6, "seats": 2, "doors": 2, "type": "automobile", "price": 83632, "category": "land" }, "ztype": { "acceleration": 0.3, "braking": 0.7, "handling": 0.68, "speed": 51.3333, "traction": 2.3, "name": "Z-Type", "make": "Truffade", "class": 5, "seats": 2, "doors": 4, "type": "automobile", "price": 84821, "category": "land" }, "hardy": { "acceleration": 0.28, "braking": 0.75, "handling": 0.65, "speed": 42.0, "traction": 2.1, "name": "Hardy", "make": "Annis", "class": 1, "seats": 4, "doors": 4, "type": "automobile", "price": 85000, "category": "land" }, "drifthardy": { "acceleration": 0.28, "braking": 0.75, "handling": 0.65, "speed": 42.0, "traction": 2.1, "name": "Hardy Drift", "make": "Annis", "class": 1, "seats": 4, "doors": 4, "type": "automobile", "price": 85000, "category": "land" }, "minimus": { "acceleration": 0.29, "braking": 0.78, "handling": 0.67, "speed": 43.5, "traction": 2.2, "name": "Minimus", "make": "Annis", "class": 1, "seats": 4, "doors": 4, "type": "automobile", "price": 120000, "category": "land" }, "tampa4": { "acceleration": 0.33, "braking": 0.9, "handling": 0.72, "speed": 48.0, "traction": 2.6, "name": "Tampa GT", "make": "Declasse", "class": 4, "seats": 2, "doors": 2, "type": "automobile", "price": 1850000, "category": "land" }, "l352": { "acceleration": 0.21, "braking": 0.6, "handling": 0.58, "speed": 38.0, "traction": 2.3, "name": "Walton L35 Stock", "make": "Declasse", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 650000, "category": "land" }, "driftl352": { "acceleration": 0.25, "braking": 0.65, "handling": 0.7, "speed": 40.0, "traction": 2.5, "name": "Drift Walton L35", "make": "Declasse", "class": 9, "seats": 2, "doors": 2, "type": "automobile", "price": 950000, "category": "land" }, "rapidgt4": { "acceleration": 0.35, "braking": 1.0, "handling": 0.75, "speed": 49.5, "traction": 2.65, "name": "Rapid GT X", "make": "Dewbauchee", "class": 6, "seats": 2, "doors": 2, "type": "automobile", "price": 1600000, "category": "land" }, "cheetah3": { "acceleration": 0.34, "braking": 0.95, "handling": 0.73, "speed": 49.0, "traction": 2.55, "name": "LSCM Cheetah Classic", "make": "Grotti", "class": 5, "seats": 2, "doors": 2, "type": "automobile", "price": 1300000, "category": "land" }, "everon3": { "acceleration": 0.27, "braking": 0.7, "handling": 0.63, "speed": 41.0, "traction": 2.4, "name": "Everon RS", "make": "Karin", "class": 2, "seats": 4, "doors": 4, "type": "automobile", "price": 1500000, "category": "land" }, "woodlander": { "acceleration": 0.26, "braking": 0.68, "handling": 0.62, "speed": 40.5, "traction": 2.35, "name": "Woodlander", "make": "Karin", "class": 2, "seats": 4, "doors": 4, "type": "automobile", "price": 1350000, "category": "land" }, "suzume": { "acceleration": 0.39, "braking": 1.2, "handling": 0.8, "speed": 53.0, "traction": 2.8, "name": "Suzume", "make": "Överflöd", "class": 7, "seats": 2, "doors": 2, "type": "automobile", "price": 2800000, "category": "land" }, "sentinel5": { "acceleration": 0.31, "braking": 0.9, "handling": 0.7, "speed": 47.0, "traction": 2.5, "name": "Sentinel GTS", "make": "Übermacht", "class": 6, "seats": 2, "doors": 2, "type": "automobile", "price": 1950000, "category": "land" }, "policeb2": { "acceleration": 0.32, "braking": 1.3, "handling": 0.7, "speed": 45.0, "traction": 2.0, "name": "Police Bike", "make": "Western", "class": 8, "seats": 1, "doors": 1, "type": "bike", "price": 200000, "category": "land" }, "stockade4": { "acceleration": 0.15, "braking": 0.4, "handling": 0.55, "speed": 35.0, "traction": 1.8, "name": "Bobcat Security Stockade", "make": "Brute", "class": 20, "seats": 2, "doors": 2, "type": "automobile", "price": 800000, "category": "land" }, "maverick2": { "acceleration": 5.096, "braking": 2.7553, "handling": 5.096, "speed": 54.0675, "traction": 1.3, "name": "Maverick", "make": "", "class": 15, "seats": 4, "doors": 2, "type": "heli", "price": 30156660, "category": "air", "weapons": true }, "flatbed2": { "acceleration": 0.14, "braking": 0.25, "handling": 0.52, "speed": 28.2361, "traction": 1.65, "name": "Flatbed", "make": "MTL", "class": 10, "seats": 2, "doors": 3, "type": "automobile", "price": 46633, "category": "land" }, "driftdominator10": { "acceleration": 0.3, "braking": 0.69, "handling": 0.68, "speed": 49.296, "traction": 2.383, "name": "Dominator FX Drift", "make": "Vapid", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 81545, "category": "land" }, "driftgauntlet4": { "acceleration": 0.36, "braking": 0.9, "handling": 0.74, "speed": 48.6553, "traction": 2.35, "name": "Gauntlet Hellfire Drift", "make": "Bravado", "class": 4, "seats": 2, "doors": 4, "type": "automobile", "price": 81048, "category": "land" }, "driftchavosv6": { "acceleration": 0.274, "braking": 0.69, "handling": 0.654, "speed": 46.6629, "traction": 2.554, "name": "Chavos V6 Drift", "make": "Dinka", "class": 1, "seats": 4, "doors": 6, "type": "automobile", "price": 77249, "category": "land" } } ================================================ FILE: common/index.ts ================================================ import { checkDependency } from "@overextended/ox_lib/"; import type { OxGroupPermissions } from "types"; if (!checkDependency("ox_lib", "3.24.0", true)) throw new Error("Failed dependency check."); export function LoadDataFile(file: string) { return JSON.parse(LoadResourceFile("ox_core", `/common/data/${file}.json`)); } export function GetGroupPermissions(groupName: string): OxGroupPermissions { return GlobalState[`group.${groupName}:permissions`] || {}; } console.info = (...args: any[]) => console.log(`^3${args.join("\t")}^0`); DEV: console.info(`Resource ${GetCurrentResourceName()} is running in development mode!`); import "./vehicles"; exports("GetGroupPermissions", GetGroupPermissions); ================================================ FILE: common/locales.ts ================================================ import { locale, type FlattenObjectKeys } from "@overextended/ox_lib"; type Locales = FlattenObjectKeys; export default (str: T, ...args: any[]) => locale(str, ...args) as string; ================================================ FILE: common/tsconfig.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "baseUrl": ".", "composite": true, "paths": { "types": ["../types"] } }, "include": ["./", "../types", "../locales/en.json"] } ================================================ FILE: common/vehicles.ts ================================================ import { LoadDataFile } from './'; import type { Dict, VehicleCategories, VehicleData, VehicleStats, Vehicles } from 'types'; const vehicles: Dict = LoadDataFile('vehicles'); const vehicleStats = LoadDataFile('vehicleStats'); export function GetTopVehicleStats(): Record; export function GetTopVehicleStats(category: VehicleCategories): VehicleStats; export function GetTopVehicleStats(category?: VehicleCategories) { return category ? (vehicleStats as any)[category] : vehicleStats; } export function GetVehicleData(): Vehicles; export function GetVehicleData(filter: T): VehicleData; export function GetVehicleData( filter: T, ): { [K in T[number]]: VehicleData; // this could be better }; export function GetVehicleData(filter?: void | string | string[]) { if (!filter) return vehicles; if (typeof filter === 'string') return vehicles[filter]; if (Array.isArray(filter)) { const obj: Record = {}; filter.forEach((name) => { const vehicle = vehicles[name]; if (vehicle) obj[name] = vehicle; }); return obj; } } /** * Remap vehicle types to their net types. * https://github.com/citizenfx/fivem/commit/1e266a2ca5c04eb96c090de67508a3475d35d6da */ export function GetVehicleNetworkType(modelName: string) { const vehicleType = vehicles[modelName]?.type; switch (vehicleType) { case 'bicycle': return 'bike'; case 'blimp': return 'heli'; case 'quadbike': case 'amphibious_quadbike': case 'amphibious_automobile': case 'submarinecar': return 'automobile'; default: return vehicleType; } } exports('GetTopVehicleStats', GetTopVehicleStats); exports('GetVehicleData', GetVehicleData); exports('GetVehicleNetworkType', GetVehicleNetworkType); ================================================ FILE: lib/client/index.ts ================================================ import { Ox as OxCore, type OxCommon } from 'lib'; interface OxClient extends OxCommon {} export const Ox = OxCore as OxClient; export * from './player'; ================================================ FILE: lib/client/init.lua ================================================ require 'lib.client.player' ================================================ FILE: lib/client/player.lua ================================================ ---@diagnostic disable: redundant-parameter ---@class OxPlayerClient : OxClass local OxPlayer = lib.class('OxPlayer') -- Support for `player.method` rather than self (:) syntax function OxPlayer:__index(index) local value = OxPlayer[index] --[[@as any]] if type(value) == 'function' then self[index] = value == OxPlayer.__call and function(...) return value(self, index, ...) end or function(...) return value(self, ...) end return self[index] end return value end function OxPlayer:constructor() pcall(function() local data = exports.ox_core.GetPlayer() self.userId = data.userId self.charId = data.charId self.stateId = data.stateId end) self.state = LocalPlayer.state end function OxPlayer:__call(...) return exports.ox_core:CallPlayer(...) end function OxPlayer:__tostring() return string.format('{\n "userId": %s\n "charId": %s\n "stateId": %s\n}', self.userId, self.charId, self.stateId) end local getters = {} function OxPlayer:on(key, callback) self.get(key) AddEventHandler(('ox:player:%s'):format(key), function(data) if GetInvokingResource() == 'ox_core' and source == '' then callback(data) end end) end function OxPlayer:get(key) if not self.charId then return end if not getters[key] then getters[key] = true self.on(key, function(data) self[key] = data end) self[key] = OxPlayer:__call('get', key); end return self[key] end function OxPlayer:getCoords() return GetEntityCoords(cache.ped); end function OxPlayer:getGroup(filter) local result = OxPlayer:__call('getGroup', filter) if type(result) == 'table' then return table.unpack(result) end return result end function OxPlayer:getGroupByType(type) local result = OxPlayer:__call('getGroupByType', type) if result then return table.unpack(result) end end ---@class OxClient local Ox = Ox local player = OxPlayer:new() function Ox.GetPlayer() return player end local function getMethods() for method in pairs(exports.ox_core:GetPlayerCalls()) do if not rawget(OxPlayer, method) then OxPlayer[method] = OxPlayer.__call end end end -- Prevent errors if resource starts before ox_core (generally during development) if not pcall(getMethods) then CreateThread(getMethods) end AddEventHandler('ox:playerLoaded', function(data) if player.charId then return end for k, v in pairs(data) do player[k] = v end end) AddEventHandler('ox:playerLogout', function() table.wipe(player) end) ================================================ FILE: lib/client/player.ts ================================================ import { cache } from "@overextended/ox_lib/client"; import type { OxPlayer } from "client/player"; import type { Dict } from "types"; class PlayerInterface { public userId: number; public charId?: number; public stateId?: string; [key: string]: any; constructor() { try { const { userId, charId, stateId } = exports.ox_core.GetPlayer(); this.userId = userId; this.charId = charId; this.stateId = stateId; } catch (e) {} this.state = LocalPlayer.state; this.constructor.prototype.toString = () => { return JSON.stringify(this, null, 2); }; const getMethods = async () => { Object.keys(exports.ox_core.GetPlayerCalls()).forEach((method: string) => { if (!this.constructor.prototype[method]) this.constructor.prototype[method] = (...args: any[]) => exports.ox_core.CallPlayer(method, ...args); }); }; // Prevent errors if resource starts before ox_core (generally during development) getMethods().catch(() => setImmediate(getMethods)); } /** * Registers an event handler which will be triggered when the specified player data is updated. */ on(key: string, callback: (data: unknown) => void) { this.get(key); on(`ox:player:${key}`, (data: unknown) => { if (GetInvokingResource() == "ox_core" && (source as any) === "") callback(data); }); } /** * Returns player data for the specified key. The data is cached and kept updated for future calls. */ get(key: string) { if (!this.charId) return; if (!(key in this)) { this[key] = exports.ox_core.CallPlayer("get", key) ?? null; this.on(key, (data: unknown) => (this[key] = data)); } return this[key]; } getCoords() { return GetEntityCoords(cache.ped); } } export type OxPlayer = typeof OxPlayer & PlayerInterface; const player = new PlayerInterface() as OxPlayer; export function GetPlayer() { return player; } on("ox:playerLoaded", (data: Dict) => { if (player.charId) return; for (const key in data) player[key] = data[key]; }); on("ox:playerLogout", () => { for (const key in player) delete player[key]; }); ================================================ FILE: lib/index.ts ================================================ import type { GetTopVehicleStats, GetVehicleData, GetVehicleNetworkType } from 'common/vehicles'; import type { OxGroup } from 'types'; export type * from 'types'; export interface OxCommon { [key: string]: (...args: any[]) => any; GetTopVehicleStats: typeof GetTopVehicleStats; GetVehicleData: typeof GetVehicleData; GetVehicleNetworkType: typeof GetVehicleNetworkType; } export const Ox = exports.ox_core as OxCommon; export function GetGroup(name: string): OxGroup { return GlobalState[`group.${name}`]; } ================================================ FILE: lib/init.lua ================================================ if Ox then return Ox end if not lib then if GetResourceState('ox_lib') ~= 'started' then error('ox_lib must be started before this resource.', 0) end local chunk = LoadResourceFile('ox_lib', 'init.lua') if not chunk then error('failed to load resource file @ox_lib/init.lua', 0) end load(chunk, '@@ox_lib/init.lua', 't')() end ---@type OxCommon Ox = setmetatable({}, { __index = function(self, index) self[index] = function(...) return exports.ox_core[index](nil, ...) end return self[index] end }) require(('@ox_core.lib.%s.init'):format(lib.context)) function Ox.GetGroup(name) return GlobalState['group.' .. name] end return Ox ================================================ FILE: lib/server/account.lua ================================================ ---@diagnostic disable: redundant-parameter ---@class OxAccountServer : OxClass local OxAccount = lib.class('OxAccount') function OxAccount:__index(index) local value = OxAccount[index] --[[@as any]] if type(value) == 'function' then self[index] = value == OxAccount.__call and function(...) return value(self, index, ...) end or function(...) return value(self, ...) end return self[index] end return value end function OxAccount:constructor(data) for k, v in pairs(data) do self[k] = v end end function OxAccount:__call(...) return exports.ox_core:CallAccount(self.accountId, ...) end function OxAccount:__tostring() return json.encode(self, { indent = true }) end for method in pairs(exports.ox_core:GetAccountCalls() or {}) do if not rawget(OxAccount, method) then OxAccount[method] = OxAccount.__call end end local function CreateAccountInstance(account) if not account then return end return OxAccount:new(account) end ---@class OxServer local Ox = Ox function Ox.GetAccount(accountId) local account = exports.ox_core:GetAccount(accountId) return CreateAccountInstance(account) end function Ox.GetCharacterAccount(charId) local account = exports.ox_core:GetCharacterAccount(charId) return CreateAccountInstance(account) end function Ox.GetGroupAccount(groupName) local account = exports.ox_core:GetGroupAccount(groupName) return CreateAccountInstance(account) end function Ox.CreateAccount(owner, label) local account = exports.ox_core:CreateAccount(owner, label) return CreateAccountInstance(account) end ================================================ FILE: lib/server/account.ts ================================================ import type { OxAccount as _OxAccount } from 'server/accounts/class'; class AccountInterface { constructor(public accountId: number) {} } Object.keys(exports.ox_core.GetAccountCalls()).forEach((method: string) => { (AccountInterface.prototype as any)[method] = function (...args: any[]) { return exports.ox_core.CallAccount(this.accountId, method, ...args); }; }); AccountInterface.prototype.toString = function () { return JSON.stringify(this, null, 2); }; export type OxAccount = _OxAccount & AccountInterface; function CreateAccountInstance(account?: _OxAccount) { if (!account) return; return new AccountInterface(account.accountId) as OxAccount; } export async function GetAccount(accountId: number) { const account = await exports.ox_core.GetAccount(accountId); return CreateAccountInstance(account); } export async function GetCharacterAccount(charId: number | string) { const account = await exports.ox_core.GetCharacterAccount(charId); return CreateAccountInstance(account); } export async function GetGroupAccount(groupName: string) { const account = await exports.ox_core.GetGroupAccount(groupName); return CreateAccountInstance(account); } export async function CreateAccount(owner: number | string, label: string) { const account = await exports.ox_core.CreateAccount(owner, label); return CreateAccountInstance(account); } ================================================ FILE: lib/server/index.ts ================================================ import type { OxVehicle } from 'server/vehicle/class'; import type { PayAccountInvoice, DeleteAccountInvoice } from 'server/accounts'; import type { OxPlayer } from 'server/player/class'; import type { BanUser, GetCharIdFromStateId, GetLicense, GetLicenses, UnbanUser } from 'server/player/db'; import type { CreateGroup, DeleteGroup, GetGroupsByType, RemoveGroupPermission, SetGroupPermission, } from 'server/groups'; import { Ox as OxCore, type OxCommon } from 'lib'; interface OxServer extends OxCommon { SaveAllPlayers: typeof OxPlayer.saveAll; SaveAllVehicles: typeof OxVehicle.saveAll; GetCharIdFromStateId: typeof GetCharIdFromStateId; GenerateVehicleVin: (model: string) => Promise; GenerateVehiclePlate: typeof OxVehicle.generatePlate; SetGroupPermission: typeof SetGroupPermission; RemoveGroupPermission: typeof RemoveGroupPermission; PayAccountInvoice: typeof PayAccountInvoice; DeleteAccountInvoice: typeof DeleteAccountInvoice; GetGroupsByType: typeof GetGroupsByType; CreateGroup: typeof CreateGroup; DeleteGroup: typeof DeleteGroup; GetLicenses: typeof GetLicenses; GetLicense: typeof GetLicense; BanUser: typeof BanUser; UnbanUser: typeof UnbanUser; } export const Ox = OxCore as OxServer; export * from './player'; export * from './vehicle'; export * from './account'; ================================================ FILE: lib/server/init.lua ================================================ require 'lib.server.player' require 'lib.server.vehicle' require 'lib.server.account' ================================================ FILE: lib/server/player.lua ================================================ ---@diagnostic disable: redundant-parameter ---@class OxPlayerServer : OxClass local OxPlayer = lib.class('OxPlayer') function OxPlayer:__index(index) local value = OxPlayer[index] --[[@as any]] if type(value) == 'function' then self[index] = value == OxPlayer.__call and function(...) return value(self, index, ...) end or function(...) return value(self, ...) end return self[index] end return value end function OxPlayer:constructor(data) for k, v in pairs(data) do self[k] = v end end function OxPlayer:__call(...) return exports.ox_core:CallPlayer(self.source, ...) end function OxPlayer:__tostring() return string.format('{\n "source": %s\n "userId": %s\n "identifier": %s\n "username": %s\n}', self.source, self.userId, self.identifier, self.username) end function OxPlayer:getCoords() return GetEntityCoords(self.ped); end function OxPlayer:getState() return Player(self.source).state; end function OxPlayer:getGroup(filter) local result = OxPlayer.__call(self, 'getGroup', filter) if type(result) == 'table' then return table.unpack(result) end return result end function OxPlayer:getAccount() return self.charId and Ox.GetCharacterAccount(self.charId) or nil end function OxPlayer:getGroupByType(type) local result = OxPlayer.__call(self, 'getGroupByType', type) if result then return table.unpack(result) end end for method in pairs(exports.ox_core:GetPlayerCalls() or {}) do if not rawget(OxPlayer, method) then OxPlayer[method] = OxPlayer.__call end end local function CreatePlayerInstance(player) if not player then return end; return OxPlayer:new(player) end ---@class OxServer local Ox = Ox function Ox.GetPlayer(playerId) return CreatePlayerInstance(exports.ox_core:GetPlayer(playerId)) end function Ox.GetPlayerFromUserId(userId) return CreatePlayerInstance(exports.ox_core:GetPlayerFromUserId(userId)) end function Ox.GetPlayerFromCharId(charId) return CreatePlayerInstance(exports.ox_core:GetPlayerFromCharId(charId)) end function Ox.GetPlayers(filter) local players = exports.ox_core:GetPlayers(filter) for i = 1, #players do players[i] = CreatePlayerInstance(players[i]) end return players end function Ox.GetPlayerFromFilter(filter) return CreatePlayerInstance(exports.ox_core:GetPlayerFromFilter(filter)) end ================================================ FILE: lib/server/player.ts ================================================ import type { OxPlayer as _OxPlayer } from 'server/player/class'; import type { Dict } from 'types'; import { GetCharacterAccount } from './account'; class PlayerInterface { public state: StateBagInterface; constructor( public source: number, public userId: number, public charId: number | undefined, public stateId: string | undefined, public username: string, public identifier: string, public ped: number, ) { this.source = source; this.userId = userId; this.charId = charId; this.stateId = stateId; this.username = username; this.identifier = identifier; this.ped = ped; } getCoords() { return GetEntityCoords(this.ped); } getState() { return Player(source).state; } async getAccount() { return this.charId ? GetCharacterAccount(this.charId) : null; } } Object.keys(exports.ox_core.GetPlayerCalls()).forEach((method: string) => { (PlayerInterface.prototype as any)[method] = function (...args: any[]) { return exports.ox_core.CallPlayer(this.source, method, ...args); }; }); PlayerInterface.prototype.toString = function () { return JSON.stringify(this, null, 2); }; export type OxPlayer = _OxPlayer & PlayerInterface; function CreatePlayerInstance(player?: _OxPlayer) { if (!player) return; return new PlayerInterface( player.source as number, player.userId, player.charId, player.stateId, player.username, player.identifier, player.ped, ) as OxPlayer; } export function GetPlayer(playerId: string | number) { return CreatePlayerInstance(exports.ox_core.GetPlayer(playerId)); } export function GetPlayerFromUserId(userId: number) { return CreatePlayerInstance(exports.ox_core.GetPlayerFromUserId(userId)); } export function GetPlayerFromCharId(charId: number) { return CreatePlayerInstance(exports.ox_core.GetPlayerFromCharId(charId)); } export function GetPlayers(filter?: Dict): OxPlayer[] { const players = exports.ox_core.GetPlayers(filter); for (const id in players) players[id] = CreatePlayerInstance(players[id]); return players; } export function GetPlayerFromFilter(filter: Dict) { return CreatePlayerInstance(exports.ox_core.GetPlayerFromFilter(filter)); } ================================================ FILE: lib/server/vehicle.lua ================================================ ---@diagnostic disable: redundant-parameter ---@class OxVehicleServer : OxClass local OxVehicle = lib.class('OxVehicle') function OxVehicle:__index(index) local value = OxVehicle[index] --[[@as any]] if type(value) == 'function' then self[index] = value == OxVehicle.__call and function(...) return value(self, index, ...) end or function(...) return value(self, ...) end return self[index] end return value end function OxVehicle:constructor(data) for k, v in pairs(data) do self[k] = v end end function OxVehicle:__call(...) return exports.ox_core:CallVehicle(self.vin, ...) end function OxVehicle:__tostring() return json.encode(self, { indent = true}) end function OxVehicle:getCoords() return GetEntityCoords(self.entity); end function OxVehicle:getState() return Entity(self.entity).state; end for method in pairs(exports.ox_core:GetVehicleCalls() or {}) do if not rawget(OxVehicle, method) then OxVehicle[method] = OxVehicle.__call end end local function CreateVehicleInstance(vehicle) if not vehicle then return end; return OxVehicle:new(vehicle) end ---@class OxServer local Ox = Ox function Ox.GetVehicle(handle) return type(handle) == 'string' and Ox.GetVehicleFromVin(handle) or Ox.GetVehicleFromEntity(handle) end function Ox.GetVehicleFromEntity(entityId) return CreateVehicleInstance(exports.ox_core:GetVehicleFromEntity(entityId)) end function Ox.GetVehicleFromNetId(netId) return CreateVehicleInstance(exports.ox_core:GetVehicleFromNetId(netId)) end function Ox.GetVehicleFromVin(vin) return CreateVehicleInstance(exports.ox_core:GetVehicleFromVin(vin)) end function Ox.GetVehicles(filter) local vehicles = exports.ox_core:GetVehicles(filter) for i = 1, #vehicles do vehicles[i] = CreateVehicleInstance(vehicles[i]) end return vehicles end function Ox.GetVehicleFromFilter(filter) return CreateVehicleInstance(exports.ox_core:GetVehicleFromFilter(filter)) end function Ox.CreateVehicle(data, coords, heading) return CreateVehicleInstance(exports.ox_core:CreateVehicle(data, coords, heading)); end function Ox.SpawnVehicle(dbId, coords, heading) return CreateVehicleInstance(exports.ox_core:SpawnVehicle(dbId, coords, heading)); end ================================================ FILE: lib/server/vehicle.ts ================================================ import type { OxVehicle as _OxVehicle } from 'server/vehicle/class'; import type { CreateVehicleData } from 'server/vehicle'; import type { VehicleRow } from 'server/vehicle/db'; import { Dict } from 'types'; class VehicleInterface { constructor( public entity: number | undefined, public netId: number | undefined, public script: string, public plate: string, public model: string, public make: string, public id?: number, public vin?: string, public owner?: number, public group?: string, ) { this.entity = entity; this.netId = netId; this.script = script; this.plate = plate; this.model = model; this.make = make; this.id = id; this.vin = vin; this.owner = owner; this.group = group; } getCoords() { return this.entity ? GetEntityCoords(this.entity) : null; } getState() { return this.entity ? Entity(this.entity).state : null; } } Object.keys(exports.ox_core.GetVehicleCalls()).forEach((method: string) => { (VehicleInterface.prototype as any)[method] = function (...args: any[]) { return exports.ox_core.CallVehicle(this.vin, method, ...args); }; }); VehicleInterface.prototype.toString = function () { return JSON.stringify(this, null, 2); }; export type OxVehicle = _OxVehicle & VehicleInterface; function CreateVehicleInstance(vehicle: _OxVehicle) { if (!vehicle) return; return new VehicleInterface( vehicle.entity, vehicle.netId, vehicle.script, vehicle.plate, vehicle.model, vehicle.make, vehicle.id, vehicle.vin, vehicle.owner, vehicle.group, ) as OxVehicle; } export function GetVehicle(entityId: number): OxVehicle; export function GetVehicle(vin: string): OxVehicle; export function GetVehicle(handle: number | string) { return typeof handle === 'string' ? GetVehicleFromVin(handle) : GetVehicleFromEntity(handle); } export function GetVehicleFromEntity(entityId: number) { return CreateVehicleInstance(exports.ox_core.GetVehicleFromEntity(entityId)); } export function GetVehicleFromNetId(netId: number) { return CreateVehicleInstance(exports.ox_core.GetVehicleFromNetId(netId)); } export function GetVehicleFromVin(vin: string) { return CreateVehicleInstance(exports.ox_core.GetVehicleFromVin(vin)); } export function GetVehicles(filter?: Dict): OxVehicle[] { const vehicles = exports.ox_core.GetVehicles(filter); for (const id in vehicles) vehicles[id] = CreateVehicleInstance(vehicles[id]); return vehicles; } export function GetVehicleFromFilter(filter: Dict) { return CreateVehicleInstance(exports.ox_core.GetVehicleFromFilter(filter)); } export async function CreateVehicle( data: string | (CreateVehicleData & Partial), coords?: number | number[] | { x: number; y: number; z: number }, heading?: number, ) { return CreateVehicleInstance(await exports.ox_core.CreateVehicle(data, coords, heading)); } export async function SpawnVehicle( dbId: number, coords: number | number[] | { x: number; y: number; z: number }, heading?: number, ) { return CreateVehicleInstance(await exports.ox_core.SpawnVehicle(dbId, coords, heading)); } ================================================ FILE: lib/tsconfig.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "emitDeclarationOnly": false, "composite": true }, "include": ["./", "../types/"], "references": [{ "path": "../client" }, { "path": "../server" }] } ================================================ FILE: locales/ar.json ================================================ { "create_character": "إنشاء شخصية جديدة", "firstname": "الاسم الأول", "lastname": "اسم العائلة", "gender": "الجنس", "male": "ذكر", "female": "أنثى", "non_binary": "غير ثنائي", "date_of_birth": "تاريخ الميلاد", "withdraw": "سحب", "deposit": "إيداع", "transfer": "تحويل بنكي", "invoice_payment": "دفع الفاتورة", "hours": "ساعات", "minutes": "دقائق", "seconds": "ثوانٍ", "ban_indefinite": "هذا الحظر ليس له مدة انتهاء.", "ban_expires_in": "ينتهي الحظر خلال %s ${hours}، %s ${minutes}، و %s ${seconds}.", "ban_notice": "⚠️ لقد تم حظرك من هذا الخادم! ⚠️\nتاريخ الحظر: %s\nالسبب: %s\n\n%s", "userid_is_active": "معرّف المستخدم الخاص بك (%s) نشط بالفعل.", "no_license": "تعذر علينا التحقق من رخصة لعبتك.", "server_restarting": "الخادم على وشك إعادة التشغيل. لا يمكنك الانضمام في الوقت الحالي." } ================================================ FILE: locales/bg.json ================================================ { "create_character": "Създай нов герой", "firstname": "Име", "lastname": "Фамилия", "gender": "Пол", "male": "Мъж", "female": "Жена", "non_binary": "Небинарен", "date_of_birth": "Дата на раждане", "withdraw": "Теглене", "deposit": "Депозит", "transfer": "Банков превод", "invoice_payment": "Плащане на фактура" } ================================================ FILE: locales/cs.json ================================================ { "create_character": "Vytvořit novou postavu", "firstname": "Jméno", "lastname": "Příjmení", "gender": "Pohlaví", "male": "Muž", "female": "Žena", "non_binary": "Nebinární", "date_of_birth": "Datum narození", "withdraw": "Výběr", "deposit": "Vklad", "transfer": "Bankovní převod", "invoice_payment": "Úhrada faktury" } ================================================ FILE: locales/da.json ================================================ { "create_character": "Opret en ny karakter", "firstname": "Fornavn", "lastname": "Efternavn", "gender": "Køn", "male": "Mand", "female": "Kvinde", "non_binary": "Ikke-binær", "date_of_birth": "Fødselsdato", "withdraw": "Hæv", "deposit": "Indsæt", "transfer": "Bankoverførsel", "invoice_payment": "Fakturabetaling" } ================================================ FILE: locales/de.json ================================================ { "create_character": "Einen neuen Charakter erstellen", "firstname": "Vorname", "lastname": "Nachname", "gender": "Geschlecht", "male": "Männlich", "female": "Weiblich", "non_binary": "Nicht-binär", "date_of_birth": "Geburtsdatum", "withdraw": "Abheben", "deposit": "Einzahlen", "transfer": "Banküberweisung", "invoice_payment": "Rechnungszahlung", "hours": "Stunden", "minutes": "Minuten", "seconds": "Sekunden", "ban_indefinite": "Dieses Verbot läuft nicht ab.", "ban_expires_in": "Das Verbot läuft in %s ${hours}, %s ${minutes} und %s ${seconds} ab.", "ban_notice": "⚠️ Du wurdest von diesem Server gebannt! ⚠️\nVerbot erteilt: %s\nGrund: %s\n\n%s", "userid_is_active": "Deine Benutzer-ID (%s) ist bereits aktiv.", "no_license": "Wir konnten deine Spiel-Lizenz nicht verifizieren.", "server_restarting": "Der Server wird neu gestartet. Du kannst dich derzeit nicht verbinden." } ================================================ FILE: locales/en.json ================================================ { "create_character": "Create a new character", "firstname": "First name", "lastname": "Last name", "gender": "Gender", "male": "Male", "female": "Female", "non_binary": "Non-Binary", "date_of_birth": "Date of birth", "withdraw": "Withdraw", "deposit": "Deposit", "transfer": "Bank transfer", "invoice_payment": "Invoice payment", "hours": "hours", "minutes": "minutes", "seconds": "seconds", "ban_indefinite": "This ban will not expire.", "ban_expires_in": "Ban expires in %s ${hours}, %s ${minutes}, and %s ${seconds}.", "ban_notice": "⚠️ You have been banned from this server! ⚠️\nBan issued: %s\nReason: %s\n\n%s", "userid_is_active": "Your userId (%s), is already active.", "no_license": "We were unable to verify your game license.", "server_restarting": "The server is about to restart. You cannot join at this time." } ================================================ FILE: locales/es.json ================================================ { "create_character": "Crear un nuevo personaje", "firstname": "Nombre", "lastname": "Apellido", "gender": "Género", "male": "Hombre", "female": "Mujer", "non_binary": "No binario", "date_of_birth": "Fecha de nacimiento", "withdraw": "Retirar", "deposit": "Depositar", "transfer": "Transferencia bancaria", "invoice_payment": "Pago de factura", "hours": "horas", "minutes": "minutos", "seconds": "segundos", "ban_indefinite": "Este baneo es indefinido.", "ban_expires_in": "El baneo expira en %s ${hours}, %s ${minutes}, y %s ${seconds}.", "ban_notice": "⚠️ Has sido baneado de este servidor! ⚠️\nBaneado el: %s\nRazón: %s\n\n%s", "userid_is_active": "Tu userId (%s), ya está activo.", "no_license": "No pudimos verificar tu licencia de juego.", "server_restarting": "El servidor está a punto de reiniciar. No puedes unirte en estos momentos." } ================================================ FILE: locales/et.json ================================================ { "create_character": "Loo uus karakter", "firstname": "Eesnimi", "lastname": "Perekonnanimi", "gender": "Sugu", "male": "Mees", "female": "Naine", "non_binary": "Mittebinaarne", "date_of_birth": "Sünnikuupäev", "withdraw": "Väljamakse", "deposit": "Sissemakse", "transfer": "Pangaülekanne", "invoice_payment": "Arve tasumine" } ================================================ FILE: locales/fr.json ================================================ { "create_character": "Créer un personnage", "firstname": "Prénom", "lastname": "Nom", "gender": "Sexe", "male": "Homme", "female": "Femme", "non_binary": "Non-Binaire", "date_of_birth": "Date de naissance", "withdraw": "Retrait", "deposit": "Dépôt", "transfer": "Virement", "invoice_payment": "Facture", "hours": "heures", "minutes": "minutes", "seconds": "secondes", "ban_indefinite": "Ce ban n'expirera pas.", "ban_expires_in": "Ce ban expire dans %s ${hours}, %s ${minutes}, et %s ${seconds}.", "ban_notice": "⚠️ Vous avez été banni de ce serveur ! ⚠️\nBanni le : %s\nRaison : %s\n\n%s", "userid_is_active": "Votre userId (%s), est déjà actif.", "no_license": "Nous n'avons pas pu vérifier votre licence de jeu.", "server_restarting": "Le serveur est en train de redémarrer. Vous ne pouvez pas vous connecter pour le moment." } ================================================ FILE: locales/hu.json ================================================ { "create_character": "Új karakter létrehozása", "firstname": "Keresztnév", "lastname": "Vezetéknév", "gender": "Neme", "male": "Férfi", "female": "Nő", "non_binary": "Nem-bináris", "date_of_birth": "Születési dátum", "withdraw": "Felvevés", "deposit": "Befizetés", "transfer": "Banki átutalás", "invoice_payment": "Számla kifizetése", "hours": "Óra", "minutes": "Perc", "seconds": "Másodperc", "ban_indefinite": "Ez a kitiltás nem fog lejárni.", "ban_expires_in": "A tiltás le fog járni %s ${hours}, %s ${minutes}, és %s ${seconds} múlva.", "ban_notice": "⚠️ Ki lettél tiltva erről a szerverről! ⚠️\nEkkor: %s\nIndok: %s\n\n%s", "userid_is_active": "A te userId-d (%s), éppen használatban van.", "no_license": "Nem bírtuk ellenőrizni a játék liszenszedet.", "server_restarting": "A szerver mindjárt újra indul. Ezalatt az idő alatt nem lehet csatlakozni." } ================================================ FILE: locales/it.json ================================================ { "create_character": "Crea un nuovo personaggio", "firstname": "Nome", "lastname": "Cognome", "gender": "Genere", "male": "Maschio", "female": "Femmina", "non_binary": "Non-Binario", "date_of_birth": "Data di nascita", "withdraw": "Preleva", "deposit": "Deposita", "transfer": "Bonifico bancario", "invoice_payment": "Pagamento fattura" } ================================================ FILE: locales/jp.json ================================================ { "create_character": "新しいキャラクターを作成", "firstname": "名前", "lastname": "苗字", "gender": "性別", "male": "男性", "female": "女性", "non_binary": "ノンバイナリー", "date_of_birth": "生年月日", "withdraw": "引き出し", "deposit": "預金", "transfer": "銀行振込", "invoice_payment": "請求書の支払い" } ================================================ FILE: locales/lt.json ================================================ { "create_character": "Sukurti naują veikėją", "firstname": "Vardas", "lastname": "Pavardė", "gender": "Lytis", "male": "Vyras", "female": "Moteris", "non_binary": "Nebinarinis lytiškumas", "date_of_birth": "Gimimo data", "withdraw": "Išėmimas", "deposit": "Įmokėjimas", "transfer": "Banko pervedimas", "invoice_payment": "Sąskaitos apmokėjimas", "hours": "valandos", "minutes": "minutes", "seconds": "sekundes", "ban_indefinite": "Šis užblokavimas niekada nesibaigs.", "ban_expires_in": "Užblokavimas baigsis už %s ${hours}, %s ${minutes}, ir %s ${seconds}.", "ban_notice": "⚠️ Jūs buvote užblokuotas iš šito serverio! ⚠️\nAtsakingas asmuo: %s\nKodėl: %s\n\n%s", "userid_is_active": "Jūsų userId (%s), yra jau aktyvus.", "no_license": "Negalėjome patvirtinti jūsų žaidimo licenzijos.", "server_restarting": "Serveris restartuojasi. Negalima dabar prisijungti." } ================================================ FILE: locales/nl.json ================================================ { "create_character": "Maak een nieuw karakter", "firstname": "Voornaam", "lastname": "Achternaam", "gender": "Geslacht", "male": "Man", "female": "Vrouw", "non_binary": "Non-binair", "date_of_birth": "Geboortedatum", "withdraw": "Opnemen", "deposit": "Storten", "transfer": "Overschrijving", "invoice_payment": "Factuurbetaling" } ================================================ FILE: locales/no.json ================================================ { "create_character": "Opprett ny karakter", "firstname": "Fornavn", "lastname": "Etternavn", "gender": "Kjønn", "male": "Mann", "female": "Kvinne", "non_binary": "Ikke-binær", "date_of_birth": "Fødselsdato", "withdraw": "Ta ut", "deposit": "Sett inn", "transfer": "Bankoverføring", "invoice_payment": "Fakturabetaling", "hours": "timer", "minutes": "minutter", "seconds": "sekunder", "ban_indefinite": "Denne utestengelsen utløper ikke.", "ban_expires_in": "Utestengelsen utløper om %s ${hours}, %s ${minutes} og %s ${seconds}.", "ban_notice": "⚠️ Du er utestengt fra denne serveren! ⚠️\nUtestengt: %s\nÅrsak: %s\n\n%s", "userid_is_active": "Din bruker (%s) er allerede aktiv.", "no_license": "Vi kunne ikke verifisere spill-lisensen din.", "server_restarting": "Serveren er i ferd med å starte på nytt. Du kan ikke koble til nå." } ================================================ FILE: locales/pl.json ================================================ { "create_character": "Stwórz nową postać", "firstname": "Imię", "lastname": "Nazwisko", "gender": "Płeć", "male": "Mężczyzna", "female": "Kobieta", "non_binary": "Niebinarny/a", "date_of_birth": "Data urodzenia", "withdraw": "Wypłać", "deposit": "Wpłać", "transfer": "Przelew Bankowy", "invoice_payment": "Opłata Faktury" } ================================================ FILE: locales/ro.json ================================================ { "create_character": "Creează un nou caracter", "firstname": "Prenume", "lastname": "Nume", "gender": "Gen", "male": "Masculin", "female": "Feminin", "non_binary": "Non-Binar", "date_of_birth": "Dată de naștere", "withdraw": "Retragere", "deposit": "Depunere", "transfer": "Transfer bancar", "invoice_payment": "Plată factură", "hours": "ore", "minutes": "minute", "seconds": "secunde", "ban_indefinite": "Acest ban nu va expira.", "ban_expires_in": "Banul expiră în %s ${hours}, %s ${minutes}, și %s ${seconds}.", "ban_notice": "⚠️ Ai fost banat de pe server! ⚠️\nBan emis la: %s\nMotiv: %s\n\n%s", "userid_is_active": "ID-ul tău de utilizator (%s), este deja activ.", "no_license": "Nu am putut să-ți verificăm licența jocului tău.", "server_restarting": "Serverul este pe cale să se repornească. Nu poți să te conectezi în acest moment." } ================================================ FILE: locales/ru.json ================================================ { "create_character": "Создать нового персонажа", "firstname": "Имя", "lastname": "Фамилия", "gender": "Пол", "male": "Мужской", "female": "Женский", "non_binary": "Небинарный", "date_of_birth": "Дата рождения", "withdraw": "Вывести", "deposit": "Внести", "transfer": "Банковский перевод", "invoice_payment": "Оплата по счету" } ================================================ FILE: locales/sk.json ================================================ { "create_character": "Vytvoriť novú postavu", "firstname": "Meno", "lastname": "Priezvisko", "gender": "Pohlavie", "male": "Muž", "female": "Žena", "non_binary": "Ne-binárne", "date_of_birth": "Dátum narodenia", "withdraw": "Výber", "deposit": "Vklad", "transfer": "Bankový prevod", "invoice_payment": "Úhrada faktúry" } ================================================ FILE: locales/tr.json ================================================ { "create_character": "Yeni bir karakter oluştur", "firstname": "Ad", "lastname": "Soyad", "gender": "Cinsiyet", "male": "Erkek", "female": "Kadın", "non_binary": "İkili Olmayan", "date_of_birth": "Doğum Tarihi", "withdraw": "Para çek", "deposit": "Para yatır", "transfer": "Banka transferi", "invoice_payment": "Fatura ödemesi", "hours": "saat", "minutes": "dakika", "seconds": "saniye", "ban_indefinite": "Bu yasak süresizdir.", "ban_expires_in": "Yasağın süresi %s ${hours}, %s ${minutes} ve %s ${seconds} sonra sona erecek.", "ban_notice": "⚠️ Bu sunucudan yasaklandın! ⚠️\nYasak tarihi: %s\nSebep: %s\n\n%s", "userid_is_active": "Kullanıcı ID'niz (%s) zaten aktif.", "no_license": "Oyun lisansınızı doğrulayamadık.", "server_restarting": "Sunucu yeniden başlatılmak üzere. Bu anda katılamazsınız." } ================================================ FILE: locales/zh-cn.json ================================================ { "create_character": "创建新角色", "firstname": "名字", "lastname": "姓氏", "gender": "性别", "male": "男", "female": "女", "non_binary": "非二元性别", "date_of_birth": "出生日期", "withdraw": "取款", "deposit": "存款", "transfer": "银行转账", "invoice_payment": "账单付款" } ================================================ FILE: locales/zh-tw.json ================================================ { "create_character": "創建新角色", "firstname": "名字", "lastname": "姓氏", "gender": "性別", "male": "男", "female": "女", "non_binary": "非二元性別", "date_of_birth": "出生日期", "withdraw": "取款", "deposit": "存款", "transfer": "銀行轉賬", "invoice_payment": "賬單付款" } ================================================ FILE: package.json ================================================ { "name": "@overextended/ox_core", "author": "Overextended", "version": "1.5.10", "license": "LGPL-3.0-or-later", "description": "A modern FiveM framework.", "type": "module", "files": [ "./tsconfig.json", "./package/**/*.js", "./package/**/*.d.ts" ], "exports": { ".": "./package/lib/index.js", "./client": "./package/lib/client/index.js", "./server": "./package/lib/server/index.js" }, "scripts": { "build": "bun run build.js", "watch": "bun run build.js --watch", "format": "bun run biome format --write", "lint": "bun run biome lint --write" }, "keywords": [ "fivem", "ox_core", "ox", "overextended", "overextended" ], "repository": { "type": "git", "url": "git+https://github.com/overextended/ox_core.git" }, "bugs": "https://github.com/overextended/ox_core/issues", "devDependencies": { "@citizenfx/client": "latest", "@citizenfx/server": "latest", "@overextended/fx-utils": "^0.0.6", "@types/node": "^22.13.10", "esbuild": "^0.23.1", "tsc-alias": "^1.8.11", "typescript": "^5.8.2" }, "dependencies": { "@biomejs/biome": "^1.9.4", "@nativewrappers/fivem": "^0.0.86", "@nativewrappers/server": "^0.0.86", "@overextended/ox_lib": "^3.32.6", "mariadb": "^3.4.1" }, "engines": { "node": ">=22.9.0" } } ================================================ FILE: server/accounts/class.ts ================================================ import { ClassInterface } from 'classInterface'; import { OxPlayer } from 'player/class'; import { GetCharIdFromStateId } from 'player/db'; import type { Dict, OxAccountMetadata, OxAccountRole, OxAccountPermissions, OxCreateInvoice } from 'types'; import { SelectAccount, UpdateBalance, PerformTransaction, DepositMoney, WithdrawMoney, DeleteAccount, SelectAccountRole, UpdateAccountAccess, SetAccountType, CreateInvoice, } from './db'; import { CanPerformAction } from './roles'; interface UpdateAccountBalance { amount: number; message?: string; } interface RemoveAccountBalance extends UpdateAccountBalance { overdraw?: boolean; } interface TransferAccountBalance { toId: number; amount: number; overdraw?: boolean; message?: string; note?: string; actorId?: number; } export class OxAccount extends ClassInterface { protected static members: Dict = {}; static async get(accountId: number) { if (accountId in this.members) this.members[accountId]; const validAccount = await SelectAccount(accountId); if (!validAccount) throw new Error(`No account exists with accountId ${accountId}.`); return new OxAccount(accountId); } static getAll() { return this.members; } constructor(public accountId: number) { super(); OxAccount.add(accountId, this); } /** * Get the value of specific key(s) from account metadata. */ async get(key: T): Promise; async get(keys: T[]): Promise>; async get( keys: T | T[], ): Promise | null> { const metadata = await SelectAccount(this.accountId); if (!metadata) return null; if (Array.isArray(keys)) return keys.reduce( (acc, key) => { acc[key] = metadata[key]; return acc; }, {} as Pick, ); return metadata[keys]; } /** * Add funds to the account. */ async addBalance({ amount, message }: UpdateAccountBalance) { return UpdateBalance(this.accountId, amount, 'add', false, message); } /** * Remove funds from the account. */ async removeBalance({ amount, overdraw = false, message }: RemoveAccountBalance) { return UpdateBalance(this.accountId, amount, 'remove', overdraw, message); } /** * Transfer funds to another account. */ async transferBalance({ toId, amount, overdraw = false, message, note, actorId }: TransferAccountBalance) { return PerformTransaction(this.accountId, toId, amount, overdraw, message, note, actorId); } /** * Deposit money into the account. */ async depositMoney(playerId: number, amount: number, message?: string, note?: string) { return DepositMoney(playerId, this.accountId, amount, message, note); } /** * Withdraw money from the account. */ async withdrawMoney(playerId: number, amount: number, message?: string, note?: string) { return WithdrawMoney(playerId, this.accountId, amount, message, note); } /** * Mark the account as deleted. It can no longer be accessed, but remains in the database. */ async deleteAccount() { return DeleteAccount(this.accountId); } /** * Get the account access role of a character by charId or stateId. */ async getCharacterRole(id: number | string) { const charId = typeof id === 'string' ? await GetCharIdFromStateId(id) : id; return charId ? SelectAccountRole(this.accountId, charId) : null; } /** * Set the account access role of a character by charId or stateId. */ async setCharacterRole(id: number | string, role?: OxAccountRole) { const charId = typeof id === 'string' ? await GetCharIdFromStateId(id) : id; return charId && UpdateAccountAccess(this.accountId, charId, role); } /** * Checks if a player's active character has permission to perform an action on the account. */ async playerHasPermission(playerId: number, permission: keyof OxAccountPermissions) { const player = OxPlayer.get(playerId); if (!player?.charId) return false; const role = await this.getCharacterRole(player.charId); return await CanPerformAction(player, this.accountId, role, permission); } /** * Set the account as shared, allowing permissions to be assigned to other characters. */ async setShared() { return SetAccountType(this.accountId, 'shared'); } /** * Create an unpaid invoice on the account. */ async createInvoice(data: Omit) { const invoice = { fromAccount: this.accountId, ...data, }; return await CreateInvoice(invoice); } } OxAccount.init(); ================================================ FILE: server/accounts/db.ts ================================================ import { getRandomInt } from "@overextended/ox_lib"; import { OxAccount } from "accounts/class"; import { type Connection, GetConnection, db } from "db"; import { OxPlayer } from "player/class"; import type { OxAccountMetadata, OxAccountRole, OxAccountUserMetadata, OxCreateInvoice } from "types"; import locales from "../../common/locales"; import { CanPerformAction } from "./roles"; const addBalance = "UPDATE accounts SET balance = balance + ? WHERE id = ?"; const removeBalance = "UPDATE accounts SET balance = balance - ? WHERE id = ?"; const safeRemoveBalance = `${removeBalance} AND (balance - ?) >= 0`; const addTransaction = "INSERT INTO accounts_transactions (actorId, fromId, toId, amount, message, note, fromBalance, toBalance) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"; const getBalance = "SELECT balance FROM accounts WHERE id = ?"; const doesAccountExist = "SELECT 1 FROM accounts WHERE id = ?"; async function GenerateAccountId(conn: Connection) { const date = new Date(); const year = date.getFullYear().toString().slice(-2); const month = ("0" + (date.getMonth() + 1)).slice(-2); const baseId = Number(year + month) * 1e3; while (true) { const accountId = getRandomInt(10, 99) * 1e7 + baseId + getRandomInt(0, 9999); const existingId = await conn.scalar(doesAccountExist, [accountId]); if (!existingId) return accountId; } } export async function UpdateBalance( accountId: number, amount: number, action: "add" | "remove", overdraw: boolean, message?: string, note?: string, actorId?: number, ): Promise<{ success: boolean; message?: string }> { amount = Number.parseInt(String(amount)); if (isNaN(amount)) return { success: false, message: "amount_not_number" }; if (amount <= 0) return { success: false, message: "invalid_amount" }; using conn = await GetConnection(); const balance = await conn.scalar(getBalance, [accountId]); if (balance === null) return { success: false, message: "no_balance", }; const addAction = action === "add"; const success = addAction ? await conn.update(addBalance, [amount, accountId]) : await conn.update(overdraw ? removeBalance : safeRemoveBalance, [amount, accountId, amount]); if (!success) return { success: false, message: "insufficient_balance", }; !message && (message = locales(action === "add" ? "deposit" : "withdraw")); const didUpdate = (await conn.update(addTransaction, [ actorId || null, addAction ? null : accountId, addAction ? accountId : null, amount, message, note, addAction ? null : balance - amount, addAction ? balance + amount : null, ])) === 1; if (!didUpdate) return { success: false, message: "something_went_wrong", }; emit("ox:updatedBalance", { accountId, amount, action }); return { success: true }; } export async function PerformTransaction( fromId: number, toId: number, amount: number, overdraw: boolean, message?: string, note?: string, actorId?: number, ): Promise<{ success: boolean; message?: string }> { amount = Number.parseInt(String(amount)); if (isNaN(amount)) return { success: false, message: "amount_not_number" }; if (amount <= 0) return { success: false, message: "invalid_amount" }; using conn = await GetConnection(); const fromBalance = await conn.scalar(getBalance, [fromId]); const toBalance = await conn.scalar(getBalance, [toId]); if (fromBalance === null || toBalance === null) return { success: false, message: "no_balance" }; await conn.beginTransaction(); try { const query = overdraw ? removeBalance : safeRemoveBalance; const values = [amount, fromId]; if (!overdraw) values.push(amount); const removedBalance = await conn.update(query, values); const addedBalance = removedBalance && (await conn.update(addBalance, [amount, toId])); if (addedBalance) { await conn.execute(addTransaction, [ actorId, fromId, toId, amount, message ?? locales("transfer"), note, fromBalance - amount, toBalance + amount, ]); emit("ox:transferredMoney", { fromId, toId, amount }); return { success: true }; } } catch (e) { console.error(`Failed to transfer $${amount} from account<${fromId}> to account<${toId}>`); console.log(e); } conn.rollback(); return { success: false, message: "something_went_wrong" }; } export async function SelectAccounts(column: "owner" | "group" | "id", id: number | string) { return db.execute(`SELECT * FROM accounts WHERE \`${column}\` = ?`, [id]); } export async function SelectDefaultAccountId(column: "owner" | "group" | "id", id: number | string) { return await db.column(`SELECT id FROM accounts WHERE \`${column}\` = ? AND isDefault = 1`, [id]); } export async function SelectAccount(id: number) { return db.single(await SelectAccounts("id", id)); } export async function IsAccountIdAvailable(id: number) { return !(await db.exists(doesAccountExist, [id])); } export async function CreateNewAccount(owner: string | number, label: string, isDefault?: boolean) { using conn = await GetConnection(); const accountId = await GenerateAccountId(conn); const column = typeof owner === "string" ? "group" : "owner"; const result = await conn.update( `INSERT INTO accounts (id, label, \`${column}\`, type, isDefault) VALUES (?, ?, ?, ?, ?)`, [accountId, label, owner, column === "group" ? "group" : "personal", isDefault || 0], ); if (result && column === "owner") conn.execute("INSERT INTO accounts_access (accountId, charId, role) VALUE (?, ?, ?)", [accountId, owner, "owner"]); return accountId; } export async function DeleteAccount(accountId: number): Promise<{ success: boolean; message?: string }> { const success = await db.update(`UPDATE accounts SET \`type\` = 'inactive' WHERE id = ?`, [accountId]); if (!success) return { success: false, message: "something_went_wrong", }; return { success: true }; } const selectAccountRole = "SELECT role FROM accounts_access WHERE accountId = ? AND charId = ?"; export function SelectAccountRole(accountId: number, charId: number) { return db.column(selectAccountRole, [accountId, charId]); } export async function DepositMoney( playerId: number, accountId: number, amount: number, message?: string, note?: string, ): Promise<{ success: boolean; message?: string }> { amount = Number.parseInt(String(amount)); if (isNaN(amount)) return { success: false, message: "amount_not_number" }; if (amount <= 0) return { success: false, message: "invalid_amount" }; const player = OxPlayer.get(playerId); if (!player?.charId) return { success: false, message: "no_charid", }; const money = exports.ox_inventory.GetItemCount(playerId, "money"); if (amount > money) return { success: false, message: "insufficient_funds" }; using conn = await GetConnection(); const balance = await conn.scalar(getBalance, [accountId]); if (balance === null) return { success: false, message: "no_balance" }; const role = await conn.scalar(selectAccountRole, [accountId, player.charId]); if (!(await CanPerformAction(player, accountId, role, "deposit"))) return { success: false, message: "no_access" }; await conn.beginTransaction(); const affectedRows = await conn.update(addBalance, [amount, accountId]); if (!affectedRows || !exports.ox_inventory.RemoveItem(playerId, "money", amount)) { conn.rollback(); return { success: false, message: "something_went_wrong", }; } await conn.execute(addTransaction, [ player.charId, null, accountId, amount, message ?? locales("deposit"), note, null, balance + amount, ]); emit("ox:depositedMoney", { playerId, accountId, amount }); return { success: true, }; } export async function WithdrawMoney( playerId: number, accountId: number, amount: number, message?: string, note?: string, ): Promise<{ success: boolean; message?: string }> { amount = Number.parseInt(String(amount)); if (isNaN(amount)) return { success: false, message: "amount_not_number" }; if (amount <= 0) return { success: false, message: "invalid_amount" }; const player = OxPlayer.get(playerId); if (!player?.charId) return { success: false, message: "no_charId" }; using conn = await GetConnection(); const role = await conn.scalar(selectAccountRole, [accountId, player.charId]); if (!(await CanPerformAction(player, accountId, role, "withdraw"))) return { success: false, message: "no_access" }; const balance = await conn.scalar(getBalance, [accountId]); if (balance === null) return { success: false, message: "no_balance" }; await conn.beginTransaction(); const affectedRows = await conn.update(safeRemoveBalance, [amount, accountId, amount]); if (!affectedRows || !exports.ox_inventory.AddItem(playerId, "money", amount)) { conn.rollback(); return { success: false, message: "something_went_wrong", }; } await conn.execute(addTransaction, [ player.charId, accountId, null, amount, message ?? locales("withdraw"), note, balance - amount, null, ]); emit("ox:withdrewMoney", { playerId, accountId, amount }); return { success: true }; } export async function UpdateAccountAccess( accountId: number, id: number, role?: string, ): Promise<{ success: boolean; message?: string }> { if (!role) { const success = await db.update("DELETE FROM accounts_access WHERE accountId = ? AND charId = ?", [accountId, id]); if (!success) return { success: false, message: "something_went_wrong" }; return { success: true }; } const success = await db.update( "INSERT INTO accounts_access (accountId, charId, role) VALUE (?, ?, ?) ON DUPLICATE KEY UPDATE role = VALUES(role)", [accountId, id, role], ); if (!success) return { success: false, message: "something_went_wrong" }; return { success: true }; } export async function UpdateInvoice( invoiceId: number, charId: number, ): Promise<{ success: boolean; message?: string }> { const player = OxPlayer.getFromCharId(charId); if (!player?.charId) return { success: false, message: "no_charId" }; const invoice = await db.row<{ amount: number; payerId?: number; fromAccount: number; toAccount: number }>( "SELECT * FROM `accounts_invoices` WHERE `id` = ?", [invoiceId], ); if (!invoice) return { success: false, message: "no_invoice" }; if (invoice.payerId) return { success: false, message: "invoice_paid" }; const account = await OxAccount.get(invoice.toAccount); const hasPermission = await account?.playerHasPermission(player.source as number, "payInvoice"); if (!hasPermission) return { success: false, message: "no_permission" }; const updateReceiver = await UpdateBalance( invoice.toAccount, invoice.amount, "remove", false, locales("invoice_payment"), undefined, charId, ); if (!updateReceiver.success) return { success: false, message: "no_balance" }; const updateSender = await UpdateBalance( invoice.fromAccount, invoice.amount, "add", false, locales("invoice_payment"), undefined, charId, ); if (!updateSender.success) return { success: false, message: "no_balance" }; const invoiceUpdated = await db.update("UPDATE `accounts_invoices` SET `payerId` = ?, `paidAt` = ? WHERE `id` = ?", [ player.charId, new Date(), invoiceId, ]); if (!invoiceUpdated) return { success: false, message: "invoice_not_updated", }; invoice.payerId = charId; emit("ox:invoicePaid", invoice); return { success: true, }; } export async function CreateInvoice({ actorId, fromAccount, toAccount, amount, message, dueDate, }: OxCreateInvoice): Promise<{ success: boolean; message?: string }> { if (isNaN(amount)) return { success: false, message: "amount_not_number" }; if (amount <= 0) return { success: false, message: "invalid_amount" }; if (actorId) { const player = OxPlayer.getFromCharId(actorId); if (!player?.charId) return { success: false, message: "no_charid" }; const account = await OxAccount.get(fromAccount); const hasPermission = await account?.playerHasPermission(player.source as number, "sendInvoice"); if (!hasPermission) return { success: false, message: "no_permission" }; } const targetAccount = await OxAccount.get(toAccount); if (!targetAccount) return { success: false, message: "no_target_account" }; const success = await db.insert( "INSERT INTO accounts_invoices (`actorId`, `fromAccount`, `toAccount`, `amount`, `message`, `dueDate`) VALUES (?, ?, ?, ?, ?, ?)", [actorId, fromAccount, toAccount, amount, message, dueDate], ); if (!success) return { success: false, message: "invoice_insert_error" }; return { success: true }; } export async function DeleteInvoice(invoiceId: number): Promise<{ success: boolean; message?: string }> { const success = await db.update("DELETE FROM `accounts_invoices` WHERE `id` = ?", [invoiceId]); if (!success) return { success: false, message: "invoice_delete_error" }; return { success: true }; } export async function SetAccountType(accountId: number, type: string): Promise<{ success: boolean; message?: string }> { const success = await db.update("UPDATE `accounts` SET `type` = ? WHERE `id` = ?", [type, accountId]); if (!success) return { success: false, message: "update_account_error" }; return { success: true }; } ================================================ FILE: server/accounts/index.ts ================================================ import { CreateNewAccount, SelectDefaultAccountId, UpdateInvoice, DeleteInvoice } from './db'; import { GetCharIdFromStateId } from 'player/db'; import { OxAccount } from './class'; setInterval(() => { const accounts = OxAccount.getAll(); for (const accountId in accounts) { const account = accounts[accountId]; OxAccount.remove(account.accountId); } }, 60000); /** * Return the default account for a character. * @param id The charId or stateId used to identify the character. */ export async function GetCharacterAccount(id: number | string) { const charId = typeof id === 'string' ? await GetCharIdFromStateId(id) : id; const accountId = charId && (await SelectDefaultAccountId('owner', charId)); return accountId ? OxAccount.get(accountId) : null; } /** * Return the default account for a group. */ export async function GetGroupAccount(groupName: string) { const accountId = await SelectDefaultAccountId('group', groupName); return accountId ? OxAccount.get(accountId) : null; } export async function CreateAccount(owner: number | string, label: string) { const accountId = await CreateNewAccount(owner, label); return OxAccount.get(accountId); } export function PayAccountInvoice(invoiceId: number, charId: number) { return UpdateInvoice(invoiceId, charId); } export function DeleteAccountInvoice(invoiceId: number) { return DeleteInvoice(invoiceId); } exports('GetCharacterAccount', GetCharacterAccount); exports('GetGroupAccount', GetGroupAccount); exports('CreateAccount', CreateAccount); exports('PayAccountInvoice', PayAccountInvoice); exports('DeleteAccountInvoice', DeleteAccountInvoice); ================================================ FILE: server/accounts/roles.ts ================================================ import { db } from 'db'; import type { OxAccountPermissions, OxAccountRole } from 'types'; import { SelectAccount } from './db'; import { GetGroup } from 'groups'; import type { OxPlayer } from 'player/class'; type OxAccountMetadataRow = OxAccountPermissions & { id?: number; name?: OxAccountRole }; const accountRoles = {} as Record; const blacklistedGroupActions = { addUser: true, removeUser: true, manageUser: true, transferOwnership: true, manageAccount: true, closeAccount: true, } as Record; export function CheckRolePermission(roleName: OxAccountRole | null, permission: keyof OxAccountPermissions) { if (!roleName) return; return accountRoles?.[roleName.toLowerCase()]?.[permission]; } export async function CanPerformAction( player: OxPlayer, accountId: number, role: OxAccountRole | null, action: keyof OxAccountPermissions, ) { if (CheckRolePermission(role, action)) return true; const groupName = (await SelectAccount(accountId))?.group; if (groupName) { if (action in blacklistedGroupActions) return false; const group = GetGroup(groupName); const groupRole = group.accountRoles[player.getGroup(groupName)]; if (CheckRolePermission(groupRole, action)) return true; } return false; } async function LoadRoles() { const roles = await db.execute('SELECT * FROM account_roles'); if (!roles[0]) return; roles.forEach((role) => { const roleName = (role.name as string).toLowerCase() as OxAccountRole; delete role.name; delete role.id; accountRoles[roleName] = role; GlobalState[`accountRole.${roleName}`] = role; }); GlobalState['accountRoles'] = Object.keys(accountRoles); } setImmediate(LoadRoles); ================================================ FILE: server/bridge/index.ts ================================================ import './npwd'; import './ox_inventory'; ================================================ FILE: server/bridge/npwd.ts ================================================ import { OnPlayerLoaded, OnPlayerLogout } from '../player/events'; SetConvar('npwd:useResourceIntegration', 'true'); SetConvar( 'npwd:database', JSON.stringify({ playerTable: 'characters', identifierColumn: 'charId', phoneNumberColumn: 'phoneNumber', }), ); OnPlayerLoaded('npwd', (player) => { exports.npwd.newPlayer({ source: player.source, identifier: player.charId, phoneNumber: player.get('phoneNumber'), firstname: player.get('firstName'), lastname: player.get('lastName'), }); }); OnPlayerLogout((player) => exports.npwd.unloadPlayer(player.source)); export function GeneratePhoneNumber() { try { return exports.npwd.generatePhoneNumber(); } catch (e) {} } ================================================ FILE: server/bridge/ox_inventory.ts ================================================ import { OnPlayerLoaded } from '../player/events'; SetConvarReplicated('inventory:framework', 'ox'); SetConvarReplicated('inventory:trimplate ', 'false'); OnPlayerLoaded('ox_inventory', (player) => { exports.ox_inventory.setPlayerInventory({ source: player.source, identifier: player.charId, name: `${player.get('firstName')} ${player.get('lastName')}`, sex: player.get('gender'), dateofbirth: player.get('dateOfBirth'), groups: {}, }); }); ================================================ FILE: server/classInterface.ts ================================================ import type { Dict } from 'types'; export class ClassInterface { protected static members: Dict; protected static keys?: Dict>; protected static callableMethods: Dict; static isCallValid(method: string, id: string | number, member: any) { if (!member) return console.error(`cannot call method ${method} on ${this.name}<${id}> (invalid id)`); if (!member[method]) return console.error(`cannot call method ${method} on ${this.name}<${id}> (method does not exist)`); if (!this.callableMethods[method]) return console.error(`cannot call method ${method} on ${this.name}<${id}> (method is not exported)`); return true; } /** Exports several class methods and makes non-private methods callable from external resources. */ static init() { const classMethods = Object.getOwnPropertyNames(this.prototype); if (classMethods) { this.callableMethods = {}; classMethods.forEach((method) => { if (method !== 'constructor') this.callableMethods[method] = true; }); } const name = this.name; const expName = this.name.replace('Ox', ''); // e.g. exports.ox_core.GetPlayer exports(`Get${expName}`, (id: string | number) => this.get(id)); // e.g. exports.ox_core.GetPlayerCalls exports(`Get${expName}Calls`, () => this.callableMethods); // e.g. exports.ox_core.CallPlayer exports(`Call${expName}`, (id: string | number, method: string, ...args: any[]) => { // Maintain backwards compatibility with OxVehicle indexed by entityId.. const member = expName === 'Vehicle' && typeof id === 'number' ? this.keys?.entity[id] : this.get(id); if (member instanceof Promise) { return member.then((resolvedMember) => { if (!this.isCallValid(method, id, resolvedMember)) return; return resolvedMember.call(method, ...args); }); } if (!this.isCallValid(method, id, member)) return; return member.call(method, ...args); }); DEV: console.info(`Instantiated ClassInterface<${name}> and exports`); return this; } call(method: string, ...args: any) { return (this as any)[method](...args); } /** Get a member of the class by its id. */ static get(id: string | number) { return this.members[id]; } /** Get all members of the class. */ static getAll() { return this.members; } /** Adds a new member of the class to its registries. */ static add(id: string | number, member: any) { if (this.members[id]) return false; this.members[id] = member; if (this.keys) { Object.entries(this.keys).forEach(([key, obj]) => { if (member[key]) { obj[member[key]] = member; } }); } return true; } /** Removes a member of the class from its registries. */ static remove(id: string | number) { const member = this.members[id]; if (!member) return false; if (this.keys) { Object.entries(this.keys).forEach(([key, obj]) => { if (member[key]) { delete obj[member[key]]; } }); } delete this.members[id]; return true; } } ================================================ FILE: server/commands.ts ================================================ import { addCommand } from "@overextended/ox_lib/server"; import { OxPlayer } from "player/class"; import { OxVehicle } from "vehicle/class"; addCommand( "saveall", async () => { OxPlayer.saveAll(); OxVehicle.saveAll(); }, { help: "Saves all players and vehicles to the database.", restricted: "group.admin", }, ); ================================================ FILE: server/config.ts ================================================ import { DEBUG } from 'config'; export * from '../common/config'; export const CREATE_DEFAULT_ACCOUNT = GetConvarInt('ox:createDefaultAccount', 1) === 1; if (DEBUG) SetConvar('ox:callbackTimeout', '1200000'); ================================================ FILE: server/db/config.ts ================================================ import type { PoolConfig } from 'mariadb'; import type { Dict } from 'types'; export function GetConfig(): PoolConfig { const connectionString = GetConvar('mysql_connection_string', 'mysql://root@localhost').replace( 'mysql://', 'mariadb://', ); function parseUri() { const splitMatchGroups = connectionString.match( /^(?:([^:\/?#.]+):)?(?:\/\/(?:([^\/?#]*)@)?([\w\d\-\u0100-\uffff.%]*)(?::([0-9]+))?)?([^?#]+)?(?:\?([^#]*))?$/, ) as RegExpMatchArray; if (!splitMatchGroups) throw new Error(`mysql_connection_string structure was invalid (${connectionString})`); const authTarget = splitMatchGroups[2] ? splitMatchGroups[2].split(':') : []; return { user: authTarget[0] || undefined, password: authTarget[1] || undefined, host: splitMatchGroups[3], port: Number.parseInt(splitMatchGroups[4]), database: splitMatchGroups[5].replace(/^\/+/, ''), ...(splitMatchGroups[6] && splitMatchGroups[6].split('&').reduce>((connectionInfo, parameter) => { const [key, value] = parameter.split('='); connectionInfo[key] = value; return connectionInfo; }, {})), }; } const options: any = connectionString.includes('mariadb://') ? parseUri() : connectionString .replace(/(?:host(?:name)|ip|server|data\s?source|addr(?:ess)?)=/gi, 'host=') .replace(/(?:user\s?(?:id|name)?|uid)=/gi, 'user=') .replace(/(?:pwd|pass)=/gi, 'password=') .replace(/(?:db)=/gi, 'database=') .split(';') .reduce((connectionInfo: any, parameter: any) => { const [key, value] = parameter.split('='); if (key) connectionInfo[key] = value; return connectionInfo; }, {}); if (typeof options.ssl === 'string') { try { options.ssl = JSON.parse(options.ssl); } catch (err) { console.log(`^3Failed to parse ssl in configuration (${err})!^0`); } } return { connectTimeout: 60000, connectionLimit: 5, acquireTimeout: 30000, ...options, namedPlaceholders: false, multipleStatements: true, dateStrings: true, insertIdAsNumber: true, decimalAsNumber: true, autoJsonMap: true, jsonStrings: false, }; } ================================================ FILE: server/db/index.ts ================================================ import { waitFor } from "@overextended/ox_lib"; import { pool } from "./pool"; import type { Dict } from "types"; import type { PoolConnection, QueryOptions } from "mariadb"; (Symbol as any).dispose ??= Symbol("Symbol.dispose"); export interface MySqlRow | undefined> { [column: string]: T; } export interface OkPacket { affectedRows: number; insertId: number; warningStatus: any; } function getScalar(resp: T[] | null) { if (resp && resp[0]) for (const key in resp[0]) return resp[0][key] as T; return null; } function getRow(resp: T[] | null) { return resp ? resp[0] : null; } export class Connection { public transaction?: boolean; constructor(public connection: PoolConnection) {} async execute(query: string | QueryOptions, values?: any[]) { return (await this.connection.execute(query, values)) as T; } async query(query: string | QueryOptions, values?: any[]) { return (await this.connection.query(query, values)) as T; } async scalar(query: string | QueryOptions, values?: any[]) { return getScalar(await this.execute(query, values)) as T | null; } async row(query: string | QueryOptions, values?: any[]) { return getRow(await this.execute(query, values)) as T | null; } async insert(query: string | QueryOptions, values?: any[]) { return (await this.execute(query, values))?.insertId; } async update(query: string | QueryOptions, values?: any[]) { return (await this.execute(query, values))?.affectedRows; } batch(query: string | QueryOptions, values?: any[]) { return this.connection.batch(query, values); } beginTransaction() { this.transaction = true; return this.connection.beginTransaction(); } rollback() { delete this.transaction; return this.connection.rollback(); } commit() { delete this.transaction; return this.connection.commit(); } [Symbol.dispose]() { if (this.transaction) this.commit(); this.connection.release(); } } export async function GetConnection() { while (!pool) { await waitFor(() => pool, "Failed to acquire database connection.", 30000); } return new Connection(await pool.getConnection()); } export const db = { async query(query: string | QueryOptions, values?: any[]) { using conn = await GetConnection(); return conn.query(query, values); }, async execute(query: string | QueryOptions, values?: any[]) { using conn = await GetConnection(); return conn.execute(query, values); }, async column(query: string | QueryOptions, values?: any[]) { return db.scalar(await db.execute(query, values)) as T | null; }, async exists(query: string | QueryOptions, values?: any[]) { return (db.scalar(await db.execute(query, values)) as T) === 1; }, async row(query: string | QueryOptions, values?: any[]) { return db.single(await db.execute(query, values)) as T | null; }, async insert(query: string | QueryOptions, values?: any[]) { return (await db.execute(query, values))?.insertId; }, async update(query: string | QueryOptions, values?: any[]) { return (await db.execute(query, values))?.affectedRows; }, batch(query: string | QueryOptions, values?: any[]) { return pool.batch(query, values); }, scalar(resp: T[] | null) { if (resp && resp[0]) for (const key in resp[0]) return resp[0][key] as T; return null; }, single(resp: T[] | null) { return resp ? resp[0] : null; }, }; ================================================ FILE: server/db/pool.ts ================================================ import { createPool } from 'mariadb'; import { GetConfig } from './config'; import type { Pool } from 'mariadb'; import schema from './schema'; export let pool: Pool; setImmediate(async () => { const config = GetConfig(); try { const dbPool = createPool(config); const conn = await dbPool.getConnection(); const info = conn.info!; // when would info be null? i'm sure we'll find out eventually! const version = info.serverVersion; const recommendedDb = 'Install MariaDB 11.4+ for the best experience.\n- https://mariadb.com/kb/en/changes-improvements-in-mariadb-11-4/'; conn.release(); if (!version.mariaDb) return console.error(`MySQL ${version?.raw} is not supported. ${recommendedDb}`); if (!info.hasMinVersion(11, 4, 0)) return console.error(`${version.raw} is not supported. ${recommendedDb}`); console.log(`${`^5[${version.raw}]`} ^2Database server connection established!^0`); await schema(dbPool); pool = dbPool; } catch (err) { console.log( `^3Unable to establish a connection to the database (${err.code})!\n^1Error ${err.errno}: ${err.message}^0`, ); if (config.password) config.password = '******'; console.log(config); } }); ================================================ FILE: server/db/schema.ts ================================================ import { Pool } from 'mariadb'; /** * Validate some database settings, tables, etc. and add anything missing (e.g. version changes). */ export default async function (pool: Pool) { await pool.query(`CREATE TABLE IF NOT EXISTS user_tokens ( userId INT UNSIGNED NOT NULL, token VARCHAR(50) NOT NULL, PRIMARY KEY (userId, token), INDEX token (token), CONSTRAINT FK_user_tokens_users FOREIGN KEY (userId) REFERENCES users (userId) ON UPDATE CASCADE ON DELETE CASCADE )`); await pool.query(`CREATE TABLE IF NOT EXISTS banned_users ( userId INT UNSIGNED NOT NULL, banned_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, unban_at TIMESTAMP DEFAULT NULL, reason VARCHAR(255), PRIMARY KEY (userId), CONSTRAINT FK_banned_users_users FOREIGN KEY (userId) REFERENCES users (userId) ON UPDATE CASCADE ON DELETE CASCADE )`); } ================================================ FILE: server/groups/db.ts ================================================ import { GetConnection, db } from 'db'; import type { UpsertResult } from 'mariadb'; import type { DbGroup } from 'types'; export function SelectGroups() { return db.query(` SELECT ox_groups.*, JSON_OBJECTAGG(ox_group_grades.grade, ox_group_grades.accountRole) AS accountRoles, JSON_ARRAYAGG(ox_group_grades.label ORDER BY ox_group_grades.grade) AS grades FROM ox_groups JOIN ox_group_grades ON ox_groups.name = ox_group_grades.group GROUP BY ox_groups.name; `); } export async function InsertGroup({ name, label, type, colour, hasAccount, grades, accountRoles }: DbGroup) { using conn = await GetConnection(); await conn.beginTransaction(); const insertedGroup = await conn.update( 'INSERT IGNORE INTO `ox_groups` (`name`, `label`, `type`, `colour`, `hasAccount`) VALUES (?, ?, ?, ?, ?)', [name, label, type, colour, hasAccount], ); if (!insertedGroup) return true; const insertedGrades = (await conn.batch( 'INSERT INTO `ox_group_grades` (`group`, `grade`, `label`, `accountRole`) VALUES (?, ?, ?, ?)', grades.map((gradeLabel, index) => [name, index + 1, gradeLabel, accountRoles[index + 1]]), )) as UpsertResult; return insertedGrades.affectedRows > 0; } export function RemoveGroup(groupName: string) { return db.update('DELETE FROM `ox_groups` WHERE name = ?', [groupName]); } export async function AddCharacterGroup(charId: number, name: string, grade: number) { return ( (await db.update('INSERT INTO character_groups (charId, name, grade) VALUES (?, ?, ?)', [charId, name, grade])) === 1 ); } export async function UpdateCharacterGroup(charId: number, name: string, grade: number) { return ( (await db.update('UPDATE character_groups SET grade = ? WHERE charId = ? AND name = ?', [grade, charId, name])) === 1 ); } export async function RemoveCharacterGroup(charId: number, name: string) { return (await db.update('DELETE FROM character_groups WHERE charId = ? AND name = ?', [charId, name])) === 1; } export function GetCharacterGroups(charId: number) { return db.execute<{ name: string; grade: number; isActive: boolean }>( 'SELECT name, grade, isActive FROM character_groups WHERE charId = ?', [charId], ); } export async function SetActiveGroup(charId: number, groupName?: string) { using conn = await GetConnection(); const params: [number, string?] = [charId]; conn.execute('UPDATE character_groups SET isActive = 0 WHERE charId = ? AND isActive = 1', params); if (groupName) { params.push(groupName); conn.execute('UPDATE character_groups SET isActive = 1 WHERE charId = ? AND name = ?', params); } } ================================================ FILE: server/groups/index.ts ================================================ import { addAce, addCommand, addPrincipal, removeAce, removePrincipal } from "@overextended/ox_lib/server"; import { InsertGroup, RemoveGroup, SelectGroups } from "./db"; import { OxPlayer } from "player/class"; import type { Dict, OxGroup, DbGroup, CreateGroupProperties, OxAccountRole } from "types"; import { GetGroupPermissions } from "../../common"; import { GetGroupAccount } from "accounts"; import { CreateNewAccount } from "accounts/db"; const groups: Dict = {}; GlobalState.groups = []; export function GetGroup(name: string) { return groups[name]; } export function GetGroupsByType(type: string) { return Object.values(groups).reduce((acc, group) => { if (group.type === type) acc.push(group.name); return acc; }, [] as string[]); } export function GetGroupActivePlayers(groupName: string) { const group = groups[groupName]; return group ? [...group.activePlayers] : []; } export function GetGroupActivePlayersByType(type: string) { return Object.values(groups).reduce((acc, group) => { if (group.type === type) { acc.push(...group.activePlayers); } return acc; }, [] as number[]); } export function SetGroupPermission(groupName: string, grade: number, permission: string, value: "allow" | "deny") { const permissions = GetGroupPermissions(groupName); if (!permissions[grade]) permissions[grade] = {}; permissions[grade][permission] = value === "allow" ? true : false; GlobalState[`group.${groupName}:permissions`] = permissions; } export function RemoveGroupPermission(groupName: string, grade: number, permission: string) { const permissions = GetGroupPermissions(groupName); if (!permissions[grade]) return; delete permissions[grade][permission]; GlobalState[`group.${groupName}:permissions`] = permissions; } function SetupGroup(data: DbGroup) { const group: OxGroup = { ...data, principal: `group.${data.name}`, hasAccount: Boolean(data.hasAccount), }; GlobalState[group.principal] = group; GlobalState[`${group.name}:count`] = 0; GlobalState[`${group.name}:activeCount`] = 0; group.activePlayers = new Set(); groups[group.name] = group; group.grades = group.grades.reduce( (acc, value, index) => { acc[index + 1] = value; return acc; }, {} as Record, ) as any; let parent = group.principal; for (const i in group.grades) { const child = `${group.principal}:${i}`; if (!IsPrincipalAceAllowed(child, child)) { addAce(child, child, true); addPrincipal(child, parent); } parent = child; } if (group.hasAccount) { GetGroupAccount(group.name).then((account) => { if (!account) CreateNewAccount(group.name, group.label, true); }); } DEV: console.info(`Instantiated OxGroup<${group.name}>`); return group; } // @todo more data validation and error handling export async function CreateGroup(data: CreateGroupProperties) { if (groups[data.name]) throw new Error(`Cannot create OxGroup<${data.name}> (group already exists with that name)`); const grades = data.grades.map((grade) => grade.label); const accountRoles = data.grades.reduce((acc, grade, index) => { if (grade.accountRole) acc[index + 1] = grade.accountRole; return acc; }, {} as Dict); const group: DbGroup = { ...data, grades: grades, accountRoles: accountRoles, hasAccount: data.hasAccount ?? false, activePlayers: new Set(), }; const response = await InsertGroup(group); if (response) { SetupGroup(group); GlobalState.groups = [...GlobalState.groups, data.name]; } } export async function DeleteGroup(groupName: string) { const deleted = await RemoveGroup(groupName); const group = deleted && groups[groupName]; if (!group) throw new Error(`Cannot delete OxGroup<${groupName}> (no group exists with that name)`); let parent = group.principal; removeAce(parent, parent, true); for (const i in group.grades) { const child = `${group.principal}:${i}`; removeAce(child, child, true); removePrincipal(child, parent); parent = child; } const players = OxPlayer.getAll({ groups: groupName, }); for (const id in players) { const player = players[id]; player.setGroup(groupName, 0, true); } GlobalState[group.principal] = null; GlobalState[`${group.name}:count`] = null; GlobalState[`${group.name}:activeCount`] = null; GlobalState.groups = GlobalState.groups.filter((name: string) => name !== groupName); delete groups[group.name]; } async function LoadGroups() { const dbGroups = await SelectGroups(); GlobalState.groups = dbGroups.map((group) => SetupGroup(group).name); } setImmediate(LoadGroups); addCommand("reloadgroups", LoadGroups, { help: "Reload groups from the database.", restricted: "group.admin", }); addCommand<{ target: string; group: string; grade?: number }>( "setgroup", async (playerId, args, raw) => { const player = OxPlayer.get(args.target); player?.setGroup(args.group, args.grade || 0); }, { help: `Update a player's grade for a group.`, restricted: "group.admin", params: [ { name: "target", paramType: "playerId" }, { name: "group", paramType: "string" }, { name: "grade", paramType: "number", help: "The new grade to set. Set to 0 or omit to remove the group.", optional: true, }, ], }, ); exports("GetGroupsByType", GetGroupsByType); exports("SetGroupPermission", SetGroupPermission); exports("RemoveGroupPermission", RemoveGroupPermission); exports("CreateGroup", CreateGroup); exports("DeleteGroup", DeleteGroup); exports("GetGroupActivePlayers", GetGroupActivePlayers); exports("GetGroupActivePlayersByType", GetGroupActivePlayersByType); ================================================ FILE: server/index.ts ================================================ export * from "../common"; import "./bridge"; import "player"; import "utils"; import "accounts"; import "vehicle"; import { versionCheck } from "@overextended/ox_lib/server"; import { DEBUG } from "config"; if (!DEBUG) { versionCheck("overextended/ox_core"); } ================================================ FILE: server/player/class.ts ================================================ import { ClassInterface } from "classInterface"; import { AddCharacterLicense, CreateCharacter, DeleteCharacter, GetCharacterLicenses, GetCharacterMetadata, GetCharacters, IsStateIdAvailable, RemoveCharacterLicense, SaveCharacterData, UpdateCharacterLicense, } from "./db"; import { getRandomChar, getRandomInt } from "@overextended/ox_lib"; import { GetGroup, GetGroupsByType } from "groups"; import { GeneratePhoneNumber } from "bridge/npwd"; import { Statuses } from "./status"; import { addPrincipal, removePrincipal } from "@overextended/ox_lib/server"; import { AddCharacterGroup, GetCharacterGroups, RemoveCharacterGroup, UpdateCharacterGroup, SetActiveGroup, } from "groups/db"; import { PayAccountInvoice } from "accounts"; import type { Character, Dict, NewCharacter, PlayerMetadata, OxGroup, CharacterLicense, BanDetails } from "types"; import { GetGroupPermissions } from "../../common"; import { Licenses } from "./license"; import { CHARACTER_SLOTS } from "config"; import locales from "../../common/locales"; export class OxPlayer extends ClassInterface { source: number | string; userId: number; charId?: number; stateId?: string; username: string; identifier: string; ped: number; #characters: Character[]; #metadata: Dict; #statuses: Dict; #groups: Dict; #licenses: Dict; protected static members: Dict = {}; protected static keys: Dict> = { userId: {}, charId: {}, }; /** Get an instance of OxPlayer with the matching playerId. */ static get(id: string | number) { return this.members[id]; } /** Get an instance of OxPlayer with the matching userId. */ static getFromUserId(id: number) { return this.keys.userId[id]; } /** Get an instance of OxPlayer with the matching charId. */ static getFromCharId(id: number) { return this.keys.charId[id]; } static formatBanReason(ban: BanDetails) { const unbanTime = ban.unban_at ? new Date(ban.unban_at) : null; let timeRemainingMessage; if (unbanTime) { const timeRemaining = +unbanTime - Date.now(); const hours = Math.floor(timeRemaining / (1000 * 60 * 60)); const minutes = Math.floor((timeRemaining % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((timeRemaining % (1000 * 60)) / 1000); timeRemainingMessage = locales("ban_expires_in", hours, minutes, seconds); } else timeRemainingMessage = locales("ban_indefinite"); return locales("ban_notice", new Date(ban.banned_at).toLocaleString(), ban.reason, timeRemainingMessage); } /** Compares player fields and metadata to a filter, returning the player if all values match. */ private filter(criteria: Dict) { const { groups, ...filter } = criteria; if (groups && !this.getGroup(groups)) return; for (const key in filter) { const value = filter[key]; if (this[key as keyof OxPlayer] !== value && this.#metadata[key] !== value) return; } return this; } /** Get an instance of OxPlayer with that matches the filter. */ static getFromFilter(filter: Dict) { for (const id in this.members) { const player = this.members[id].filter(filter); if (player) return player; } } /** Gets all instances of OxPlayer, optionally comparing against a filter. */ static getAll(filter?: Dict, asArray?: false): Dict; static getAll(filter?: Dict, asArray?: true): OxPlayer[]; static getAll(filter?: Dict, asArray = false): Dict | OxPlayer[] { if (!filter) return asArray ? Object.values(this.members) : this.members; const obj: Dict = {}; for (const id in this.members) { const player = this.members[id].filter(filter); if (player) obj[id] = player; } return asArray ? Object.values(obj) : obj; } /** Saves all players to the database, and optionally kicks them from the server. */ static saveAll(kickWithReason?: string) { const parameters = []; for (const id in this.members) { const player = this.members[id]; if (player.charId) { parameters.push(player.#getSaveData()); } if (kickWithReason) { delete player.charId; DropPlayer(player.source as string, kickWithReason); } } DEV: console.info(`Saving ${parameters.length} players to the database.`); if (parameters.length > 0) { SaveCharacterData(parameters, true); emit("ox:savedPlayers", parameters.length); } } constructor(source: number) { super(); this.source = source; this.#characters = []; this.#metadata = {}; this.#statuses = {}; this.#groups = {}; this.#licenses = {}; } /** Triggers an event on the player's client. */ emit(eventName: string, ...args: any[]) { emitNet(eventName, this.source, ...args); } /** Stores a value in the active character's metadata. */ set( key: K | keyof PlayerMetadata, value: K extends keyof PlayerMetadata ? PlayerMetadata[K] : any, replicated?: boolean, ): void; set(key: string, value: any, replicated?: boolean) { this.#metadata[key] = value; if (replicated) this.emit("ox:setPlayerData", key, value); } /** Gets a value stored in active character's metadata. */ get(key: K | keyof PlayerMetadata): K extends keyof PlayerMetadata ? PlayerMetadata[K] : any { return this.#metadata[key]; } async payInvoice(invoiceId: number) { if (!this.charId) return; return await PayAccountInvoice(invoiceId, this.charId); } setActiveGroup(groupName?: string, temp?: boolean) { if (!this.charId || (groupName && !(groupName in this.#groups))) return false; const currentActiveGroup = this.get("activeGroup"); if (currentActiveGroup) { GlobalState[`${currentActiveGroup}:activeCount`] -= 1; const group = GetGroup(currentActiveGroup); group.activePlayers.delete(+this.source); } if (groupName) { GlobalState[`${groupName}:activeCount`] += 1; const group = GetGroup(groupName); group.activePlayers.add(+this.source); } SetActiveGroup(this.charId, temp ? undefined : groupName); this.set("activeGroup", groupName, true); emit("ox:setActiveGroup", this.source, groupName, currentActiveGroup); return true; } /** Sets the active character's grade in a group. If the grade is 0 they will be removed from the group. */ async setGroup(groupName: string, grade = 0, force = false) { if (!this.charId) return false; const group = GetGroup(groupName); if (!group) return console.warn(`Failed to set OxPlayer<${this.userId}> ${groupName}:${grade} (invalid group)`); const currentGrade = this.#groups[groupName]; if (currentGrade === grade) return; if (!grade) { if (!currentGrade) return; if (!force && !(await RemoveCharacterGroup(this.charId, group.name))) return; this.#removeGroup(group, currentGrade, true); if (this.get("activeGroup") === groupName) this.set("activeGroup", undefined, true); } else { if (!group.grades[grade] && grade > 0) return console.warn(`Failed to set OxPlayer<${this.userId}> ${group.name}:${grade} (invalid grade)`); if (currentGrade) { if (!(await UpdateCharacterGroup(this.charId, group.name, grade))) return; this.#removeGroup(group, currentGrade, false); this.#addGroup(group, grade); } else { const relatedGroups = group.type && GetGroupsByType(group.type); if ( relatedGroups && relatedGroups.some((name) => { return name in this.#groups; }) ) return console.warn( `Failed to set OxPlayer<${this.userId}> ${group.name}:${grade} (already has group of type '${group.type}')`, ); if (!(await AddCharacterGroup(this.charId, group.name, grade))) return; this.#addGroup(group, grade); } } emit("ox:setGroup", this.source, group.name, grade ? grade : null); this.emit("ox:setGroup", group.name, grade ? grade : null); return true; } /** Returns the current grade of a given group name, or the first matched name and grade in the filter. */ getGroup(filter: string): number; getGroup(filter: string[] | Record): [string, number] | []; getGroup(filter: string | string[] | Record) { if (typeof filter === "string") { return this.#groups[filter]; } if (Array.isArray(filter)) { for (const name of filter) { const grade = this.#groups[name]; if (grade) return [name, grade]; } } else if (typeof filter === "object") { for (const [name, requiredGrade] of Object.entries(filter)) { const grade = this.#groups[name]; if (grade && (requiredGrade as number) <= grade) { return [name, grade]; } } } } getGroupByType(type: string) { return this.getGroup(GetGroupsByType(type)); } getGroups() { return this.#groups; } hasPermission(permission: string): boolean { const matchResult = permission.match(/^group\.([^.]+)\.(.*)/); const groupName = matchResult?.[1]; permission = matchResult?.[2] ?? permission; if (groupName) { const grade = this.#groups[groupName]; if (!grade) return false; const permissions = GetGroupPermissions(groupName); for (let g = grade; g > 0; g--) { const value = permissions[g] && permissions[g][permission]; if (value !== undefined) return value; } } return false; } /** Sets the value of a status. */ setStatus(statusName: string, value = Statuses[statusName].default) { if (Statuses[statusName] === undefined) return; const newValue = value < 0 ? 0 : value > 100 ? 100 : Number.parseFloat(value.toPrecision(8)); this.#statuses[statusName] = newValue; this.emit("ox:setPlayerStatus", statusName, newValue, true); return true; } /** Returns the current value of a status. */ getStatus(statusName: string) { return this.#statuses[statusName]; } /** Returns an object containing all status names and their values. */ getStatuses() { return this.#statuses; } /** Increases the status's value by the given amount. */ addStatus(statusName: string, value: number) { if (this.#statuses[statusName] === undefined) return; let newValue = this.#statuses[statusName] + value; newValue = newValue < 0 ? 0 : newValue > 100 ? 100 : Number.parseFloat(newValue.toPrecision(8)); this.#statuses[statusName] = newValue; this.emit("ox:setPlayerStatus", statusName, newValue); return true; } /** Reduces the status's value by the given amount. */ removeStatus(statusName: string, value: number) { if (this.#statuses[statusName] === undefined) return; let newValue = this.#statuses[statusName] - value; newValue = newValue < 0 ? 0 : newValue > 100 ? 100 : Number.parseFloat(newValue.toPrecision(8)); this.#statuses[statusName] = newValue; this.emit("ox:setPlayerStatus", statusName, newValue); return true; } getLicense(licenseName: string) { return this.#licenses[licenseName]; } getLicenses() { return this.#licenses; } async addLicense(licenseName: string) { if (!this.charId || this.#licenses[licenseName] || !Licenses[licenseName]) return false; const license = { issued: Date.now(), }; if (!(await AddCharacterLicense(this.charId, licenseName, license))) return false; this.#licenses[licenseName] = license; emit("ox:licenseAdded", this.source, licenseName); this.emit("ox:licenseAdded", licenseName); return true; } async removeLicense(licenseName: string) { if (!this.charId || !(await RemoveCharacterLicense(this.charId, licenseName))) return false; delete this.#licenses[licenseName]; emit("ox:licenseRemoved", this.source, licenseName); this.emit("ox:licenseRemoved", licenseName); return true; } async updateLicense(licenseName: string, key: string, value: any) { if (!this.charId) return false; const license = this.#licenses[licenseName]; if (!license || key === "issued") return false; if (!(await UpdateCharacterLicense(this.charId, licenseName, key, value))) return false; value == null ? delete license[key] : (license[key] = value); return true; } /** Returns an array of values to be saved in the database. */ #getSaveData() { return [ ...GetEntityCoords(this.ped), GetEntityHeading(this.ped), Player(this.source).state.isDead || false, GetEntityHealth(this.ped), GetPedArmour(this.ped), JSON.stringify(this.#statuses || {}), this.charId, ]; } /** Adds the active character to the group and sets permissions. */ #addGroup(group: string | OxGroup, grade: number) { const groupName = typeof group === "string" ? group : group.name; group = GetGroup(groupName); if (!group) return console.warn(`Failed to add group '${groupName}' to OxPlayer<${this.userId}> (invalid group)`); addPrincipal(this.source as string, `${group.principal}:${grade}`); DEV: console.info(`Added OxPlayer<${this.userId}> to group '${group.name}' as grade ${grade}.`); this.#groups[group.name] = grade; GlobalState[`${group.name}:count`] += 1; } /** Removes the active character from the group and sets permissions. */ #removeGroup(group: string | OxGroup, grade: number, canRemoveActiveCount = false) { const groupName = typeof group === "string" ? group : group.name; group = GetGroup(groupName); if (!group) return console.warn(`Failed to remove group '${groupName}' from OxPlayer<${this.userId}> (invalid group)`); removePrincipal(this.source as string, `${group.principal}:${grade}`); DEV: console.info(`Removed OxPlayer<${this.userId}> from group '${group.name}'.`); delete this.#groups[group.name]; GlobalState[`${group.name}:count`] -= 1; if (canRemoveActiveCount && group.name === this.get("activeGroup")) { GlobalState[`${group.name}:activeCount`] -= 1; group.activePlayers.delete(+this.source); } } /** Saves the active character to the database. */ save() { if (this.charId) return SaveCharacterData(this.#getSaveData()); } /** Adds the player to the player registry and starts character selection. */ async setAsJoined() { if (!OxPlayer.getFromUserId(this.userId)) { OxPlayer.add(this.source, this); Player(this.source).state.set("userId", this.userId, true); } DEV: console.info(`Starting character selection for OxPlayer<${this.userId}>`); this.emit("ox:startCharacterSelect", this.userId, await this.#getCharacters()); } /** Returns an array of all characters owned by the player, excluding soft-deleted characters. */ async #getCharacters() { this.#characters = await GetCharacters(this.userId); return this.#characters; } /** * Clears data for the active character. If the player is still connected then transition them to character selection. * @param dropped If the player has been dropped from the server. * @param save If character data should be saved to the database (defaults to true). */ async logout(save = true, dropped = false) { if (!this.charId) return; for (const name in this.#groups) this.#removeGroup(name, this.#groups[name], true); emit("ox:playerLogout", this.source, this.userId, this.charId); if (save) await this.save(); if (dropped) return; delete OxPlayer.keys.charId[this.charId]; delete this.charId; this.emit("ox:startCharacterSelect", this.userId, await this.#getCharacters()); } /** Creates a stateId for a newly created character. */ async #generateStateId() { const arr = []; while (true) { for (let i = 0; i < 2; i++) arr[i] = getRandomChar(); for (let i = 2; i < 6; i++) arr[i] = getRandomInt(); const stateId = arr.join(""); if (await IsStateIdAvailable(stateId)) return stateId; } } /** Registers a new character for the player. */ async createCharacter(data: NewCharacter) { if (this.charId || this.#characters.length >= CHARACTER_SLOTS) return; const stateId = await this.#generateStateId(); const phoneNumber = await GeneratePhoneNumber(); const character: Character = { firstName: data.firstName, lastName: data.lastName, stateId: stateId, charId: await CreateCharacter( this.userId, stateId, data.firstName, data.lastName, data.gender, data.date, phoneNumber, ), isNew: true, gender: data.gender, }; this.#characters.push(character); emit("ox:createdCharacter", this.source, this.userId, character.charId); return this.#characters.length - 1; } /** Returns the current index for a character with the given charId. */ #getCharacterSlotFromId(charId: number) { if (this.charId) return -1; return this.#characters.findIndex((character) => { return character.charId === charId; }); } /** Loads and sets the player's active character. */ async setActiveCharacter(data: number | NewCharacter) { if (this.charId) return; const characterSlot = typeof data === "object" ? await this.createCharacter(data) : this.#getCharacterSlotFromId(data); if (characterSlot == null) return; const character = this.#characters[characterSlot]; this.#characters.length = 0; this.charId = character.charId; this.stateId = character.stateId; this.ped = GetPlayerPed(this.source as string); const metadata = await GetCharacterMetadata(character.charId); if (!metadata) return; const statuses = metadata.statuses || this.#statuses; const { isDead, gender, dateOfBirth, phoneNumber, health, armour } = metadata; const groups = await GetCharacterGroups(this.charId); const licenses = await GetCharacterLicenses(this.charId); const activeGroup = groups.find((group) => group.isActive)?.name; character.health = isDead ? 0 : health; character.armour = armour; groups.forEach(({ name, grade }) => this.#addGroup(name, grade)); licenses.forEach(({ name, data }) => (this.#licenses[name] = data)); for (const name in Statuses) this.setStatus(name, statuses[name]); this.emit("ox:setActiveCharacter", character, this.#groups); // Values stored in metadata and synced to client. this.set("name", `${character.firstName} ${character.lastName}`, true); this.set("firstName", character.firstName, true); this.set("lastName", character.lastName, true); this.set("gender", gender, true); this.set("dateOfBirth", dateOfBirth, true); this.set("phoneNumber", phoneNumber, true); this.set("activeGroup", activeGroup, true); if (activeGroup) { GlobalState[`${activeGroup}:activeCount`] += 1; const group = GetGroup(activeGroup); group.activePlayers.add(+this.source); } DEV: console.info(`Restored OxPlayer<${this.userId}> previous active group: ${activeGroup}`); OxPlayer.keys.charId[character.charId] = this; /** * @todo Player metadata can ideally be handled with statebags, but requires security features. * Rejection of client-set values is a must-have. * "Private" states only visible to the owner would be :chefskiss: * https://github.com/citizenfx/fivem/pull/2257 - state bag filters * https://github.com/citizenfx/fivem/pull/2257 - state bag write policies */ const state = Player(this.source).state; state.set("isDead", isDead === 1, true); DEV: console.info(`OxPlayer<${this.userId}> loaded character ${this.get("name")} (${this.charId})`); emit("ox:playerLoaded", this.source, this.userId, character.charId); return character; } /** Deletes a character with the given charId if it's owned by the player. */ async deleteCharacter(charId: number) { const isActive = this.charId === charId; if (this.charId && !isActive) return; const characterSlot = isActive ? 0 : this.#getCharacterSlotFromId(charId); if (characterSlot === -1) return; if (await DeleteCharacter(charId)) { if (isActive) this.logout(false); else this.#characters.splice(characterSlot, 1); emit("ox:deletedCharacter", this.source, this.userId, charId); DEV: console.info(`Deleted character ${charId} for OxPlayer<${this.userId}>`); return true; } } } OxPlayer.init(); exports("SaveAllPlayers", (arg: any) => OxPlayer.saveAll(arg)); exports("GetPlayerFromUserId", (arg: any) => OxPlayer.getFromUserId(arg)); exports("GetPlayerFromCharId", (arg: any) => OxPlayer.getFromCharId(arg)); exports("GetPlayerFromFilter", (arg: any) => OxPlayer.getFromFilter(arg)); exports("GetPlayers", (arg: any) => OxPlayer.getAll(arg, true)); ================================================ FILE: server/player/commands.ts ================================================ import { addCommand } from "@overextended/ox_lib/server"; import { OxPlayer } from "player/class"; // NOTE: These commands are intended for development purposes. addCommand("logout", async (playerId) => OxPlayer.get(playerId).logout(true), { help: "Logout and return to character selection.", restricted: "group.admin", }); addCommand( "deletechar", async (playerId) => { const player = OxPlayer.get(playerId); if (!player.charId) return; player.deleteCharacter(player.charId); }, { help: "Hard delete your current character (cannot be reversed).", restricted: "group.admin", }, ); addCommand( "charinfo", async (playerId) => { const player = OxPlayer.get(playerId); console.log(`${player.get("firstName")} ${player.get("lastName")} (${player.charId}) - ${player.stateId}`); }, { help: "Display basic character information.", }, ); ================================================ FILE: server/player/db.ts ================================================ import type { Character, Dict, OxStatus, CharacterLicense, OxLicense, BanDetails } from 'types'; import { CHARACTER_SLOTS } from '../../common/config'; import { db } from '../db'; import { OxPlayer } from './class'; export function GetUserIdFromIdentifier(identifier: string, offset?: number) { return db.column('SELECT userId FROM users WHERE license2 = ? LIMIT ?, 1', [identifier, offset || 0]); } export function CreateUser(username: string, { license2, steam, fivem, discord }: Dict) { return db.insert('INSERT INTO users (username, license2, steam, fivem, discord) VALUES (?, ?, ?, ?, ?)', [ username, license2, steam, fivem, discord, ]); } export async function IsStateIdAvailable(stateId: string) { return !(await db.exists('SELECT 1 FROM characters WHERE stateId = ?', [stateId])); } export function CreateCharacter( userId: number, stateId: string, firstName: string, lastName: string, gender: string, date: number, phoneNumber?: number, ) { return db.insert( 'INSERT INTO characters (userId, stateId, firstName, lastName, gender, dateOfBirth, phoneNumber) VALUES (?, ?, ?, ?, ?, ?, ?)', [userId, stateId, firstName, lastName, gender, new Date(Number(date)), phoneNumber], ); } export function GetCharacters(userId: number) { return db.execute( 'SELECT charId, stateId, firstName, lastName, gender, x, y, z, heading, DATE_FORMAT(lastPlayed, "%d/%m/%Y") AS lastPlayed FROM characters WHERE userId = ? AND deleted IS NULL LIMIT ?', [userId, CHARACTER_SLOTS], ); } export function SaveCharacterData(values: any[] | any[][], batch?: boolean) { const query = 'UPDATE characters SET x = ?, y = ?, z = ?, heading = ?, isDead = ?, lastPlayed = CURRENT_TIMESTAMP(), health = ?, armour = ?, statuses = ? WHERE charId = ?'; return batch ? db.batch(query, values) : db.update(query, values); } export async function DeleteCharacter(charId: number) { return (await db.update('UPDATE characters SET deleted = curdate() WHERE charId = ?', [charId])) === 1; } export function GetCharacterMetadata(charId: number) { return db.row<{ isDead: number; gender: string; dateOfBirth: string; phoneNumber: string; health: number; armour: number; statuses: Dict; }>( 'SELECT isDead, gender, DATE_FORMAT(dateOfBirth, "%d/%m/%Y") AS dateOfBirth, phoneNumber, health, armour, statuses FROM characters WHERE charId = ?', [charId], ); } export function GetStatuses() { return db.query('SELECT name, `default`, onTick FROM ox_statuses'); } export function GetLicenses() { return db.query>('SELECT name, label FROM ox_licenses'); } export function GetLicense(name: string) { return db.row('SELECT name, label FROM ox_licenses WHERE name = ?', [name]); } export function GetCharacterLicenses(charId: number) { return db.query<{ name: string; data: CharacterLicense }>( 'SELECT name, data FROM character_licenses WHERE charId = ?', [charId], ); } export function AddCharacterLicense(charId: number, name: string, data: CharacterLicense) { return db.update('INSERT INTO character_licenses (charId, name, data) VALUES (?, ?, ?)', [ charId, name, JSON.stringify(data), ]); } export function RemoveCharacterLicense(charId: number, name: string) { return db.update('DELETE FROM character_licenses WHERE charId = ? AND name = ?', [charId, name]); } export function UpdateCharacterLicense(charId: number, name: string, key: string, value: any) { const params = [`$.${key}`, name, charId]; if (value == null) return db.update('UPDATE character_licenses SET data = JSON_REMOVE(data, ?) WHERE name = ? AND charId = ?', params); params.splice(1, 0, value); return db.update('UPDATE character_licenses SET data = JSON_SET(data, ?, ?) WHERE name = ? AND charId = ?', params); } export function GetCharIdFromStateId(stateId: string) { return db.column('SELECT charId FROM characters WHERE stateId = ?', [stateId]); } export async function UpdateUserTokens(userId: number, tokens: string[]) { if (tokens.length === 0) return; const parameters = tokens.map((token) => [userId, token]); await db.batch('INSERT IGNORE INTO user_tokens (userId, token) VALUES (?, ?)', parameters); } export async function IsUserBanned(userId: number): Promise { const banDetails = await db.query( `SELECT bu.reason, bu.banned_at, bu.unban_at, bu.userId, ut.token FROM user_tokens ut JOIN banned_users bu ON ut.userId = bu.userId WHERE ut.userId = ? GROUP BY bu.userId`, [userId], ); if (!banDetails?.[0]) return; const currentDate = new Date(); const expiredBans = banDetails.filter((ban) => ban.unban_at && new Date(ban.unban_at) <= currentDate); if (expiredBans.length > 0) { await db.query(`DELETE FROM banned_users WHERE userId IN (?)`, [expiredBans.map((ban) => ban.userId)]); return; } return banDetails[0]; } export async function BanUser(userId: number, reason?: string, hours?: number) { const success = await db.update( 'INSERT INTO banned_users (userId, banned_at, unban_at, reason) VALUES (?, NOW(), DATE_ADD(NOW(), INTERVAL ? HOUR), ?)', [userId, hours, reason], ); if (!success) { console.error(`Failed to ban ${userId}`); return false; } const playerId = OxPlayer.getFromUserId(userId)?.source as string; if (playerId) { const banned_at = Date.now(); const unban_at = banned_at + (hours ? hours * 60 * 60 * 1000 : 0); DropPlayer(playerId, OxPlayer.formatBanReason({ userId, banned_at, unban_at, reason })); } return true; } export async function UnbanUser(userId: number) { const success = await db.update('DELETE FROM banned_users WHERE userId = ?', [userId]); return success; } ================================================ FILE: server/player/events.ts ================================================ import { onClientCallback } from "@overextended/ox_lib/server"; import { OxPlayer } from "./class"; import { sleep } from "@overextended/ox_lib"; import { db } from "db"; import { Statuses } from "./status"; import { CreateNewAccount } from "accounts/db"; import type { Dict, NewCharacter, OxStatus } from "types"; import { CREATE_DEFAULT_ACCOUNT } from "config"; import "./license"; const playerLoadEvents: Dict = {}; const playerLogoutEvents: Function[] = []; /** Triggers a callback when a player is fully loaded, or when the resource starts. */ export function OnPlayerLoaded(resource: string, cb: (player: OxPlayer) => void) { playerLoadEvents[resource] = cb; } /** Triggers a callback when a player logs out. */ export function OnPlayerLogout(cb: (player: OxPlayer) => void) { playerLogoutEvents.push(cb); } on("ox:playerLoaded", (playerId: string | number) => { for (const resource in playerLoadEvents) { const player = OxPlayer.get(playerId); if (player.charId) try { playerLoadEvents[resource](player); } catch (e) { DEV: console.info(e.message); } } }); on("onServerResourceStart", async (resource: string) => { const event = playerLoadEvents[resource]; if (!event) return; await sleep(1000); const players = OxPlayer.getAll(); for (const id in players) { const player = players[id]; if (player.charId) try { event(player); } catch (e) { DEV: console.info(e.message); } } }); on("ox:playerLogout", (playerId: number) => { const player = OxPlayer.get(playerId); if (player.charId) for (const i in playerLogoutEvents) try { playerLogoutEvents[i](player); } catch (e) { DEV: console.info(e.message); } }); on("onResourceStop", (resource: string) => { if (resource !== "ox_core") return; const players = OxPlayer.getAll(); for (const id in players) { const player = players[id]; if (player.charId) emit("ox:playerLogout", player.source, player.userId, player.charId); } }); onNet("ox:setActiveCharacter", async (data: number | NewCharacter) => { const player = OxPlayer.get(source); if (!player) return; return await player.setActiveCharacter(data); }); onClientCallback("ox:deleteCharacter", async (playerId, charId: number) => { const player = OxPlayer.get(playerId); if (!player) return; return await player.deleteCharacter(charId); }); on("ox:createdCharacter", async (playerId: number, userId: number, charId: number) => { db.execute("INSERT INTO character_inventory (charId) VALUES (?)", [charId]); if (CREATE_DEFAULT_ACCOUNT) CreateNewAccount(charId, "Personal", true); }); onNet("ox:updateStatuses", async (data: Dict) => { const player = OxPlayer.get(source); if (!player) return; for (const name in data) { const status = Statuses[name]; const value = data[name]; if (status && typeof value === "number") { player.setStatus(name, value); } } }); onClientCallback("ox:setActiveGroup", (playerId, groupName: string) => { const player = OxPlayer.get(playerId); if (!player) return false; return player.setActiveGroup(groupName); }); onClientCallback("ox:getLicense", (playerId, licenseName: string, target?: number) => { const player = OxPlayer.get(target || playerId); if (player) return licenseName ? player.getLicense(licenseName) : player.getLicenses(); }); on("txAdmin:events:playerHealed", ({ target, author }: { target: number; author: string }) => { if (target === -1) { const players = OxPlayer.getAll(); for (const id in players) { const state = Player(id).state; state.set("isDead", false, true); } } else { const state = Player(target).state; state.set("isDead", false, true); } }); ================================================ FILE: server/player/index.ts ================================================ import './loading'; import './events'; import './commands'; import { OxPlayer } from './class'; import { BanUser, GetCharIdFromStateId, UnbanUser } from './db'; /** * Sets an interval to save every 10 minutes. * @todo Consider performance on servers with a high player-count. * Multiple staggered saves may improve load. */ setInterval(() => OxPlayer.saveAll(), 600000); exports('GetCharIdFromStateId', GetCharIdFromStateId); exports('BanUser', BanUser); exports('UnbanUser', UnbanUser); ================================================ FILE: server/player/license.ts ================================================ import { addCommand } from "@overextended/ox_lib/server"; import type { Dict, OxLicense } from "types"; import { GetLicense, GetLicenses } from "./db"; export const Licenses: Dict = {}; async function LoadLicenses() { const rows = await GetLicenses(); if (!rows[0]) return; for (let i = 0; i < rows.length; i++) { const license = rows[i]; const name = license.name as string; delete license.name; Licenses[name] = license; GlobalState[`license.${name}`] = license; } } setImmediate(LoadLicenses); addCommand("reloadlicenses", LoadLicenses, { help: "Reload licenses from the database.", restricted: "group.admin", }); exports("GetLicenses", GetLicenses); exports("GetLicense", GetLicense); ================================================ FILE: server/player/loading.ts ================================================ import { OxPlayer } from 'player/class'; import { CreateUser, GetUserIdFromIdentifier, IsUserBanned, UpdateUserTokens } from './db'; import { GetIdentifiers, GetPlayerLicense } from 'utils'; import { DEBUG, SV_LAN } from '../config'; import type { Dict } from 'types'; import locales from '../../common/locales'; const connectingPlayers: Dict = {}; /** Loads existing data for the player, or inserts new data into the database. */ async function loadPlayer(playerId: number) { let player: OxPlayer | undefined; try { if (serverLockdown) return serverLockdown; player = new OxPlayer(playerId); const license = SV_LAN ? 'fayoum' : GetPlayerLicense(playerId); if (!license) return locales('no_license'); const identifier = license.substring(license.indexOf(':') + 1); let userId: number; userId = (await GetUserIdFromIdentifier(identifier)) ?? 0; if (userId && OxPlayer.getFromUserId(userId)) { const kickReason = locales('userid_is_active', userId); if (!DEBUG) return kickReason; userId = (await GetUserIdFromIdentifier(identifier, 1)) ?? 0; if (userId && OxPlayer.getFromUserId(userId)) return kickReason; } const tokens = getPlayerTokens(playerId); await UpdateUserTokens(userId, tokens); const ban = await IsUserBanned(userId); if (ban) { return OxPlayer.formatBanReason(ban); } player.username = GetPlayerName(player.source as string); player.userId = userId ? userId : await CreateUser(player.username, GetIdentifiers(playerId)); player.identifier = identifier; DEV: console.info(`Loaded player data for OxPlayer<${player.userId}>`); return player; } catch (err) { console.error('Error loading player:', err); if (player?.userId) { try { OxPlayer.remove(player.source); } catch (cleanupErr) { console.error('Error during cleanup:', cleanupErr); } } return err.message; } } let serverLockdown: string; setInterval(() => { for (const tempId in connectingPlayers) { if (!DoesPlayerExist(tempId)) delete connectingPlayers[tempId]; } }, 10000); on('txAdmin:events:serverShuttingDown', () => { serverLockdown = locales('server_restarting'); OxPlayer.saveAll(serverLockdown); }); on('playerConnecting', async (username: string, _: any, deferrals: any) => { const tempId = source; deferrals.defer(); if (serverLockdown) return deferrals.done(serverLockdown); const player = await loadPlayer(tempId); if (!(player instanceof OxPlayer)) return deferrals.done(player || 'Failed to load player.'); connectingPlayers[tempId] = player; deferrals.done(); }); on('playerJoining', async (tempId: string) => { if (serverLockdown) return DropPlayer(source.toString(), serverLockdown); const player = connectingPlayers[tempId]; if (!player) return; delete connectingPlayers[tempId]; connectingPlayers[source] = player; player.source = source; DEV: console.info(`Assigned id ${source} to OxPlayer<${player.userId}>`); }); onNet('ox:playerJoined', async () => { const playerSrc = source; const player = connectingPlayers[playerSrc] || (await loadPlayer(playerSrc)); delete connectingPlayers[playerSrc]; if (!(player instanceof OxPlayer)) return DropPlayer(playerSrc.toString(), player || 'Failed to load player.'); player.setAsJoined(); }); on('playerDropped', () => { const player = OxPlayer.get(source); if (!player) return; player.logout(true, true); OxPlayer.remove(player.source); DEV: console.info(`Dropped OxPlayer<${player.userId}>`); }); RegisterCommand( 'saveplayers', () => { OxPlayer.saveAll(); }, true, ); ================================================ FILE: server/player/status.ts ================================================ import { addCommand } from "@overextended/ox_lib/server"; import { GetStatuses } from "./db"; import { OxPlayer } from "./class"; import type { Dict, OxStatus } from "types"; export const Statuses: Dict = {}; async function LoadStatuses() { const rows = await GetStatuses(); if (!rows[0]) return; const players = OxPlayer.getAll(); for (let i = 0; i < rows.length; i++) { const status = rows[i]; for (const id in players) { const player = players[id]; const value = player.charId && player.getStatus(status.name); if (!value || value > 100) player.setStatus(status.name, status.default); } Statuses[status.name] = status; GlobalState[`status.${status.name}`] = status; } } setImmediate(LoadStatuses); addCommand("reloadstatuses", LoadStatuses, { help: "Reload statuses from the database.", restricted: "group.admin", }); ================================================ FILE: server/tsconfig.json ================================================ { "extends": "../tsconfig.json", "compilerOptions": { "baseUrl": ".", "types": ["@citizenfx/server"], "composite": true, "paths": { "types": ["../types"] } }, "include": ["./", "../types", "../common/", "../locales/en.json"] } ================================================ FILE: server/utils.ts ================================================ import { SV_LAN } from 'config'; import type { Dict } from 'types'; export function GetPlayerLicense(playerId: number | string) { return ( GetPlayerIdentifierByType(playerId as string, 'license2') || GetPlayerIdentifierByType(playerId as string, 'license') ); } export function GetIdentifiers(playerId: number | string) { const identifiers: Dict = {}; playerId = playerId.toString(); for (let index = 0; index < GetNumPlayerIdentifiers(playerId); index++) { const [prefix, identifier] = GetPlayerIdentifier(playerId, index).split(':'); if (prefix !== 'ip') identifiers[prefix] = identifier; } if (!identifiers.license2) { identifiers.license2 = SV_LAN ? 'fayoum' : identifiers.license; if (!identifiers.license2) throw new Error(`No license2 found for user [${playerId}] ${GetPlayerName(playerId)}`); } return identifiers; } ================================================ FILE: server/vehicle/class.ts ================================================ import { ClassInterface } from "classInterface"; import { DeleteVehicle, IsPlateAvailable, IsVinAvailable, SaveVehicleData, SetVehicleColumn } from "./db"; import { getRandomString, getRandomAlphanumeric, getRandomChar, getRandomInt, type VehicleProperties, } from "@overextended/ox_lib"; import { PLATE_PATTERN } from "../../common/config"; import type { Dict, VehicleData } from "types"; import { GetVehicleData, GetVehicleNetworkType } from "../../common/vehicles"; import { setVehicleProperties } from "@overextended/ox_lib/server"; import { Vector3 } from "@nativewrappers/server"; import { SpawnVehicle } from "vehicle"; export type Vec3 = number[] | { x: number; y: number; z: number } | { buffer: any }; const setEntityOrphanMode = typeof SetEntityOrphanMode !== "undefined" ? SetEntityOrphanMode : () => {}; export class OxVehicle extends ClassInterface { script: string; plate: string; model: string; make: string; id?: number; vin: string; owner?: number; group?: string; entity?: number; netId?: number; #metadata: Dict; #properties: Partial; #stored: string | null; protected static members: Dict = {}; protected static keys: Dict> = { id: {}, netId: {}, entity: {}, }; static spawn(model: string, coords: Vector3, heading?: number) { const entityId = CreateVehicleServerSetter( model, GetVehicleNetworkType(model), coords.x, coords.y, coords.z, heading || 0, ); setEntityOrphanMode(entityId, 2); return entityId; } /** Get an instance of OxVehicle with the matching vin. */ static get(vin: string) { const vehicle = this.members[vin]; if (vehicle) return vehicle; return SpawnVehicle(vin); } /** Get an instance of OxVehicle with the matching vehicleId. */ static getFromVehicleId(vehicleId: number) { return this.keys.id[vehicleId]; } /** Get an instance of OxVehicle with the matching netId. */ static getFromNetId(id: number) { return this.keys.netId[id]; } /** Get an instance of OxVehicle with the matching entityId. */ static getFromEntity(entityId: number) { return this.keys.entity[entityId]; } /** Compares vehicle fields and metadata to a filter, returning the vehicle if all values match. */ private filter(criteria: Dict) { for (const key in criteria) { const value = criteria[key]; if (this[key as keyof OxVehicle] !== value && this.#metadata[key] !== value) return; } return this; } /** Get an instance of OxVehicle that matches the filter. */ static getFromFilter(filter: Dict) { for (const id in this.members) { const vehicle = this.members[id].filter(filter); if (vehicle) return vehicle; } } /** Gets all instances of OxVehicle, optionally comparing against a filter. */ static getAll(filter?: Dict, asArray?: false): Dict; static getAll(filter?: Dict, asArray?: true): OxVehicle[]; static getAll(filter?: Dict, asArray = false): Dict | OxVehicle[] { if (!filter) return asArray ? Object.values(this.members) : this.members; const obj: Dict = {}; for (const id in this.members) { const vehicle = this.members[id].filter(filter); if (vehicle) obj[id] = vehicle; } return asArray ? Object.values(obj) : obj; } static async generateVin({ make, name }: VehicleData, isOwned = true) { if (!name) throw new Error("generateVin received invalid VehicleData (invalid model)"); const arr = [ getRandomInt(), make ? make.slice(0, 2).toUpperCase() : "OX", name.slice(0, 2).toUpperCase(), null, null, Math.floor(Date.now() / 1000), ]; let vin: string; while (true) { arr[3] = getRandomAlphanumeric(); arr[4] = getRandomChar(); vin = arr.join(""); if (!isOwned || (await IsVinAvailable(vin))) break; } return isOwned ? vin : `T${vin}`; } static async generatePlate(pattern: string = PLATE_PATTERN) { while (true) { const plate = getRandomString(pattern); if (await IsPlateAvailable(plate)) return plate; } } static saveAll(resource?: string) { if (resource === "ox_core") resource = ""; const parameters = []; for (const id in this.members) { const vehicle = this.members[id]; if (!resource || resource === vehicle.script) { if (vehicle.owner || vehicle.group) { vehicle.#stored = "impound"; parameters.push(vehicle.#getSaveData()); } vehicle.remove(); } } DEV: console.info(`Saving ${parameters.length} vehicles to the database.`); if (parameters.length > 0) { SaveVehicleData(parameters, true); emit("ox:savedVehicles", parameters.length); } } constructor( vin: string, script: string, plate: string, model: string, make: string, stored: string | null, metadata: Dict, properties: Partial, id?: number, owner?: number, group?: string, ) { super(); this.script = script; this.plate = plate; this.model = model; this.make = make; this.id = id; this.vin = vin; this.owner = owner; this.group = group; this.#properties = properties; this.#metadata = metadata || {}; this.#stored = stored; OxVehicle.add(this.vin, this); } /** Stores a value in the vehicle's metadata. */ set(key: string, value: any) { this.#metadata[key] = value; } /** Gets a value stored in vehicle's metadata. */ get(key: string) { return this.#metadata[key]; } getState() { return this.entity ? Entity(this.entity).state : null; } getStored() { return this.#stored; } getProperties() { return this.#properties; } #getSaveData() { if (!this.id) return; return [this.#stored, JSON.stringify({ ...this.#metadata, properties: this.#properties }), this.id]; } save() { const saveData = this.#getSaveData(); return saveData && SaveVehicleData(saveData); } despawn(save?: boolean) { if (!this.entity) return; emit("ox:despawnVehicle", this.entity, this.id); const saveData = save && this.#getSaveData(); if (saveData) SaveVehicleData(saveData); if (DoesEntityExist(this.entity)) DeleteEntity(this.entity); } delete() { if (this.id) DeleteVehicle(this.id); this.despawn(false); OxVehicle.remove(this.vin); } remove() { this.despawn(true); OxVehicle.remove(this.vin); } setStored(value: string | null, despawn?: boolean) { this.#stored = value; if (despawn) return this.remove(); SetVehicleColumn(this.id, "stored", value); } setOwner(charId?: number) { if (this.owner === charId) return; charId ? (this.owner = charId) : delete this.owner; SetVehicleColumn(this.id, "owner", this.owner); } setGroup(group?: string) { if (this.group === group) return; group ? (this.group = group) : delete this.group; SetVehicleColumn(this.id, "group", this.group); } setPlate(plate: string) { if (this.plate === plate) return; this.plate = plate.length > 8 ? plate.substring(0, 8) : plate.padEnd(8); SetVehicleColumn(this.id, "plate", this.plate); } setProperties(properties: Partial, apply?: boolean) { if (!this.entity) return; this.#properties = typeof properties === "string" ? JSON.parse(properties) : properties; if (apply) setVehicleProperties(this.entity, this.#properties); } respawn(coords?: Vec3, rotation: Vector3 | number = 0): number | null { const hasEntity = !!this.entity && DoesEntityExist(this.entity); if (!coords && hasEntity) { coords = GetEntityCoords(this.entity as number); } else if (coords) { coords = Vector3.fromObject(coords); } if (!coords) return null; rotation = typeof rotation === "number" ? rotation : Vector3.fromObject(rotation || hasEntity ? GetEntityRotation(this.entity as number) : null); // Clean up existing entity and registry entries before spawning new one if (hasEntity) { emit("ox:despawnVehicle", this.entity, this.id); DeleteEntity(this.entity as number); } // Remove from registry before creating new entity to avoid conflicts OxVehicle.remove(this.vin); // Create new entity this.entity = OxVehicle.spawn(this.model, coords as Vector3, typeof rotation === "number" ? rotation : 0); this.netId = NetworkGetNetworkIdFromEntity(this.entity); if (typeof rotation !== "number") SetEntityRotation(this.entity, rotation.x, rotation.y, rotation.z, 2, false); // Re-add to registry after successful spawn OxVehicle.add(this.vin, this); SetVehicleNumberPlateText(this.entity, this.#properties.plate || this.plate); setVehicleProperties(this.entity, this.#properties); emit("ox:spawnedVehicle", this.entity, this.id); const state = this.getState(); if (state) state.set("initVehicle", true, true); return this.entity; } } OxVehicle.init(); exports("SaveAllVehicles", (arg: any) => OxVehicle.saveAll(arg)); exports("GetVehicleFromNetId", (arg: any) => OxVehicle.getFromNetId(arg)); exports("GetVehicleFromVin", (arg: any) => OxVehicle.get(arg)); exports("GetVehicleFromEntity", (arg: any) => OxVehicle.getFromEntity(arg)); exports("GetVehicleFromFilter", (arg: any) => OxVehicle.getFromFilter(arg)); exports("GetVehicles", (arg: any) => OxVehicle.getAll(arg, true)); exports("GenerateVehicleVin", (model: string) => OxVehicle.generateVin(GetVehicleData(model))); exports("GenerateVehiclePlate", (pattern?: string) => OxVehicle.generatePlate(pattern)); ================================================ FILE: server/vehicle/commands.ts ================================================ import { addCommand, triggerClientCallback } from "@overextended/ox_lib/server"; import { OxVehicle } from "./class"; import { sleep } from "@overextended/ox_lib"; import { CreateVehicle } from "vehicle"; import { OxPlayer } from "player/class"; export function DeleteCurrentVehicle(ped: number) { const entity = GetVehiclePedIsIn(ped, false); if (!entity) return; const vehicle = OxVehicle.getFromEntity(entity); if (!vehicle) return DeleteEntity(entity); vehicle.setStored("impound", true); vehicle.remove(); } addCommand<{ model: string; owner?: number }>( "car", async (playerId, args, raw) => { const ped = playerId && GetPlayerPed(playerId as any); if (!ped) return; const player = args.owner ? OxPlayer.get(args.owner) : null; const data = { model: args.model, owner: player?.charId || undefined, }; const vehicle = await CreateVehicle(data, GetEntityCoords(ped), GetEntityHeading(ped)); if (!vehicle.entity) return vehicle.remove(); DeleteCurrentVehicle(ped); await sleep(200); SetPedIntoVehicle(ped, vehicle.entity, -1); }, { help: "Spawn a vehicle with the given model.", params: [ { name: "model", paramType: "string", help: "The vehicle archetype." }, { name: "owner", paramType: "playerId", help: "Create a persistent vehicle owned by the target's active character.", optional: true, }, ], restricted: "group.admin", }, ); addCommand<{ radius?: number; owned?: string }>( "dv", async (playerId, args, raw) => { const ped = GetPlayerPed(playerId as any); if (!args.radius) return DeleteCurrentVehicle(ped); const vehicles = await triggerClientCallback("ox:getNearbyVehicles", playerId, args.radius); if (!vehicles) return; vehicles.forEach((netId) => { const vehicle = OxVehicle.getFromNetId(netId); if (!vehicle) DeleteEntity(NetworkGetEntityFromNetworkId(netId)); else if (args.owned) { vehicle.setStored("impound", true); vehicle.remove(); } }); }, { help: "Deletes your current vehicle, or any vehicles within range.", params: [ { name: "radius", paramType: "number", help: "The radius to despawn vehicles (defaults to 2).", optional: true }, { name: "owned", help: "Include player-owned vehicles.", optional: true }, ], restricted: "group.admin", }, ); ================================================ FILE: server/vehicle/db.ts ================================================ import { db } from "../db"; import type { VehicleProperties } from "@overextended/ox_lib"; import { DEFAULT_VEHICLE_STORE } from "config"; export type VehicleRow = { id: number; owner?: number; group?: string; plate: string; vin: string; model: string; data: { properties: Partial; [key: string]: any }; }; if (DEFAULT_VEHICLE_STORE) setImmediate(() => db.query("UPDATE vehicles SET `stored` = ? WHERE `stored` IS NULL", [DEFAULT_VEHICLE_STORE])); export async function IsPlateAvailable(plate: string) { return !(await db.exists("SELECT 1 FROM vehicles WHERE plate = ?", [plate])); } export async function IsVinAvailable(plate: string) { return !(await db.exists("SELECT 1 FROM vehicles WHERE vin = ?", [plate])); } export async function GetStoredVehicleFromId(id: number | string, column = "id") { const row = await db.row( `SELECT id, owner, \`group\`, plate, vin, model, data FROM vehicles WHERE ${column} = ? AND \`stored\` IS NOT NULL`, [id], ); if (row && typeof row.data === "string") { console.warn( "vehicle.data was selected from the database as a string rather than JSON.\nLet us know if this warning occurred..", ); row.data = JSON.parse(row.data); } return row; } export async function SetVehicleColumn(id: number | void, column: string, value: any) { if (!id) return; return (await db.update(`UPDATE vehicles SET \`${column}\` = ? WHERE id = ?`, [value, id])) === 1; } export function SaveVehicleData( values: any, // -.- batch?: boolean, ) { const query = "UPDATE vehicles SET `stored` = ?, data = ? WHERE id = ?"; return batch ? db.batch(query, values) : db.update(query, values); } export function CreateNewVehicle( plate: string, vin: string, owner: number | null, group: string | null, model: string, vehicleClass: number, data: object, stored: string | null, ) { return db.insert( "INSERT INTO vehicles (plate, vin, owner, `group`, model, class, data, `stored`) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [plate, vin, owner, group, model, vehicleClass, JSON.stringify(data), stored], ); } export async function DeleteVehicle(id: number) { return (await db.update("DELETE FROM vehicles WHERE id = ?", [id])) === 1; } ================================================ FILE: server/vehicle/events.ts ================================================ import { OxVehicle } from './class'; on('onResourceStop', (resource: string) => OxVehicle.saveAll(resource)); on('entityRemoved', (entityId: number) => OxVehicle.getFromEntity(entityId)?.respawn()); ================================================ FILE: server/vehicle/index.ts ================================================ import { OxVehicle, Vec3 } from "./class"; import { CreateNewVehicle, GetStoredVehicleFromId, IsPlateAvailable, type VehicleRow } from "./db"; import { GetVehicleData } from "../../common/vehicles"; import { DEBUG } from "../../common/config"; import "./class"; import "./commands"; import "./events"; import type { VehicleProperties } from "@overextended/ox_lib/server"; if (DEBUG) import("./parser"); export interface CreateVehicleData { model: string; owner?: number; group?: string; stored?: string; properties?: Partial; } export async function CreateVehicle( data: string | (CreateVehicleData & Partial), coords?: Vec3, heading?: number, invokingScript = GetInvokingResource(), ) { if (typeof data === "string") data = { model: data }; const vehicleData = GetVehicleData(data.model as string); if (!vehicleData) throw new Error( `Failed to create vehicle '${data.model}' (model is invalid).\nEnsure vehicle exists in '@ox_core/common/data/vehicles.json'`, ); if (data.id) { const vehicle = OxVehicle.getFromVehicleId(data.id); if (vehicle) { if (vehicle.entity && DoesEntityExist(vehicle.entity)) { return vehicle; } vehicle.despawn(true); } } const isOwned = !!(data.owner || data.group); if (!data.vin) data.vin = await OxVehicle.generateVin(vehicleData, isOwned); data.plate = data.vin && data.plate ? data.plate : data.plate && (await IsPlateAvailable(data.plate)) ? data.plate : await OxVehicle.generatePlate(); const metadata = data.data || ({} as { properties?: Partial; [key: string]: any }); metadata.properties = data.properties || data.data?.properties || ({} as Partial); if (!data.id && data.vin && isOwned) { data.id = await CreateNewVehicle( data.plate, data.vin, data.owner || null, data.group || null, data.model, vehicleData.class, metadata, data.stored || null, ); } const properties = data.properties || metadata.properties || ({} as Partial); delete metadata.properties; const vehicle = new OxVehicle( data.vin, invokingScript, data.plate, data.model, vehicleData.make, data.stored || null, metadata, properties, data.id, data.owner, data.group, ); if (coords) { vehicle.respawn(coords, heading || 0); } if (vehicle.entity) vehicle.setStored(null, false); return vehicle; } export async function SpawnVehicle(id: number | string, coords?: Vec3, heading?: number) { const invokingScript = GetInvokingResource(); const vehicle = await GetStoredVehicleFromId(id, typeof id === "string" ? "vin" : "id"); if (!vehicle) return; return await CreateVehicle(vehicle, coords, heading, invokingScript); } exports("CreateVehicle", CreateVehicle); exports("SpawnVehicle", SpawnVehicle); ================================================ FILE: server/vehicle/parser.ts ================================================ import { addCommand, triggerClientCallback } from "@overextended/ox_lib/server"; import { GetTopVehicleStats, GetVehicleData } from "../../common/vehicles"; import type { VehicleData, TopVehicleStats } from "types"; function SortObjectProperties(obj: object) { return Object.fromEntries(Object.entries(obj).sort()); } addCommand<{ parseAll: boolean }>( "parsevehicles", async (playerId, args) => { const response: [Record, TopVehicleStats, string[]] | void = await triggerClientCallback( "ox:generateVehicleData", playerId, args.parseAll, ); if (!response) return; const updatedVehicles = args.parseAll ? SortObjectProperties({ ...response[0] }) : SortObjectProperties({ ...GetVehicleData(), ...response[0] }); const updatedStats = args.parseAll ? SortObjectProperties({ ...response[1] }) : SortObjectProperties({ ...GetTopVehicleStats(), ...response[1] }); if (response[2].length) console.log( `^3Failed to parse data for ${response[2].length} invalid vehicles.\n${JSON.stringify(response[2], null, 2)}^0`, ); SaveResourceFile("ox_core", "/common/data/vehicleStats.json", JSON.stringify(updatedStats, null, 2), -1); SaveResourceFile("ox_core", "/common/data/vehicles.json", JSON.stringify(updatedVehicles, null, 2), -1); }, { help: "Parses and generates vehicle data for all vehicle models available on a client.", params: [{ name: "parseAll", optional: true, help: "Include vehicles with existing data in the data generation." }], restricted: "group.admin", }, ); ================================================ FILE: sql/install.sql ================================================ /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET NAMES utf8 */; /*!50503 SET NAMES utf8mb4 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; CREATE DATABASE IF NOT EXISTS `overextended` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE `overextended`; CREATE TABLE IF NOT EXISTS `users` ( `userId` INT UNSIGNED NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) DEFAULT NULL, `license2` VARCHAR(50) DEFAULT NULL, `steam` VARCHAR(20) DEFAULT NULL, `fivem` VARCHAR(10) DEFAULT NULL, `discord` VARCHAR(20) DEFAULT NULL, PRIMARY KEY (`userId`) ); CREATE TABLE IF NOT EXISTS `characters` ( `charId` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `userId` INT UNSIGNED NOT NULL, `stateId` VARCHAR(7) NOT NULL, `firstName` VARCHAR(50) NOT NULL, `lastName` VARCHAR(50) NOT NULL, `fullName` VARCHAR(101) AS (CONCAT(`firstName`, ' ', `lastName`)) STORED, `gender` VARCHAR(10) NOT NULL, `dateOfBirth` DATE NOT NULL, `phoneNumber` VARCHAR(20) NULL, `lastPlayed` DATETIME DEFAULT CURRENT_TIMESTAMP() NOT NULL, `isDead` TINYINT(1) DEFAULT 0 NOT NULL, `x` FLOAT NULL, `y` FLOAT NULL, `z` FLOAT NULL, `heading` FLOAT NULL, `health` TINYINT UNSIGNED NULL, `armour` TINYINT UNSIGNED NULL, `statuses` LONGTEXT COLLATE utf8mb4_bin DEFAULT JSON_OBJECT() NOT NULL CHECK (JSON_VALID(`statuses`)), `deleted` DATE NULL, CONSTRAINT `characters_stateId_unique` UNIQUE (`stateId`), CONSTRAINT `characters_userId_fk` FOREIGN KEY (`userId`) REFERENCES `users` (`userId`) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE FULLTEXT INDEX IF NOT EXISTS `characters_fullName_index` ON `characters` (`fullName`); CREATE INDEX IF NOT EXISTS `characters_userId_key` ON `characters` (`userId`); CREATE TABLE IF NOT EXISTS `character_inventory` ( `charId` INT UNSIGNED NOT NULL, `inventory` JSON NULL DEFAULT NULL, PRIMARY KEY (`charId`), KEY `character_inventory_charId_key` (`charId`), CONSTRAINT `character_inventory_charId_fk` FOREIGN KEY (`charId`) REFERENCES `characters` (`charId`) ON DELETE CASCADE ON UPDATE CASCADE ); CREATE TABLE IF NOT EXISTS `ox_groups` ( `name` VARCHAR(20) NOT NULL, `label` VARCHAR(50) NOT NULL, `type` VARCHAR(50) NULL, `colour` TINYINT UNSIGNED DEFAULT NULL, `hasAccount` TINYINT(1) NOT NULL DEFAULT '0', PRIMARY KEY (`name`) ); CREATE TABLE IF NOT EXISTS `character_groups` ( `charId` INT UNSIGNED NOT NULL, `name` VARCHAR(20) NOT NULL, `grade` TINYINT UNSIGNED NOT NULL DEFAULT 1, `isActive` TINYINT(1) NOT NULL DEFAULT 0, UNIQUE KEY `name` (`name`, `charId`), KEY `character_groups_charId_key` (`charId`), CONSTRAINT `character_groups_charId_fk` FOREIGN KEY (`charId`) REFERENCES `characters` (`charId`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `character_groups_name_fk` FOREIGN KEY (`name`) REFERENCES `ox_groups` (`name`) ON DELETE CASCADE ON UPDATE CASCADE ); CREATE TABLE IF NOT EXISTS `ox_inventory` ( `owner` VARCHAR(60) DEFAULT NULL, `name` VARCHAR(60) NOT NULL, `data` JSON DEFAULT NULL, `lastupdated` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY `owner` (`owner`, `name`) ); CREATE TABLE IF NOT EXISTS `vehicles` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `plate` CHAR(8) NOT NULL DEFAULT '', `vin` CHAR(17) NOT NULL, `owner` INT UNSIGNED NULL DEFAULT NULL, `group` VARCHAR(20) NULL DEFAULT NULL, `model` VARCHAR(20) NOT NULL, `class` TINYINT UNSIGNED NULL DEFAULT NULL, `data` JSON NOT NULL, `trunk` JSON NULL DEFAULT NULL, `glovebox` JSON NULL DEFAULT NULL, `stored` VARCHAR(50) NULL DEFAULT NULL, PRIMARY KEY (`id`), UNIQUE KEY `plate` (`plate`), UNIQUE KEY `vin` (`vin`), KEY `vehicles_owner_key` (`owner`), CONSTRAINT `vehicles_owner_fk` FOREIGN KEY (`owner`) REFERENCES `characters` (`charId`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `vehicles_group_fk` FOREIGN KEY (`group`) REFERENCES `ox_groups` (`name`) ON DELETE CASCADE ON UPDATE CASCADE ); CREATE TABLE IF NOT EXISTS `ox_statuses` ( `name` VARCHAR(20) NOT NULL, `default` TINYINT (3) UNSIGNED NOT NULL DEFAULT 0, `onTick` DECIMAL(8, 7) DEFAULT 0 ); INSERT INTO `ox_statuses` (`name`, `default`, `onTick`) VALUES ('hunger', 0, 0.02), ('thirst', 0, 0.05), ('stress', 0, -0.10); CREATE TABLE IF NOT EXISTS `ox_licenses` ( `name` VARCHAR(20) NOT NULL, `label` VARCHAR(50) NOT NULL, UNIQUE KEY `name` (`name`) ); INSERT INTO `ox_licenses` (`name`, `label`) VALUES ('weapon', 'Weapon License'), ('driver', "Driver's License"); CREATE TABLE IF NOT EXISTS `character_licenses` ( `charId` INT UNSIGNED NOT NULL, `name` VARCHAR(20) DEFAULT NULL, `data` JSON NOT NULL DEFAULT (JSON_OBJECT()), UNIQUE KEY `name` (`name`, `charId`), KEY `character_licenses_charId_key` (`charId`), CONSTRAINT `character_licenses_charId_fk` FOREIGN KEY (`charId`) REFERENCES `characters` (`charId`) ON DELETE CASCADE ON UPDATE CASCADE ); CREATE TABLE IF NOT EXISTS `accounts` ( `id` INT UNSIGNED NOT NULL PRIMARY KEY, `label` VARCHAR(50) NOT NULL, `owner` INT UNSIGNED NULL, `group` VARCHAR(20) NULL, `balance` INT DEFAULT 0 NOT NULL, `isDefault` TINYINT(1) DEFAULT 0 NOT NULL, `type` ENUM ('personal', 'shared', 'group', 'inactive') DEFAULT 'personal' NOT NULL, CONSTRAINT `accounts_group_fk` FOREIGN KEY (`group`) REFERENCES `ox_groups` (`name`) ON UPDATE SET NULL ON DELETE SET NULL, CONSTRAINT `accounts_owner_fk` FOREIGN KEY (`owner`) REFERENCES `characters` (`charId`) ON UPDATE SET NULL ON DELETE SET NULL ); CREATE FULLTEXT INDEX IF NOT EXISTS `accounts_label_index` ON `accounts` (`label`); CREATE TABLE `account_roles` ( `id` INT(11) NOT NULL AUTO_INCREMENT, `name` VARCHAR(50) NOT NULL DEFAULT '', `deposit` TINYINT(1) NOT NULL DEFAULT '0', `withdraw` TINYINT(1) NOT NULL DEFAULT '0', `addUser` TINYINT(1) NOT NULL DEFAULT '0', `removeUser` TINYINT(1) NOT NULL DEFAULT '0', `manageUser` TINYINT(1) NOT NULL DEFAULT '0', `transferOwnership` TINYINT(1) NOT NULL DEFAULT '0', `viewHistory` TINYINT(1) NOT NULL DEFAULT '0', `manageAccount` TINYINT(1) NOT NULL DEFAULT '0', `closeAccount` TINYINT(1) NOT NULL DEFAULT '0', `sendInvoice` TINYINT(1) NOT NULL DEFAULT '0', `payInvoice` TINYINT(1) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE INDEX `name` (`name`) ); INSERT INTO `account_roles` (`id`, `name`, `deposit`, `withdraw`, `addUser`, `removeUser`, `manageUser`, `transferOwnership`, `viewHistory`, `manageAccount`, `closeAccount`, `sendInvoice`, `payInvoice`) VALUES (1, 'viewer', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), (2, 'contributor', 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0), (3, 'manager', 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1), (4, 'owner', 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1); CREATE TABLE IF NOT EXISTS `ox_group_grades` ( `group` VARCHAR(20) NOT NULL, `grade` TINYINT UNSIGNED NOT NULL DEFAULT 1, `label` VARCHAR(50) NOT NULL, `accountRole` VARCHAR(50) NULL DEFAULT NULL, PRIMARY KEY (`group`, `grade`), CONSTRAINT `ox_group_grades_group_fk` FOREIGN KEY (`group`) REFERENCES `ox_groups` (`name`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `FK_ox_group_grades_account_roles` FOREIGN KEY (`accountRole`) REFERENCES `account_roles` (`name`) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS `accounts_access` ( `accountId` INT UNSIGNED NOT NULL, `charId` INT UNSIGNED NOT NULL, `role` VARCHAR(50) NOT NULL DEFAULT 'viewer', PRIMARY KEY (`accountId`, `charId`), CONSTRAINT `accounts_access_accountId_fk` FOREIGN KEY (`accountId`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `accounts_access_charId_fk` FOREIGN KEY (`charId`) REFERENCES `characters` (`charId`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `FK_accounts_access_account_roles` FOREIGN KEY (`role`) REFERENCES `account_roles` (`name`) ON UPDATE CASCADE ON DELETE CASCADE ); CREATE TABLE IF NOT EXISTS `accounts_transactions` ( `id` INT UNSIGNED NOT NULL AUTO_INCREMENT, `actorId` INT UNSIGNED DEFAULT NULL, `fromId` INT UNSIGNED DEFAULT NULL, `toId` INT UNSIGNED DEFAULT NULL, `amount` INT NOT NULL, `message` VARCHAR(255) NOT NULL, `note` VARCHAR(255) DEFAULT NULL, `fromBalance` INT DEFAULT NULL, `toBalance` INT DEFAULT NULL, `date` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), CONSTRAINT `accounts_transactions_actorId_fk` FOREIGN KEY (`actorId`) REFERENCES `characters` (`charId`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `accounts_transactions_fromId_fk` FOREIGN KEY (`fromId`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE, CONSTRAINT `accounts_transactions_toId_fk` FOREIGN KEY (`toId`) REFERENCES `accounts` (`id`) ON DELETE CASCADE ON UPDATE CASCADE ); CREATE FULLTEXT INDEX IF NOT EXISTS `accounts_transactions_message_index` ON `accounts_transactions` (`message`); CREATE TABLE IF NOT EXISTS `accounts_invoices` ( `id` INT UNSIGNED AUTO_INCREMENT PRIMARY KEY, `actorId` INT UNSIGNED NULL, `payerId` INT UNSIGNED NULL, `fromAccount` INT UNSIGNED NOT NULL, `toAccount` INT UNSIGNED NOT NULL, `amount` INT UNSIGNED NOT NULL, `message` VARCHAR(255) NULL, `sentAt` TIMESTAMP DEFAULT CURRENT_TIMESTAMP() NOT NULL, `dueDate` TIMESTAMP NOT NULL, `paidAt` TIMESTAMP NULL, CONSTRAINT `accounts_invoices_accounts_id_fk` FOREIGN KEY (`fromAccount`) REFERENCES `accounts` (`id`), CONSTRAINT `accounts_invoices_accounts_id_fk_2` FOREIGN KEY (`toAccount`) REFERENCES `accounts` (`id`), CONSTRAINT `accounts_invoices_characters_charId_fk` FOREIGN KEY (`payerId`) REFERENCES `characters` (`charId`), CONSTRAINT `accounts_invoices_characters_charId_fk_2` FOREIGN KEY (`actorId`) REFERENCES `characters` (`charId`) ); CREATE FULLTEXT INDEX IF NOT EXISTS `idx_message_fulltext` ON `accounts_invoices` (`message`); /*!40101 SET SQL_MODE=IFNULL(@OLD_SQL_MODE, '') */; /*!40014 SET FOREIGN_KEY_CHECKS=IFNULL(@OLD_FOREIGN_KEY_CHECKS, 1) */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40111 SET SQL_NOTES=IFNULL(@OLD_SQL_NOTES, 1) */; ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "baseUrl": ".", "rootDir": ".", "noImplicitAny": true, "strictNullChecks": true, "module": "es2022", "target": "es2023", "lib": ["es2023", "esnext.disposable"], "types": ["@citizenfx/client", "@citizenfx/server"], "resolveJsonModule": true, "esModuleInterop": true, "allowUnreachableCode": false, "strictFunctionTypes": true, "moduleResolution": "bundler", "noImplicitThis": true, "noUnusedLocals": true, "skipLibCheck": true, "declaration": true, "noEmit": false, "emitDeclarationOnly": true, "removeComments": true, "outDir": "./package/" }, "include": ["./", "./types/"], "references": [{ "path": "./lib" }] } ================================================ FILE: types/index.ts ================================================ export type Dict = { [key: string]: T }; export interface Character { charId: number; stateId: string; firstName: string; lastName: string; gender: string; x?: number; y?: number; z?: number; heading?: number; lastPlayed?: string; health?: number; armour?: number; isNew?: boolean; } export interface NewCharacter { firstName: string; lastName: string; gender: string; date: number; } export interface PlayerMetadata { name: string; firstName: string; lastName: string; gender: string; dateOfBirth: string; phoneNumber: string; activeGroup?: string; } export interface CharacterLicense { issued: number; suspended?: [number, number]; [key: string]: any; } export type Vehicles = Dict; export type VehicleCategories = 'air' | 'land' | 'sea'; export type TopVehicleStats = Record; export interface VehicleStats { acceleration: number; braking: number; handling: number; speed: number; traction: number; } export enum VehicleClasses { COMPACT, SEDAN, SUV, COUPE, MUSCLE, SPORTS_CLASSIC, SPORTS, SUPER, MOTORCYCLE, OFFROAD, INDUSTRIAL, UTILITY, VANS, CYCLES, BOATS, HELICOPTERS, PLANES, SERVICE, EMERGENCY, MILITARY, COMMERCIAL, TRAINS, OPEN_WHEEL, } export type VehicleTypes = | 'amphibious_automobile' | 'amphibious_quadbike' | 'automobile' | 'bicycle' | 'bike' | 'blimp' | 'boat' | 'heli' | 'plane' | 'quadbike' | 'submarine' | 'submarinecar' | 'trailer' | 'train'; export interface VehicleData extends VehicleStats { class: VehicleClasses; doors: number; make: string; name: string; price: number; seats: number; type: VehicleTypes; category: VehicleCategories; weapons?: true; [key: string]: unknown; } export interface OxLicense { name?: string; label?: string; } export interface OxStatus { name: string; default: number; onTick: number; } export interface OxAccountMetadata { id: number; balance: number; isDefault: boolean; label: string; type: 'personal' | 'shared' | 'group'; owner?: number; group?: string; } export interface OxAccountUserMetadata extends OxAccountMetadata { role: OxAccountRole; ownerName: string; } export interface DbGroup { name: string; label: string; grades: string[]; accountRoles: Dict; type?: string; colour?: number; hasAccount: boolean; activePlayers: Set; } export interface OxGroup extends DbGroup { grades: string[]; principal: string; } export interface CreateGroupProperties { name: string; label: string; grades: { label: string; accountRole?: OxAccountRole; }[]; type?: string; colour?: number; hasAccount?: boolean; } export interface OxGroupPermissions { [grade: string]: { [permission: string]: boolean }; } export type OxAccountRole = 'viewer' | 'contributor' | 'manager' | 'owner'; export interface OxAccountPermissions { deposit: boolean; withdraw: boolean; addUser: boolean; removeUser: boolean; manageUser: boolean; transferOwnership: boolean; viewHistory: boolean; manageAccount: boolean; closeAccount: boolean; sendInvoice: boolean; payInvoice: boolean; } export interface OxAccountInvoice { id: number; actorId?: number; payerId?: number; fromAccount: number; toAccount: number; amount: number; message?: string; sentAt: number; dueDate: number; paidAt?: number; } export interface OxCreateInvoice { /** The charId of the player creating the invoice. */ actorId?: number; /** The accountId of the account issuing the invoice. */ fromAccount: number; /** The accountId of the account receiving the invoice. */ toAccount: number; amount: number; message: string; dueDate: string; } export interface BanDetails { userId: number; token?: string; reason?: string; banned_at: number; unban_at?: number; }