> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fenixstudios.net/llms.txt
> Use this file to discover all available pages before exploring further.

# Fnx PVP Matches

> Complete queues, matchmaking, maps, loadouts, hooks, theme, and API reference.

> Complete queues, matchmaking, maps, loadouts, hooks, theme, and API reference.

`fnx-pvp-matches` provides ranked queues, custom team matches, rounds, Elo, inventories, stats, and leaderboard integration.

## First installation

```cfg theme={null}
ensure oxmysql
ensure community_bridge

ensure fnx-pvp-panel
ensure fnx-pvp-matches
```

The resource creates its required database tables automatically. You do not need to import an SQL file.

## First setup

Configure client behavior, server rules, and `shared/public/_main.lua`. Every enabled map needs unique identification, valid team spawns, and correct inventory item names.

## Client configuration

### Runtime

| Key                                 | Default         | Explanation                                                                  |
| ----------------------------------- | --------------- | ---------------------------------------------------------------------------- |
| `Runtime.closePanelOnJoin`          | `true`          | Closes the panel after joining.                                              |
| `Runtime.deathEvents`               | `true`          | Enables built-in death events. Disable if one custom detector replaces them. |
| `Runtime.countdown.enabled`         | `true`          | Enables the visual countdown.                                                |
| `Runtime.countdown.sound`           | `true`          | Enables countdown sound.                                                     |
| `Runtime.countdown.volume`          | `0.5`           | Sound volume from `0` to `1`.                                                |
| `Runtime.countdown.holdMs`          | `2200`          | Milliseconds the final countdown state remains visible.                      |
| `Runtime.invincible.enabled`        | `true`          | Master transition-invincibility switch.                                      |
| `Runtime.invincible.beforeRound`    | `true`          | Protects during prestart.                                                    |
| `Runtime.invincible.afterRound`     | `true`          | Protects after a round.                                                      |
| `Runtime.antiTeamKill.enabled`      | `true`          | Blocks teammate damage using relationship groups.                            |
| `Runtime.antiTeamKill.disableMelee` | `true`          | Also blocks teammate melee damage.                                           |
| `Runtime.antiTeamKill.groupA`       | `"PVP_MATCH_A"` | Relationship-group name for team A.                                          |
| `Runtime.antiTeamKill.groupB`       | `"PVP_MATCH_B"` | Relationship-group name for team B.                                          |
| `Runtime.returnCoords`              | `nil`           | Optional client return `vec4`.                                               |
| `ClientCfg.Death`                   | `{}`            | Overrides panel death defaults; empty inherits them.                         |

### Client hooks

| Key                           | Arguments              | Called when                                                |
| ----------------------------- | ---------------------- | ---------------------------------------------------------- |
| `Events.OnMatchStart`         | `payload`              | Match begins.                                              |
| `Events.OnRoundPrestart`      | `payload`              | Prestart begins; default code equips ox\_inventory weapon. |
| `Events.OnRoundStart`         | `payload`              | Combat begins.                                             |
| `Events.OnRoundEnd`           | `payload`              | Round ends.                                                |
| `Events.OnMatchEnd`           | `payload`              | Match completes.                                           |
| `Events.OnMatchLeave`         | `payload`              | Local player leaves.                                       |
| `Events.OnPlayerDeadOnMatch`  | `payload, activeMatch` | Local death is accepted.                                   |
| `Events.OnPlayerKillOnMatch`  | `payload, activeMatch` | Local kill is accepted.                                    |
| `Events.OnGameEventTriggered` | callback or `nil`      | Optional raw game-event callback.                          |

### Client commands

These commands are registered client-side by the match runtime.

| Command              | Purpose                                                       |
| -------------------- | ------------------------------------------------------------- |
| `/leavematch`        | Leaves the active match.                                      |
| `/leaveparty`        | Leaves the current party.                                     |
| `+fnx_pvp_match_tab` | Internal key-mapped command that opens the match scoreboard.  |
| `-fnx_pvp_match_tab` | Internal key-mapped command that closes the match scoreboard. |

Default key mapping:

| Key mapping          | Default key | Label                   |
| -------------------- | ----------- | ----------------------- |
| `+fnx_pvp_match_tab` | `TAB`       | `Show match scoreboard` |

## Server configuration

### Queue and warning

