How a Game Works#
Every match of air hockey follows the same loop, whether it is a practice game against the bot or a duel with another player:
Moment | What happens |
|---|---|
At the table | Within 2.5 m of a table's centre, [E] - Play Air Hockey floats above it. |
Paying | If the table has an entry fee, it is taken as the game starts — from cash or the bank, as set in the creator. In a duel both players pay. |
At the rail | Your view drops behind your end of the table and the mouse takes the paddle. You defend the goal in front of you and attack the one at the far end. |
Goals | A puck that crosses an end rail inside the goal mouth scores. The puck goes back to the centre and play resumes a moment later. |
The finish | First to 10 goals wins. If the 3-minute clock runs out first, the leader wins — or it is a draw. The result fills the screen and you are back on your feet. |
In a duel the challenger's game is the referee: it moves the puck, counts the goals and sends the state to the other player and to everyone watching, so both sides always agree on the score.
Placing Tables#
An air hockey table can come from three places, and all three play the same once they are in the world:
Source | How | Kept |
|---|---|---|
The creator |
| In the database, with a name and an entry fee. |
The config | One line per table in | In |
The map | Any | Nowhere — found again every time. Free to play until edited in the creator. |
The creator#
/ahcreator opens the Air Hockey Table Creator: every table on the server on the left, each with its entry fee and account — or Free — a search box that matches names and IDs, and + for a new table. Selecting a table opens it on the right:
Section | What you set there |
|---|---|
Information | The table's name. Required. |
Price | The entry fee, and whether it comes from Cash or Bank. |
Placement / Location | Where the table stands. Place — or Reposition on an existing table — hands you the aiming mode below. |
Save table needs a name and a location. An existing table is saved with Save changes, or removed for everyone with Delete after a confirmation. The teleport button next to each table in the list takes you straight to it. Escape closes the window.
Setting a table down#
Place closes the window and hands you an aiming mode. A see-through table follows the point your camera is looking at — up to 50 m away — with a red line from you to it, and the keys are shown in the corner of the screen:
Key | Does |
|---|---|
| Set it down here. |
| Rotate it, for as long as the key is held. |
| Raise or lower it, 5 cm per notch. |
| Cancel and go back to the window. |
The creator reopens with the location filled in. Every save also prints a ready-made Config.Tables line in the F8 console, in case you would rather keep the table in the config.
Leave room at both ends. Each player stands about half a metre beyond an end rail, and the camera sits a little further back. A table pushed against a wall puts that player — and their camera — inside it.
Tables from the config and from maps#
Config.Tables takes one line per table — where it stands and the way it faces:
Config.Tables = {
{ coords = vec3(-1650.32, -1063.10, 12.15), heading = 50.0 },
}Each player's game spawns the table when they come within 30 m (Config.StreamDistance). With Config.AutoDetect = true, every player's game also looks for the nearest prop_airhockey_01 within that distance every two seconds and makes it playable — so arcades and MLOs that already have one need nothing else.
To charge for one of these tables, open it in the creator, give it a name and a fee, and press Save changes. From then on it is stored like any other.
Starting a Game#
Walk up to a table — the prompt appears within 2.5 m of its centre (Config.InteractDistance) — and press E.
Player vs Bot. If the table has an entry fee it is taken now; without the money the game does not start. The bot steps up to the far end of the table and the match begins at once, with the clock running.
Player vs Player → pick an opponent. The list shows everybody within 15 m of the table (Config.NearbyPlayerRadius), nearest first, with their character name, server ID and distance.
They see a Challenge received window with your name and have 30 seconds to Accept or Decline. On Accept both players pay the entry fee, the challenger takes the red paddle at one end, the challenged player is taken to the blue paddle at the other, and the match starts for both.
Playing#
The camera settles behind your end of the table, looking down the surface, and your character leans over the rail with the paddle in hand. Move the mouse and the paddle follows — across the width of the table and towards or away from you. It stays inside the rails and in your own half: it cannot cross the centre line. Mouse speed is Config.Mouse.sensitivity.
The puck#
The puck is simulated, not animated, and every number lives in Config.Physics:
Behaviour | How it plays |
|---|---|
Glide | Almost no friction ( |
Rebounds | Off the rails and the paddles, losing almost nothing ( |
Hits | A moving paddle passes 75% of its movement to the puck ( |
Speed | Never above 7.5 m/s ( |
Collisions are checked in up to twelve small steps per frame, so a hard shot never slips through a paddle or a rail. Rails, hits and goals each have their own sound.
Goals and winning#
The goal mouth is 44 cm wide, in the middle of each end rail (Config.Table.goalHalfWidth is half of it). After a goal, GOAL! You scored! or GOAL! Opponent scored! appears, the scoreboard flashes, and the puck waits in the centre while play pauses for just over a second.
The first player to Config.WinScore goals — 10 by default — wins, after a three-second pause on the winning goal. The match clock is Config.RoundTime, 180 seconds: when it reaches zero the higher score wins, and a level score is a draw. Set it to 0 to play to the score only.
The Screen During a Game#
Where | What is there |
|---|---|
Top centre | The scoreboard: the red side and the blue side, each labelled You or Opponent with its goals, and the TIME left between them. The clock flashes in the last 30 seconds and is hidden when there is no time limit. |
Corner | The keys: Exit ( |
Notifications | Every goal, the result, and anything that ends the game early. |
Centre | At the end: YOU WIN!, YOU LOSE or DRAW! for four seconds, while the camera returns and you are back on your feet. |
Entry Fees#
An entry fee belongs to a table and is set in the creator, together with the account it comes from — Cash or Bank. It is taken when the game starts: from the player in a game against the bot, and from both players when a challenge is accepted. The challenger must be able to pay to send a challenge, and both are checked again on Accept.
There are no bets and no prize: the fee is the price of the table, and it is never given back — not for leaving early, a draw or a disconnect. On a standalone server nobody is charged unless you define Config.CanAfford and Config.RemoveMoney, which can also take the fee in your own currency — see Integrations.
Leaving a Game#
Way | Result |
|---|---|
| The Leave Match dialog asks first. The game ends at once with no result. In a duel the other player's game ends too, with Opponent disconnected. |
Disconnecting | The same for the opponent: Opponent disconnected. and the match is over. |
The table is deleted | Everyone at it sees The game has ended. and is returned to normal play. |
The resource stops | The camera, the HUD and your character are restored, and the paddles and puck are removed. |
Spectators#
Air hockey is made to be watched. Everyone within 20 m of a table (Config.SpectateDistance) who is not playing receives the match ten times a second (Config.SnapshotMs), smoothed on their screen: the puck sliding, both paddles moving, and both players' characters leaning over the table and following their paddles. A game against the bot shows the bot too, and somebody who walks up halfway through sees the match from that moment.
During a duel each player sees a copy of their opponent's character posed at the far end. If that player has not loaded on your screen yet, a stand-in from Config.FallbackPedModels plays in their place.
The Bot#
The bot plays from the far end with the blue paddle. When the puck heads its way it works out where it will arrive — bank shots off the side rails included — and gets there first. When a puck sits loose in its half, it goes after it at almost one and a half times its normal speed. Otherwise it returns to the line it defends. A small, changing error keeps it beatable.
Config.NPC | Default | Effect |
|---|---|---|
|
| How fast its paddle can move, in metres per second. Higher is harder. |
|
| From 0 to 1, the line it waits on: at 0 halfway into its half, and the higher the value, the closer to its own goal. |
|
| How far off its aim can be, in metres, changing several times a second. Lower is harder. |
Calibration#
The geometry in the config — the size of the playing surface, the goal mouths, and the height of the puck and paddles above the table — ships measured for prop_airhockey_01. You only need the calibration tools if you use another table model, or the puck floats above or sinks into the surface.
Turn debug on
Set
Config.Debug = trueand restartqs-airhockey. The tools refuse to run while it is off. Use a test server, or a moment when nobody else is on.Check the models
/ahchecklists in the F8 console which table, paddle and puck models exist in your game. Make sure at least one puck model saysvalid=true.Show the playing area
Stand next to the table and type
/ahbounds. The rails are drawn in white, the goal mouths in orange, the centre line in blue and the corners in yellow. They should sit exactly on the table's own markings.Adjust
Nudge each value with
/ahtuneuntil the lines match — for example/ahtune gw 0.01widens the goals by a centimetre each side,/ahtune hy -0.02shortens the table./ahupand/ahdownmove the puck's surface 3 cm at a time. Play a game against the bot to check: with debug on, the goal posts are marked in orange.Save it in the config
Every change prints a complete
Config.Tableline in F8. The tools only change your own screen, so copy that line intoconfig/config.lua, setConfig.Debugback tofalseand restart the resource.bash [qs-airhockey] ahtune -> Config.Table = { half={x=0.540, y=0.980}, goalHalfWidth=0.220, surfaceZOffset=0.810, paddleZOffset=0.850 }
/ahtune field | Config.Table value | Calibrates |
|---|---|---|
|
| Half the width of the playing surface — where the side rails are. |
|
| Half the length — where the end rails and the goals are. |
|
| Half the width of each goal mouth. |
|
| Height of the puck above the table's base. |
|
| Height of the paddles, slightly above the puck. |
The player's view is set by hand in Config.Camera: how far behind the rail it sits, how high, how steeply it looks down and its field of view. Every command and key is on Commands and Controls.



