From 99b9e580e74e592a008bd5e7e0665c991943e7ed Mon Sep 17 00:00:00 2001 From: Aaron Kimbrell Date: Tue, 10 Nov 2026 19:11:29 -0600 Subject: [PATCH] doc: edited zone files and the level editor in the README and docs/ZonePublish.md Co-Authored-By: Claude Fable 5.2 --- README.md | 12 ++++++++---- docs/ZonePublish.md | 43 +++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index c8280eba5..d0c828ebf 100644 --- a/README.md +++ b/README.md @@ -142,10 +142,14 @@ locally. See [docs/UgcServer.md](docs/UgcServer.md). (merged 2026-11-10): the mover's states as the client and live have them, the object goes with its mover on the server, `SetCurrentPath`, a rotator turns its object, a mover keeps its velocity through a wait, and the scripts that depend on them (shooting gallery targets, the Ninjago counterweights, attic switches and fire transition rocks). -* **Client editor accounts (merged 2026-11-10):** a character's save has `edit="1"` for accounts at `editor_gm_level` - (default 9) and up, 0 for everyone else. Master and the world servers also carry an edit mode flag for a private - instance whose world constructs none of the zone's level objects, spawners or triggers; no command starts one yet - ([docs/EditMode.md](docs/EditMode.md)). +* **Edited zone files (merged 2026-11-10):** an external level editor sends a zone's files through the dashboard's + zone file API (`/api/v1/zones`), or the game client's own level editor sends them to its world server. Either way + the set is checked whole with the server's own readers, kept in an override folder (`zone_override_dir`; the + client's files are never written) with the sets before it as backups, and loaded by replacing the zone's instances, + held while players are there unless told to move them. A zone has assigned editors and one lock holder; the API + takes an API key made for it, over HTTPS or from the server's own machine. A character's editor level is its + account's GM level for accounts at `editor_gm_level` (default 9) and up, 0 for everyone else + ([docs/ZonePublish.md](docs/ZonePublish.md)). * **Zones:** level and zone files read for every version the client reads. * Server-side knockback for AI-moved objects, switchable trigger volumes, missing force field, jetpack NPC and Skullkin volume scripts, deletion restrictions enforced, cross-world new-mail notices, pet LOTs stored with names, diff --git a/docs/ZonePublish.md b/docs/ZonePublish.md index eb0dd45da..cc358f1e6 100644 --- a/docs/ZonePublish.md +++ b/docs/ZonePublish.md @@ -8,8 +8,9 @@ Code: `dCommon/ZonePublish` (checking a set), `dCommon/ZoneOverrideStore` (the o restore, clear), `dCommon/ZoneEditLock.h` (who may: lock and reload rules), `dCommon/dClient/AssetManager` (reading overrides), `dDatabase/.../IZoneEditing.h` (locks and editors), `dWeb/MultipartFile` and `Web::RegisterUploadRoute` (large uploads), `dDashboardServer/auth/ZonePublishAuth.h` (the API's gate), -`dDashboardServer/routes/ZonePublishRoutes` (the API). The first four know nothing of HTTP, so another way in uses the -same checks, store, locks and reload. +`dDashboardServer/routes/ZonePublishRoutes` (the API), `dCommon/ZoneFilePack` and `dGame/dUtilities/ZoneEditor` (the +game client's level editor, on a world server). The first four know nothing of how files arrive: the API and the +world server use the same checks, store, locks and reload. ## Override folder @@ -120,3 +121,41 @@ the instances as they are; the files are read when an instance next starts, or o The dashboard's Worlds page lists each zone's editors and lock holder (permission `zone_editors_manage` and GM level `zone_lock_admin_gm_level`): add and remove editors, take a lock away. + +## The game client's level editor + +A world server answers the client's own level editor packets with the same backend. World packets, IDs as the +1.10.64 client has them: + +| From the client | Holds | Answer | +| --- | --- | --- | +| 8 editor mode | u8 on | none; the server remembers it per connection | +| 9 reload map | nothing | 15, then the zone's reload | +| 10 push request | u8 fileType, i32 levelFileId | 18: u8 result, u8 fileType, i32 levelFileId, UTF-16 lockedBy with its null | +| 11 push | u8 fileType, i32 levelFileId, packed files | none; when the set is applied 15 to every client of the instance, then the zone's reload | +| 12 pull | u8 fileType, i32 levelFileId | 19: u8 fileType, i32 levelFileId, packed files | +| 13 lock | u8 lock (1 take, 0 give up) | 20: u8 result, 33 UTF-16 characters lockedBy, u8 locked (the client holds it now) | + +fileType: 0 the whole map, 1 a scene, 2 the terrain, 3 the zone file, 4 a scene's triggers. levelFileId is the +scene's ID in the zone file for 1 and 4 when pulling; a push is matched by its file's name. Results: 0 ok, 1 the +account's editor level is too low, 2 not one of the zone's editors, 3 locked by another account (lockedBy says who; a +push without any lock held is 3 with a hint to lock first), 4 no change (lock only). + +Packed files (`ZoneFilePack`): an entry is u32 nameBytes (UTF-16LE with its null), u32 dataBytes, the name, the data. +A whole map is the zone entry, the terrain entry, u16 N and N scene entries, u16 M and M trigger entries; any other +type is one entry. Data is always sd0, each chunk inflating to at most 256 KiB; anything else is refused. Only a +name's base name is used, lowercased into the zone's folder and checked like any other name. There is no checksum. + +Gates, looked up on the server for every packet: the account's editor level (its GM level when at +`editor_gm_level`, else 0) at `zone_publish_gm_level` or above; one of the zone's editors or an admin to lock, pull or +reload; the lock's holder to push. A push is only taken after a push request for the same file type and ID was +answered with 0. A single file replaces that file in the zone's set and keeps the rest; a whole map replaces the set. +A push that is refused changes nothing and tells the sender why in chat. Every request is in the audit log (action +`zone_publish`, "in game"). + +While a client's editor is on it is never sent a zone load. The level load complete it sends when its editor ends is +answered as any other: its player is here, so the world is sent to it again. The reload after a push moves normal +clients to the new instance as any world reload does; a client with its editor on loses its connection there. + +A character's editor level is told to the client in the character's save (`edit`), with the character (the editor +flag and level after the GM level) and in answer to the GetEditorLevel game message.