audio-code 0.3.0

A simple, high level game engine for audio games: 3D sound, speech, virtual menus, physics, timing and online play. No graphics required.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
# Net


Online play: `ctx.net` (type `Net`), plus `Packet`, `Arg`, `PeerId`, `NetEvent`, `Reason`
and `net::DEFAULT_PORT`. The guide is [online.md](../online.md).

One player hosts, the others join. Connecting, sending and receiving run on background
threads; the game never waits. Connections are WebSockets; packets arrive complete,
unchanged and in order. Everything is always encrypted (X25519 key exchange per
connection, XChaCha20-Poly1305 per message). Both sides ping every second, and a connection
that hears nothing for 10 seconds is closed.

```rust
use audio_code::prelude::*;   // Net, NetEvent, Packet, PeerId, Reason, net
use audio_code::net::Arg;     // only for matching on raw values
```

## Setup


### `ctx.net.set_name(name: impl Into<String>)`


The name others see. Default `"Player"`. Control characters are removed and the name is cut
to 32 characters. Call before `host` or `join`.

```rust
let name: String = ctx.storage.get_or("name", "Player".to_string());
ctx.net.set_name(name);
```

### `ctx.net.set_version(version: impl Into<String>)`


The version of your game. Default empty. A client with another version than the host is
turned away with `Reason::WrongVersion(host_version)`. Call before `host` or `join`.

```rust
ctx.net.set_version("1.2");
```

### `ctx.net.set_max_players(max: usize)`


How many clients may join. Default 32; the host does not count. Further clients get
`Reason::ServerFull`.

```rust
ctx.net.set_max_players(4);
```

### `Net::new(game: impl Into<String>) -> Net`


An offline network for the game `game`. The engine already creates `ctx.net` with the
game title; only clients with the same game name can join. Useful for tools and tests.

```rust
use audio_code::net::Net;
let mut net = Net::new("My game");
```

## Hosting and joining


### `ctx.net.host(port: u16) -> Result<()>`


Starts a WebSocket server others can join. Ends any open connection first. Port 0 picks a
free port (see `port`). Errors with `Error::Net` when the port is in use. Any URL path is
accepted, so a web server in front can pass on `wss://example.com/game`. Players outside
the local network need the host's public address and a forwarded port in the host's router.

```rust
match ctx.net.host(net::DEFAULT_PORT) {
    Ok(()) => ctx.speak("Waiting for players"),
    Err(e) => ctx.speak(e.to_string()),
}
```

### `ctx.net.join(address: &str)`


Joins a server. `address` is a host name or IP, with or without port (`"example.com"`,
`"192.168.1.20:7777"`, `"[::1]:7777"`), or a WebSocket URL (`"ws://example.com:7777/"`,

`"wss://example.com/game"`). Without a port, `net::DEFAULT_PORT` is used; URLs use 80 for
`ws://` and 443 for `wss://`. `wss://` checks the server's TLS certificate and needs the
`tls` feature (default on); without it, joining a `wss://` address ends with
`Reason::Failed`. Ends any open connection first. Returns at once: later a `NetEvent::Connected(PeerId::SERVER)`
says it worked, or a `NetEvent::Disconnected(PeerId::SERVER, reason)` says why not (after
at most about 10 seconds).

```rust
ctx.net.join("localhost");
ctx.net.join("wss://game.example.com/play");
ctx.speak("Connecting");
```

### `ctx.net.close()`


Leaves the game (the host sees `Reason::Left`), or stops the server (every client gets
`Reason::Closed`). Called automatically when the game quits. Produces no event for
yourself. Does nothing when offline.

```rust
if ctx.pressed(Key::Escape) {
    ctx.net.close();
    ctx.switch_scene(MainMenu);
}
```

### `ctx.net.kick(peer: PeerId, reason: &str)`


Host only: sends a player away. They get `Reason::Kicked(reason)`; `reason` may be empty.
The host gets `NetEvent::Disconnected(peer, Reason::Kicked(..))` next frame.

```rust
ctx.net.kick(player, "Please be nice in the chat");
```

### `net::DEFAULT_PORT: u16`


7777. Used by `join` when the address has no port.

```rust
ctx.net.host(net::DEFAULT_PORT)?;
```

### `Net::local_ip() -> Option<IpAddr>`


This computer's address in the local network (e.g. `192.168.1.20`): what players in the
same network type to join. `None` without a network. Sends nothing.