| Key                            | Default          | Explanation                                        |
| ------------------------------ | ---------------- | -------------------------------------------------- |
| `Queue.modes`                  | `1v1` to `6v6`   | Allowed ranked queue sizes. Format must be `NvN`.  |
| `Queue.defaultElo`             | `0`              | Starting match Elo.                                |
| `Queue.matchFoundNotifyTime`   | `6500`           | Match-found notification duration in milliseconds. |
| `Queue.warning.enabled`        | bridge-dependent | Enables inventory-change confirmation.             |
| `Queue.warning.holdSeconds`    | `1`              | Confirmation hold time.                            |
| `Queue.warning.timeoutSeconds` | `30`             | Confirmation expiry.                               |

### Matchmaking

| Key                                     | Default | Explanation                                     |
| --------------------------------------- | ------- | ----------------------------------------------- |
| `Matchmaking.ranked`                    | `true`  | Uses Elo restrictions/updates for ranked queue. |
| `Matchmaking.requireSameMap`            | `true`  | Requires compatible selected map.               |
| `Matchmaking.eloGap.initial`            | `250`   | Initial maximum Elo difference.                 |
| `Matchmaking.eloGap.expandEverySeconds` | `30`    | Search time between gap expansions.             |
| `Matchmaking.eloGap.expandBy`           | `100`   | Elo added at every expansion.                   |
| `Matchmaking.eloGap.max`                | `1000`  | Final maximum Elo difference.                   |

### Custom matches

| Key                           | Default    | Explanation                             |
| ----------------------------- | ---------- | --------------------------------------- |
| `Custom.firstTo`              | `{6,9,13}` | Allowed target scores.                  |
| `Custom.maxTeamSize`          | `6`        | Maximum members per custom team.        |
| `Custom.updateStats`          | `false`    | Saves normal stats from custom matches. |
| `Custom.updateElo`            | `false`    | Changes Elo from custom matches.        |
| `Custom.requireBalancedTeams` | `false`    | Requires equal team sizes when true.    |

### Match runtime

| Key                   | Default | Explanation                              |
| --------------------- | ------- | ---------------------------------------- |
| `Match.firstTo`       | `6`     | Default rounds/points required to win.   |
| `Match.roundTime`     | `300`   | Round limit in seconds.                  |
| `Match.countdown`     | `5`     | Pre-round countdown in seconds.          |
| `Match.prestartDelay` | `2500`  | Server prestart delay in milliseconds.   |
| `Match.roundEndDelay` | `5500`  | Delay after a round in milliseconds.     |
| `Match.loadTimeout`   | `15000` | Player/map load timeout in milliseconds. |
| `Match.bucketBase`    | `40000` | First match routing bucket.              |
| `Match.returnBucket`  | `0`     | Bucket restored after leaving.           |
| `Match.returnCoords`  | `nil`   | Optional server return `vec4`.           |

### Elo and inventory

| Key                                     | Default          | Explanation                                                                                 |
| --------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------- |
| `Elo.kFactor`                           | `32`             | Elo sensitivity. Higher values produce larger changes.                                      |
| `Elo.minDelta`                          | `6`              | Minimum applied Elo change.                                                                 |
| `Elo.maxDelta`                          | `40`             | Maximum applied Elo change.                                                                 |
| `Inventory.enabled`                     | bridge-dependent | Enables snapshot/loadout management.                                                        |
| `Inventory.keepInventory`               | `false`          | Disables match inventory/loadout management and lets players keep items.                    |
| `Inventory.mode`                        | `"auto"`         | `auto`, `bridge`, or `native`.                                                              |
| `Inventory.weaponMetadata.enabled`      | `true`           | Gives weapon ammo/components inside weapon metadata instead of as separate inventory items. |
| `Inventory.weaponMetadata.durability`   | `10000`          | Durability value written to weapon metadata when metadata mode is enabled.                  |
| `Inventory.weaponMetadata.ammo`         | `true`           | Stores configured ammo in weapon metadata and skips the separate ammo item grant.           |
| `Inventory.weaponMetadata.components`   | `true`           | Stores configured components in weapon metadata and skips separate component item grants.   |
| `Inventory.ammoRefill.enabled`          | `true`           | Enables automatic ammo refill checks.                                                       |
| `Inventory.ammoRefill.threshold`        | `0.5`            | Refill only when current ammo is below this fraction of configured ammo.                    |
| `Inventory.ammoRefill.roundEnd`         | `true`           | Checks/refills ammo at round end.                                                           |
| `Inventory.snapshotConfiguredItemsOnly` | `false`          | When true, snapshots only items known to match configuration.                               |
| `Inventory.restoreOnNextJoin`           | `true`           | Restores a pending snapshot when the player next joins after interruption.                  |

