# Dedicated Server Settings

This document is the operator-facing reference for Trouble in Terrorist Town dedicated server configuration.

`server_settings.json` is the source of truth for game-specific dedicated server settings. Do not mirror rich TTT config into Steam server tags; the platform already advertises core discovery fields such as server name and map identity.

For dedicated servers, s&box starts from native launch context: `+game <packageIdent> [mapPackageIdent]`. Launch with the same map package that is configured as `Server.StartupMap`, for example `+game thieves.terrortown thieves.rooftops +hostname "TTT Server"`. TTT uses the native launch map as the boot/browser map, and `Server.StartupMap` remains the TTT-owned baseline used for empty-server reset. Keep those values aligned.

Steam server tags have a 128-character limit. Keep runtime metadata tiny. Do not publish the game ident or rich TTT settings through `Networking.SetData`; the game package is native launch/browser context and TTT-specific settings belong in `server_settings.json`.

## Files

The active config file is:

```text
server_settings.json
```

It is read through `FileSystem.Data`. On a SteamCMD dedicated install, that should be under the currently running game ident's data folder:

```text
steamcmd/ttt/data/thieves/<game-ident>/server_settings.json
```

For example, dev builds may use `thieves/terrortowndev`, while public builds may use `thieves/terrortown`.

If the active config is missing, the server writes active defaults to:

```text
server_settings.json
```

When an active `server_settings.json` exists, missing fields are backfilled from the current default template on startup. Existing values and unknown extra fields are preserved. This keeps older configs working as new settings are added, while still making the new defaults visible to server operators.

If `server_settings.json` is invalid, the server logs an error and falls back to scene/prefab defaults for settings that could not be loaded. Fix the file and restart the dedicated server.

## Full Example

This is the full currently supported JSON surface. If a field is not listed here, the dedicated settings facade does not currently read it.

```json
{
  "SchemaVersion": 1,
  "Server": {
    "StartupMap": "thieves.rooftops"
  },
  "Lobby": {
    "MaxPlayers": 24,
    "Privacy": 0,
    "MinPlayerCount": 2,
    "MaxRounds": 6,
    "RoundTimeMinutes": 5,
    "PrepareDurationSeconds": 20,
    "HasteMode": true,
    "MapVoteOptionCount": 8,
    "AllowPlayerCosmetics": true,
    "ProximityVoiceEnabled": false,
    "ProximityVoiceRange": 2000,
    "KarmaEnabled": true,
    "RockTheVoteEnabled": true
  },
  "MapVote": {
    "Maps": "thieves.innocent_hotel;thieves.aesthetic;thieves.community_bowling;thieves.skyscraper;thieves.kakariko;thieves.rooftops;thieves.dolls;thieves.casino;hillrop.ttt_depot_fof;throwupproducs.islandtropical;thieves.dream;thieves.whitehouse;thieves.marine_base_ttt;thieves.minecraft_t5;thieves.clue;fyn.nicks_desk;noctric.ginzastation;thieves.67thwayv1"
  },
  "AdminSteamIds": []
}
```

## Load Timing

`server_settings.json` is read once per server process through `ServerSettingsStore`.

Restart the dedicated server after editing the file. Do not expect hot reload.

The settings apply only on dedicated servers unless explicitly documented otherwise. P2P/listen-host lobbies continue to use the main menu lobby settings path.

## Settings Ownership

Runtime code should read dedicated settings through helper methods on the settings store, not by reaching into the raw JSON model. That keeps the file layout flexible: map rotation, balance, weapon stats, or rule presets can move into separate files later without changing gameplay callers.

Current helper ownership:

- `LoadStartupMap()` reads the dedicated baseline map.
- `LoadLobbyStartSettings()` reads lobby and round bootstrap settings.
- `ApplyMapVoteSettings()` applies the server's curated vote rotation.
- `LoadAdminSteamIds()` reads bootstrap admin IDs.

## Root Fields

### `SchemaVersion`

Type: integer  
Default/example: `1`

Reserved for future migrations. Keep this at `1` for the current schema.

### `Server`

Type: object or omitted

Dedicated server bootstrap and identity settings. These are the first settings loaded and are expected to stay small even if later gameplay configuration moves into separate files.

### `Server.StartupMap`

Type: package ident string
Default/example: `"thieves.rooftops"`

Dedicated-only baseline map. Set this to the same map ident used as the second native `+game` argument, for example `+game thieves.terrortown thieves.rooftops`.

If a dedicated server somehow boots without a native launch map, TTT can still run the normal map-change pipeline to `Server.StartupMap` after networking starts. Treat that as a fallback, not the recommended launch shape.

This is also the empty-server reset target. Restart the dedicated server after changing it.

### `Lobby`

Type: object or omitted

Controls dedicated lobby/round settings normally selected through the create-lobby UI. If omitted from the active config, default values are backfilled on startup.

`MaxPlayers` and `Privacy` are applied when the dedicated server creates its lobby. The rest of the `Lobby` section is applied when the in-scene lobby settings runtime initializes.

Dedicated servers always use the curated map-vote runtime path from `MapVote.Maps`. Uncurated/discovered map-vote modes are not exposed in `server_settings.json`.

### `MapVote`

Type: object or omitted

Controls the curated maps that can appear in this server's map vote. If omitted from the active config, default values are backfilled on startup.

The generated defaults come from `MapRegistry.WhitelistMaps`. Operators can trim or reorder `MapVote.Maps` to choose their rotation. This does not edit the built-in registry; it only changes that server's active vote pool.

Entries outside `MapRegistry.WhitelistMaps` are ignored by dedicated server settings. `MapRegistry.BlacklistMaps` remains a built-in block list.

You can print the current built-in map lists from the server console:

```text
t.server.maps
```

### `AdminSteamIds`

