# esx_christmas (/docs/seasonal/esx_christmas)

esx_christmas is a seasonal ESX Legacy resource that spawns a landmark and a few presents at a configured location on a timer. Players walk up to a present, press E and receive cash and items depending on how early they opened it. It lives in the separate `ESX-Legacy-Seasonal` repository, not in `[esx_addons]`.

## Overview [#overview]

| | |
| --- | --- |
| Resource | `esx_christmas` (version 1.0.0) |
| Repository | [esx-framework/ESX-Legacy-Seasonal](https://github.com/esx-framework/ESX-Legacy-Seasonal), folder `[esx_seasonal]` |
| Side | Server state, client props and prompts |
| Dependencies | `es_extended` in `fxmanifest.lua`; the shared scripts also load `@ox_lib/init.lua`; the prompt needs `esx_textui` |
| Database | None; event state lives in memory and resets on restart |
| Locales | `en` only (`locales/en.lua`) |

The manifest only lists `es_extended` under `dependencies`, but it loads `@ox_lib/init.lua` as a shared script. Start `ox_lib` before this resource, see [ox_lib](/docs/guides/ox-lib).

## Installation [#installation]

Copy `esx_christmas` into your resources folder and start it after its dependencies:

```bash title="server.cfg"
ensure ox_lib
ensure es_extended
ensure esx_textui
ensure esx_christmas
```

The resource streams its own props (`stream/*`, with `this_is_a_map 'yes'` and a `.ytyp` file), so no extra map resource is needed.

## How it works [#how-it-works]

1. A server thread calls `StartCycle` immediately on start and then every `Config.EventInterval` seconds.
2. A cycle first clears the previous locations (if `DespawnLocationOnCycleEnd` is on), then picks `Config.MaxLocationsPerCycle` entries from `Config.Locations`, randomly when `UseRandomLocation` is on, and broadcasts them to all clients.
3. Each client spawns the landmark, an optional hint prop and one present prop per `PresentCoords` entry as local, non-networked objects. A present shows the text UI "Press E to open the present" within 2.0 units.
4. Pressing E sends `esx_christmas:server:claim`. The server validates the claim, picks the reward tier from the claim order at that location and pays the player.
5. All clients get the claim notification, the present disappears and, when every present at a location is taken, the whole location is removed.

## Configuration [#configuration]

All keys are in `config/main.lua`.

| Key | Default | Description |
| --- | --- | --- |
| `Config.Enabled` | `true` | Master switch; when `false`, cycles do nothing |
| `Config.Debug` | `true` | Prints `[ESX_Christmas]` debug lines on server and client |
| `Config.EventInterval` | `600` | Seconds between cycles; `0` or lower disables the cycle thread |
| `Config.MaxLocationsPerCycle` | `1` | Locations activated per cycle |
| `Config.UseRandomLocation` | `true` | Pick locations randomly instead of in list order |
| `Config.GlobalMaxClaims` | `20` | Claim cap across all locations until counters reset; `nil` or `0` disables it |
| `Config.PerPlayerMaxClaimsPerCycle` | `3` | Claim cap per player (by ESX identifier); `nil` or `0` disables it |
| `Config.UseTierScaling` | `true` | Use `Config.Tiers` by claim order; when `false` everyone gets the default tier |
| `Config.DefaultTierName` | `"Common"` | Tier used when no tier matches the claim order |
| `Config.DespawnPresentOnClaim` | `true` | Remove a present for all players when it is opened |
| `Config.DespawnLocationWhenAllClaimed` | `true` | Remove the location once all its presents are taken |
| `Config.DespawnLocationOnCycleEnd` | `true` | Clear locations and reset claim counters when a new cycle starts |
| `Config.UseHints` | `true` | Spawn a hint prop next to the landmark |
| `Config.Account` | `"bank"` | Account for `cash` rewards; `nil` pays cash in hand via `addMoney` |
| `Config.MinClaimInterval` | `2` | Minimum seconds between claims (and sync requests) per player; `0` disables the check |
| `Config.MaxViolations` | `5` | Violations before the kick |
| `Config.KickOnMaxViolations` | `true` | Drop the player at `MaxViolations` |
| `Config.MaxClaimDistance` | `5.0` | Max distance in units between player and present on the server; `0` disables the check |
| `Config.Locale` | `GetConvar("esx:locale", "en")` | Locale for notifications |

### Locations [#locations]

```lua title="config/main.lua"
Config.Locations = {
  {
    Name = "Legion Square",
    LandmarkCoords = vec3(203.525284, -941.169250, 29.55),
    LandmarkModel = "sp_medium_christmas_santa",
    PresentCoords = {
      vec3(199.437363, -935.485718, 29.678345),
      vec3(199.318680, -953.432983, 29.088623),
      vec3(175.002197, -950.268127, 29.088623),
    },
    HintModel = "sp_christmas_snowman"
  }
}
```

| Field | Type | Description |
| --- | --- | --- |
| `Name` | `string` | Label used in debug output |
| `LandmarkCoords` | `vector3` | Where the landmark prop spawns |
| `LandmarkModel` | `string?` | Fixed landmark model; when empty a random one from `Config.Props.LandmarkModels` is used |
| `PresentCoords` | `vector3[]` | One present per entry; this count is the number of claims at the location |
| `HintModel` | `string?` | Hint prop, spawned 2 units to the side of the landmark when `UseHints` is on |

A hint model only shows its floating text when it has an entry in `Config.Hints`, which maps the model name to a locale key (`xmas_hint_snowman`, `xmas_hint_deer` ship by default).

### Props [#props]

`Config.Props` lists the model names the resource may use: `LandmarkModels`, `PresentModels` (picked randomly per present), `HintModels` and `ExtraDecorModels`. The shipped names (`sp_*`, `prop_xmas_*`) come from the files in `stream/`. In the current code only `LandmarkModels` (server, random landmark) and `PresentModels` (client, random present) are read. `HintModels` and `ExtraDecorModels` are not used anywhere, so a hint prop comes from `HintModel` in the location. Custom models you add must be streamed by this or another resource.

### Tiers and rewards [#tiers-and-rewards]

```lua title="config/main.lua"
Config.Tiers = {
  {
    Name = "Legendary",
    ClaimOrder = { 1 },
    Rewards = {
      { Type = "cash", Amount = 10000 },
      { Type = "item", Name = "armor",            Amount = 1 },
      { Type = "item", Name = "christmas_ticket", Amount = 1 }
    }
  },
  -- Epic: ClaimOrder = { 2 }, Common: ClaimOrder = { 3 }
}
```

The first present opened at a location pays the `Legendary` tier, the second `Epic`, the third `Common`. `ClaimOrder` lists the claim numbers a tier applies to, so `{ 3, 4, 5 }` stretches a tier. Rewards use `xPlayer.addAccountMoney(Config.Account, amount)` for `cash` and `xPlayer.addInventoryItem(name, amount)` for `item`, see [xPlayer](/docs/es_extended/server/xplayer).

## Events [#events]

Client to server (both are validated on the server):

| Event | Arguments | Purpose |
| --- | --- | --- |
| `esx_christmas:server:claim` | `locationId`, `presentIndex` | Open a present |
| `esx_christmas:server:requestSync` | none | Ask for the active locations; sent once after the player loads |

Server to client: `setLocations`, `removeLocation`, `clearLocations`, `despawnPresent`, `onClaim`, `presentAlreadyClaimed`, `cannotClaim` and `notify`, all prefixed with `esx_christmas:client:`. They only drive props and notifications.

## Common issues [#common-issues]

- **Rewards do nothing or throw errors.** The default item names are not defined in the official `legacy.sql` or addons. Add rows to the `items` table or change `Config.Tiers`, see [Add items](/docs/guides/add-items).
- **Debug spam in the console.** `Config.Debug` is `true` by default. Set it to `false` for production.
- **Claim limits never reset.** The global and per-player counters are only reset when a cycle clears the locations. With `DespawnLocationOnCycleEnd = false` they keep growing until a restart.
- **No "Press E" prompt.** The prompt uses `ESX.TextUI`, which only works while `esx_textui` is running. Without it the present can still be opened with E, but nothing is shown.
- **Only English text.** Only `locales/en.lua` ships. Add a file in `locales/` for other languages.

## Related [#related]

- [Addons overview](/docs/addons)
- [xPlayer](/docs/es_extended/server/xplayer)
- [esx_textui](/docs/core/esx_textui)
- [Add items](/docs/guides/add-items)
- [ox_lib](/docs/guides/ox-lib)
