# Foosball - Table Football/Soccer

[📥 **Play it now!**](https://store.sergioverse.com/package/foosball)

### 📸 **Preview**

<div><figure><img src="https://1594809754-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7F3GWo5B2ghxY7IJZ25i%2Fuploads%2FGxLQNYJ2MrHv2csFAwyf%2FTimeline%201_01_00_15_10.jpg?alt=media&#x26;token=4e083b0c-ae92-4174-a346-9ccb0ed6200a" alt=""><figcaption></figcaption></figure> <figure><img src="https://1594809754-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7F3GWo5B2ghxY7IJZ25i%2Fuploads%2FXBECeRAhmZ41hMQXGwwU%2FTimeline%201_01_00_55_11.jpg?alt=media&#x26;token=e1b9b3e6-f9fa-40d7-9ad4-dff9aad74fbb" alt=""><figcaption></figcaption></figure></div>

### 📦 **Features**

✅ **Fully synced multiplayer gameplay** (requires OneSync)\
✅ **Standalone or framework-compatible** — works with ESX, QBCore, or no framework at all\
✅ **Configurable rules** — set match type (solo, 1v1, 2v2), who starts, goals to win, defenders, strikers, and even one-leg or two-leg foosball\
✅ **Custom play prices** — charge with money, items, or tokens\
✅ **Business integration** — link table income to a job or society, or make tables playable only when staff are nearby\
✅ **Smooth and responsive controls** — play in simple or advanced mode, mouse or keyboard-based\
✅ **Custom skins & cosmetics** — 12 colors, 11 hats, 2 skin tones, all configurable via store\
✅ **In-game Store NPCs** — define vendors who sell cosmetics with your chosen economy\
✅ **Donation or event-exclusive cosmetics** — lock rare items behind custom currencies or events\
✅ **3D positional sound** — immersive and realistic match atmosphere\
✅ **Optimized and lightweight** — built for RP and public servers\
✅ **Configurable HUD & Controls** — all shortcuts editable from config\
✅ **Event system** — trigger rewards automatically when a player wins\
✅ **Customizable table placement** — place tables anywhere via config or mapping tools\
✅ **Localization ready** — includes English, Spanish, and supports all FiveM locales

***

### ⚙️ **Installation**

#### 1️⃣ **Dependencies**

* **Framework (autoselected or choose one):**
  * [ESX Legacy](https://github.com/esx-framework/esx-legacy)
  * [QBCore](https://github.com/qbcore-framework/qb-core)
  * Your custom framework
  * Standalone
* **Target system (autoselected or choose one):**
  * [ox\_target](https://github.com/overextended/ox_target)
  * [qb-target](https://github.com/qbcore-framework/qb-target)
  * Your custom target system
  * Standalone
* **Notification system (autoselected or choose one):**
  * [ox\_lib](https://github.com/communityox/ox_lib)
  * [ESX Legacy](https://github.com/esx-framework/esx-legacy)
  * [QBCore](https://github.com/qbcore-framework/qb-core)
  * Your custom notification system
  * Standalone
* **Persistence system (autoselected or choose one):**
  * [oxmysql](https://github.com/communityox/oxmysql)
  * Your custom persistence system
  * Standalone
* **Inventory Hud system (autoselected or choose one):**
  * [ox\_inventory](https://github.com/overextended/ox_inventory)
  * [esx\_inventoryhud](https://github.com/Trsak/esx_inventoryhud)
  * [qb-inventory](https://github.com/qbcore-framework/qb-inventory)
  * Your custom inventory hud system
  * Standalone
* OneSync (required for multiplayer sync)

#### 2️⃣ **Add Resource**

* Download and install:

  * **sverse\_table\_football**
  * **sverse\_table\_football\_assets**

  Place them inside your `resources` folder wherever you want.

#### 3️⃣ **Add to server.cfg**

```
ensure sverse_table_football
ensure sverse_table_football_assets
```

#### 4️⃣ **Table Integration**

* Define tables directly inside `config/basic.lua` under `Config.tables`.
* Or place them dynamically through your housing or mapping system.
* Each table can have its own price and business owner.

#### 5️⃣ **Customization Store**

* Add NPC vendors in `config/economy.lua` under `Config.stores`.
* Customize items, prices, and currencies freely.

***

### 🎮 **How to Play**

1️⃣ Approach any foosball table and press **E** to start or use third eye.\
2️⃣ Play solo, 1v1, or 2v2 depending on who joins.\
3️⃣ Use your mouse and keyboard to control the bars and shoot.\
4️⃣ Customize your player’s color and hat during the match with **U**.\
5️⃣ The game keeps running as long as one player stays, no time limit.\
6️⃣ All animations, goals, and sounds are synced for everyone nearby.

***

### 🧠 **Configuration Overview**

All main settings are located in `config/basic.lua` and `config/economy.lua`.

#### 🗺️ Basic Setup

* Define language, framework, and integration type

```lua
-- 🌐 Language and Framework Settings
Config.locale = "en" -- Language code for translations (e.g. "en", "es")
Config.framework = "auto" -- Framework detection: "es_extended" | "qb-core" | "standalone" | "auto"
Config.interaction = "auto" -- Interaction system: "ox_target" | "qb-target" | "standalone" | "auto"
Config.persistence = "auto" -- Data persistence layer: "oxmysql" | "standalone" | "auto"
Config.notification = "auto" -- Notification system: "ox_lib" | "es_extended" | "qb-core" | "standalone" | "auto"
Config.inventoryHud = "auto" -- Inventory system: "esx_inventoryhud" | "qb-inventory" | "ox_inventory" | "standalone" | "auto"
```

* Place tables anywhere using coordinates and heading
* Set per-table custom prices and business ownership

```lua
-- ⚙️ Table Setup
-- Each entry defines a foosball table in the world.
-- position: table center | heading: facing direction
Config.tables = {
    {
        position = vector3(-1080.936279, -253.305496, 44.006592),
        heading = 206.763779,
        owner = nil, -- Set to nil for public table, or a organization/job/society name, needs Config.business.enabled = true in economy.lua
        -- Optional per-table default price override, set price to nil to make it as default in Config.defaultPlayPrice
        price = {
            amount = 50,
            -- item name
            currency = "money"
        }
    }
}

```

#### ⚙️ Game Options

```lua
-- ⚽ Default Game Settings
Config.defaultGameOptions = {
    mode = '2v2',           -- Match type: '1v1' or '2v2'
    bestTo = 4,             -- Number of goals to win (e.g. 4 means "first to 4")
    whoStarts = 'random',   -- Starting team: 'red' | 'blue' | 'random'
    goalkeeper = 1,         -- Row index for goalkeeper (0–5)
    defender = 2,           -- Row index for defender
    midfielder = 5,         -- Row index for midfielder
    forward = 3,            -- Row index for forward
    legs = "single",        -- Match format: 'single' or 'double'
}
```

#### 💰 Economy Settings

```lua
-- 💰 Play Price Settings
-- Define the cost to start a match.
Config.defaultPlayPrice = {
    amount = 50, -- Set 0 or nil to make it free
    currency = "money", -- "money" or an item ID (e.g. "token")
    onlyApplyToDefaultTables = true -- Only charge at tables spawned through Config.tables, so for example the one in your housing system can be free
}

-- 🏢 Business Integration
-- Optional system to route income to an organization (like a job or society).
-- Each table can be owned by a business, requiring an employee nearby to play.
Config.business = {
    enabled = false, -- Enable or disable business integration
    recipientShare = 1.0, -- % of price that goes to the organization (0.0 - 1.0)
    requiresEmployeeNearby = true, -- Require an employee from that job nearby to play
    employeeRadius = 50.0, -- Distance to check for employees
}

Config.allowPayWithBank = true -- Allow players to pay with bank if they lack cash
```

#### 🎨 Store Customization

Over 12 team colors and 11 hats — all individually priced and editable:

```lua
-- 🛒 Store NPC Setup
-- Each entry defines a vendor who sells customization items.
Config.stores = {
    {
        model = "cs_lifeinvad_01", -- Ped model
        position = vector3(-1082.769287, -251.301102, 44.006592),
        heading = 257.952759
    }
}

-- 🛍️ Store Items (Customization)
-- Balanced for RP economy with ordered rarity progression. Currency is an item name
Config.store = {
    team_color = {
        red = { cost = 400, currency = "money" },
        blue = { cost = 400, currency = "money" },
        green = { cost = 400, currency = "money" },
        yellow = { cost = 400, currency = "money" },
        orange = { cost = 500, currency = "money" },
        skyblue = { cost = 500, currency = "money" },

        purple = { cost = 3500, currency = "money" },
        pink = { cost = 3500, currency = "money" },
        navy = { cost = 4000, currency = "money" },
        maroon = { cost = 4000, currency = "money" },
        black = { cost = 5000, currency = "money" },
        white = { cost = 5000, currency = "money" }
    },

    hat = {
        child = { cost = 1000, currency = "money" },
        irish = { cost = 1500, currency = "money" },
        miner = { cost = 2000, currency = "money" },
        winter = { cost = 2500, currency = "money" },

        monk = { cost = 3000, currency = "money" },
        gentleman = { cost = 4000, currency = "money" },
        pirate = { cost = 5000, currency = "money" },
        viking = { cost = 6000, currency = "money" },

        pope = { cost = 8000, currency = "money" },
        crown = { cost = 9000, currency = "money" },
        wizzard = { cost = 10000, currency = "money" }
    }
}

```

***

### 🧩 Exports

Client-side exports that let external scripts drive the foosball flow without going through the interaction prompt or the NUI setup menu.

#### `showStore()`

Opens the cosmetics store NUI for the calling player.

```lua
exports.sverse_table_football:showStore()
```

***

#### `showLeaderboard()`

Opens the leaderboard NUI for the calling player.

```lua
exports.sverse_table_football:showLeaderboard()
```

***

#### `joinTableByEntity(entity)`

Joins the calling player to a table whose match is already running, as if they had walked up and pressed the interaction key. No NUI is opened. The closest free spot (side + position) is picked automatically.

```lua
-- entity: number, the foosball table prop entity handle (client-side)
-- returns: boolean success, string? reason
local ok, reason = exports.sverse_table_football:joinTableByEntity(tableEntity)
```

**Failure reasons:**

* `invalid_entity` — entity is nil or does not exist.
* `table_not_found` — entity is not a tracked foosball table (player must be within \~50m for the table to be registered).
* `already_playing` — the caller is already playing another table.
* `game_not_started` — the table is idle; use `startGameOnEntity` instead.
* `no_spot_available` — no free spot left on the table.

***

#### `startGameOnEntity(entity, gameMode)`

Starts a new match on an idle table with the supplied game options, bypassing the setup NUI. The caller is placed on the closest available spot.

```lua
-- entity:   number, the foosball table prop entity handle
-- gameMode: table, partial or full GameOptions (missing keys fall back to Config.defaultGameOptions)
-- returns:  boolean success, string? reason, string? option (name of the offending field when validation fails)

local ok, reason, offendingOption = exports.sverse_table_football:startGameOnEntity(tableEntity, {
    mode = '2v2',           -- '1v1' | '2v2'
    bestTo = 4,             -- integer 1..9
    whoStarts = 'random',   -- 'host' | 'guest' | 'random'
    goalkeeper = 1,         -- integer 1..5
    defender = 2,           -- integer 2..5
    midfielder = 5,         -- integer 2..5
    forward = 3,            -- integer 2..5
    legs = 'single',        -- 'single' | 'double'
})
```

**Failure reasons:**

* `invalid_entity` — entity is nil or does not exist.
* `table_not_found` — entity is not a tracked foosball table.
* `already_playing` — the caller is already playing another table.
* `game_already_running` — the table is not idle.
* `no_spot_available` — no free spot left on the table.
* `invalid_type` / `invalid_range` / `invalid_value` / `invalid_option` — `gameMode` validation failed; the third return value is the field name.

***

#### `exitCurrentGame()`

Leaves the match the caller is currently playing. Releases the table spot, hides the HUD and restores the camera, as if the player had pressed the cancel key.

```lua
-- returns: boolean success, string? reason
local ok, reason = exports.sverse_table_football:exitCurrentGame()
```

**Failure reasons:**

* `not_in_game` — the caller is not currently playing any table.

***

### 🧑‍💻 Game Win Event

Server-side event triggered when a game finishes. Provides full details about the game and participants.

```lua

-- Event
AddEventHandler('sverse_table_football:onGameWin', function(data)
    -- data table structure:
    -- data.prop         : string, model or identifier of the foosball table used
    -- data.coords       : vector3, position where the match was played
    -- data.rotation     : vector3, table rotation
    -- data.bucket       : integer, routing bucket (dimension) where the match occurred
    -- data.winner       : string, winning team ("red" or "blue")
    -- data.host         : string, starting side ("red" or "blue")
    -- data.teams        : table containing both teams' info:
    --   ├─ data.teams.red.score      : integer, red team’s final score
    --   ├─ data.teams.red.players    : table, list of red team player IDs
    --   ├─ data.teams.red.skin       : table, skin data for red team
    --   ├─ data.teams.blue.score     : integer, blue team’s final score
    --   ├─ data.teams.blue.players   : table, list of blue team player IDs
    --   └─ data.teams.blue.skin      : table, skin data for blue team

    print("Foosball match ended!")
    print("Table prop: " .. tostring(data.prop))
    print(string.format("Location: %s | Rotation: %s | Bucket: %s", data.coords, data.rotation, data.bucket))
    print("Winning team: " .. tostring(data.winner))
    print("Host side: " .. tostring(data.host))

    print(("Red Team - Score: %s | Players: %s"):format(data.teams.red.score, table.concat(data.teams.red.players, ", ")))
    print(("Blue Team - Score: %s | Players: %s"):format(data.teams.blue.score, table.concat(data.teams.blue.players, ", ")))

    print("Red Team Skin:")
    print(json.encode(data.teams.red.skin, { indent = true }))
    print("Blue Team Skin:")
    print(json.encode(data.teams.blue.skin, { indent = true }))
end)

```

***

### ❓ **FAQ**

**Q:** Can I play solo?\
**A:** Yes, the game is started once the setup is done.

**Q:** Can I use tokens or any item instead of money?\
**A:** Absolutely, set `currency = "token"` in `Config.defaultPlayPrice`.

**Q:** Does it support my framework?\
**A:** Yes, it automatically detects ESX, QBCore, or runs standalone.

**Q:** Can I make tables work only when employees are nearby?\
**A:** Yes, enable `Config.business.requiresEmployeeNearby = true` and `Config.business.enable = true`&#x20;

**Q:** Is everything synced for nearby players?\
**A:** Yes, goals, shots, and player movements are fully synchronized with OneSync.

**Q:** Is it optimized?\
**A:** Fully. Lightweight and tested for heavy RP servers.

***

> ⚠️ **Credits Notice**\
> All hat models assets included in this resource belong to [**Zekirakm**](https://sketchfab.com/Zekirakm).\
> Used and distributed under their license/permission.


---

# 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://docs.sergioverse.com/foosball-table-football-soccer.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.