### Rewards

`ServerCfg.Rewards` controls item/account rewards. Rewards are given independently from match loadouts, so they still work when `Inventory.keepInventory` is true.

| Key                                   | Explanation                                                                      |
| ------------------------------------- | -------------------------------------------------------------------------------- |
| `Rewards.kill.enabled`                | Enables rewards for every kill.                                                  |
| `Rewards.kill.items`                  | Items/accounts given on kill.                                                    |
| `Rewards.killStreaks.enabled`         | Enables killstreak rewards.                                                      |
| `Rewards.killStreaks.public`          | When false, players need command-granted access before receiving streak rewards. |
| `Rewards.killStreaks.rewards[streak]` | Rewards given when the player reaches that streak value.                         |
| `reward.item`                         | Inventory item name given through the configured inventory bridge.               |
| `reward.account`                      | Framework account name such as `bank`; requires a framework/account bridge.      |
| `reward.amount`                       | Base reward amount. It is multiplied by the player's reward multiplier.          |
| `reward.metadata`                     | Optional item metadata passed to the inventory.                                  |

Example:

```lua theme={null}
ServerCfg.Rewards = {
    kill = {
        enabled = true,
        items = {
            { item = "bread", amount = 1 },
            { item = "money", amount = 1 },
            { account = "bank", amount = 5000 },
        }
    },
    killStreaks = {
        enabled = true,
        public = true,
        rewards = {
            [3] = {
                { item = "bread", amount = 3 },
            }
        }
    }
}
```

### Logs

Each log category supports `enabled`, `username`, `avatar`, `webhooks`, and `embed`. `webhooks` is an array, so one category can send to multiple Discord webhooks. `embed` is a Lua function and can be fully customized per server.

| Category                | Sent when                                                                                         |
| ----------------------- | ------------------------------------------------------------------------------------------------- |
| `Logs.admin`            | Admin/internal actions such as reward permission or multiplier changes.                           |
| `Logs.matchCreated`     | A match is created.                                                                               |
| `Logs.matchEnd`         | A match ends; includes map, mode, reason, winner, score, duration, team totals, and player stats. |
| `Logs.matchEndByType`   | Optional per-mode override for `matchEnd` webhooks/settings, e.g. `1v1`, `2v2`, or `custom`.      |
| `Logs.inventoryStored`  | A player's inventory snapshot is stored before match loadout is applied.                          |
| `Logs.inventoryRestore` | A player's original inventory and pending rewards are restored.                                   |

Example per-mode match-end routing:

```lua theme={null}
ServerCfg.Logs.matchEndByType = {
    ["1v1"] = { webhooks = { "https://..." } },
    ["2v2"] = { webhooks = { "https://..." } },
    custom = { webhooks = { "https://..." } }
}
```

### Reward commands

| Key                                       | Explanation                                       |
| ----------------------------------------- | ------------------------------------------------- |
| `Commands.giveStreakRewards.active`       | Registers the streak reward access command.       |
| `Commands.giveStreakRewards.name`         | Default command name: `givematchstreakrewards`.   |
| `Commands.giveStreakRewards.permission`   | Permission required to use the command.           |
| `Commands.setRewardMultiplier.active`     | Registers the reward multiplier command.          |
| `Commands.setRewardMultiplier.name`       | Default command name: `setmatchrewardmultiplier`. |
| `Commands.setRewardMultiplier.permission` | Permission required to use the command.           |

Use `giveStreakRewards` only when `Rewards.killStreaks.public` is false. `setRewardMultiplier` changes the multiplier used by kill and streak rewards.

### Server hooks

| Key                          | Arguments                                  |
| ---------------------------- | ------------------------------------------ |
| `Events.OnMatchStart`        | `match`                                    |
| `Events.OnRoundPrestart`     | `match`                                    |
| `Events.OnRoundStart`        | `match`                                    |
| `Events.OnRoundEnd`          | `match, winnerTeam, reason`                |
| `Events.OnMatchEnd`          | `match, winnerTeam, reason`                |
| `Events.OnPlayerDeadOnMatch` | `match, deadPlayer, killerPlayer, payload` |
| `Events.OnPlayerKillOnMatch` | `match, killerPlayer, deadPlayer, payload` |
| `Events.OnPlayerLeaveMatch`  | `match, player, dropped`                   |

## Shared files

`shared/public/_main.lua` obtains `REVIVE_EVENT` from the panel and defines `Maps`.

