FFSPeak
This package contains a PEAK mod and a python server and allows multiple players to play the same map with the same items and have observers see all players at onceFFSPeak
Version: 0.9.0
Compatible with: PEAK 1.65.a
FFSPeak is a multiplayer observer and synchronization system for PEAK. It consists of a BepInEx plugin (Unity mod) and a Python WebSocket server that enables real-time spectating, lobby management, and item spawn synchronization.
Features
- Observer System: Watch multiple players in real-time with configurable lobbies
- Lobby Management: Create password-protected lobbies with player and time limits
- State Synchronization: Real-time player position, animation, and status updates
- Item Spawn Sync: Synchronized item spawning across all players
- API Integration: REST and SSE endpoints for score tracking and event monitoring
- Recording/Playback: Session recording and replay (⚠️ experimental, not yet complete)
Quick Start
Running Multiple PEAK Instances
To test or run multiple PEAK clients simultaneously:
- Navigate to your PEAK installation directory (e.g.,
SteamLibrary\steamapps\common\PEAK\) - Create a file named
steam_appid.txtnext to the PEAK executable - Add the following content:
3527290 - Launch
PEAK.exedirectly (not through Steam) — you can now run multiple instances
Server Setup
The Python server requires Redis as a message broker and data store.
Prerequisites
- Python: 3.10 or later
- Redis: 7.0 or later
- Windows: Redis on Windows (Memurai) or Redis via WSL
- Linux: Official Redis installation guide
- Docker (all platforms):
docker run -d --name redis -p 6379:6379 redis:7
Installation
Note: The Python server files are included in the Thunderstore package under the
server/directory. After installing the mod, you can find them in your BepInEx plugins folder atBepInEx/plugins/FFSPeak/server/.
-
Navigate to the server directory:
# If installed via Thunderstore cd BepInEx/plugins/FFSPeak/server # Or if cloning from repository cd FFSPeakServer -
Install Python dependencies:
pip install -r requirements.txt -
Start Redis (if not already running):
docker run -d --name redis -p 6379:6379 redis:7 -
Launch the WebSocket server:
python server_ws.py --api-key-file api_key.txt --port 4000 --http-port 4001
Command Line Options
The server accepts the following arguments:
| Option | Default | Description |
|---|---|---|
--port |
4000 |
WebSocket server port |
--http-port |
4001 |
HTTP REST API and SSE port |
--api-key-file |
— | Path to file containing API key (recommended) |
--api-key |
— | API key as plain string (⚠️ insecure, visible in process list) |
Environment variables can also be used:
WS_PORT,HTTP_PORT: Override default portsSSE_API_KEY_FILE: Path to API key file
Configuration File
Server behavior is configured via environment variables. See FFSPeakServer/docs/configuration.md for details on:
- Redis connection settings (
REDIS_HOST,REDIS_PORT) - Pub/Sub channels (
REDIS_STATE_CHANNEL,REDIS_EVENTS_CHANNEL) - Performance tuning (observer tick rates, LOD settings)
Client Configuration
Command Line Options
The FFSPeak client plugin supports several command line options for easier testing and configuration:
| Option | Aliases | Description |
|---|---|---|
-observer |
— | Force observer mode (disables player mode) |
-player |
— | Force player mode (disables observer mode) |
-serverurl <url> |
--server-url, --serverurl, -url, --url |
Override WebSocket server URL |
-port <port> |
--port, -wsport, --ws-port, --wsport |
Override WebSocket server port |
Example usage:
# Launch as observer connecting to a custom server
PEAK.exe -observer -serverurl ws://example.com:4000
# Launch as player with custom port
PEAK.exe -player -port 8080
Using FFSPeak (Observer Workflow)
Creating a Game (as Observer)
- Start an offline game
- Create a lobby:
- Set an optional password
- Select a level from the dropdown
- Set player limit (max players allowed)
- Set time limit (match duration)
- Freeze on Join: Toggle whether players should be frozen when they connect
- Observers can unfreeze players later using the "Start/Resume" button
- Click "Start/Set" to configure the lobby
- Click "Join" to enter the game as an observer
Joining a Game (as Player)
- Start an offline game
- Select a lobby from the list
- Enter password (if required)
- Click "Join"
Observer UI Utilities
When connected as an observer, the following hotkeys open administration panels:
| Key | Panel | Description |
|---|---|---|
| F3 | Remote Player Administration | Manage connected players: kick, teleport, spawn items, apply afflictions, modify stats |
| F4 | Message System | Send messages to players or broadcast announcements |
| F8 | FPS Display | Toggle FPS counter (available to all users) |
| F9 | Lobby Manager | Main lobby creation and configuration panel |
| F10 | Admin Commands | Execute administrative commands and server operations |
| F11 | Toolbox | Additional observer utilities and tools |
Note: F8 (FPS display) is available to both observers and players.
Item Spawn Synchronization
FFSPeak integrates with ItemSpawnSync to synchronize item spawns across all players.
Creating/Uploading Spawn Data
- Join as an observer using the "Join Without Spawn" button (found in the Advanced foldout)
- Use ItemSpawnSync shortcuts (default keybinds):
- Ctrl+F8: Load loot data from level
- Ctrl+F4: Spawn items at current position
- Ctrl+F5: Save spawned items to file
- Restart the game (optional, to clear local state)
- Select the spawn data file in the FFSPeak Advanced foldout
- Click "Send" to upload spawn data to the server
- Restart again (if needed) — spawn data should now sync to all players
Note: The spawn synchronization workflow is still being refined. Users may need to restart the game to get the last spawn data.
Observer Authentication
Observer Key Setup
Observer keys control who can connect to the server as an observer. If no observer keys are defined, anyone can join as an observer (open registration mode).
Creating Observer Keys
Observer keys are stored in the FFSPeakServer/observer_keys/ directory as .txt or .json files.
Option 1: Manual creation
-
Create a file in
FFSPeakServer/observer_keys/:# Text file (one key per line) echo "my-secure-observer-key-123" > observer_keys/team_keys.txt # JSON file (array format) echo '["key1", "key2", "key3"]' > observer_keys/team_keys.json -
Reload keys into Redis:
- While the server is running, press
kin the server console to reload keys - Or restart the server (keys are loaded automatically on startup)
- While the server is running, press
Option 2: Interactive server commands
While the server is running, use the following console commands:
| Command | Description |
|---|---|
k |
Reload observer keys from observer_keys/ directory |
ka |
Add a new observer key interactively |
kg |
Generate and save a random observer key to a JSON file |
Option 3: Programmatic generation
from observer_keys import generate_and_save_random_key
key = generate_and_save_random_key('my_team.json')
print(f"Generated key: {key}")
Using Observer Keys
When connecting as an observer:
- The client will prompt for an observer key (if keys are configured on the server)
- Enter your assigned key
- If authentication succeeds, you'll be granted observer access
Security Note: If you want to restrict observer access, always configure at least one observer key. Without any keys defined, the server operates in open mode and accepts all observer connections.
HTTP API
The server exposes an HTTP/SSE API for external integrations (e.g., overlays, bots, dashboards).
API Key Setup
API keys (different from observer keys) are required to access protected HTTP/SSE endpoints. To generate a key:
-
Create a file (e.g.,
api_key.txt) containing a secure random string:# Generate a random key (Linux/Mac) openssl rand -base64 32 > api_key.txt # Generate a random key (Windows PowerShell) -join ((48..57) + (65..90) + (97..122) | Get-Random -Count 32 | % {[char]$_}) > api_key.txt -
Pass it to the server via
--api-key-file api_key.txt -
Use this key in HTTP headers:
X-API-Key: <your-key>
Available Endpoints
- GET
/api/score— Retrieve current game score/state - SSE
/api/events— Subscribe to real-time events:- Timer start/stop/finish
- Player finish events
- Level changes
See FFSPeakServer/WEBSOCKET_README.md for full protocol documentation.
Recording and Playback (⚠️ Experimental)
FFSPeak includes a recording system to capture game sessions and replay them later.
Status: This feature is incomplete and not fully functional in the current version. Use at your own risk.
To use (when functional):
- Start
server_playback.pyalongside the WebSocket server - Sessions are automatically recorded to
records/ - Replay using playback commands (see
FFSPeakServer/docs/server_playback.md)
Building the Plugin
Prerequisites
- .NET SDK: 6.0 or later
- BepInEx 5: Installed in your PEAK game directory
- Game assemblies: Located in
<PEAK_Install>/PEAK_Data/Managed/
Setup
- Copy
Config.Build.user.props.templatetoConfig.Build.user.props - Edit
Config.Build.user.props:<GamePath>C:\Path\To\Your\PEAK</GamePath> <PluginOutputPath>C:\Path\To\BepInEx\plugins\</PluginOutputPath>
Build Commands
Debug build (includes testing shortcuts):
dotnet build -c Debug
Release build:
dotnet build -c Release
Thunderstore package:
dotnet build -c Publish -target:PackTS -v d
Package output: artifacts/thunderstore/
Debug Mode Features
When built in Debug configuration, the plugin includes testing shortcuts:
- F9: Open checkpoint teleport UI
- Ctrl + Numpad 0–9: Teleport to checkpoints (0 = spawn, ascending to peak)
- Invincibility toggle: Available via checkpoint UI
⚠️ Debug shortcuts are disabled in Release and Publish builds. They should not appear in distributed versions.
Documentation
For detailed server architecture and API documentation:
Dependencies
Required Mods
Recommended Mods
License
See LICENSE for details.
Contributing (The project not opened to contributions yet)
Contributions are welcome! Please ensure:
- Code follows existing patterns (see
.github/copilot-instructions.md) - C# code adheres to
.github/instructions/csharp.instructions.md - Python code adheres to
.github/instructions/python.instructions.md
Support
For issues, questions, or feature requests, please contact us on the peak modding discord thread dedicated to this mod
