# Air Hockey

{% embed url="<https://www.youtube.com/watch?v=I09ghJr8YS0>" %}

## Purchase

[Purchase this resource](https://rtx.tebex.io/package/7349555)

## Installation

{% hint style="info" %}
**Framework compatibility**

This resource is **standalone** and works on any FiveM server.

Compatible with **ESX**, **QBCore**, **QBox**, **vRP**, and other frameworks.

If you are using a framework other than ESX, QBCore, or QBox, follow the **Standalone** installation instructions.
{% endhint %}

### Standalone

{% stepper %}
{% step %}

### Standalone installation

* Put the `rtx_airhockey` folder into your `resources`.
* Open `config.lua`.
* Configure your `config.lua` to your preferences.
* Add `rtx_airhockey` to your `server.cfg`.
* Put the `rtx_airhockey_objects` folder into your `resources`.
* Add `rtx_airhockey_objects` to your `server.cfg`.
  {% endstep %}
  {% endstepper %}

### QBCore

{% stepper %}
{% step %}

### QBCore installation

* Put the `rtx_airhockey` folder into your `resources`.
* Open `config.lua`.
* Replace `Config.Framework = "standalone"` with `Config.Framework = "qbcore"`.
* Configure your `config.lua` to your preferences.
* Add `rtx_airhockey` to your `server.cfg`.
* Put the `rtx_airhockey_objects` folder into your `resources`.
* Add `rtx_airhockey_objects` to your `server.cfg`.
  {% endstep %}
  {% endstepper %}

### ESX

{% stepper %}
{% step %}

### ESX installation

* Put the `rtx_airhockey` folder into your `resources`.
* Open `config.lua`.
* Replace `Config.Framework = "standalone"` with `Config.Framework = "esx"`.
* Configure your `config.lua` to your preferences.
* Add `rtx_airhockey` to your `server.cfg`.
* Put the `rtx_airhockey_objects` folder into your `resources`.
* Add `rtx_airhockey_objects` to your `server.cfg.`
  {% endstep %}
  {% endstepper %}

## Config Preview

<details>

<summary>Show Config</summary>

```lua
Config = {}

Config.Framework = "standalone"  -- Supported frameworks: standalone, qbcore, esx

Config.InterfaceColor = "#ff66ff" -- UI theme color in HEX format (e.g. #ff66ff)

Config.Language = "English" -- UI language used by the script (English)

Config.DefaultTablePrice = 5 -- Default price to play; set to 0 to allow free play

Config.CanPlayAlone = false -- Allow solo play (true = can play alone, false = waits for another player)

Config.AirHockeyInteractionSystem = 1 -- Interaction style: 1 = Custom interaction, 2 = 3D Text, 3 = GTA Online style, 4 = Floating Text Box Style

Config.AirHockeyPlayKey = "E" -- Key used to interact and start playing Air Hockey

Config.AirHockeyCameraChangeKey = "CAPITAL" -- Key used to switch camera views

Config.Target = false -- Enable target-based interaction (true/false)

Config.Targettype = "oxtarget" -- Target system type: qtarget, qbtarget, oxtarget

Config.TargetSystemsNames = {qtarget = "qtarget", qbtarget = "qb-target", oxtarget = "ox_target"} -- target resource names

Config.TargetIcons = {play = "fa-solid fa-hockey-puck", settings = "fa-solid fa-gear"} -- icons for target

Config.TableSettingsCommand = "managetable" -- Command used to open table management menu

Config.Tables = {
    {
        position = vector3(-1616.06, -1062.84, 13.137695), -- Table world position
        heading = 0.0,                                     -- Table rotation (heading)
        price = 5,                                         -- Price to play at this table
        owned = true,                                      -- Is table owned (enables permissions & management)
        ownedsociety = "arcade",                           -- Society/job that owns the table
        open = true,                                       -- Is table open for public play
        permissions = { -- List of permissions allowed to manage this table (ace, job, identifier)
			{permissiontype = "identifier", permission = "license:59cda6cea2cba2d00a2866476b76a12cf58be27a"}, -- Example identifier permission
			{permissiontype = "job", permission = "arcade"}, -- Example job permission
			{permissiontype = "ace", permission = "airhockey.settings"}, -- Example ace permission
        },
    },
    {
        position = vector3(-790.18, 186.41, 72.83), -- Table world position
        heading = 0.0,                              -- Table rotation (heading)
        price = 5,                                  -- Price to play at this table
        owned = true,                               -- Is table owned (enables permissions & management)
        ownedsociety = "test",                      -- Society/job that owns the table
        open = true,                                -- Is table open for public play
        permissions = { -- List of permissions allowed to manage this table (ace, job, identifier)
			{permissiontype = "identifier", permission = "license:59cda6cea2cba2d00a2866476b76a12cf58be27a"}, -- Example identifier permission
        },
    },
    {
        position = vector3(-1616.06, -1062.84, 13.137695), -- Table world position
        heading = 0.0,                                     -- Table rotation (heading)
        price = 5,                                         -- Price to play at this table
	},
}

Config.GameOptions = {
    totalPucks = 7,        -- Number of goals required to win the match
    whoStarts = "random",  -- Who starts the game: "red" | "blue" | "random"
    timeLimit = 120,       -- Match time limit in seconds
    playerAlpha = 0,       -- Player opacity while playing (0 = invisible, 255 = fully visible)
    opponentAlpha = 255,   -- Opponent opacity while playing (0 = invisible, 255 = fully visible)
}

Config.Stakes = {
    enabled = false,           -- Enable/disable betting system
    allowScoreChange = true,   -- Allow players to change max score when using stakes
    minStake = 0,              -- Minimum allowed stake amount
    maxStake = 100000,         -- Maximum allowed stake amount
    defaultStake = 500,        -- Default stake value when opening menu
    defaultMaxScore = 5        -- Default max score when using stake mode
}

Config.DefaultCameraPreset = 1 -- Default camera preset index (starts from 1)

Config.CameraPresets = {
    {
        name = "default",                       -- Balanced gameplay camera (recommended)
        fov = 40.0,                            -- Camera field of view
        offset = vector3(-2.5, 0.0, 1.25),     -- Camera position offset relative to table
        lookOffset = vector3(-0.25, 0.0, 0.0), -- Camera look-at offset
    },
    {
        name = "mid",                          -- Centered mid-height camera
        fov = 40.0,                            -- Camera field of view
        offset = vector3(-0.0, 0.0, 3.5),      -- Camera position offset
        lookOffset = vector3(0.25, 0.0, -300.0), -- Camera look-at offset (slightly angled down)
    },
    {
        name = "top",                          -- Top-down camera view
        fov = 50.0,                            -- Camera field of view (wider for overview)
        offset = vector3(-1.8, 1.0, 1.0),      -- Camera position offset
        lookOffset = vector3(0.0, 0.0, 0.0),   -- Camera look-at offset
    },
    {
        name = "split",                        -- Side/angled competitive view
        fov = 50.0,                            -- Camera field of view
        offset = vector3(0.0, 1.2, 1.5),       -- Camera position offset
        lookOffset = vector3(-0.0, 0.0, 0.0),  -- Camera look-at offset
    }
}

Config.ESXFramework = {
	newversion = true, -- Set to true if you're using the newer ESX versions (prevents old SharedObject errors)
	getsharedobject = "esx:getSharedObject", -- Event name used to fetch ESX shared object (old ESX compatibility)
	resourcename = "es_extended" -- Name of your ESX resource folder
}

Config.QBCoreFrameworkResourceName = "qb-core" -- QBCore resource name (change if your core resource has a different folder name)

function Notify(text, title, notifytype)
	exports["rtx_notify"]:Notify(title, text, 5000, notifytype) -- if you get error in this line its because you dont use our notify system buy it here https://rtx.tebex.io/package/5402098 or you can use some other notify system just replace this notify line with your notify system
	--exports["mythic_notify"]:SendAlert("inform", text, 5000)
end here
```

</details>

***

## Adding a New Table

To add a new Air Hockey table, you must insert it into `Config.Tables`.

⚠️ **Each table must be written as its own block and placed under each other inside the table.**

***

#### ✅ Example (Correct)

```lua
Config.Tables = {
    {
        position = vector3(-1616.06, -1062.84, 13.137695),
        heading = 0.0,
        price = 5,
        owned = true,
        ownedsociety = "arcade",
        open = true,
        permissions = {
            permissions = {
                {permissiontype = "identifier", permission = "license:xxxxxxxxxxxxxxxx"},
            },
        },
    },

    {
        position = vector3(-790.18, 186.41, 72.83),
        heading = 0.0,
        price = 5,
        owned = true,
        ownedsociety = "test",
        open = true,
        permissions = {
            permissions = {
                {permissiontype = "identifier", permission = "license:xxxxxxxxxxxxxxxx"},
            },
        },
    },
}
```

***

#### ❌ Common mistake

```lua
Config.Tables = {
    {
        position = vector3(...),
    }
    {
        position = vector3(...), -- ❌ missing comma between tables
    }
}
```

👉 Always separate tables with a comma `,`

***

#### Table options

* `position` → table location in the world
* `heading` → table rotation
* `price` → price to play
* `owned` → enables management system
* `ownedsociety` → job/society owner
* `open` → if table is opened by default
* `permissions` → who can manage the table

***

## Using Existing World Tables

Tables work even if they are already spawned in the world.

You can use:

* MLO props
* housing systems
* object spawners

#### Model name:

```lua
prop_airhockey_01
```

Just spawn this object anywhere and it will work as an Air Hockey table.

***

## Client Export

You can check if a player is currently playing Air Hockey using this export:

```lua
exports["rtx_airhockey"]:IsPlayerPlayingAirHockey()
```

#### Returns:

* `true` → player is playing
* `false` → player is not playing

#### Example:

```lua
if exports["rtx_airhockey"]:IsPlayerPlayingAirHockey() then
    print("Player is playing Air Hockey")
end
```

## Notes

The script is designed to be highly flexible and fully customizable. All framework-related functions are editable, allowing you to easily adapt the script to your own server logic.

Most core systems can be modified, and you are free to implement your own logic, interaction system, target system, or any other custom features based on your needs.

The script also includes a language file, making it easy to translate and customize all texts.

## Support

If you need help with installation, configuration or have any questions regarding this resource, feel free to contact us on our Discord server:

[Join our Discord](https://discord.gg/rtxdev)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://rtx-dev.gitbook.io/rtxdev/air-hockey.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