### Map schema

| Key                  | Explanation                                                                                                                             |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `name`               | Unique map ID. Duplicate names make selection ambiguous.                                                                                |
| `resource`           | One or more map resource names used as availability requirements.                                                                       |
| `label`              | Human-readable map name.                                                                                                                |
| `image`              | Local filename or supported URL shown in the UI.                                                                                        |
| `coords`             | General `vec3` map position.                                                                                                            |
| `swap`               | Enables team-side swapping between rounds.                                                                                              |
| `firstTo`            | Map-specific target score.                                                                                                              |
| `roundTime`          | Map-specific round limit in seconds.                                                                                                    |
| `weapons`            | Loadouts offered on this map.                                                                                                           |
| `extraItems`         | Extra inventory items given with the match loadout. Ignored when `Inventory.keepInventory` is true or inventory management is disabled. |
| `winningTeamRewards` | Items/accounts given to winners at match end. These are real rewards and are given even when `Inventory.keepInventory` is true.         |
| `teams`              | Available team spawn definitions.                                                                                                       |

Example:

```lua theme={null}
extraItems = {
    { item = "bread", amount = 2 },
},

winningTeamRewards = {
    { item = "money", amount = 100 },
    { account = "bank", amount = 5000 },
}
```

### Shared weapon and team keys

| Key                              | Explanation                    |
| -------------------------------- | ------------------------------ |
| `weapons[].id`                   | Unique weapon selection ID.    |
| `weapons[].hash`                 | GTA weapon hash.               |
| `weapons[].label`                | Display label.                 |
| `weapons[].ammo`                 | Ammo issued.                   |
| `weapons[].components`           | GTA component hashes.          |
| `weapons[].itemsData.weapon`     | Inventory weapon item.         |
| `weapons[].itemsData.ammo`       | Inventory ammo item.           |
| `weapons[].itemsData.components` | Inventory component items.     |
| `teams[].name`                   | Unique team display/name.      |
| `teams[].spawn`                  | Team `vec4` spawn and heading. |

## Theme system

| Key                     | Explanation                              |
| ----------------------- | ---------------------------------------- |
| `colors.panelBg`        | Main panel background.                   |
| `colors.panelBgStrong`  | Darker background.                       |
| `colors.accent`         | Primary highlight.                       |
| `colors.accentStrong`   | Strong active highlight.                 |
| `colors.text`           | Primary text.                            |
| `colors.mutedText`      | Secondary text.                          |
| `colors.surface`        | Normal card surface.                     |
| `colors.surfaceStrong`  | Strong card surface.                     |
| `colors.border`         | Borders and separators.                  |
| `colors.overlay`        | Modal overlay.                           |
| `colors.success`        | Success foreground.                      |
| `colors.successBg`      | Success background.                      |
| `colors.danger`         | Error foreground.                        |
| `colors.dangerBg`       | Error background.                        |
| `colors.warning`        | Warning foreground.                      |
| `colors.warningBg`      | Warning background.                      |
| `colors.info`           | Information foreground.                  |
| `colors.infoBg`         | Information background.                  |
| `colors.ratingElite`    | Elite rating tier.                       |
| `colors.ratingMaster`   | Master rating tier.                      |
| `colors.ratingDiamond`  | Diamond rating tier.                     |
| `colors.ratingPlatinum` | Platinum rating tier.                    |
| `colors.ratingGold`     | Gold rating tier.                        |
| `colors.ratingSilver`   | Silver rating tier.                      |
| `colors.ratingBase`     | Base/unranked tier.                      |
| `radii.panel`           | Main panel corner radius in pixels.      |
| `radii.card`            | Card corner radius in pixels.            |
| `radii.control`         | Input/control corner radius in pixels.   |
| `radii.pill`            | Badge radius; `999` creates a pill.      |
| `effects`               | Reserved effects table, currently empty. |
| `fonts.body`            | Normal-text CSS font stack.              |
| `fonts.display`         | Heading CSS font stack.                  |

## Developer info

### Client exports

| Export           | Parameters | Returns / purpose                                         |
| ---------------- | ---------- | --------------------------------------------------------- |
| `IsInMatch`      | none       | Returns whether the local player is in an active match.   |
| `ReportDeath`    | `payload`  | Reports a local match death.                              |
| `CanReportDeath` | none       | Returns whether a new death report is currently accepted. |

### Server exports

