Dateien nach "/" hochladen
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
---
|
||||
name: Voice Architecture Plan
|
||||
overview: "Development-ready SDK/ServerLib plan (Option B): custom Conan Voice Server on TeamSpeak 3 ServerLib + custom ClientLib app, with server-side whisper-list routing. Includes concrete SDK function names, init/shutdown order, data-flow, threading, and per-module API mapping. Isolated on branch ts3-Testing."
|
||||
todos:
|
||||
- id: create-branch
|
||||
content: "Create branch ts3-Testing off current branch; do not touch tsmain / V8 plugin"
|
||||
status: pending
|
||||
- id: sdk-license-gate
|
||||
content: "Phase 0: get TS3 SDK (ClientLib+ServerLib) + evaluation license; build official examples; confirm the API names/signatures flagged VERIFY in this plan against serverlib.h/clientlib.h"
|
||||
status: pending
|
||||
- id: serverlib-bootstrap
|
||||
content: "Phase 1: conan-voice-server bootstrap (initServerLib -> createVirtualServer -> loop -> stopVirtualServer -> destroyServerLib)"
|
||||
status: pending
|
||||
- id: sdk-client-poc
|
||||
content: "Phase 2: custom ClientLib app connects (initClientLib -> spawnNewServerConnectionHandler -> createIdentity -> open devices -> startConnection)"
|
||||
status: pending
|
||||
- id: metadata-transport
|
||||
content: "Phase 2/3: game-data transport via CLIENT_META_DATA (+ flushClientSelfUpdates); confirm server read + change event"
|
||||
status: pending
|
||||
- id: routing-poc
|
||||
content: "Phase 3: server whisper-list routing (ts3server_setClientWhisperList): prove A hears B, not C"
|
||||
status: pending
|
||||
- id: threed-voice
|
||||
content: "Phase 4/5: 3D via ts3client 3D listener/channel attributes fed by server positions"
|
||||
status: pending
|
||||
- id: write-en-doc
|
||||
content: "Mirror this plan into docs/ as the living design doc once Phase 0 confirms API names"
|
||||
status: pending
|
||||
- id: keep-v8-untouched
|
||||
content: "Do not modify REWRITE_PLAN_V8.md / REWRITE_PLAN.md or the V8 plugin on tsmain"
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
|
||||
# Conan Voice on TeamSpeak 3 SDK (ClientLib + ServerLib) — Development-Ready Plan
|
||||
|
||||
New product, new branch `ts3-Testing`. The V8 retail plugin on `tsmain` stays intact and is reused for *logic* only.
|
||||
|
||||
> Items marked **[VERIFY]** are exact names/signatures to confirm against `serverlib.h` / `clientlib.h` and the bundled API reference during Phase 0. Everything unmarked is confirmed from TeamSpeak docs/examples.
|
||||
|
||||
---
|
||||
|
||||
## 0. The two SDKs (why this is a real switch, not a rename)
|
||||
|
||||
| | Client Plugin SDK (today, V8) | TeamSpeak 3 SDK (this plan) |
|
||||
|---|---|---|
|
||||
| Header(s) | `ts3_functions.h` (`TS3Functions`) | `clientlib.h` + `serverlib.h` |
|
||||
| Function family | `ts3plugin_*` callbacks | `ts3client_*` and `ts3server_*` |
|
||||
| Who hosts voice | retail `ts3server` | **our** `conan-voice-server` process |
|
||||
| Who runs the client | retail TeamSpeak app loads our DLL | **our** app embeds ClientLib |
|
||||
| Can decide who hears whom | no (edit local PCM only) | **yes** (`ts3server_setClientWhisperList`) |
|
||||
| License | free | paid; free evaluation for dev |
|
||||
| Interop | plugin only talks to retail TS | SDK client only talks to SDK server (`0x0705` otherwise) |
|
||||
|
||||
Proof today is Plugin SDK: `sdk/README.md` line 1 = "TeamSpeak 3 Client Plugin SDK"; repo has `ts3plugin_*`, no `ts3client_*`/`ts3server_*`.
|
||||
|
||||
**Consequence:** the "Conan Voice Client" is a standalone ClientLib app (users run it like the Mumble client), not retail TeamSpeak + plugin.
|
||||
|
||||
---
|
||||
|
||||
## 1. Component overview
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph ClientHost [Player PC]
|
||||
game[Conan Exiles]
|
||||
prov[IGameDataProvider]
|
||||
cstate[VoiceClientState]
|
||||
cadapt[TeamSpeakClientAdapter]
|
||||
clib[SDK ClientLib]
|
||||
game --> prov --> cstate --> cadapt --> clib
|
||||
clib -->|"onEditPlaybackVoiceDataEvent, 3D"| cadapt --> hud[HUD]
|
||||
end
|
||||
subgraph ServerHost [Voice server]
|
||||
slib[SDK ServerLib]
|
||||
sadapt[TeamSpeakServerAdapter]
|
||||
pm[PlayerManager]
|
||||
prox[ProximityEngine]
|
||||
router[VoiceRouter]
|
||||
slib --> sadapt --> pm --> prox --> router --> sadapt --> slib
|
||||
end
|
||||
clib <-->|"SDK voice + CLIENT_META_DATA"| slib
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Server process — exact lifecycle
|
||||
|
||||
File: `server/src/main.c` (+ `server/src/ts_server_adapter.c`).
|
||||
|
||||
Startup order (each step checks the return code with `ts3server_getGlobalErrorMessage` on failure):
|
||||
|
||||
```text
|
||||
1. load config (server/config/*.ini) -> our Config module
|
||||
2. start logger -> our Logging module
|
||||
3. fill ServerLibFunctions (memset 0, assign callbacks)
|
||||
4. ts3server_initServerLib(&funcs, LogType_FILE|LogType_CONSOLE, logDir)
|
||||
5. ts3server_createServerKeyPair(...) OR load stored keypair [VERIFY exact name]
|
||||
6. ts3server_createVirtualServer(port, "0.0.0.0", name, keyPair, maxClients, &serverID)
|
||||
7. init Voice backend (PlayerManager, ProximityEngine, VoiceRouter)
|
||||
8. run event loop (routing tick; SDK callbacks fire on SDK threads)
|
||||
9. shutdown: ts3server_stopVirtualServer(serverID)
|
||||
ts3server_destroyServerLib()
|
||||
```
|
||||
|
||||
Confirmed signatures:
|
||||
|
||||
- `unsigned int ts3server_initServerLib(const struct ServerLibFunctions* funcs, int usedLogTypes, const char* logFileFolder);`
|
||||
- `unsigned int ts3server_createVirtualServer(unsigned int serverPort, const char* serverIp, const char* serverName, const char* serverKeyPair, unsigned int serverMaxClients, uint64* result);`
|
||||
- `unsigned int ts3server_stopVirtualServer(uint64 serverID);`
|
||||
- `unsigned int ts3server_destroyServerLib(void);`
|
||||
- Routing: `unsigned int ts3server_setClientWhisperList(uint64 serverID, anyID clientID, const uint64* channelIDs, const anyID* clientIDs);`
|
||||
- Enumerate: `ts3server_getClientList(serverID, anyID** result)`, `ts3server_getChannelList(serverID, uint64** result)`, free with `ts3server_freeMemory(ptr)`.
|
||||
|
||||
Callbacks we register in `ServerLibFunctions` (assign only what we use):
|
||||
|
||||
- `onClientConnected(serverID, clientID, channelID, ...)` **[VERIFY arg list]** -> PlayerManager.add
|
||||
- `onClientDisconnected(serverID, clientID, channelID, ...)` -> PlayerManager.remove + clear whisper list
|
||||
- `onClientMoved(serverID, clientID, oldCh, newCh, ...)`
|
||||
- `onServerTextMessageEvent` / `onChannelTextMessageEvent` -> fallback game-data channel (see 5)
|
||||
- `onClientStartTalkingEvent` / `onClientStopTalkingEvent` -> HUD "who speaks" hints
|
||||
- `onUserLoggingMessageEvent` -> our logger
|
||||
- `onAccountingErrorEvent` -> license/slot errors (must handle for eval license)
|
||||
|
||||
---
|
||||
|
||||
## 3. Client app — exact lifecycle
|
||||
|
||||
File: `client/src/main.c` (+ `client/src/ts_client_adapter.c`, `client/src/game_provider_*.c`).
|
||||
|
||||
```text
|
||||
1. fill ClientUIFunctions (callbacks)
|
||||
2. ts3client_initClientLib(&clientFuncs, NULL, LogType_FILE, NULL, resourcesPath) [VERIFY arg list]
|
||||
3. ts3client_spawnNewServerConnectionHandler(0, &scHandlerID)
|
||||
4. identity: load stored, else ts3client_createIdentity(&id) then persist
|
||||
5. ts3client_openPlaybackDevice(scHandlerID, "", "") // default device
|
||||
ts3client_openCaptureDevice(scHandlerID, "", "")
|
||||
6. ts3client_startConnection(scHandlerID, identity, host, port,
|
||||
nickname, NULL/*default channel*/, "", serverPassword)
|
||||
7. wait for onConnectStatusChangeEvent == STATUS_CONNECTION_ESTABLISHED
|
||||
8. loop: push game data (see 5), set 3D (see 6), render HUD
|
||||
9. shutdown: ts3client_stopConnection(scHandlerID, "leaving")
|
||||
ts3client_destroyServerConnectionHandler(scHandlerID)
|
||||
ts3client_destroyClientLib()
|
||||
```
|
||||
|
||||
Confirmed:
|
||||
|
||||
- `ts3client_startConnection(scHandlerID, identity, ip, port, nickname, defaultChannelArray, defaultChannelPassword, serverPassword)`
|
||||
- `ts3client_stopConnection(scHandlerID, quitMessage)`
|
||||
- `ts3client_getConnectionStatus(scHandlerID, int* result)`
|
||||
- Callback `onConnectStatusChangeEvent(scHandlerID, newStatus, errorNumber)`; `onServerStopEvent(scHandlerID, shutdownMessage)`.
|
||||
|
||||
ClientLib callbacks we implement: `onConnectStatusChangeEvent`, `onNewChannelEvent`, `onClientMoveEvent`, `onTalkStatusChangeEvent`, `onServerStopEvent`, `onEditPlaybackVoiceDataEvent` (local audio effects: reverb/lowpass reuse from V8), `onCustom3dRolloffCalculationClientEvent` (custom distance curve). **[VERIFY exact callback names in clientlib.h]**
|
||||
|
||||
---
|
||||
|
||||
## 4. Data flow: connect -> position -> routing -> voice
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant G as Conan (game)
|
||||
participant C as Client app (ClientLib)
|
||||
participant S as Server (ServerLib)
|
||||
participant R as VoiceRouter
|
||||
|
||||
C->>S: ts3client_startConnection
|
||||
S-->>C: onConnectStatusChangeEvent(ESTABLISHED)
|
||||
S->>S: onClientConnected -> PlayerManager.add(clientID)
|
||||
|
||||
loop ~20 Hz
|
||||
G->>C: IGameDataProvider.getPlayerState(pos,rot,ids)
|
||||
C->>C: ts3client_setClientSelfVariableAsString(CLIENT_META_DATA, packed)
|
||||
C->>S: ts3client_flushClientSelfUpdates
|
||||
S->>S: metadata change -> PlayerManager.updatePos(clientID,pos)
|
||||
end
|
||||
|
||||
loop ~5-10 Hz (routing tick)
|
||||
R->>R: spatial hash -> audible set per client
|
||||
R->>S: ts3server_setClientWhisperList(serverID, A, {}, {B,C})
|
||||
R->>S: ts3server_setClientWhisperList(serverID, D, {}, {}) // hears no one
|
||||
end
|
||||
|
||||
Note over C,S: A speaks -> ClientLib routes A's voice only to B,C (whisper list)
|
||||
S-->>C: voice packets to B,C only
|
||||
C->>C: onEditPlaybackVoiceDataEvent -> distance gain/pan/reverb
|
||||
C->>C: 3D attributes -> spatialization
|
||||
```
|
||||
|
||||
Whisper-list routing detail (from TeamSpeak Whisper Lists example): a client with a whisper list bypasses normal channel voice and sends **only** to the listed clients/channels. So the router expresses the full audible matrix as per-speaker client-ID lists. Clearing = `ts3server_setClientWhisperList(serverID, client, NULL, NULL)`.
|
||||
|
||||
Caveat to design around: while a client has an active whisper list, its **normal channel talk is disabled**. Therefore ALL proximity/radio/whisper voice must be expressed through whisper lists (do not mix with channel voice for routed players).
|
||||
|
||||
---
|
||||
|
||||
## 5. Game-data transport (the previously-missing piece)
|
||||
|
||||
Requirement: client must tell the server its position ~20 Hz **without an external HTTP/REST/WebSocket API** (user constraint). Two in-SDK mechanisms:
|
||||
|
||||
- **Primary: `CLIENT_META_DATA`** (string, up to 4096 bytes, Read/Write).
|
||||
- Client: `ts3client_setClientSelfVariableAsString(scHandlerID, CLIENT_META_DATA, packedState)` then `ts3client_flushClientSelfUpdates(scHandlerID, NULL)`.
|
||||
- Server: read with `ts3server_getClientVariableAsString(serverID, clientID, CLIENT_META_DATA, &out)` **[VERIFY server-side getter + whether a change event fires; if no push event, poll on the routing tick]**.
|
||||
- Payload = compact base64/binary: `playerId,charId,x,y,z,yaw,voiceMode,flags,seq` (reuse V8 CEPOS packing idea, rewritten).
|
||||
- **Fallback: text messages** `TextMessageTarget_SERVER` -> server `onServerTextMessageEvent`. Heavier; use only for discrete events (RADIO_JOIN, WHISPER_START) if metadata proves too slow.
|
||||
|
||||
Small control protocol (carried over the chosen mechanism): `HELLO, AUTH, AUTH_RESULT, POSITION, VOICE_STATE, RADIO_JOIN/LEAVE, WHISPER_START/STOP, CONFIG, PING/PONG` (from v2 draft, kept).
|
||||
|
||||
---
|
||||
|
||||
## 6. 3D voice
|
||||
|
||||
Server feeds authoritative positions; **client** applies 3D locally via ClientLib 3D API **[VERIFY exact names]**:
|
||||
|
||||
- Listener (self): `ts3client_systemset3DListenerAttributes(scHandlerID, &posSelf, &forward, &up)`
|
||||
- Per remote speaker: `ts3client_channelset3DAttributes(scHandlerID, remoteClientID, &posRemote)` (or `ts3client_set3DWaveAttributes`)
|
||||
- Custom rolloff: implement `onCustom3dRolloffCalculationClientEvent` to match Conan voice ranges.
|
||||
- Vectors use `TS3_VECTOR {float x,y,z}`.
|
||||
|
||||
We do NOT build a voice engine; TeamSpeak does encode/decode/transport/spatialize.
|
||||
|
||||
---
|
||||
|
||||
## 7. Module map (server + client) with the APIs each owns
|
||||
|
||||
Server (`server/src/`):
|
||||
- `ts_server_adapter` — the ONLY file calling `ts3server_*`. Exposes `ITeamSpeakServer` (connectClient/disconnect/getClientState/setWhisperList/getClientMeta/moveClient).
|
||||
- `player_manager` — `VoicePlayer{tsClientId, conanPlayerId, characterId, pos, voiceMode, authenticated}`; add/remove on connect/disconnect; updatePos from metadata.
|
||||
- `proximity_engine` — spatial grid (cells), neighbor query, distance -> audible; reuse V8 `proximity_math`/`zone_resolve` logic (rewrite).
|
||||
- `voice_router` — merges proximity + radio + whisper + global + permissions -> per-client target list -> `ITeamSpeakServer.setWhisperList`; routing cache + hysteresis.
|
||||
- `auth` — SDK identity -> Conan player mapping via short-lived token.
|
||||
- `radio_manager`, `whisper_manager`, `permissions` — logical groups.
|
||||
- `config`, `logging`.
|
||||
|
||||
Client (`client/src/`):
|
||||
- `ts_client_adapter` — the ONLY file calling `ts3client_*`. Exposes `ITeamSpeakClient`.
|
||||
- `game_provider` — implements `IGameDataProvider` (file/IPC, ported from Mumble/V8 `pos_file`).
|
||||
- `voice_client_state`, `audio_effects` (reuse V8 reverb/lowpass in `onEditPlaybackVoiceDataEvent`), `hud`, `config`, `logging`.
|
||||
|
||||
Shared (`shared/`): `protocol/` (message + meta-data packing), `types/` (Vector3, PlayerId, VoiceMode), `config/`.
|
||||
|
||||
---
|
||||
|
||||
## 8. Threading model
|
||||
|
||||
- **SDK callback threads:** `ts3server_*` / `ts3client_*` callbacks fire on SDK-owned threads. Keep them short; hand off to our queues. (Same discipline as V8's callback-thread rule, but now applied to SDK callbacks.)
|
||||
- **Routing tick thread (server):** 5-10 Hz; reads player snapshots, computes audible sets, calls `setClientWhisperList`. Never blocks on game I/O.
|
||||
- **Game provider thread (client):** ~20-30 Hz reads Conan position; publishes snapshot; the flush to metadata happens on a controlled cadence.
|
||||
- **Audio (client):** `onEditPlaybackVoiceDataEvent` — no locks, snapshot read only (reuse V8 PCM ownership pattern).
|
||||
|
||||
---
|
||||
|
||||
## 9. Repository layout (branch ts3-Testing)
|
||||
|
||||
```text
|
||||
conan-voice/
|
||||
├── client/ { src/ include/ CMakeLists.txt }
|
||||
├── server/ { src/ include/ config/ CMakeLists.txt }
|
||||
├── shared/ { protocol/ types/ config/ }
|
||||
├── sdk/ { include/ lib/ bin/ examples/ } # examples untouched
|
||||
├── tests/ { unit/ integration/ load/ }
|
||||
├── docs/
|
||||
└── CMakeLists.txt # SDK path configurable, no C:\Users\... hardcodes
|
||||
```
|
||||
|
||||
Kept isolated from `src/` so the V8 DLL build is never disturbed and `tsmain` merges stay clean.
|
||||
|
||||
---
|
||||
|
||||
## 10. Phases (with concrete API deliverables)
|
||||
|
||||
**Phase 0 — SDK + license + API verification.** Get SDK (ClientLib+ServerLib) + evaluation license; build official ServerLib + ClientLib examples; confirm every **[VERIFY]** name against `serverlib.h`/`clientlib.h`. Exit: examples run; API list frozen.
|
||||
|
||||
**Phase 1 — server bootstrap (Milestone 1).** `main.c` runs steps 1-9 of section 2; start/stop/restart clean. No Conan, no routing.
|
||||
|
||||
**Phase 2 — client PoC + metadata.** ClientLib app connects (section 3); publishes `CLIENT_META_DATA`; server reads it. Two clients talk in one channel.
|
||||
|
||||
**Phase 3 — routing PoC (make-or-break).** `voice_router` sets whisper lists: `A -> {B}` only; then chained `A->B, B->C, C-/->A`. Proves server-authoritative "who hears whom".
|
||||
|
||||
**Phase 4 — Conan integration.** `IGameDataProvider` feeds real position/rotation/ids/voice-state (ported from Mumble/V8).
|
||||
|
||||
**Phase 5 — proximity + 3D.** spatial grid, cache, hysteresis in `proximity_engine`; 3D via section 6.
|
||||
|
||||
**Phase 6 — features.** radio, whisper groups, global, cave reverb (server env state -> client effect), HUD, voice modes, root-channel config.
|
||||
|
||||
**Phase 7 — performance.** 10/25/50/100/250 clients; CPU, RAM, network, routing latency, voice latency, reconnects.
|
||||
|
||||
**Phase 8 — production.** Linux/systemd, auto-restart, logging, monitoring, config validation, crash recovery, security review, **commercial license finalized**.
|
||||
|
||||
---
|
||||
|
||||
## 11. Definition of done
|
||||
|
||||
- **Milestone 1:** ServerLib up, virtual server created, SDK client connects, two clients pass voice, server changes whisper routing, clean shutdown + restart.
|
||||
- **v0.1:** Conan position -> proximity voice -> 3D voice; player mapping; voice range; reconnect; basic HUD.
|
||||
- **v0.2:** radio, whisper, global, root-channel config, cave reverb, HUD colors, permissions.
|
||||
- **v1.0:** production server+client, 250+ tested, perf documented, monitoring, crash recovery, security review, license cleared, full docs.
|
||||
|
||||
---
|
||||
|
||||
## 12. Hard gates / risks
|
||||
|
||||
- **License:** paid, negotiated; evaluation only for dev. ([licensing](https://business.teamspeak.com/licensing/))
|
||||
- **EOL:** TS3 + TS3 SDK end 2027-12-31 — keep `IVoiceTransport`/`ITeamSpeakServer` seams so transport can be swapped; do not leak SDK calls outside the two adapter files.
|
||||
- **API-name risk:** some 3D/keypair/metadata-getter names are **[VERIFY]** until Phase 0.
|
||||
- **Whisper-only routing:** routed clients lose normal channel voice — design accepts this.
|
||||
- **Position trust:** v1 client-reported (spoofable); v2 game-server-fed.
|
||||
|
||||
---
|
||||
|
||||
## 13. Open questions (resolved in Phase 0/PoC)
|
||||
|
||||
1. Exact `ServerLibFunctions` / `ClientUIFunctions` callback sets and arg lists.
|
||||
2. Server-side getter + change event for `CLIENT_META_DATA` (push vs poll).
|
||||
3. Exact 3D + keypair function signatures.
|
||||
4. Whisper-list update rate that stays artifact-free at 5-10 Hz.
|
||||
5. Real client cap under the negotiated license.
|
||||
6. Position authority (client vs game server) and id binding (Conan/Steam/SDK identity).
|
||||
7. EOL-2027 successor/exit path.
|
||||
|
||||
---
|
||||
|
||||
## Guardrails
|
||||
|
||||
- Work only on `ts3-Testing`. Do not touch `REWRITE_PLAN_V8.md` / `REWRITE_PLAN.md` or the V8 plugin.
|
||||
- Rewrite from V8/Mumble references; never blind-copy.
|
||||
- Every change gets a `doku/` entry (project rule).
|
||||
- No code, no branch, no commit until you approve.
|
||||
+218
@@ -0,0 +1,218 @@
|
||||
# Live Plugin (V8) vs. Planned SDK/ServerLib Architecture
|
||||
|
||||
A detailed, side-by-side comparison of **what the shipping plugin does today** (V8, branch `tsmain`)
|
||||
versus **what the new plan proposes** (branch `ts3-Testing`, "Option B"): which API each uses,
|
||||
what stays the same, and what fundamentally changes.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Both use "a TeamSpeak SDK" — but **two completely different ones**.
|
||||
> **Today:** the *Client Plugin SDK* (a guest inside the retail TeamSpeak client).
|
||||
> **Plan:** the *TeamSpeak 3 SDK* (ClientLib + ServerLib — your own client and your own server).
|
||||
> Same word, opposite architecture.
|
||||
|
||||
---
|
||||
|
||||
## 1. At a glance
|
||||
|
||||
| Topic | Live V8 (today) | Plan (Option B) |
|
||||
|---|---|---|
|
||||
| SDK used | **Client Plugin SDK** | **TeamSpeak 3 SDK** (ClientLib + ServerLib) |
|
||||
| Header(s) | `sdk/include/ts3_functions.h` (`TS3Functions`) | `clientlib.h` + `serverlib.h` |
|
||||
| Function family | `ts3plugin_*` (callbacks), `ts3Functions->*` | `ts3client_*` and `ts3server_*` |
|
||||
| What "the server" is | Retail `ts3server` (foreign) | **Our own** `conan-voice-server` (ServerLib) |
|
||||
| What "the client" is | Retail TeamSpeak loads our DLL | **Our own** app embedding ClientLib |
|
||||
| Who decides "who hears whom" | The client, locally (volume/mute) | The **server** (`ts3server_setClientWhisperList`) |
|
||||
| Position exchange | Peer-to-peer via `sendPluginCommand` (CEPOS) | Client to server via `CLIENT_META_DATA` |
|
||||
| Audio control | `onEditPlaybackVoiceDataEvent` (edits samples) | Server-side whisper routing + client-side 3D/PCM |
|
||||
| 3D sound | `systemset3D*` / `channelset3DAttributes` (in plugin) | Same `ts3client_*3D*` functions (in our app) |
|
||||
| Needs retail TeamSpeak | **Yes** (users already have it) | **No** — a custom client app is required |
|
||||
| License | Free | **Paid** (free evaluation for dev only) |
|
||||
| Lifecycle | Tied to TS3 | TS3 SDK **EOL 2027-12-31** |
|
||||
| "Who hears whom" reach | Local volume only; everyone still gets packets | Real packet routing (B/C never receive A) |
|
||||
| Effort | Exists, ships today | Build **both** a client and a server |
|
||||
|
||||
---
|
||||
|
||||
## 2. The two SDKs — the root of the confusion
|
||||
|
||||
The word "SDK" refers to two different TeamSpeak products.
|
||||
|
||||
### 2a. Today: Client Plugin SDK
|
||||
- Proof in the repo: `sdk/README.md`, line 1 = "TeamSpeak 3 Client Plugin SDK".
|
||||
- We ship a **DLL** that the **retail TeamSpeak client loads**.
|
||||
- We are a guest: we react to events (`ts3plugin_*`) and trigger a few client actions
|
||||
through the function table `TS3Functions`.
|
||||
- We **cannot** route voice at the server level. We can only alter incoming voice
|
||||
**locally** (quieter, pan, filter).
|
||||
|
||||
### 2b. Plan: TeamSpeak 3 SDK (ClientLib + ServerLib)
|
||||
- A separate download, different libraries, different function families
|
||||
(`ts3client_*` and `ts3server_*`).
|
||||
- We **host our own voice server** (ServerLib) and **build our own client** (ClientLib).
|
||||
- The retail TeamSpeak client is no longer involved — and in fact **cannot** connect to
|
||||
an SDK server (error `0x0705`, per TeamSpeak docs).
|
||||
|
||||
> [!NOTE]
|
||||
> This is not an "update" — it is an architecture switch: from "a plugin guest inside
|
||||
> TeamSpeak" to "a standalone client + server product".
|
||||
|
||||
---
|
||||
|
||||
## 3. Architecture, side by side
|
||||
|
||||
### Live V8 (today)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
mod["Conan Exiles mod"] -->|"Pos.txt"| plugin["Our plugin (DLL inside retail TeamSpeak)"]
|
||||
plugin -->|"sendPluginCommand CEPOS"| peers["Other clients"]
|
||||
peers -->|"CEPOS packets"| plugin
|
||||
plugin -->|"onEditPlaybackVoiceDataEvent: gain/pan/filter"| out["Local audio output"]
|
||||
ts["Retail ts3server"] -.->|"transport + channels only, no game logic"| plugin
|
||||
```
|
||||
|
||||
- Each client computes **for itself** how loud it hears the others.
|
||||
- Positions travel **peer-to-peer** via plugin commands (the retail server just relays them).
|
||||
- "Who hears whom" is really just a **volume**: voice packets still reach everyone in the
|
||||
channel; they are only mixed quiet/muted locally.
|
||||
|
||||
### Plan (Option B)
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
game["Conan Exiles"] -->|"local IPC"| client["Conan Voice Client (custom app, ClientLib)"]
|
||||
client -->|"CLIENT_META_DATA = position"| server["Conan Voice Server (custom process, ServerLib)"]
|
||||
server -->|"PlayerManager + ProximityEngine + VoiceRouter"| router["setClientWhisperList A to B,C"]
|
||||
router --> transport["TeamSpeak voice transport (SDK)"]
|
||||
transport -->|"A's voice only to B and C"| listeners["Clients B, C (D gets nothing)"]
|
||||
```
|
||||
|
||||
- The **server** decides centrally who hears whom, and enforces it via whisper lists (real routing).
|
||||
- Positions go **client to server** (no longer peer-to-peer).
|
||||
|
||||
---
|
||||
|
||||
## 4. API comparison — concrete functions
|
||||
|
||||
### 4a. Callbacks (events we react to)
|
||||
|
||||
| Purpose | Live V8 (`ts3plugin_*`) | Plan (`ts3client_*` / `ts3server_*`) |
|
||||
|---|---|---|
|
||||
| Connection status | `onConnectStatusChangeEvent` | Client: `onConnectStatusChangeEvent` |
|
||||
| Our own data transfer | `onPluginCommandEvent` (CEPOS/CEDRAIN) | Server: read `CLIENT_META_DATA` change |
|
||||
| Alter audio | `onEditPlaybackVoiceDataEvent` | Client: `onEditPlaybackVoiceDataEvent` (local effects only) |
|
||||
| 3D rolloff | `onCustom3dRolloffCalculationClientEvent` | Client: `onCustom3dRolloffCalculationClientEvent` |
|
||||
| Client moves | `onClientMoveEvent` / `onClientMoveMovedEvent` | Server: `onClientMoved`; Client: `onClientMoveEvent` |
|
||||
| Channel description (hub) | `onChannelDescriptionUpdateEvent`, `onUpdateChannelEvent`, `onUpdateChannelEditedEvent` | Handled differently (root-channel config) |
|
||||
| Who is talking | `onTalkStatusChangeEvent` | Server: `onClientStartTalkingEvent` / `onClientStopTalkingEvent` |
|
||||
| Connect/disconnect (server) | not available to us | Server: `onClientConnected` / `onClientDisconnected` |
|
||||
|
||||
*Evidence: `src/ts/entry/ts3_exports.h`, `src/ts/entry/ts3_entry.c`.*
|
||||
|
||||
### 4b. Functions we actively call (what we trigger)
|
||||
|
||||
| Purpose | Live V8 (`TS3Functions`, `g_ts3.*`) | Plan (SDK) |
|
||||
|---|---|---|
|
||||
| Send our own data | `sendPluginCommand("CEPOS:...")` | Client: `setClientSelfVariableAsString(CLIENT_META_DATA)` + `flushClientSelfUpdates` |
|
||||
| Wakeup / tick | `sendPluginCommand("CEDRAIN:1")` | Own event loop in the server |
|
||||
| Unmute clients | `requestUnmuteClientsTemporary` | Not needed (routing is server-side) |
|
||||
| Per-client volume | `setClientVolumeModifier` | Mainly via whisper routing; fine gain optionally local |
|
||||
| Move into channel (hub) | `requestClientMove` | Server: `ts3server_*` channel ops |
|
||||
| 3D global | `systemset3DSettings` | Client: `ts3client_systemset3DSettings` |
|
||||
| 3D listener (self) | `systemset3DListenerAttributes` | Client: `ts3client_systemset3DListenerAttributes` |
|
||||
| 3D per speaker | `channelset3DAttributes` | Client: `ts3client_channelset3DAttributes` |
|
||||
| **Who hears whom (routing)** | **does not exist** (local volume only) | **Server: `ts3server_setClientWhisperList`** |
|
||||
| Start server | not applicable | `ts3server_initServerLib` then `ts3server_createVirtualServer` |
|
||||
| Stop server | not applicable | `ts3server_stopVirtualServer` then `ts3server_destroyServerLib` |
|
||||
| Start client | (retail client does this) | `ts3client_initClientLib` then `spawnNewServerConnectionHandler` then `startConnection` |
|
||||
|
||||
*Evidence: `src/ts/adapter/ts3_adapter.c` (around lines 427, 653-690, 737-768).*
|
||||
|
||||
> [!TIP]
|
||||
> The 3D functions (`systemset3DListenerAttributes`, `channelset3DAttributes`,
|
||||
> `systemset3DSettings`) are named almost identically in **both** SDKs, so that part is
|
||||
> largely portable. The **big** difference is routing: `ts3server_setClientWhisperList`
|
||||
> exists only in the ServerLib.
|
||||
|
||||
---
|
||||
|
||||
## 5. Data flow in detail
|
||||
|
||||
### 5a. Position exchange
|
||||
|
||||
| | Live V8 | Plan |
|
||||
|---|---|---|
|
||||
| Source | `Pos.txt` (game mod) via `pos_file.c` | `IGameDataProvider` (ported from Mumble/V8) |
|
||||
| Packing | CEPOS, 56 bytes, base64 | compact binary/base64 in `CLIENT_META_DATA` (max 4096 B) |
|
||||
| Path | Client to **all** clients (peer-to-peer, `sendPluginCommand`) | Client to **server** (`CLIENT_META_DATA`) |
|
||||
| Who computes distance | **each client itself** | **the server** (authoritative) |
|
||||
| Trust | Client reports its position (spoofable) | Client reports position (spoofable) — server is still the authority for routing/permissions |
|
||||
|
||||
### 5b. "Who hears whom"
|
||||
|
||||
- **Live V8:** purely client-side. All voice packets arrive; the plugin only makes remote
|
||||
speakers **quieter or muted** (`onEditPlaybackVoiceDataEvent` + `setClientVolumeModifier`).
|
||||
Bandwidth: every packet always flows.
|
||||
- **Plan:** server-side. The `VoiceRouter` builds a target list per speaker and calls
|
||||
`ts3server_setClientWhisperList(A, ..., {B,C})`. Anyone not on the list never receives A's
|
||||
voice at all.
|
||||
|
||||
> [!WARNING]
|
||||
> While a whisper list is active, that client's normal channel talk path is disabled.
|
||||
> Therefore **everything** must be routed through whisper lists for routed players.
|
||||
|
||||
### 5c. Audio effects (cave reverb, lowpass)
|
||||
|
||||
- **Live V8:** applied directly on the samples in `onEditPlaybackVoiceDataEvent`
|
||||
(`ts3_proximity_audio.c`, lock-free snapshot pattern).
|
||||
- **Plan:** the same logic stays **client-side** (the custom app also has
|
||||
`onEditPlaybackVoiceDataEvent`), driven by a server-provided environment state
|
||||
(NORMAL/CAVE/...). So the effect code is **reusable**.
|
||||
|
||||
---
|
||||
|
||||
## 6. Operations, deployment, license
|
||||
|
||||
| Topic | Live V8 | Plan |
|
||||
|---|---|---|
|
||||
| User install | DLL into `%AppData%\TS3Client\plugins\` | Install/run a custom client app |
|
||||
| Server operation | none (retail server is enough) | Own process (Linux/systemd), monitoring, crash recovery |
|
||||
| Build | MSVC + MinGW cross, gcc unit tests | CMake for client + server + shared |
|
||||
| License | free (Plugin SDK) | paid, case by case; evaluation free for dev only |
|
||||
| Lifecycle | as long as TS3 lives | TS3 SDK **EOL 2027-12-31** — keep an `IVoiceTransport` seam as exit |
|
||||
| Scale | callback thread + PCM, ~200 players target | server-side routing, but slot cap per license |
|
||||
|
||||
---
|
||||
|
||||
## 7. What stays the same / is reusable
|
||||
|
||||
- **Proximity math** (`proximity_math`, `zone_resolve`) → port into the ServerLib engine (rewrite, don't copy).
|
||||
- **Audio effects** (reverb/lowpass in `ts3_proximity_audio.c`) → usable almost as-is, client-side.
|
||||
- **3D calls** (`systemset3D*`, `channelset3DAttributes`) → nearly identical names.
|
||||
- **Position source** (`pos_file.c`, path detection) → wrap as `IGameDataProvider`.
|
||||
- **HUD / voice modes / config / hub-parser ideas** → carry over as logic.
|
||||
|
||||
## 8. What is fundamentally different
|
||||
|
||||
1. **Two new processes** (a custom client **and** a custom server) instead of one DLL.
|
||||
2. **Routing moves from client to server** (whisper lists instead of local volume).
|
||||
3. **Position path reverses**: no longer peer-to-peer, but client to server.
|
||||
4. **Retail TeamSpeak disappears** — users need our app.
|
||||
5. **License and EOL** become new concerns.
|
||||
|
||||
---
|
||||
|
||||
## 9. Conclusion
|
||||
|
||||
- Live V8 is a **plugin inside someone else's TeamSpeak**: easy to distribute, free, but
|
||||
"who hears whom" is only a **local volume** (all packets always flow).
|
||||
- The plan is a **standalone voice product** on the TeamSpeak **server** library: **real**
|
||||
server-side routing, at the cost of building a client and server, a paid license, and a
|
||||
2027 EOL date.
|
||||
- Reusable above all: **proximity math, audio effects, and 3D calls**. New and risky:
|
||||
**ServerLib hosting, whisper routing, data transport (`CLIENT_META_DATA`), and licensing**.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> The plan only makes sense once the PoC (phases 0-3) proves that the ServerLib starts
|
||||
> cleanly and whisper routing "A hears B, not C" actually works. Before that, no Conan code
|
||||
> is worth writing.
|
||||
Reference in New Issue
Block a user