```rust
if let Some(ip) = Net::local_ip() {
    ctx.speak(format!("Your address is {ip}"));
}
```

## Sending


All sending functions take `impl Into<Packet>`: a `Packet`, a `&Packet`, or text for a
packet without values. Sending to a peer that is not connected does nothing. A packet larger
than 1 MB is dropped with a warning.

### `ctx.net.send(peer: PeerId, packet: impl Into<Packet>)`


Sends to one peer. On a client the only peer is `PeerId::SERVER`.

```rust
ctx.net.send(player, Packet::new("welcome").with("You are on the red team"));
```

### `ctx.net.send_all(packet: impl Into<Packet>)`


Sends to every connected peer. On a client: to the host.

```rust
ctx.net.send_all(Packet::new("move").with(position));
ctx.net.send_all("ready");
```

### `ctx.net.send_all_except(except: PeerId, packet: impl Into<Packet>)`


Sends to every connected peer except one. The usual way for a host to pass on what one
player did.

```rust
for (from, packet) in ctx.net.received("shot") {
    ctx.net.send_all_except(from, Packet::new("shot").with(from).with(packet.vec3(0)));
}
```

## Receiving


Events are collected at the start of every frame and kept until the next one, like key
presses. The helpers return owned lists, so you can send or speak inside the loop.

### `ctx.net.joined() -> Vec<PeerId>`


Players who connected this frame. On a client: `PeerId::SERVER` when the connection became
ready.

```rust
for player in ctx.net.joined() {
    ctx.speak(format!("{} joined", ctx.net.name(player)));
}
```

### `ctx.net.left() -> Vec<(PeerId, Reason)>`


Players who left this frame, and why. On a client: `PeerId::SERVER` when the connection
ended or could not be made.

```rust
for (_, reason) in ctx.net.left() {
    ctx.speak(ctx.lang.describe_reason(&reason));
}
```

### `ctx.net.received(kind: &str) -> Vec<(PeerId, Packet)>`


The packets of one kind that arrived this frame, with their sender, in order.

```rust
for (from, packet) in ctx.net.received("chat") {
    ctx.speak(format!("{}: {}", ctx.net.name(from), packet.text(0)));
}
```

### `ctx.net.messages() -> Vec<(PeerId, Packet)>`


All packets that arrived this frame.

```rust
for (from, packet) in ctx.net.messages() {
    match packet.kind() {
        "move" => {}
        "chat" => {}
        _ => {}
    }
}
```

### `ctx.net.events() -> &[NetEvent]`


Everything that happened this frame, in order.

```rust
let busy = ctx.net.events().len();
```

### `Scene::on_net(&mut self, event: &NetEvent, ctx: &mut Context)`


