Before You Begin#
Before installing Ping Pong, make sure your server meets the minimum requirements. Four things are mandatory; everything else is optional and detected on its own.
Requirement | Status | Notes |
|---|---|---|
OneSync | Required | Declared as a dependency — the resource does not start without it. |
| Required | Stores the tables placed with the creator and the leaderboard. |
| Required | Callbacks, the table menu, notifications and the spectator zones. |
| Required | Included in the download. Streams the racket models and is declared as a dependency. |
Framework | Optional |
|
| Optional | Table options on the target eye instead of the [E] - Ping Pong prompt. Off by default. |
| Optional | Notifications in its style instead of ox_lib's. |
| Optional | Draws the [E] - Ping Pong prompt instead of the built-in 3D text. |
Downloading Ping Pong#
After purchasing Ping Pong, the resource is automatically linked to your CFX Portal account.
Sign in to the CFX Portal using the same account used during checkout, open Granted Assets, and download Ping Pong.
After extracting the download, you should have two folders:
qs-pingpong/
├── config/ -- main.lua: everything you may edit
├── custom/ -- money, names and identifiers per framework (editable)
├── client/custom/ -- notifications, the 3D prompt and ox_target (editable)
├── server/custom/ -- the debug console commands (editable)
├── database/ -- sql.sql, to import once
├── locales/ -- 41 languages
└── web/ -- the interface, ready to use
pingpong-objects/ -- streams the racket modelsOnly
config/,custom/,client/custom/,server/custom/andlocales/are open for editing. Everything else is protected by the CFX asset escrow and is replaced as a whole by each update. Keep both folder names as they are:qs-pingpongasks forpingpong-objectsby name.
Installing the Resource#
Move both folders into your server's resources directory. They can live anywhere inside it, including a category folder:
resources/
└── [qs]/
├── pingpong-objects/
└── qs-pingpong/Once copied, open your server.cfg and make sure every dependency — and everything it should detect — starts before Ping Pong:
ensure oxmysql
ensure ox_lib
# Framework
ensure es_extended
# ensure qb-core
# ensure qbx_core
# Optional
# ensure ox_target
ensure qs-interface
ensure qs-textui
ensure pingpong-objects
ensure qs-pingpongThe loading order matters. The framework and qs-textui are detected once, when Ping Pong starts, so one that starts later is not found: a late framework leaves Ping Pong in standalone mode — bets move no money — and a late qs-textui leaves the built-in prompt in place.
Importing the Database#
Ping Pong does not create its tables on its own. Import database/sql.sql once, before the first start, with your database manager — HeidiSQL, phpMyAdmin, DBeaver or Navicat — or the MySQL command line. It is safe to run again: both statements only create what is missing.
CREATE TABLE IF NOT EXISTS `qs_pingpong_tables` (
`id` VARCHAR(32) NOT NULL,
`coords` LONGTEXT DEFAULT NULL,
`model` VARCHAR(64) NOT NULL,
`label` VARCHAR(128) NOT NULL DEFAULT '',
`price` INT NOT NULL DEFAULT 0,
`payment_method` VARCHAR(16) NOT NULL DEFAULT 'money',
`allowed_modes` TEXT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
CREATE TABLE IF NOT EXISTS `qs_pingpong_leaderboard` (
`identifier` VARCHAR(60) PRIMARY KEY,
`name` VARCHAR(50) NOT NULL,
`elo` INT DEFAULT 1200,
`wins` INT DEFAULT 0,
`losses` INT DEFAULT 0,
`streak` INT DEFAULT 0,
`best_streak` INT DEFAULT 0,
`last_played` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);Table | Holds |
|---|---|
| One row per table placed with the creator: its ID (worked out from where it stands), position and heading as JSON in |
| One row per player who has finished a rated duel: their name, ELO rating (1200 to start), wins, losses, current and best winning streak, and when they last played. |
Granting Creator Access#
The table creator is behind an ACE permission, command.pingpongcreator by default. Give it to your admin group in server.cfg and put yourself in that group:
add_ace group.admin command.pingpongcreator allow
add_principal identifier.license:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx group.adminIf your staff are already in group.admin, the first line is all you need. Aces in server.cfg are read when it is executed, so restart the server after adding them.
The permission's name is Config.Creator.permission. Set it to an ace of your own and grant that one instead:
Config.Creator = {
permission = 'qs-pingpong.admin',
model = `prop_table_tennis`,
rotateSpeed = 80.0,
heightStep = 0.05,
}The server checks the same ace again on every save, move and delete, so a player without it cannot change tables even by sending the events by hand.
Configuring the Resource#
Everything lives in one commented file, config/main.lua. It is read when the resource starts, so restart qs-pingpong after editing it.
Section | Contents |
|---|---|
| The language — one of 41 codes — and debug output with its two console commands. |
| How close a player must be to use a table (2.5 m), to see it (30 m), to be challenged at it (15 m) and to watch a match (15 m). |
| ox_target options instead of the [E] prompt. |
| Bets on or off, the stake range and slider step, and how much a win against the bot pays per difficulty and speed. |
| Rating on or off, the shortest duel that counts, how many players are listed, the starting rating and the K-factor. |
| The modes on offer and their score limits, the ball speeds on offer, and the three bots' reaction time, miss chance and aim error. |
| Gravity, the racket's size, the cooldown between hits and the grace period after a serve; how the ball bounces and how fast it travels at each speed; and how the mouse maps onto your half of the table. |
| How often a duel is synchronised (every 16 ms) and how the second player's ball is pulled back into line. |
| The camera behind each end of the table; the landing ring, swing arrow and hit flash; and the hit, win and lose sounds. |
| Where each player stands, how the racket sits in a ped's hand, the hit, serve and reaction animations, and the characters used for the bot and for stand-ins. |
| The table and racket models, and the creator's permission, model, rotation speed and height step. |
| The interact key ( |
The values worth reviewing first:
Config.Locale = 'en' -- ar, bg, bn ... uk, ur, vi, zh
Config.Debug = false -- ships as true: turn it off on a live server
Config.UseTarget = false -- true = ox_target options instead of the [E] prompt
Config.Betting = {
enabled = true,
min = 100, -- smallest stake accepted against the bot
max = 10000, -- top of the stake slider
step = 100, -- slider step
}
Config.Leaderboard = {
enabled = true,
minMatchDuration = 15, -- seconds; shorter duels are not rated
topCount = 20, -- players listed
}
Config.ELO = {
base = 1200, -- starting rating
k = 32, -- how far one duel moves a rating
}
Config.Debugships switched on. Besides extra console output, it registers two console commands that reset or seed the leaderboard, and draws the spectator zone around every match in progress for everyone to see. Turn it off before opening the server to players.
A few keys in the file have no effect in this version: Config.AutoDetect, Config.TableModel, Config.ChallengeTimeout (invitations always last 30 seconds), Config.PeerJS, the multiplier values of Config.SpeedLevels (ball speed comes from Config.Ball.speeds), and the leaderboard, mouseLock and cameraToggle entries of Config.Keys.
Verifying the Installation#
Restart your server and read the console. On a working install you will see lines like these:
[qs-pingpong:INFO] Locale loaded: en (14 keys)
[qs-pingpong:INFO] Server framework loaded: esx
[qs-pingpong:INFO] Loaded 0 ping pong table(s) from SQL.
[qs-pingpong:INFO] Matchmaking initialized with 0 tablesServer framework loaded names the mode Ping Pong picked — esx, qb (QBCore and Qbox) or standalone when it found none. The table count is how many tables the creator has saved; if that line never appears, or an SQL error appears instead, the database was not imported or oxmysql is not connected.
Then join your server and work through the first loop:
Place a table
Type
/pingpongcreator, press +, name the table and press Select. Aim at open floor, turn it withZ/X, left click to place it, then Save Table.Play the bot
Walk up to the table and press
E→ Play vs Bot → Ready. When the ball is tossed at your end, move the mouse to bring the racket into it and flick forward to send it over.Challenge somebody
With a second player within 15 m of the table, choose Play vs Player, pick them from the list, choose the settings and press Ready. They accept on their own screen and the match starts for both.
Nothing in the creator needs a restart: new tables, names, moves and deletions reach every player at once. Usage covers everything a match can do.



