# Lobby

*Empires lobby, discovery, vote, and redirect orchestration for SourceMod 1.12.*

> This plugin is maintained for a specific Empires deployment and is not intended to be a
> generic public-release plugin.

Lobby is a deployable Empires lobby package, not just a vote menu. It keeps a lobby server
aware of live game servers, stores and refreshes server metadata through MySQL, decides when
the lobby should stay open versus move players elsewhere, and validates redirect targets live
before sending players to them.

## Overview

| Item | Value |
| --- | --- |
| Target runtime | SourceMod `1.12.0.7223` |
| Main compile entrypoint | `addons/sourcemod/scripting/Lobby.sp` |
| Config file | `cfg/sourcemod/Lobby.cfg` |
| Database DSN | `lobby` |
| Discovery modes | UDP master server, Steam Web API |
| Published docs mirror | <https://sourcemod.docs.empiresmod.com/Lobby/> |
| Published package mirror | <https://sourcemod.docs.empiresmod.com/Lobby/package/> |

## What Lobby Handles

Lobby currently covers all of the following:

- discovery of Empires servers through either UDP master-server queries or the Steam Web API
- DB-backed storage and refresh of server metadata
- vote candidate selection from the live server list
- native SourceMod vote flow for choosing a server
- runoff votes for close first-round outcomes
- automatic populated-server redirects when that is more appropriate than a vote
- preferred-server behavior after successful vote-selected redirects
- live A2S validation before vote, populated, single-player, and forced redirects
- hold-and-retry handling for temporarily flaky redirect targets
- player-facing intro messaging, HUD text, periodic info announcements, and sound cues
- bot enable/disable voting through Empires `ai_inhibit_spawners`
- solo simulation tooling for one-client testing

## Requirements

- Empires Mod on Source
- SourceMod `1.12`
- a configured MySQL DSN named `lobby` in `addons/sourcemod/configs/databases.cfg`
- Linux hosts with 32-bit zlib available, typically `lib32z1`

Bundled runtime components:

- `SteamWorks`
- `socket`

Notes:

- The package already ships the extension binaries and SourcePawn include files for both
  bundled extensions.
- The plugin no longer depends on the cURL extension at runtime.

## Install and First Run

1. Install the packaged files into the game server root.
2. Verify `addons/sourcemod/configs/databases.cfg` contains a working `lobby` DSN.
3. Review and adjust `cfg/sourcemod/Lobby.cfg`.
4. Choose the discovery mode you want to use:
   - `l_querymethod 0` for UDP master-server discovery
   - `l_querymethod 1` for Steam Web API discovery
5. If using Steam Web API discovery, set `l_steamapikey`.
6. Reload the plugin or restart the server.
7. Confirm the plugin can:
   - resolve `lobby_version`
   - connect to the database
   - discover servers
   - populate vote and redirect candidates

## Runtime Flow

### Discovery and Refresh

Lobby maintains a DB-backed list of active Empires servers.

- `l_querymethod 0` uses UDP master-server querying through `socket`
- `l_querymethod 1` uses Steam Web API querying through SteamWorks HTTP
- if Web API discovery is requested without `l_steamapikey`, Lobby falls back to UDP
- discovery updates server presence in the DB, then schedules bounded A2S and XP refresh work
- the local `lobby_version` is resolved from `empires/steam.inf` first, with engine-version
  parsing as fallback

### Voting

When the lobby reaches the configured threshold, Lobby starts a vote countdown and opens the
native vote UI.

- vote candidates come from the DB-backed server list
- candidates must match the local Empires version
- candidates must be compatible with the current transfer size
- close first-round outcomes can trigger a runoff round
- runoff finalists are revalidated with targeted A2S checks before the runoff menu opens

### Redirects

Lobby can redirect through several paths:

- vote-selected redirect
- populated-server redirect
- single-player populated redirect
- forced redirect through `l_forceredirect`
- backup redirect behavior when DB-driven target selection fails

All automated redirect targets are validated live before execution.

- stale DB rows are not trusted blindly
- temporary failures move the target into a hold-and-retry window
- held targets can recover and continue normally
- dead targets are abandoned cleanly after the hold timeout

### Preferred Targets

Successful vote-selected redirects can make the chosen server preferred for a time.

- preferred targets influence later populated-server checks
- temporary target failures do not immediately discard preference
- preference is still subject to live compatibility checks before redirecting

### Player-Facing Behavior

Lobby also manages the player experience on the lobby server.

- staged intro messages are shown to joining players
- the lobby displays HUD text for its identity and vote-restriction state
- sound cues exist for welcome, vote start, redirect, and vote failure events
- Discord and charts links can be announced periodically and opened via MOTD panels

### Bot Control

Lobby integrates with Empires bot spawning through `ai_inhibit_spawners`.

- `sm_botsoff` starts or participates in a vote to stop bot spawning
- `sm_botson` starts or participates in a vote to allow bot spawning again
- `l_botsenabled` can disable the bot-vote feature entirely

## Commands

### Player Commands

| Command | Purpose |
| --- | --- |
| `sm_discord` | Open the configured Discord link. |
| `sm_charts` | Open the configured charts or population link. |
| `sm_botsoff` | Vote to stop bot spawning. |
| `sm_botson` | Vote to re-enable bot spawning. |

### Server Commands

