Skip to main content
Verticopolis engineGitHub

verticopolis_engine/view/
layout.rs

1//! Every slot and record width of the view's frame and structural snapshot,
2//! by name. Decode both with these, such as `frame[header::MONEY]`, never
3//! with bare numbers. [`super::layout_json`] hands the same tables over as
4//! JSON, with the state, weather, mode and calendar codes, for a host in
5//! another language.
6use serde_json::{json, Map, Value};
7
8/// The version of the view's layout. It goes up when a slot moves, a width
9/// changes, or a slot or code changes meaning.
10pub const VIEW_LAYOUT_VERSION: u32 = 15;
11
12/// The dynamic frame's header slots, one name each. `UNLOCKED` holds the
13/// unlocked kinds as a bitmask by kind code in one double, with more than 32
14/// bits in use: read bit k as `Math.floor(mask / 2 ** k) % 2`, since
15/// JavaScript's bitwise operators cut a number to 32 bits. `FRAME_SEQ` counts
16/// the reader's frames from 1: a host that sees it skip a number missed a
17/// frame, and asks for a full one before it applies another. `DAY` through
18/// `WEEKEND_DAYS` are the clock's readings on the tower's calendar (the day
19/// count, the day of the week, the weekend flag as 0 or 1, the quarter and the
20/// year, all counted from 0) and the calendar's week and weekend lengths in
21/// days, so a host shows dates without doing the date arithmetic.
22/// `BOMB_SEARCH_SEQ` is 0 while no bomb search runs and otherwise moves with
23/// every change to it, so a host rereads the search only when it moved.
24// Each slot is named for the value it holds.
25#[allow(missing_docs)]
26pub mod header {
27    pub const MINUTES: usize = 0;
28    pub const TOWER_REVISION: usize = 1;
29    pub const META_REVISION: usize = 2;
30    pub const MEAL_OVERLAY_REVISION: usize = 3;
31    pub const LOG_SEQ: usize = 4;
32    pub const MONEY: usize = 5;
33    pub const STAR: usize = 6;
34    pub const UNLOCKED: usize = 7;
35    pub const POPULATION: usize = 8;
36    pub const WEATHER: usize = 9;
37    pub const SANTA_FX_SEQ: usize = 10;
38    pub const EXPLOSION_FX_SEQ: usize = 11;
39    pub const EXPLOSION_FX_FLOOR: usize = 12;
40    pub const EXPLOSION_FX_X: usize = 13;
41    pub const THIEF_FX_SEQ: usize = 14;
42    pub const THIEF_FX_CAUGHT: usize = 15;
43    pub const THIEF_FX_FLOOR: usize = 16;
44    pub const TREASURE_FX_SEQ: usize = 17;
45    pub const TREASURE_FX_FLOOR: usize = 18;
46    pub const TREASURE_FX_X: usize = 19;
47    pub const VIP_FX_SEQ: usize = 20;
48    pub const FIRES_STARTED: usize = 21;
49    pub const FIRES_GUT_ROOMS: usize = 22;
50    pub const BOMBS_DETONATED: usize = 23;
51    pub const PENDING_CHOICE: usize = 24;
52    pub const ON_HOUR_RUNS: usize = 25;
53    pub const ROOMS: usize = 26;
54    pub const FULL: usize = 27;
55    pub const FRAME_SEQ: usize = 28;
56    pub const PEOPLE: usize = 29;
57    pub const UNITS: usize = 30;
58    pub const TRANSPORTS: usize = 31;
59    pub const DAY: usize = 32;
60    pub const DAY_OF_WEEK: usize = 33;
61    pub const IS_WEEKEND: usize = 34;
62    pub const QUARTER: usize = 35;
63    pub const YEAR: usize = 36;
64    pub const WEEK_DAYS: usize = 37;
65    pub const WEEKEND_DAYS: usize = 38;
66    pub const BOMB_SEARCH_SEQ: usize = 39;
67}
68
69/// Slots of the dynamic frame's header.
70pub const HEADER: usize = header::BOMB_SEARCH_SEQ + 1;
71
72/// The slots of a drawn person's record.
73// Each slot is named for the value it holds.
74#[allow(missing_docs)]
75pub mod person {
76    pub const ID: usize = 0;
77    pub const SEED: usize = 1;
78    pub const STAFF: usize = 2;
79    pub const STATE: usize = 3;
80    pub const FLOOR: usize = 4;
81    pub const X: usize = 5;
82    pub const FY: usize = 6;
83    pub const WAIT: usize = 7;
84}
85
86/// Slots of a person record.
87pub const PERSON_RECORD: usize = person::WAIT + 1;
88
89/// The slots of a changed room's record: its slot in the structural
90/// snapshot's room list, then its frame fields.
91// Each slot is named for the value it holds.
92#[allow(missing_docs)]
93pub mod unit {
94    pub const SLOT: usize = 0;
95    pub const STATE: usize = 1;
96    pub const OCCUPANTS: usize = 2;
97    pub const CUSTOMERS_IN: usize = 3;
98    pub const HOTEL_CUSTOMERS_IN: usize = 4;
99    pub const OUT_FOR_MEAL: usize = 5;
100}
101
102/// Slots of a room record in the dynamic frame.
103pub const UNIT_RECORD: usize = unit::OUT_FOR_MEAL + 1;
104
105/// The slots of a transport's record in the dynamic frame: `ID` and `CARS`,
106/// then `CARS` car records.
107// Each slot is named for the value it holds.
108#[allow(missing_docs)]
109pub mod transport {
110    pub const ID: usize = 0;
111    pub const CARS: usize = 1;
112}
113
114/// Slots of a transport record before its cars.
115pub const TRANSPORT_FIXED: usize = transport::CARS + 1;
116
117/// The slots of one car, counted from the car's start.
118// Each slot is named for the value it holds.
119#[allow(missing_docs)]
120pub mod car {
121    pub const POSITION: usize = 0;
122    pub const LOAD: usize = 1;
123    pub const DIRECTION: usize = 2;
124}
125
126/// Slots of each car.
127pub const CAR_RECORD: usize = car::DIRECTION + 1;
128
129/// The structural snapshot's header slots. `HIGHEST_FLOOR` and
130/// `LOWEST_FLOOR` are the highest and lowest floor any unit stands on (1 on
131/// an empty tower), the bounds the camera reads; `MODE` and `CALENDAR` are
132/// the game mode and the Modern calendar, by the codes in `MODES` and
133/// `CALENDARS`; `SCENE_SEED` is the founding seed the scenery derives from
134/// (so the scenery stays the same across saves), fixed for a tower's life.
135// Each slot is named for the value it holds.
136#[allow(missing_docs)]
137pub mod structure {
138    pub const REVISION: usize = 0;
139    pub const ROOMS: usize = 1;
140    pub const RUNS: usize = 2;
141    pub const SHAFTS: usize = 3;
142    pub const HIGHEST_FLOOR: usize = 4;
143    pub const LOWEST_FLOOR: usize = 5;
144    pub const MODE: usize = 6;
145    pub const CALENDAR: usize = 7;
146    pub const SCENE_SEED: usize = 8;
147}
148
149/// Slots of the structural snapshot's header.
150pub const STRUCTURE_HEADER: usize = structure::SCENE_SEED + 1;
151
152/// The slots of a room in the structural snapshot.
153// Each slot is named for the value it holds.
154#[allow(missing_docs)]
155pub mod room {
156    pub const ID: usize = 0;
157    pub const KIND: usize = 1;
158    pub const FLOOR: usize = 2;
159    pub const X: usize = 3;
160    pub const WIDTH: usize = 4;
161}
162
163/// Slots of a room record in the structural snapshot.
164pub const ROOM_RECORD: usize = room::WIDTH + 1;
165
166/// The slots of a run of floor or lobby tiles: `COUNT` tiles of one kind and
167/// width side by side on one floor, the k-th at `X + k * WIDTH`, every one
168/// with the same frame fields.
169// Each slot is named for the value it holds.
170#[allow(missing_docs)]
171pub mod run {
172    pub const KIND: usize = 0;
173    pub const FLOOR: usize = 1;
174    pub const X: usize = 2;
175    pub const WIDTH: usize = 3;
176    pub const COUNT: usize = 4;
177    pub const STATE: usize = 5;
178    pub const OCCUPANTS: usize = 6;
179    pub const CUSTOMERS_IN: usize = 7;
180    pub const HOTEL_CUSTOMERS_IN: usize = 8;
181    pub const OUT_FOR_MEAL: usize = 9;
182}
183
184/// Slots of a run record.
185pub const RUN_RECORD: usize = run::OUT_FOR_MEAL + 1;
186
187/// The slots of a transport in the structural snapshot, before its
188/// `SKIPS` skipped floors.
189// Each slot is named for the value it holds.
190#[allow(missing_docs)]
191pub mod shaft {
192    pub const ID: usize = 0;
193    pub const KIND: usize = 1;
194    pub const X: usize = 2;
195    pub const WIDTH: usize = 3;
196    pub const BOTTOM: usize = 4;
197    pub const TOP: usize = 5;
198    pub const CARS: usize = 6;
199    pub const SKIPS: usize = 7;
200}
201
202/// Slots of a shaft record before its skipped floors.
203pub const SHAFT_FIXED: usize = shaft::SKIPS + 1;
204
205/// One named table of slots, as `layout_json` hands it over: its name in the
206/// JSON, the slots by camelCase name, the width key and the width.
207pub(crate) struct Table {
208    pub(crate) name: &'static str,
209    pub(crate) slots: &'static [(&'static str, usize)],
210    pub(crate) width_name: &'static str,
211    pub(crate) width: usize,
212}
213
214/// Every table of the layout, in the order `layout_json` writes them.
215pub(crate) const TABLES: &[Table] = &[
216    Table {
217        name: "header",
218        slots: &[
219            ("minutes", header::MINUTES),
220            ("towerRevision", header::TOWER_REVISION),
221            ("metaRevision", header::META_REVISION),
222            ("mealOverlayRevision", header::MEAL_OVERLAY_REVISION),
223            ("logSeq", header::LOG_SEQ),
224            ("money", header::MONEY),
225            ("star", header::STAR),
226            ("unlocked", header::UNLOCKED),
227            ("population", header::POPULATION),
228            ("weather", header::WEATHER),
229            ("santaFxSeq", header::SANTA_FX_SEQ),
230            ("explosionFxSeq", header::EXPLOSION_FX_SEQ),
231            ("explosionFxFloor", header::EXPLOSION_FX_FLOOR),
232            ("explosionFxX", header::EXPLOSION_FX_X),
233            ("thiefFxSeq", header::THIEF_FX_SEQ),
234            ("thiefFxCaught", header::THIEF_FX_CAUGHT),
235            ("thiefFxFloor", header::THIEF_FX_FLOOR),
236            ("treasureFxSeq", header::TREASURE_FX_SEQ),
237            ("treasureFxFloor", header::TREASURE_FX_FLOOR),
238            ("treasureFxX", header::TREASURE_FX_X),
239            ("vipFxSeq", header::VIP_FX_SEQ),
240            ("firesStarted", header::FIRES_STARTED),
241            ("firesGutRooms", header::FIRES_GUT_ROOMS),
242            ("bombsDetonated", header::BOMBS_DETONATED),
243            ("pendingChoice", header::PENDING_CHOICE),
244            ("onHourRuns", header::ON_HOUR_RUNS),
245            ("rooms", header::ROOMS),
246            ("full", header::FULL),
247            ("frameSeq", header::FRAME_SEQ),
248            ("people", header::PEOPLE),
249            ("units", header::UNITS),
250            ("transports", header::TRANSPORTS),
251            ("day", header::DAY),
252            ("dayOfWeek", header::DAY_OF_WEEK),
253            ("isWeekend", header::IS_WEEKEND),
254            ("quarter", header::QUARTER),
255            ("year", header::YEAR),
256            ("weekDays", header::WEEK_DAYS),
257            ("weekendDays", header::WEEKEND_DAYS),
258            ("bombSearchSeq", header::BOMB_SEARCH_SEQ),
259        ],
260        width_name: "headerWidth",
261        width: HEADER,
262    },
263    Table {
264        name: "person",
265        slots: &[
266            ("id", person::ID),
267            ("seed", person::SEED),
268            ("staff", person::STAFF),
269            ("state", person::STATE),
270            ("floor", person::FLOOR),
271            ("x", person::X),
272            ("fy", person::FY),
273            ("wait", person::WAIT),
274        ],
275        width_name: "personWidth",
276        width: PERSON_RECORD,
277    },
278    Table {
279        name: "unit",
280        slots: &[
281            ("slot", unit::SLOT),
282            ("state", unit::STATE),
283            ("occupants", unit::OCCUPANTS),
284            ("customersIn", unit::CUSTOMERS_IN),
285            ("hotelCustomersIn", unit::HOTEL_CUSTOMERS_IN),
286            ("outForMeal", unit::OUT_FOR_MEAL),
287        ],
288        width_name: "unitWidth",
289        width: UNIT_RECORD,
290    },
291    Table {
292        name: "transport",
293        slots: &[("id", transport::ID), ("cars", transport::CARS)],
294        width_name: "transportFixed",
295        width: TRANSPORT_FIXED,
296    },
297    Table {
298        name: "car",
299        slots: &[
300            ("position", car::POSITION),
301            ("load", car::LOAD),
302            ("direction", car::DIRECTION),
303        ],
304        width_name: "carWidth",
305        width: CAR_RECORD,
306    },
307    Table {
308        name: "structure",
309        slots: &[
310            ("revision", structure::REVISION),
311            ("rooms", structure::ROOMS),
312            ("runs", structure::RUNS),
313            ("shafts", structure::SHAFTS),
314            ("highestFloor", structure::HIGHEST_FLOOR),
315            ("lowestFloor", structure::LOWEST_FLOOR),
316            ("mode", structure::MODE),
317            ("calendar", structure::CALENDAR),
318            ("sceneSeed", structure::SCENE_SEED),
319        ],
320        width_name: "structureWidth",
321        width: STRUCTURE_HEADER,
322    },
323    Table {
324        name: "room",
325        slots: &[
326            ("id", room::ID),
327            ("kind", room::KIND),
328            ("floor", room::FLOOR),
329            ("x", room::X),
330            ("width", room::WIDTH),
331        ],
332        width_name: "roomWidth",
333        width: ROOM_RECORD,
334    },
335    Table {
336        name: "run",
337        slots: &[
338            ("kind", run::KIND),
339            ("floor", run::FLOOR),
340            ("x", run::X),
341            ("width", run::WIDTH),
342            ("count", run::COUNT),
343            ("state", run::STATE),
344            ("occupants", run::OCCUPANTS),
345            ("customersIn", run::CUSTOMERS_IN),
346            ("hotelCustomersIn", run::HOTEL_CUSTOMERS_IN),
347            ("outForMeal", run::OUT_FOR_MEAL),
348        ],
349        width_name: "runWidth",
350        width: RUN_RECORD,
351    },
352    Table {
353        name: "shaft",
354        slots: &[
355            ("id", shaft::ID),
356            ("kind", shaft::KIND),
357            ("x", shaft::X),
358            ("width", shaft::WIDTH),
359            ("bottom", shaft::BOTTOM),
360            ("top", shaft::TOP),
361            ("cars", shaft::CARS),
362            ("skips", shaft::SKIPS),
363        ],
364        width_name: "shaftFixed",
365        width: SHAFT_FIXED,
366    },
367];
368
369/// The unit state names by the code a frame carries (`frame::unit_state_code`).
370pub(crate) const UNIT_STATES: [&str; 10] = [
371    "construction",
372    "empty",
373    "occupied",
374    "moving_in",
375    "vacating",
376    "asleep",
377    "dirty",
378    "infested",
379    "fire",
380    "gutted",
381];
382
383/// The person state names by code (`frame::person_state_code`).
384pub(crate) const PERSON_STATES: [&str; 7] = [
385    "toShaft", "waiting", "riding", "climbing", "toDest", "dwelling", "done",
386];
387
388/// The weather names by code (`frame::weather_code`).
389pub(crate) const WEATHER: [&str; 3] = ["clear", "cloudy", "rain"];
390
391/// The game modes by code, as a save spells them (`structure::mode_code`).
392pub(crate) const MODES: [&str; 2] = ["classic", "modern"];
393
394/// The Modern calendars by code, as a save spells them
395/// (`structure::calendar_code`).
396pub(crate) const CALENDARS: [&str; 2] = ["canon", "realWorld"];
397
398/// The fields of a unit's metadata record in `unitMetaSince`'s listing, in
399/// order (`meta::meta_since`).
400pub(crate) const META_RECORD: [&str; 3] = ["id", "label", "subtype"];
401
402/// The layout as a value: the version, every table's slots and width, the
403/// codes (kinds, unit and person states, weather, modes, calendars) and the
404/// metadata record's fields. A kind's code is its index in `kinds` (the
405/// catalog's order, `Kind as usize`), and `names` holds each kind's catalog
406/// name, which is a unit's label when a full metadata listing leaves the
407/// unit out.
408pub fn layout_json() -> Value {
409    let mut m = Map::new();
410    m.insert("version".into(), json!(VIEW_LAYOUT_VERSION));
411    for t in TABLES {
412        let slots: Map<String, Value> = t
413            .slots
414            .iter()
415            .map(|(name, slot)| ((*name).to_owned(), json!(slot)))
416            .collect();
417        m.insert(t.name.into(), Value::Object(slots));
418        m.insert(t.width_name.into(), json!(t.width));
419    }
420    let facilities = &crate::facilities::FACILITIES;
421    m.insert(
422        "kinds".into(),
423        json!(facilities.iter().map(|f| f.key).collect::<Vec<_>>()),
424    );
425    m.insert(
426        "names".into(),
427        json!(facilities.iter().map(|f| f.name).collect::<Vec<_>>()),
428    );
429    m.insert("unitStates".into(), json!(UNIT_STATES));
430    m.insert("personStates".into(), json!(PERSON_STATES));
431    m.insert("weather".into(), json!(WEATHER));
432    m.insert("modes".into(), json!(MODES));
433    m.insert("calendars".into(), json!(CALENDARS));
434    m.insert("metaRecord".into(), json!(META_RECORD));
435    Value::Object(m)
436}