| Export                  | Parameters           | Returns / purpose                                            |
| ----------------------- | -------------------- | ------------------------------------------------------------ |
| `GetLeaderboardPage`    | `request`            | Returns the requested match leaderboard page.                |
| `GetStats`              | `identifier`         | Returns stored match statistics for an identifier.           |
| `GetPanelStats`         | `source, identifier` | Returns match statistics formatted for the PVP panel.        |
| `GetPanelRank`          | `source, identifier` | Returns panel rank data for the identifier.                  |
| `GetLeaderboardEntries` | `limit, sortMetric`  | Returns leaderboard entries with optional limit and sorting. |
| `SyncPlayer`            | `source`             | Reloads/synchronizes one player's match data.                |
| `GetActiveMatch`        | `source`             | Returns the active match associated with the player.         |
| `IsInMatch`             | `source`             | Returns whether the server player is in a match.             |

## Common issues

### No opponent is found

Check mode, selected map, same-map requirement, and Elo gap expansion.

### Wrong spawn or map

Ensure every map name is unique and every team spawn is valid.

### Inventory is not restored

Check inventory mode, item names, snapshot scope, and `restoreOnNextJoin`.

### Some configured weapons are not given

Each weapon is applied with its ammunition and components as one package. If a complete package does not fit, only that weapon is skipped. Other weapons are still given and the player receives one notification showing how many weapons were skipped.

### Deaths count twice

Use one detector and call `CanReportDeath()` before a custom report.

## Common folders

| Path                             | What it is for                                                                       |
| -------------------------------- | ------------------------------------------------------------------------------------ |
| `fxmanifest.lua`                 | FiveM manifest. It loads the resource files in the correct order.                    |
| `settings/public/client/cfg.lua` | Client settings that server owners are expected to edit.                             |
| `settings/public/server/cfg.lua` | Server settings that server owners are expected to edit.                             |
| `settings/private/**/_cfg.lua`   | Internal/default settings. Do not edit unless you are modifying the resource itself. |
| `client/public/*.lua`            | Public client API files and safe integration files.                                  |
| `client/private/*.lua`           | Internal client runtime code.                                                        |
| `server/public/*.lua`            | Public server API files and exports.                                                 |
| `server/private/*.lua`           | Internal server runtime code.                                                        |
| `shared/public/*.lua`            | Shared public data or integration constants.                                         |
| `shared/private/*.lua`           | Shared internal helpers loaded by the resource.                                      |
| `locales/*.json`                 | Locales for ui and game.                                                             |
| `web/dist/`                      | Built NUI files used in-game. Do not edit these by hand.                             |

## Weapon is holstered/folded instead of equipped during PvP flow

Open:\
`fnx-pvp-matches/settings/public/server/cfg.lua`\
\
Find:\
`ServerCfg.Events.OnRoundPrestart`

Write inside:

```lua theme={null}
if GetResourceState("ox_inventory") == "started" then
    if exports.ox_inventory:getCurrentWeapon() then return end
    local inventory = exports.ox_inventory:GetPlayerItems()
    if not inventory then return end
    Citizen.CreateThreadNow(function()
        for i=1, #inventory, 1 do
            local item = inventory[i]
            if (item.name or ""):match("^WEAPON_") then
                exports.ox_inventory:useSlot(item.slot)
                break
            end
        end
        Wait(1500)
        MakePedReload(PlayerPedId())
    end)
end
```

## File structure

```txt theme={null}
fnx-pvp-matches/
|-- client/
|   |-- private/
|   |   \-- _main.lua
|   \-- public/
|       \-- main.lua
|-- locales/
|   \-- *.json
|-- server/
|   |-- private/
|   |   |-- _database.lua
|   |   |-- _events.lua
|   |   |-- _inventory.lua
|   |   |-- _main.lua
|   |   |-- _maps.lua
|   |   |-- _party.lua
|   |   |-- _profiles.lua
|   |   |-- _runtime.lua
|   |   |-- _state.lua
|   |   \-- _stats.lua
|   \-- public/
|       \-- main.lua
|-- settings/
|   |-- private/
|   |   |-- client/
|   |   |   \-- _cfg.lua
|   |   \-- server/
|   |       \-- _cfg.lua
|   \-- public/
|       |-- client/
|       |   \-- cfg.lua
|       \-- server/
|           \-- cfg.lua
|-- shared/
|   |-- private/
|   |   \-- _main.lua
|   \-- public/
|       \-- _main.lua
|-- web/
|   \-- dist/
\-- fxmanifest.lua
```