| Command | Purpose |
| --- | --- |
| `l_query` | Run a debug vote-candidate query. |
| `l_queryx` | Print details from the current vote-candidate cache to the server console. |
| `l_startvote` | Force a lobby vote to start immediately. |
| `l_redirect` | Redirect all current human players one time. Usage: `l_redirect IP:PORT`. |
| `l_rsdump` | Dump cached refresh snapshot data to the server console. |
| `l_test_reset` | Reset solo simulation test state. |
| `l_test_status` | Show solo simulation test state. |
| `l_test_players` | Set simulated BE and NF player counts. Usage: `l_test_players BE NF`. |
| `l_test_voteprofile` | Set the synthetic vote profile. Usage: `off`, `abstain`, `random`, or `index N`. |

## Configuration

### Vote and Redirect

| Cvar | Default | Description |
| --- | --- | --- |
| `l_minplayers` | `8` | Start a vote when at least this many players have joined a team. |
| `l_minplayersext` | `8` | Minimum live players required before a server counts as populated. |
| `l_votetimer` | `60` | Primary vote duration in seconds. |
| `l_runoffvotetimer` | `30` | Runoff vote duration in seconds. |
| `l_votemaxservers` | `6` | Maximum total servers shown across the vote. |
| `l_threshold` | `50` | Required vote percentage for a successful result. |
| `l_runoffmargin` | `1` | Maximum vote gap from first place that still triggers a runoff. |
| `l_restrictvote` | `60` | Cooldown after a vote before another one may start. |
| `l_preferredservertimer` | `1800` | How long a vote-selected server stays preferred. |
| `l_preferredservermp` | `1` | Minimum players required for a preferred server to remain eligible. |
| `l_backupserver` | `5.9.180.78:27017` | Backup redirect target when DB-driven selection cannot provide one. |
| `l_selfserveraddr` | empty | Optional self-exclusion override; leave empty or `0` to auto-detect. |
| `l_forceredirect` | `0` | Force redirect target as `IP:PORT`; leave empty or `0` to disable. |
| `l_redirectholdtimeout` | `30` | Hold-and-retry window for flaky automated redirect targets. |

### Discovery and Logging

| Cvar | Default | Description |
| --- | --- | --- |
| `l_refreshservers` | `10` | Server refresh interval in seconds; `0` disables scheduled refreshes. |
| `l_refreshmaxservers` | `100` | Hard cap on the number of servers refreshed per cycle. |
| `l_querymethod` | `0` | Discovery mode: `0` = UDP master server, `1` = Steam Web API. |
| `l_steamapikey` | empty | Steam Web API key used when `l_querymethod` is `1`. |
| `l_log_level` | `-1` | Structured Lobby log level. |
| `l_log_console` | `1` | Enable structured logging to the server console. |
| `l_log_file` | `1` | Enable structured logging to `addons/sourcemod/logs/lobby/`. |
| `l_debug` | `0` | Deprecated compatibility cvar bridged into the structured log-level system. |
| `l_serverformat` | `%name - %map (%numplayers/%maxplayers)` | Debug display format for server snapshots. |
| `l_fastconnecttest` | `0` | Internal or test redirect-method selector. |

### Links, Bots, and Testing

| Cvar | Default | Description |
| --- | --- | --- |
| `l_discordurl` | `https://discord.gg/UH6Sc6B` | URL used by `sm_discord` and periodic announcements. |
| `l_chartsurl` | `https://steamcharts.com/app/17740` | URL used by `sm_charts` and periodic announcements. |
| `l_discordannouncetimer` | `260` | Discord announcement interval in seconds; `0` disables it. |
| `l_chartsannouncetimer` | `431` | Charts announcement interval in seconds; `0` disables it. |
| `l_botsenabled` | `1` | Enable or disable the bot-vote feature and bot control integration. |
| `l_testmode` | `0` | Enable solo simulation testing for vote and redirect decisions. |

## Solo Simulation Test Mode

`l_testmode` adds an opt-in simulation layer for testing Lobby while you are the only real
client on the server.

- simulated players are added on top of real BE and NF counts
- vote thresholds, candidate slot checks, and populated-server slot checks use effective real
  plus simulated counts
- vote menus still open only for real human clients
- redirect commands still execute only on real human clients
- discovery and database inputs stay real; test mode changes only Lobby decision logic

Suggested flow:

```text
l_testmode 1
l_test_players 4 4
l_test_voteprofile random
l_startvote
l_test_status
```

Vote profiles:

- `off`: no synthetic voting
- `abstain`: simulated players count toward thresholds but cast no votes
- `random`: simulated players vote randomly across visible candidates
- `index N`: simulated players all vote for visible candidate `N`

## Self-exclusion behavior

- the lobby excludes only itself automatically
- the self address is resolved from the detected public `ip:port`
- `l_selfserveraddr` can override that value when needed

## Licensing

- Lobby-authored code is licensed under `GPL-3.0-or-later`. See [LICENSE](LICENSE).
- Bundled third-party extension binaries and include files are listed in
  [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).
- Exact bundled third-party license copies live under
  [third_party_licenses/README.md](third_party_licenses/README.md).
- The root `README.md` and `LICENSE` files are the authoritative documentation and license
  sources.
- The generated `public/` mirror is refreshed for GitLab Pages packaging and should not be
  treated as a second manual source of truth.