Type: array of unsigned 64-bit Steam IDs

Bootstrap admin list for dedicated servers. These IDs are added to the session admin assignments when the server starts. Invalid or zero IDs are ignored.

Example:

```json
"AdminSteamIds": [
  76561198000000000,
  76561198111111111
]
```

## Lobby Fields

### `Lobby.MaxPlayers`

Type: integer  
Default/example: `24`

Maximum player count passed into `LobbyConfig` when the dedicated server creates its lobby.

Keep any external Steam/server-manager max-player setting aligned with this value. If they disagree, the engine/server-manager limit may still win outside the game code.

### `Lobby.Privacy`

Type: `LobbyPrivacy` enum number  
Default/example: `0`

Controls lobby privacy presentation and lobby config when available.

Accepted enum values:

```text
0 = Public
1 = Private
2 = FriendsOnly
```

For public dedicated servers, use `0`.

### `Lobby.MinPlayerCount`

Type: integer  
Default/example: `2`  
Runtime clamp: minimum `1`

Minimum players required by round rules before normal round start behavior can proceed.

### `Lobby.MaxRounds`

Type: integer  
Default/example: `6`  
Runtime apply clamp: minimum `1`

Maximum rounds before the normal end-of-match/map-vote path should take over.

### `Lobby.RoundTimeMinutes`

Type: number  
Default/example: `5`
Runtime apply clamp: minimum `1`

Base round time limit in minutes.

### `Lobby.PrepareDurationSeconds`

Type: number  
Default/example: `20`  
Runtime apply clamp: minimum `0`

Preparation phase duration before a round starts.

### `Lobby.HasteMode`

Type: boolean  
Default/example: `true`

Controls whether round rules use haste mode timing.

### `Lobby.MapVoteOptionCount`

Type: integer  
Default/example: `8`  
Runtime clamp: `3` to `12`

Maximum number of map choices shown during a map vote.

### `Lobby.AllowPlayerCosmetics`

Type: boolean
Default/example: `true`

Controls whether player cosmetics are allowed by the game mode.

### `Lobby.ProximityVoiceEnabled`

Type: boolean
Default/example: `false`

Controls whether proximity voice is active for normal player voice chat.

### `Lobby.ProximityVoiceRange`

Type: number
Default/example: `2000`
Runtime clamp: `500` to `5000`

Voice range in world units when proximity voice is enabled.

### `Lobby.KarmaEnabled`

Type: boolean
Default/example: `true`

Controls whether the karma system is active. When disabled, karma penalties/rewards, low-karma damage scaling, karma target labels, and scoreboard karma fields are hidden or inactive.

### `Lobby.RockTheVoteEnabled`

Type: boolean
Default/example: `true`

Controls whether players can start Rock the Vote map-change votes. Admin-forced map votes and votekick are unaffected.

## MapVote Fields

### `MapVote.Maps`

Type: string  
Format: package idents separated by semicolon, comma, newline, carriage return, or tab  
Default/example: full built-in curated map list

Curated map rotation used by dedicated servers. When at least one entry resolves to a currently curated map, the server applies this as the runtime map vote pool and ignores entries that are not in `MapRegistry.WhitelistMaps`.

The parser normalizes entries by trimming whitespace, removing empty entries, de-duplicating case-insensitively, and storing them semicolon-separated internally.

If no entries resolve to currently curated maps, the server logs `map_vote_maps_skipped` and leaves the runtime `MapVoteConfig` values in place.

Older configs may still contain `Lobby.MapVoteMode`, `Lobby.IncludeBlacklistInWhitelist`, `MapVote.Whitelist`, or `MapVote.Blacklist` from previous development builds. New generated configs do not include them. Unknown extra fields are preserved on disk but are not part of the current dedicated settings surface.

Example:

```json
"Maps": "thieves.rooftops;thieves.dolls;throwupproducs.islandtropical"
```

## Dedicated Server Convars

These are not fields inside `server_settings.json`; they are launch/convar options.

### `t.dedicated.empty_reset`

Type: boolean  
Default: `1`

Controls whether an empty dedicated server reloads back to its boot baseline map after all real players leave.

### `t.dedicated.empty_reset.delay`

Type: number  
Default: `2.0`

Delay in seconds before the empty dedicated reset runs. This gives disconnect bookkeeping time to settle.

## Log Lines

Useful startup/config log lines:

```text
[server.settings] defaults_written path=server_settings.json source=missing_config
[server.settings] missing path=server_settings.json; created active defaults. Edit server_settings.json and restart the dedicated server to customize settings.
[server.settings] defaults_backfilled path=server_settings.json
[server.settings] loaded path=server_settings.json startup_map=thieves.rooftops lobby=True vote_maps=18 admin_count=1
[server.settings] map_vote_maps_skipped reason=no_curated_matches
[server.settings] failed_to_load path=server_settings.json error=...
[lobby.settings] applied public, 6 rounds, 30m, prep 20s, haste, curated, 8 vote maps
```

Useful map/lifecycle log lines:

```text
[server.launch] boot_context ...
[t.net.maptravel] event=dedicated_startup_map_begin ...
[t.net.maptravel] event=dedicated_empty_reset_begin ...
```

## Current Limitations

- `server_settings.json` is read once per process. Restart after editing.
- Missing active-config fields are backfilled from defaults on load; existing operator values are not overwritten.
- `Lobby.MaxPlayers` is applied to `LobbyConfig`, but any external server-manager max-player limit should still be kept aligned with it.
- Steam metadata does not carry the dedicated-server settings payload. Dedicated servers should keep rich TTT configuration in `server_settings.json`.
- Dedicated servers rely on native s&box discovery fields for game, map, and server name. The game package ident and map package ident are native launch/browser context, not custom TTT metadata.
