-
Notifications
You must be signed in to change notification settings - Fork 5
/
protocol.txt
584 lines (477 loc) · 30 KB
/
protocol.txt
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
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
Uses websockets and JSON. WIP.
--> send to server
<-- receive from server
For a basic graphical client, a good minimal set of message types to support are:
IDN (logging in)
MSG, ERR, CMD, PRI (sending and receiving text, sending chat commands)
MAP (map data)
MAI (information about the current map you're on)
PIN (pings)
RSC (defines the global tilesets)
IMG (request URL for an image ID that was seen on a map)
WHO (list of entities, and entity property management)
MOV (entity movement)
Building, mail, inventory management, and all extensions are optional.
List of entity types:
user An entity controlled by a client.
map A map.
group A user group.
text Holds any amount of text, like Second Life's notecards.
image Currently just holds a URL to an image, which can be used as a tileset for map tiles and entities.
map_tile A template for a tile that can be placed on a map.
tileset Holds a series of map tiles. Stored as a JSON array that alternates between names and map tile definitions.
Map tiles can reference this tileset with tileset_id:name_of_tile.
reference A reference to another entity. Interacting with it should redirect to the entity it points at.
folder Holds other entities. Any entity can hold other entities, so this is more of a UI hint.
landmark Holds a location, allowing a user to teleport to it easily.
generic No special behavior.
List of permissions:
build user can build on the map
sandbox users can delete any part of the map freely (or kick any item from a container)
admin user is an admin on the map
copy user can make copies of this object
map_bot user is given bot-related permissions (like the ability to use /listen)
move user can move this object around within the same container
move_new_map user can move this object to a new map
bulk_build user can use the builk building protocol commands
object_entry user can bring non-client entities here
persistent_object_entry user can bring non-client entities here persistently (will kick clients out when unloading if not true)
modify_properties user can modify the properties of this entity
remote_command user can make this entity do arbitrary commands
modify_appearance user can modify visual properties, like picture or description
list_contents user can look at the contents of this entity
all shorthand for assigning all permissions
List of directions (used for "dir" values):
0 = East
1 = Southeast
2 = South
3 = Southwest
4 = West
5 = Northwest
6 = North
7 = Northeast
=== Movement on the map ===
--> MOV {"from": [x1,y1], "to": [x2,y2], "dir": 0}
Move yourself
--> MOV {"bump": [x,y], "dir": 0}
Tell the server you attempted to move into a tile but stopped yourself because it was dense.
Also sent when attempting to move off of the edge of the map, which the server may respond to by sending you to another map.
--> MOV {"bump": [x,y], "dir": 0, "if_map": id}
"if_map" signals that a MOV is only valid for a specific map, and should be ignored if the client is on another one.
This helps avoid race conditions where a MOV changes the client's map and the client sends another before it knows it's on the new map.
--> MOV {"new_map": id}
Request to move to another map
--> MOV {"new_map": id, "to": [x,y]}
Request to move to a specific position on another map
--> MOV {"dir": 0}
Change direction only
--> MOV {"offset": [x,y]}
Change your pixel offset
<-- MOV {"from": [x1,y1], "to": [x2,y2], "dir": 0, "id": 0}
Move a player on the map.
"from" may be left out, and if so, a client won't ignore MOVs for its own ID.
<-- MOV {"to": [x,y], "dir": 0, "id": 0, "edge_warp": true}
Indicates a character was put into this position via moving off the edge of a map and onto another.
=== Map contents ===
--> PUT {"pos": [x,y], "atom": {atom}}
<-- PUT {"pos": [x,y], "atom": {atom}, "username": username, "remote_map": map} (for listeners)
<-- MAP {updated map}
Place a tile on the map.
Can use string instead of atom definition if it's predefined.
Optional fields:
"obj": If present and true, "atom" is an array of tiles, which go onto the map's object layer
--> MAP - requests the whole thing
--> MAP {"pos":[x1, y1, x2, y2]}
<-- MAP {"pos":[x1, y1, x2, y2], "default": default_turf, "turf": [turfs], "obj": [objs]}
get a partial (or complete) copy of the map
(currently server-->client only)
--> MAI
<-- MAI {parameters that describe the map}
Fields that may appear in MAI on the official server:
"name": string; Name of the map
"desc": string; Map description
"id": string or integer; Map's database ID
"owner_id": integer; Database ID of the person the map belongs to
"owner_username": string; Username of the person the map belongs to; may be "?"
"default": string or object; Default type of turf; may be a string or JSON object
"size": 2-element list; [width, height]
"start_pos": 2-element list; [x, y]. It's the position users are put at when they teleport to the map; only sent to map admins
"public": boolean; If true, the map shows up in commands like /whereare
"private": boolean; If true, users can not join the map unless given entry permission
"build_enabled": boolean; If true, users can build without specifically being given permission to
"full_sandbox": boolean; Not currently used, but the intention is that when it's true, users can edit or delete other users' objects on the map
"edge_links": List of 8 database IDs for the maps surrounding this one; starts with the west link and goes clockwise and each entry may be null if there is no link
"tags": dictionary; Generic entity tags set on this map
"you_allow": List of permission names that the user is granted for this map
"you_deny": List of permission names that the user is denied for this map
"topic": string; Current topic, if there is one
"topic_username": string; Username of the person who set the topic
"remote_map": integer; If set, this MAI is actually for a different map, and not the one you're on; the parameter is the database ID of that other map
"wallpaper": object, containing:
"url": string; image link
"center": boolean; if true, the wallpaper's origin point is the middle of the map, not the top left
"repeat": boolean; if true, the wallpaper repeats across the entire map
"repeat_x": boolean; if true, the wallpaper repeats across the map horizontally
"repeat_y": boolean; if true, the wallpaper repeats across the map vertically
"over_turf": boolean; if true, draw over all turf, not just default turf
"offset": 2-element list; [x,y]. Number of pixels to shift the image horizontally and vertically from the default spot it would be at
Most important fields to support are "id", "name", "desc", "default" and "size". Most of the others are just informational.
--> DEL {"pos": [x1, y1, x2, y2], "turf":true, "obj":true}
<-- DEL {"pos": [x1, y1, x2, y2], "turf":true, "obj":true, "username":username, "remote_map": map} (for listeners)
<-- MAP {updated map}
Delete a section of map.
=== Resources ===
--> IMG {"id": number}
--> IMG {"id": [number, number, number...]}
Request an image asset's URL.
Can request multiple image assets at once.
<-- IMG {"id": number, "url": string}
Have the client load an image, for tilesets or avatars or other purposes.
There may be an additional boolean field named "update" which is set to true when the IMG message is sent as a result of an image item being updated.
--> TSD {"id": number}
--> TSD {"id": [number, number, number]}
Request tileset data from the server.
Can request multiple tilesets at once.
<-- TSD {"id": number, "data": "[id, info, id, info, id, info, ...]"}
Tileset received from the server.
Received data may be in string format, which would require parsing into JSON.
There may be an additional boolean field named "update" which is set to true when the IMG message is sent as a result of an image item being updated.
=== People on the map ===
--> WHO
--> WHO {"update": {fields}}
--> WHO {"update": {fields}, "rc": id}
<-- WHO {"list": {"[id]": {"name": name, "pic": [s, x, y], "x": x, "y": y, "dir": dir, "id": id}, "you":id}
<-- WHO {"add": {"name": name, "pic": [s, x, y], "x": x, "y": y, "dir", dir, "id": id}}
<-- WHO {"update": {"id": id, other fields}}
<-- WHO {"remove": id}
<-- WHO {"new_id": {"id": old_id, "new_id", id}}
"add" can be used to do an update too, where it will replace a whole entry with new data.
"update" can be used for partial updates, where only the fields present in the message are updated and other fields are left alone.
"remove" requests that a specific item be removed.
"new_id" is mostly used when a temporary object gets saved to the database,
For WHO update, you can supply another ID instead of yours with the "rc" parameter, if you have "remote_command" permission for that entity.
Clients can send updates about themselves, but only keys that the servers knows to be OK, such as "typing".
For client-to-server WHO updates, "id" is ignored if provided.
Fields you can update with client-->server WHO update:
"typing": boolean, shows a currently-typing marker above your character if true
"mini_tilemap": null, or an object (see Mini Tilemap section)
"mini_tilemap_data": null, or object (see Mini Tilemap section)
"clickable": boolean; If true, clicking this entity will send a notification to it that the entity can act on
Other fields that may appear in WHO messages on the official server:
"name": string; Name of the entity
"pic": [sprite sheet ID or URL, x, y]; Entity appearance
"x": integer; X coordinate on the map
"y": integer; Y coordinate on the map
"dir": integer; Direction from 0 to 7
"id": integer or string; An ID to reference this entity with
"passengers": List of IDs this entity is carrying (note that there may be recursion here that you need to be careful about)
"vehicle": integer or string; ID of the entity this entity is being carried by
"is_following": boolean; If true, entity is following behind its vehicle, instead of being carried
"type": string; Entity type, like "user" or "generic"
"in_user_list": boolean; True if this entity is a real user, and should therefore go in the user list
"offset": [x,y]; Offset in pixels away from where this entity would normally be drawn
"is_forwarding": boolean; If true, this entity is not a user, but some events that happen on the map this entity is on are being forwarded to a user
"chat_listener": boolean; If true, this entity is not a user, but chat messages that are sent to this entity's map are being forwarded to a user
Most important fields to support are "id", "name", "pic", "x", "y", "dir" and ideally "offset" too.
=== Miscellaneous ===
--> IDN
--> IDN {"username": username, "password": password}
--> IDN {"map" [map_id]}
--> IDN {"map" [map_id, x, y]}
--> IDN {"features": {"feature_name": {"version": "1.0.0"}}}
--> IDN {"client_name": "name", "client_version": "1.0.0"}
<-- IDN
<-- IDN {"features": {"feature_name": {"version": "1.0.0"}}}
Log into the server with or without an account. Server sends an acknowledgement if the IDN was accepted.
Can optionally start on a specific map, with optional map coordinates.
Clients can request features and the server can confirm some amount of them.
Clients can report what name and version they're running if they like.
--> MSG {"text": "[text]"}
--> MSG {"text": "[text]", "rc": id}
Send a chat message to the current room.
If "rc" is supplied, the server will have the entity with the specified ID send a chat message to its own current room.
--> CMD {"text": "[text]", "echo": value, "rc": id}
<-- CMD {"text": "[text]", "echo": value, "data": {}}
Do a command.
"echo" is optional, and will be returned in the response, if there is one.
Command may have a machine-readable response if "echo" is provided, provided in "data".
If "rc" is supplied, the server will act as if the entity specified in the ID did the command instead, returning the command results to you instead.
For "rc", you need to either be the owner or have "remote_command" permissions on the object.
<-- MSG {"text": "[text]", "name": speaker, "class": classname, "username": username, "id": id}
<-- MSG {"text": "[text]", "name": speaker, "class": classname, "buttons": ["name 1", "command 1", "name 2", "command 2"]}
display message in log.
"class" is a CSS class to style the image with.
"buttons" provides a list of choices to present that will execute commands.
<-- PRI {"text": "[text"], "name": display name, "id": id, "username": username, "receive": true/false}
private message, displays in the log
receive is false for sender, true for recipient
--> EML {"send": {"subject": subject, "contents": contents, "to": [username, ...]}}
--> EML {"read": id}
--> EML {"delete": id}
send mail or manipulate your inbox
<-- EML {"receive": {"id": id, "subject": subject, "contents": contents, "to": [username, ...], "from": username, "flags": flags}}
<-- EML {"list": [{"id": id, "subject": subject, "contents": contents, "to": [username, ...], "from": username, "flags": flags}]}
<-- EML {"sent": {"subject", subject}}
receive mail from someone, or get a list upon logging in. "sent" acknowledges mail was successfully sent
<-- ERR {"text": "[text]"}
Receive an error.
Possible fields:
"text": Text to display to the user
"echo": A repeat of the "echo" field in the protocol message
"data": Miscellaneous machine-friendly information about the error
"code": A string for the specific kidn of error
"detail": Additional information that further clarifies the code
"subject_id": ID the error is about
Error codes you might find:
"missing_permission": Don't have permission to do the thing you're trying to do; permission needed may be in "detail" (which may be an array)
"owner_only": Can't do that unless you own the subject
"map_only": Can't do that unless you're on a map
"clients_only": Can't do that unless you're a user
"server_admin_only": Can't do that unless you're a server admin
"invalid_command": Command isn't recognized
"invalid_subcommand": Subcommand isn't recognized
"not_found": Supplied name isn't found
"not_loaded": Entity you're referencing is not currently loaded
"identify": Can't use that command until you identify
"bad_value": Value wasn't accepted for whatever reason; "detail" may provide a field or something.
"no_guests": Guests cannot do this
"exception": Exception was thrown
"blocked": Can't do that because you're on a block list of some sort
--> PIN
<-- PIN
ping, if you don't respond fast enough you disconnect
--> VER {"name": client_name, "version":1.0, "code": "https://github.com/NovaSquirrel/TilemapTown"}
<-- VER {"name": server_name, "version":1.0, "code": "https://github.com/NovaSquirrel/TilemapTown"}
version information for client and server
<-- RSC {"images": {"id": "url", ...}, "tilesets": {"id": {}, ...}}
Resources for the client to preload.
Allows servers to explicitly provide tilesets, both to allow for custom ones and to make it so that clients don't need to include a copy of the default tilesets.
=== Items ===
--> MOV {"from": [x1,y1], "to": [x2,y2], "dir": 0, "rc": 0}
<-- MOV {"from": [x1,y1], "to": [x2,y2], "dir": 0, "rc": 0}
Move something else within the same container as you, if you are allowed to
--> USE {"id": id}
Use item
(not implemented)
--> BAG {"create": {"name": name, "type": type}}
--> BAG {"create": {"name": name, "type": type, "temp": true/false}}
<-- BAG {"create": {"name": name, "type": type, "temp": true/false, "id": new_id}}
Create a new item of a given type. Valid types include: text, image, map_tile, tileset, reference, folder, landmark, generic
Can create a temporary object instead if you like
--> BAG {"update": {"id": id, "name": name, "desc": desc, "flags": flags, "folder": folder, "data": data, "allow": allow, "deny": deny, "guest_deny": deny, "tags": tags, ...}}
Update one specific item.
Allow, deny, and guest_deny are sent as a list of strings, using the permission names earlier in this file.
--> BAG {"move": {"id": id, "folder": map, "pos": [x,y]}}
Moves an entity to another position. "pos" is optional.
--> BAG {"delete": {"id": id}}
<-- BAG {"delete": {"id": id}}
Delete an item
--> BAG {"kick": {"id": id}}
<-- BAG {"kick": {"id": id}}
Send an entity to its home
--> BAG {"clone": {"id": id}}
--> BAG {"clone": {"id": id, "temp": true/false}}
<-- BAG {"clone": {"id": id, "temp": true/false, "new_id": new_id}}
Make an exact clone of an item with a new ID.
Can make the new object temporary or non-temporary regardless of if the original object was.
--> BAG {"info": {"id": id}}
<-- BAG {"info": {"id": id, ...}}
Request information on an item, whether or not it's in your inventory.
--> BAG {"list_contents": {"id": id}}
--> BAG {"list_contents": {"id": id, "recursive": true}}
<-- BAG {"list_contents": {"id": id, "contents": [{item info}]}}
Get a BAG "list" in return for the container you picked, if you have permission. If "recursive" is present and true, you will also get the contents of all of the contents.
<-- BAG {"update": {item info}}
<-- BAG {"list": [{item info}], "clear": false}
Receive an item or list of items from the server. Lists don't replace the client-side list, unless "clear" is specified.
"clear" specifies that the client-side inventory list for this container should be cleared before adding these. Optional.
<-- BAG {"new_id": {"id": old_id, "new_id": id}}
Update the ID of one item in the player's inventory
<-- BAG {"remove": {"id": id}}
Remove one item from the inventory
BAG messages can have a "container" field with an entity ID, which indicate what entity's inventory is being managed here.
Currently this isn't used or supported though.
Item info fields:
(Used for "update", "list", and "info")
"id": string or integer; Item's database ID, or a temporary ID if there isn't a database one
"name": string; Name of the item
"desc": string; Item description
"type": string; Entity type
"folder": integer or string; ID of the entity that contains this entity
"data": string, usually? It's specific to the entity type
"tags": dictionary; Generic entity tags set on this map
"allow": List of strings, using the permission names earlier in this file. These permissions are granted by default.
"deny": List of strings, using the permission names earlier in this file. These permissions are denied by default.
"guest_deny": List of strings, using the permission names earlier in this file. These permissions are denied to guests.
"owner_id": integer; Database ID of the user this entity belongs to; can be written to to give the item to someone else
"owner_username" string; Username of the user this etity belongs to; can be written to to give the item to someone else
"username"; string; Read-only and may not be present (for when you check a user entity's information)
"temporary": boolean; Changes to this entity do not get saved to the database
"delete_on_logout": boolean; Causes the entity to get removed when the owner logs out
=== Optional features ===
Clients and servers can advertise having extra features on top of the basic ones.
VER will tell the client what features are available, and the client can request specific features with IDN, which the server will acknowledge.
--> VER {
"name": client_name,
"version":1.0,
"code": "https://github.com/NovaSquirrel/TilemapTown"
}
<-- VER {
"name": server_name,
"version":1.0,
"code": "https://github.com/NovaSquirrel/TilemapTown"
"features": {
"see_past_map_edge": {
"version": "0.0.1",
"minimum_version": "0.0.1"
}
}
}
--> IDN {
"username": "text",
"password": "text"
"features": {
"see_past_map_edge": {
"version": "0.0.1"
}
}
}
<-- IDN {
"features": {
"see_past_map_edge": {
"version": "0.0.1"
}
}
}
=== Mini-tilemaps ===
Entities can display a little tilemap that overlays their graphic. You can use WHO messages to update an entity's "mini_tilemap" and "mini_tilemap_data" fields, which will be broadcast to the people on the map.
"mini_tilemap": { _Default___Purpose_
"visible": boolean, | True | If true, display the mini-tilemap; if false, hide it
"clickable", boolean, | False | If true, clients can click on the mini-tilemap to send the entity messages
"map_size": [w, h], | N/A | Size of the mini-tilemap, in tiles; max is [16, 16]
"tile_size": [w, h], | N/A | Size of each tile in the mini-tilemap, in pixels; max is [64, 64]
"tileset_url": string, | N/A | Image URL to use for the tiles
"offset": [x, y], | [0,0] | X and Y offset from the entity, in pixels
"transparent_tile": int, | 0 | Which tile is treated as transparent
}
map_size, tile_size, and tileset_url are required. Total pixel size of the map can't be bigger than 64x64.
"mini_tilemap_data": {
"data": [tile, ...]
}
The tilemap's data is a list of integers. Formatted like a binary number it would look like:
rrrrrrryyyyyyxxxxxx
|||||||||||||++++++- X position in the tileset, in tile units
|||||||++++++------- Y position in the tileset, in tile units
+++++++------------- Number of time to repeat this tile (minus 1)
This means the maximum tileset size is 64x64 tiles, for 4096 tile types.
Tiles are specified top to bottom, going through each line from left to right. If tiles match "transparent_tile" it's not drawn.
=== Extension: see_past_map_edge ====
If a client signals that it supports see_past_map_edge, then it will receive a MAI and MAP message for all of the maps linked from the one they have just joined.
After this point, they'll receive MAP messages broadcast to those maps.
Messages for linked maps are marked with a remote_map field, just like the /listen command adds.
A client is expected to keep copies of the linked maps in memory, and the server takes this into account to reduce bandwidth usage.
When a client moves to a new map, the server will send a MAI for the new map, but will not send MAIs or MAPs for the new map or any linked maps if they match the client's old map or any maps adjacent to it.
=== EXT messages ===
Experimental feature: There's a protocol message type "EXT" which is used for sending protocol messages with a type that's longer than three characters. (EXTended type names?)
This is especially helpful for Tilemap Town forks or other extensions (EXTensions?) that want to define their own protocol message types and want to avoid any potential conflicts with later versions of the official protocol. For this kind of usage, it could be a good idea to prefix the message type name with something specific to the thing you're making. Something like "tilemap_town:key_press".
Sending an unrecognized type currently does not result in an error, and you can request a list of the types that are recognized.
You can add an "rc" parameter just like on WHO, CMD, MOV, etc. and it will act as if the specified entity sent the EXT message.
Here are the currently supported types:
--> EXT {"list_available_ext_types": true}
<-- EXT {"list_available_ext_types": ["entity_click", "key_press", "take_controls", "took_controls", "list_available_ext_types"]}
Get a list of EXT message types that the server can handle.
--> EXT {"entity_click": {"x": x, "y": y, "button": 0, "target": "entity"/"mini_tilemap"}}
<-- EXT {"entity_click": {"x": x, "y": y, "button": 0, "target": "entity"/"mini_tilemap"}}
Notify an entity that they were clicked on. X and Y are in pixels, starting from the top left of the entity or mini tilemap.
"0" means left click, or a stylus tap, or anything like that.
--> EXT {"key_press": {"id": id, "key": key_name, "down": true/false}}
Message gets forwarded to the entity specified in "id". Notifies the client
Fields:
"key": Which key was pressed.
"down": If true, key was pressed. If false, key was released.
--> EXT {"take_controls": {"id": id, "keys": [key_name, key_name, key_name...], "pass_on": true/false, "key_up": true/false}}
--> EXT {"take_controls": {"id": id, "keys": []}}
<-- ERR {"ext_type": "take_controls", "code": "missing_permission", "detail": "minigame", "subject_id": id}
Message gets forwarded to the entity specified in "id", if that entity has granted you the "minigame" permission.
Similar to https://wiki.secondlife.com/wiki/LlTakeControls
Fields:
"keys": List of keys the entity wants to track. Can be an empty list in order to release all of the keys.
"pass_on": Allow the requested keys to continue to do their normal function when pressed.
"key_up": If true, request to receive key release events in addition to key press events.
--> EXT {"took_controls": {"id": id, "keys": [key_name, key_name, key_name...], "accept": true/false}}
Message gets forwarded to the entity specified in "id", in order to tell it that the "take_controls" was accepted (or declined) and to let it know which subset of the keys are actually supported by the client.
Key names:
Arrow keys and home/end/pgup/pgdn in the web client
"move-n", "move-ne", "move-e", "move-se", "move-s", "move-sw", "move-w", "move-nw"
Shift+direction in the web client
"turn-n", "turn-ne", "turn-e", "turn-se", "turn-s", "turn-sw", "turn-w", "turn-nw"
Selection
"hotbar-prev" "hotbar-next" "hotbar-1" "hotbar-2" "hotbar-3" "hotbar-4" "hotbar-5 "hotbar-6" "hotbar-7" "hotbar-8" "hotbar-9" "hotbar-10"
Other actions
"use-item" "cancel"
Miscellaneous action buttons
1 2 3 4 could be Z X C V potentially? Maybe it'd be best if the client let you configure it
"action-1", "action-2", "action-3", "action-4"
Miscellaneous action buttons, but referenced by the physical layout on the controller (if that makes sense for the client's input method)
"action-n", "action-e", "action-s", "action-w"
--> EXT {"bot_message_button": {"id": id, "text": text}}
Message gets forwarded to the entity specified in "id", in order to tell it that a [bot-message-button] button was clicked on.
The forwarded copy has additional fields for "name" and "username" as in PRI.
=== Extension: bulk_build ===
This extension allows a client to request multiple map changes in one message, including operations like copies.
Clients that support it will receive a BLK to communicate the changes, but clients that don't will receive the map changes via MAP.
--> BLK {"turf": [[x, y, type, w, h], ...], "obj": [[x, y, [type], w, h], ...]}
<-- BLK {"turf": [[x, y, type, w, h], ...], "obj": [[x, y, [type], w, h], ...], "username": username, "id": user_id}
Bulk building command. Requires the "bulk_build" permission.
Applies a series of rectangles to the map.
Width and height may be omitted, in order to just do a single tile.
Can take a "temp" optional parameter like PUT can.
--> BLK {"copy": [{"turf": true/false, "obj": true/false, "src":[x,y,w,h], "dst":[x,y]}, ...]]}
<-- BLK {"copy": [{"turf": true/false, "obj": true/false, "src":[x,y,w,h], "dst":[x,y]}, ...]], "username": username, "id" user_id}
Copies from one portion of the map to another, with overlapped rectangles allowed and supported.
If "turf" and "obj" are also provided in the same BLK message, "copy" is applied first, then "turf" and finally "obj".
This makes it easy to move something by copying it somewhere else and erasing the tile where it was.
Can take a "temp" optional parameter like PUT can.
=== Extension: batch ====
This extension allows the server to send multiple protocol messages to the client in one websocket message.
When a client supports it, the server may send messages formatted like the following:
BAT MAI {params}
MAP {params}
MOV {params}
Where BAT signals that it's a batch message, and newline characters (\n) separate the characters that make up each sub-message. Carriage returns (\r) are not used.
Batch messages cannot be nested, so none of the sub-messages can be BAT.
=== Extension: receive_build_messages ===
When the server receives any PUT or DEL messages, it will also send them to any clients on the same map supports receive_build_messages.
These relayed messages will have additional "id" and "username" fields on them.
=== Extension: entity_message_forwarding ===
When you own an entity, you can ask to have all messages with specific command types forwarded to you, instead of just being ignored.
This is persistent when you set it up. Indicating that you support this extension is required to receive the messages.
-----------------------------
This is managed with the following commands:
/message_forwarding set entity_id,entity_id,entity_id...
Disable forwarding for specific entities
/message_forwarding set entity_id,entity_id,entity_id... MAP,MAI,PRI ...
Enable forwarding for specific message types, for the specific entities
Requesting that "MSG" be forwarded will not forward messages that have a speaker name on them, with the assumption that they're chat messages.
If you want to forward chat messages, the special type "CHAT" exclusively forwards MSG messages that have speaker names.
-----------------------------
The forwarded messages take the format:
FWD ID CMD {params}
Where ID is the entity's ID and the message will follow afterward like normal.
The client should support forwarded batch messages, in the form:
FWD ID BAT CMD {params}
CMD {params}
CMD {params}
If an entity is forwarding messages, it will have a WHO field named "is_forwarding" that's set to true. If one of the forwarded types is "CHAT", then there will be another field named "chat_listener" which is also true.
=== WebSocket disconnect reasons ===
The server may send the following WebSocket disconnect reasons:
"Quit": User requested a disconnect
"Shutdown": Server is shutting down, with no guarantee on when or if it will be back
"Restart": Server is shutting down, but is expected to be back very soon
"Ban": User is banned from the server
"Kick": User's connection was terminated by another user
"BadLogin": User's login credentials are wrong
A client may choose to automatically reconnect in response to a "Restart" reason but should *not* automatically reconnect in response to the other reasons.
There will probably be other reasons in the future for things like a private server, or the client's version being too old, or not supporting a required extension.
A disconnect reason can have additional text in it that's meant to give additional context to the user about why the disconnect happened. If it's present, there will be a | separating the two parts of the disconnect message, for example:
"Restart|Restarting to update nginx"