Called for every network event before `update`. See [engine.md](engine.md#scene).

## Players and state


### `ctx.net.players() -> Vec<PeerId>`


Connected peers in the order they joined. Host: all clients. Client: `PeerId::SERVER`
once connected.

```rust
for player in ctx.net.players() {
    ctx.net.send(player, "round_start");
}
```

### `ctx.net.player_count() -> usize`


Number of connected peers. The host does not count itself.

```rust
ctx.speak(format!("{} players online", ctx.net.player_count() + 1));
```

### `ctx.net.is_connected_to(peer: PeerId) -> bool`


Whether `peer` is connected right now.

```rust
if !ctx.net.is_connected_to(player) { /* forget them */ }
```

### `ctx.net.name(peer: PeerId) -> &str`


The name a peer chose with `set_name`; `""` if not connected. Your own id gives your own
name. A client knows the host's name (`PeerId::SERVER`) but not the other clients' names;
the host sends them in packets.

```rust
let me = ctx.net.name(ctx.net.id()).to_string();
```

### `ctx.net.ping(peer: PeerId) -> f32`


Round trip time to a peer in seconds, updated every second. 0.0 until measured or when not
connected.

```rust
let ms = (ctx.net.ping(PeerId::SERVER) * 1000.0).round();
ctx.speak(format!("Ping {ms} milliseconds"));
```

### `ctx.net.id() -> PeerId`


Your own id: `PeerId::SERVER` for the host (and while offline), the number the host gave
you on a client.

```rust
ctx.net.send_all(Packet::new("hello").with(ctx.net.id()));
```

### `ctx.net.is_host() -> bool` / `is_client() -> bool` / `is_joining() -> bool` / `is_online() -> bool`


Hosting; connected to a server; a `join` still in progress; hosting or connected.

```rust
if ctx.net.is_host() {
    // decide what happens
} else if ctx.net.is_client() {
    // send what the player wants to do
}
```

### `ctx.net.port() -> Option<u16>`


The port the server listens on while hosting, `None` otherwise.

```rust
ctx.net.host(0)?;
let port = ctx.net.port().unwrap();
```

## Packet


A kind plus a list of values.

### `Packet::new(kind: impl Into<String>) -> Packet`


A packet without values. Text converts into one too: `"ready".into()`.

```rust
let packet = Packet::new("door_opened");
```

### `packet.with(value: impl Into<Arg>) -> Packet` / `packet.push(value: impl Into<Arg>)`


Adds a value. Accepted: all integer types, `f32`, `f64`, `bool`, `&str`, `String`,
`&String`, `Vec2`, `Vec3`, `PeerId`, `Vec<u8>`, `&[u8]`.

```rust
let packet = Packet::new("hit").with(enemy_number).with(25).with(true);
let mut list = Packet::new("scores");
for score in [10, 20, 30] {
    list.push(score);
}
```

### `packet.kind() -> &str` / `packet.is(kind: &str) -> bool`


What the packet is about.

```rust
if packet.is("chat") { /* ... */ }
```

### `packet.int(index) -> i64` / `float(index) -> f32` / `bool(index) -> bool` / `text(index) -> &str` / `vec2(index) -> Vec2` / `vec3(index) -> Vec3` / `peer(index) -> PeerId` / `bytes(index) -> &[u8]`


The value at `index`, converted. Never panics: a missing or unfitting value gives `0`,
`0.0`, `false`, `""`, `Vec2::ZERO`, `Vec3::ZERO`, `PeerId::SERVER` or `&[]`. Whole and
decimal numbers convert into each other, `bool` gives 0 / 1, text is parsed as a number,
a `Vec2` becomes a `Vec3` with z = 0.

```rust
let packet = Packet::new("move").with(vec3(1.0, 2.0, 0.0)).with(90);
let position = packet.vec3(0);
let facing = packet.float(1);   // 90.0
```

### `packet.len() -> usize` / `is_empty() -> bool` / `args() -> &[Arg]` / `get(index) -> Option<&Arg>`


The raw values.

```rust
use audio_code::net::Arg;
if let Some(Arg::Text(t)) = packet.get(0) { ctx.speak(t); }
```

### `Arg`


```rust
pub enum Arg { Int(i64), Float(f64), Bool(bool), Text(String), Vec2(Vec2), Vec3(Vec3), Bytes(Vec<u8>) }
```

## PeerId


A connected player. `Copy`, comparable, usable as a map key, `Display` shows the number.

### `PeerId::SERVER`


The host, number 0. Clients receive everything from this peer.

### `peer.number() -> u32` / `PeerId::from_number(n: u32) -> PeerId` / `peer.is_server() -> bool`


Clients are numbered from 1 in the order they joined; numbers are not reused while the
server runs. In packets: `.with(peer)` and `packet.peer(i)`.

```rust
let sound_pitch = 1.0 + player.number() as f32 * 0.1;
```

## NetEvent


```rust
pub enum NetEvent {
    Connected(PeerId),             // host: a player joined; client: connection ready (SERVER)
    Message(PeerId, Packet),       // a packet arrived
    Disconnected(PeerId, Reason),  // host: a player left; client: connection ended or failed
}
```

## Reason


Why a connection ended. `Display` gives an English sentence;
`ctx.lang.describe_reason(&reason)` one in the current language.

| Variant | When |
|---------|------|
| `Left` | the player left (`close`, quitting) |
| `Closed` | the host closed the server |
| `Kicked(String)` | the host kicked the player, with its text |
| `ServerFull` | the server has `max_players` clients |
| `WrongGame(String)` | another game; contains the host's title |
| `WrongVersion(String)` | another version; contains the host's version |
| `TimedOut` | nothing heard for 10 seconds |
| `Failed(String)` | joining failed (wrong address, no server, no network, invalid TLS certificate) |
| `Lost(String)` | the connection broke, or a message failed decryption (changed on the way) |

```rust
for (_, reason) in ctx.net.left() {
    if reason == Reason::WrongVersion("2.0".into()) {
        ctx.speak("Please update the game");
    }
}
```