Skip to main content

banished_formats/
savegame_schema.rs

1//! The serializers of the objects a save holds, as schemas (see [`crate::savegame`]). Each
2//! entry names the Application function it follows; field names give the in-memory offset
3//! where no better name is known.
4
5use std::collections::HashMap;
6use std::sync::OnceLock;
7
8use crate::crc::type_id;
9
10/// A version test against the enclosing block's version.
11#[derive(Debug, Clone, Copy)]
12pub enum Cond {
13    /// The block's version is at least this.
14    VersionAtLeast(u32),
15    /// The block's version is below this.
16    VersionBelow(u32),
17    /// An earlier `Bool` of the same fields is true.
18    Flag(&'static str),
19}
20
21/// One step of a serializer.
22#[derive(Debug, Clone, Copy)]
23pub enum Op {
24    /// A named bool.
25    Bool(&'static str),
26    /// A named `i8`.
27    I8(&'static str),
28    /// A named `i16`.
29    I16(&'static str),
30    /// A named `u16`.
31    U16(&'static str),
32    /// A named `i32`.
33    I32(&'static str),
34    /// A named `u32`.
35    U32(&'static str),
36    /// `System::Time`: an `i64` of ticks.
37    Time(&'static str),
38    /// A named `f32`.
39    F32(&'static str),
40    /// A fixed number of `System::Time`s.
41    Times(&'static str, usize),
42    /// A fixed number of `f32`s.
43    F32s(&'static str, usize),
44    /// A fixed number of `u32`s.
45    U32s(&'static str, usize),
46    /// `i32`s counted by an earlier field of the same fields.
47    I32sBy(&'static str, &'static str),
48    /// `f32`s counted by an earlier field.
49    F32sBy(&'static str, &'static str),
50    /// `System::String` with the 8-bit flag.
51    Str(&'static str),
52    /// A UTF-16 string.
53    WStr(&'static str),
54    /// An `ExternalPtr` in reference mode: the reference id.
55    Ext(&'static str),
56    /// An `InternalBasePtr`.
57    Ptr(&'static str),
58    /// An `i32` count, then the items.
59    List(&'static str, &'static [Op]),
60    /// Items repeated until the enclosing block ends (no count is written).
61    Rest(&'static str, &'static [Op]),
62    /// An `IO::Serializer` block written at this version.
63    Block(&'static str, u32, &'static [Op]),
64    /// Inline steps (shared fragments).
65    Sub(&'static [Op]),
66    /// Steps read only when the condition holds.
67    If(Cond, &'static [Op]),
68}
69
70use Cond::*;
71use Op::*;
72
73/// `Component::Serialize` (`FUN_14011de80`): `Internal::Persist` writes nothing, then the
74/// entity, flags (`+0x20`) and description (`+0x18`).
75/// `Work::Serialize` (`FUN_1400fc8d0`, version 3): the entity and its WorkComponent, the
76/// worker counts and the times.
77const WORK: Op = Block(
78    "work",
79    3,
80    &[
81        Ptr("entity"),
82        Ptr("component"),
83        I32("id"),
84        I32("cell"),
85        If(VersionAtLeast(1), &[I32("priority")]),
86        If(
87            VersionAtLeast(3),
88            &[Time("patience"), Time("patience_base"), I32("far_count")],
89        ),
90    ],
91);
92
93/// `AutoPickupWork`: Work, then an empty block.
94const AUTO_PICKUP_WORK: Op = Sub(&[WORK, Block("auto_pickup", 0, &[])]);
95
96/// `FUN_14002a010`: a set of citizens.
97const CITIZENS: Op = List("citizens", &[Ptr("citizen")]);
98
99/// `FUN_14002a740`: key → citizen.
100const KEYED_CITIZENS: Op = List("keyed", &[I32("key"), Ptr("citizen")]);
101
102const BASE: Op = Block(
103    "base",
104    0,
105    &[Ptr("entity"), U32("flags"), Ext("description")],
106);
107/// `Action::Serialize`: started (`+0x18`).
108const ACTION: Op = Block("action", 0, &[Bool("started")]);
109/// `ControllerComponent`'s own (empty) block.
110const CONTROLLER: Op = Block("controller", 0, &[]);
111/// `ResourceProduceComponent::Serialize` (`FUN_140130970`): switched off (`+0x48`).
112const RESOURCE_PRODUCE: Op = Sub(&[BASE, CONTROLLER, Block("produce", 0, &[Bool("disabled")])]);
113
114/// `Graphics::MeshInstance::Serialize` (`0x180101f10`): `AnimationState` entries
115/// (`FUN_18005d610`), its flag, then the material references.
116const MESH_INSTANCE: Op = Sub(&[
117    List(
118        "animations",
119        &[
120            Ext("animation"),
121            I32("index"),
122            F32("speed"),
123            Time("time0"),
124            Time("time1"),
125            Time("time2"),
126            I32("flags"),
127            Bool("looping"),
128        ],
129    ),
130    Bool("animating"),
131    List("materials", &[Ext("material")]),
132]);
133
134const GOAL_INSTANCE: Op = Block("goal", 0, &[Ext("goal"), I32("value")]);
135
136static SCHEMAS: &[(&str, &[Op])] = &[
137    // `Achievement::AchievementInstance::Persist` (Runtime `0x18009efa0`, version 1): its
138    // achievement, then its goal list's instances.
139    (
140        "AchievementInstance",
141        &[Block(
142            "instance",
143            1,
144            &[
145                Ext("achievement"),
146                Block("goals", 0, &[List("goals", &[Ptr("goal")])]),
147            ],
148        )],
149    ),
150    // `Achievement::GoalInstance::Persist` (`0x18009fe80`): its goal and its count (`+0x10`).
151    ("GoalInstance", &[GOAL_INSTANCE]),
152    // `FUN_1400e0200`: then its time (`+0x20`, `System::Time::Serialize`: a block).
153    (
154        "TimeGoalInstance",
155        &[GOAL_INSTANCE, Block("timer", 0, &[Time("time")])],
156    ),
157    // `FUN_1400dd0c0`: then its tutorial (`+0x20`).
158    ("FinishTutorialInstance", &[GOAL_INSTANCE, Ptr("tutorial")]),
159    // `FUN_1400c8610`.
160    (
161        "ComponentEntity",
162        &[Block(
163            "entity",
164            0,
165            &[
166                Ext("template"),
167                List("components", &[Ptr("component")]),
168                U32("flags"),
169            ],
170        )],
171    ),
172    // `FUN_1401298b0`.
173    (
174        "MapComponent",
175        &[
176            BASE,
177            Block(
178                "map",
179                0,
180                &[I16("x"), I16("y"), I16("w"), I16("h"), I32("rotation")],
181            ),
182        ],
183    ),
184    // `FUN_14012a040`.
185    (
186        "ModelComponent",
187        &[
188            BASE,
189            Block(
190                "model",
191                0,
192                &[
193                    I16("variant"),
194                    I16("mesh"),
195                    F32s("rotation", 4),
196                    F32s("position", 3),
197                    F32("yaw"),
198                    MESH_INSTANCE,
199                    Bool("picking"),
200                ],
201            ),
202        ],
203    ),
204    // `FUN_14012b1c0`.
205    (
206        "NaturalResourceComponent",
207        &[
208            BASE,
209            Block(
210                "natural",
211                0,
212                &[F32("value_28"), Bool("flag_2c"), Bool("flag_2d")],
213            ),
214        ],
215    ),
216    // `FUN_140125c90`.
217    (
218        "GrowthComponent",
219        &[
220            BASE,
221            Block(
222                "growth",
223                0,
224                &[
225                    Ptr("natural"),
226                    Ptr("model"),
227                    F32("growth"),
228                    F32("maximum"),
229                    Time("timer"),
230                ],
231            ),
232        ],
233    ),
234    // `FUN_140056840`.
235    (
236        "SpawnComponent",
237        &[BASE, Block("spawn", 0, &[List("timers", &[Time("timer")])])],
238    ),
239    // `FUN_140043890`.
240    (
241        "AmbientEmitterComponent",
242        &[
243            BASE,
244            Block(
245                "emitter",
246                1,
247                &[
248                    F32s("position", 3),
249                    List("slots", &[Ext("effect"), U32("handle"), F32("volume")]),
250                ],
251            ),
252        ],
253    ),
254    // `FUN_140044140`.
255    (
256        "AmbientEnvironmentComponent",
257        &[
258            BASE,
259            Block(
260                "environment",
261                0,
262                &[
263                    F32s("position", 3),
264                    I32("value_28"),
265                    I32("value_2c"),
266                    I32("value_30"),
267                    I32("value_34"),
268                ],
269            ),
270        ],
271    ),
272    // `FUN_14010c310`.
273    (
274        "AnimalComponent",
275        &[
276            BASE,
277            Block(
278                "animal",
279                1,
280                &[
281                    Time("timer"),
282                    I32("home"),
283                    F32("age"),
284                    F32("life"),
285                    Bool("male"),
286                    Time("breed"),
287                    I16("x"),
288                    I16("y"),
289                    I16("w"),
290                    I16("h"),
291                    If(VersionBelow(1), &[Ptr("population")]),
292                ],
293            ),
294        ],
295    ),
296    // `FUN_140127460`.
297    (
298        "HerdComponent",
299        &[
300            BASE,
301            Block(
302                "herd",
303                0,
304                &[
305                    List("animals", &[Ptr("animal")]),
306                    Bool("moving"),
307                    Time("rest"),
308                    Time("regrow"),
309                    I32("target"),
310                    Ptr("population"),
311                    I32("index"),
312                ],
313            ),
314        ],
315    ),
316    // `FUN_140054e00`.
317    (
318        "PopulationComponent",
319        &[
320            BASE,
321            Block(
322                "population",
323                0,
324                &[
325                    Ext("description"),
326                    I32("width"),
327                    I32("herd_width"),
328                    List("cells", &[I32("a"), I32("c"), I32("b")]),
329                    List("herds", &[Ptr("herd")]),
330                    Time("clock"),
331                    I32("cursor"),
332                    I32("value_6c"),
333                ],
334            ),
335        ],
336    ),
337    // `FUN_1401337d0`.
338    (
339        "WildAnimalComponent",
340        &[BASE, Block("wild", 0, &[Ptr("herd")])],
341    ),
342    // `FUN_1401357c0`.
343    ("ZoneComponent", &[BASE, Block("zone", 1, &[])]),
344    // `FUN_140120cf0`.
345    (
346        "DecalComponent",
347        &[
348            BASE,
349            Block(
350                "decal",
351                1,
352                &[F32("alpha"), If(VersionAtLeast(1), &[I32("material")])],
353            ),
354        ],
355    ),
356    // `FUN_140133be0`.
357    (
358        "WorkComponent",
359        &[
360            BASE,
361            Block(
362                "work",
363                3,
364                &[
365                    I32("work_id"),
366                    If(VersionBelow(3), &[Bool("old")]),
367                    If(VersionAtLeast(1), &[I32("most_workers")]),
368                    If(VersionAtLeast(2), &[I32("kept_priority")]),
369                ],
370            ),
371        ],
372    ),
373    // `FUN_140130e10`.
374    (
375        "StateMachineComponent",
376        &[
377            BASE,
378            Block(
379                "machine",
380                0,
381                &[I8("state"), I8("index"), I8("pending"), I8("transition")],
382            ),
383        ],
384    ),
385    // `FUN_14011d390`.
386    (
387        "ClearAreaComponent",
388        &[BASE, CONTROLLER, Block("clear", 0, &[I32("total")])],
389    ),
390    // `FUN_140123020`.
391    (
392        "DestroyComponent",
393        &[
394            BASE,
395            CONTROLLER,
396            Block(
397                "destroy",
398                0,
399                &[
400                    I32("count"),
401                    I32("target"),
402                    Bool("refund"),
403                    Ext("rebuild"),
404                    Bool("damaged"),
405                    Time("time"),
406                ],
407            ),
408        ],
409    ),
410    // `FUN_14012e080`.
411    (
412        "PickingComponent",
413        &[BASE, Block("picking", 0, &[Ptr("entity")])],
414    ),
415    // `FUN_1401314c0`: the status bits (`+0xf0`).
416    (
417        "StatusIconComponent",
418        &[BASE, Block("status", 0, &[U32("bits")])],
419    ),
420    // `FUN_14004d1e0`.
421    ("HighlightComponent", &[BASE, Block("highlight", 0, &[])]),
422    // `FUN_140131c00`: items (count, reserved, raw material), `+0x38`, capacity `+0x3c`.
423    (
424        "StorageComponent",
425        &[
426            BASE,
427            Block(
428                "storage",
429                1,
430                &[
431                    List("items", &[I32("count"), I32("reserved"), Ext("material")]),
432                    I32("capacity"),
433                    U32("accepts"),
434                ],
435            ),
436        ],
437    ),
438    // `FUN_140132770`.
439    (
440        "StorageLocationComponent",
441        &[BASE, CONTROLLER, Block("location", 0, &[])],
442    ),
443    // `FUN_140128810`: the MultiObject parts' InteractComponents.
444    (
445        "InteractComponent",
446        &[
447            BASE,
448            Block("interact", 0, &[List("children", &[Ptr("child")])]),
449        ],
450    ),
451    // `FUN_14004a3f0`.
452    (
453        "FireComponent",
454        &[
455            BASE,
456            Block(
457                "fire",
458                1,
459                &[
460                    Time("burn"),
461                    Time("spread"),
462                    I32("buckets"),
463                    If(VersionAtLeast(1), &[I32("value_2c")]),
464                ],
465            ),
466        ],
467    ),
468    // `FUN_140053bd0`: per slot, running.
469    (
470        "ParticleComponent",
471        &[BASE, Block("particle", 0, &[List("slots", &[Bool("on")])])],
472    ),
473    // `FUN_14010b0d0`.
474    (
475        "AbsorbResourceComponent",
476        &[BASE, CONTROLLER, Block("absorb", 0, &[])],
477    ),
478    // `FUN_14010e250`: work done (`+0x48`), stages (`+0x4c`), `+0x50`, then the models and
479    // picking components of its MultiObject parts.
480    (
481        "BuildComponent",
482        &[
483            BASE,
484            CONTROLLER,
485            Block(
486                "build",
487                1,
488                &[
489                    I32("progress"),
490                    I32("stages"),
491                    Bool("flag_50"),
492                    List("models", &[Ptr("model")]),
493                    List("pickings", &[Ptr("picking")]),
494                ],
495            ),
496        ],
497    ),
498    // `FUN_1400572d0`.
499    ("StorageDisplayComponent", &[BASE, Block("display", 0, &[])]),
500    // `FUN_1401331d0`: per page, a byte.
501    (
502        "UIComponent",
503        &[BASE, Block("ui", 0, &[List("pages", &[I8("page")])])],
504    ),
505    // `FUN_1401345f0`: workers, enabled (`+0x50`), profession (`+0x48`, from version 1).
506    (
507        "WorkPlaceComponent",
508        &[
509            BASE,
510            CONTROLLER,
511            Block(
512                "workplace",
513                1,
514                &[
515                    List("workers", &[Ptr("worker")]),
516                    Bool("enabled"),
517                    If(VersionAtLeast(1), &[Ext("profession")]),
518                ],
519            ),
520        ],
521    ),
522    // `FUN_14010ef80`.
523    (
524        "CharacterComponent",
525        &[
526            BASE,
527            Block(
528                "character",
529                0,
530                &[
531                    F32s("direction", 3),
532                    F32("target_heading"),
533                    F32("speed"),
534                    Bool("flag_28"),
535                    I32("stack_top"),
536                    List("stack", &[Ptr("action")]),
537                    Ptr("on_empty"),
538                    Ptr("model"),
539                    Ptr("picking"),
540                ],
541            ),
542        ],
543    ),
544    // `FUN_1400ce390`.
545    (
546        "MoveTo",
547        &[
548            ACTION,
549            Block(
550                "move",
551                1,
552                &[
553                    Ptr("owner"),
554                    Ext("animations"),
555                    I32("path_len"),
556                    I32("path_index"),
557                    I32sBy("path", "path_len"),
558                    F32s("target", 3),
559                    U32("flags"),
560                    I32("walk_clip"),
561                    I32("stand_clip"),
562                    If(VersionBelow(1), &[Ptr("population")]),
563                    If(VersionAtLeast(1), &[I32("cost_mode")]),
564                    Bool("following"),
565                    F32("speed_factor"),
566                ],
567            ),
568        ],
569    ),
570    // `FUN_1400cea20`.
571    (
572        "PerformWork",
573        &[
574            ACTION,
575            Block(
576                "perform",
577                0,
578                &[
579                    Ptr("owner"),
580                    F32("facing"),
581                    I32("clip"),
582                    Time("duration"),
583                    Time("elapsed"),
584                    Bool("use_tool"),
585                    I32("value_44"),
586                ],
587            ),
588        ],
589    ),
590    // `FUN_140042de0`.
591    (
592        "PlayAnimation",
593        &[
594            ACTION,
595            Block(
596                "play",
597                0,
598                &[Ptr("owner"), I32("clip"), Time("duration"), Time("elapsed")],
599            ),
600        ],
601    ),
602    // `FUN_1400d04d0`.
603    (
604        "SendComponentMessage",
605        &[
606            ACTION,
607            Block(
608                "send",
609                0,
610                &[
611                    Ptr("entity"),
612                    Ptr("argument"),
613                    U32("component_type"),
614                    I32("message"),
615                    U32("value_38"),
616                    U32("value_3c"),
617                ],
618            ),
619        ],
620    ),
621    // `FUN_1400430e0`.
622    (
623        "ShowTool",
624        &[ACTION, Block("show", 0, &[Ptr("owner"), I32("tool")])],
625    ),
626    // `FUN_1400cb0b0`.
627    // `FUN_1400cad50`: the road cell, its kind and a value.
628    (
629        "CompleteRoad",
630        &[
631            ACTION,
632            Block("road", 0, &[I32("cell"), I8("built"), I32("ty")]),
633        ],
634    ),
635    (
636        "CompleteTask",
637        &[
638            ACTION,
639            Block(
640                "complete",
641                1,
642                &[
643                    Ptr("citizen"),
644                    Ptr("work_entity"),
645                    I32("param"),
646                    If(VersionAtLeast(1), &[I32("work")]),
647                ],
648            ),
649        ],
650    ),
651    // `FUN_1400cb5f0`.
652    (
653        "ConsumeRawMaterial",
654        &[
655            ACTION,
656            Block(
657                "consume",
658                2,
659                &[
660                    Ptr("entity"),
661                    If(VersionBelow(1), &[Ext("material"), I32("count")]),
662                    If(
663                        VersionAtLeast(1),
664                        &[List("items", &[Ext("material"), I32("count")])],
665                    ),
666                    If(VersionAtLeast(2), &[Bool("flag_38")]),
667                ],
668            ),
669        ],
670    ),
671    // `FUN_140119630` (version 3).
672    (
673        "CitizenComponent",
674        &[
675            BASE,
676            Block(
677                "citizen",
678                3,
679                &[
680                    Str("name"),
681                    I32("sex"),
682                    F32("age"),
683                    F32("lifespan"),
684                    F32("hunger"),
685                    F32("cold"),
686                    F32("reproduction"),
687                    F32("health"),
688                    U32("health_events"),
689                    Time("health_timer"),
690                    Time("disease_clock"),
691                    Ext("disease"),
692                    Time("disease_left"),
693                    Time("spread"),
694                    F32("happiness"),
695                    U32("flags"),
696                    Time("happiness_timer"),
697                    I32("food_count"),
698                    I32sBy("foods", "food_count"),
699                    Ext("profession"),
700                    I32("tool_uses"),
701                    I32("value_1d8"),
702                    If(VersionAtLeast(1), &[I32("tool_status")]),
703                    I32("clothing"),
704                    Time("clothing_remaining"),
705                    If(VersionAtLeast(2), &[I32("clothing_status")]),
706                    U32("supply_activity"),
707                    I32("job"),
708                    Ptr("home"),
709                    I32("home_slot"),
710                    Ptr("spouse"),
711                    Ptr("guardian"),
712                    Ptr("ring_head"),
713                    Ptr("ring_next"),
714                    Ptr("ring_prev"),
715                    Ptr("workplace"),
716                    Ext("accident"),
717                    Ptr("school"),
718                    I32("education"),
719                    If(VersionAtLeast(3), &[Time("school_time")]),
720                    Bool("schooled"),
721                    Ptr("doctor"),
722                    Ptr("priest"),
723                    Ptr("character"),
724                    Ptr("status_icon"),
725                    Ext("citizen_description"),
726                    I32("appearance"),
727                ],
728            ),
729        ],
730    ),
731    // `FUN_140114e30` (version 5).
732    (
733        "CitizenAIComponent",
734        &[
735            BASE,
736            Block(
737                "ai",
738                5,
739                &[
740                    Ptr("citizen"),
741                    I32("queued_activity"),
742                    If(VersionBelow(5), &[Times("cooldowns", 14)]),
743                    If(VersionAtLeast(5), &[Times("cooldowns", 16)]),
744                    If(
745                        VersionAtLeast(1),
746                        &[Bool("urgent_eating"), Bool("urgent_warming")],
747                    ),
748                    If(VersionAtLeast(2), &[I32("current_activity")]),
749                    If(VersionAtLeast(3), &[Time("since_loiter")]),
750                    If(VersionAtLeast(4), &[Time("carrying_time")]),
751                ],
752            ),
753        ],
754    ),
755    // `FUN_14012f640`: per family slot, its citizens; the fuel time (`+0x48`).
756    (
757        "ResidenceComponent",
758        &[
759            BASE,
760            CONTROLLER,
761            Block(
762                "residence",
763                0,
764                &[
765                    List("families", &[List("members", &[Ptr("citizen")])]),
766                    Time("fuel"),
767                ],
768            ),
769        ],
770    ),
771    // `FUN_1401292e0`.
772    (
773        "LivestockComponent",
774        &[
775            BASE,
776            Block("livestock", 0, &[Ptr("pasture"), Time("timer")]),
777        ],
778    ),
779    // `FUN_14012d410`: the kind (`+0x28`), the animals, `+0x40`, `+0x44`.
780    (
781        "PastureComponent",
782        &[
783            BASE,
784            CONTROLLER,
785            Block(
786                "pasture",
787                0,
788                &[
789                    Ext("kind"),
790                    List("animals", &[Ptr("animal")]),
791                    I32("value_40"),
792                    I32("value_44"),
793                ],
794            ),
795        ],
796    ),
797    // `FUN_140059a90`: this year's and last year's (material, amount), the year.
798    (
799        "TrackerComponent",
800        &[
801            BASE,
802            Block(
803                "tracker",
804                0,
805                &[
806                    List("this_year", &[Ext("material"), I32("amount")]),
807                    List("last_year", &[Ext("material"), I32("amount")]),
808                    I32("year"),
809                ],
810            ),
811        ],
812    ),
813    // `FUN_140049600`.
814    (
815        "DiseaseComponent",
816        &[
817            BASE,
818            Block(
819                "disease",
820                1,
821                &[
822                    Ext("disease"),
823                    I32("value_48"),
824                    Time("time_28"),
825                    Time("time_30"),
826                    Time("time_38"),
827                    Time("time_40"),
828                ],
829            ),
830        ],
831    ),
832    // `FUN_14004b3e0`: registered (`+0x28`).
833    (
834        "HappinessComponent",
835        &[
836            BASE,
837            CONTROLLER,
838            Block("happiness", 0, &[Bool("registered")]),
839        ],
840    ),
841    // `FUN_14005d900`.
842    // RESOURCE_PRODUCE, then the time since the last hunt.
843    (
844        "HuntComponent",
845        &[RESOURCE_PRODUCE, Block("hunt", 0, &[Time("time_50")])],
846    ),
847    // The graves in use.
848    (
849        "CemeteryComponent",
850        &[
851            BASE,
852            CONTROLLER,
853            Block("cemetery", 0, &[I32("count"), I32sBy("graves", "count")]),
854        ],
855    ),
856    (
857        "BoatComponent",
858        &[
859            BASE,
860            CONTROLLER,
861            Block(
862                "boat",
863                0,
864                &[
865                    Ptr("post"),
866                    F32s("dock", 3),
867                    F32("dock_yaw"),
868                    Ptr("model"),
869                    Ptr("character"),
870                    F32s("target", 3),
871                    I32("index"),
872                    List("route", &[F32s("point", 3)]),
873                ],
874            ),
875        ],
876    ),
877    // `FUN_140120340`; the cells by `FUN_140034020`.
878    (
879        "CropFieldComponent",
880        &[
881            BASE,
882            CONTROLLER,
883            Block(
884                "field",
885                0,
886                &[
887                    Bool("flag_48"),
888                    Bool("flag_49"),
889                    Ext("crop"),
890                    List(
891                        "cells",
892                        &[
893                            I32("x"),
894                            I32("y"),
895                            Ptr("entity"),
896                            Ptr("resource"),
897                            Bool("flag"),
898                        ],
899                    ),
900                ],
901            ),
902        ],
903    ),
904    // `FUN_140052740`; the trees by `FUN_14000a230`.
905    (
906        "OrchardComponent",
907        &[
908            BASE,
909            CONTROLLER,
910            Block(
911                "orchard",
912                0,
913                &[
914                    Bool("flag_48"),
915                    I32("value_4c"),
916                    List(
917                        "trees",
918                        &[
919                            I32("x"),
920                            I32("y"),
921                            Ptr("entity"),
922                            Ptr("resource"),
923                            Ptr("fruit"),
924                            Bool("flag"),
925                        ],
926                    ),
927                    Ext("tree"),
928                ],
929            ),
930        ],
931    ),
932    // `FUN_14005c6b0`.
933    (
934        "TradeComponent",
935        &[
936            BASE,
937            CONTROLLER,
938            Block(
939                "trade",
940                0,
941                &[
942                    List("stock", &[Ext("material"), I32("value_4"), I32("value_8")]),
943                    I32("value_2c"),
944                    // `FUN_14000ebe0`: a map of description to count.
945                    List("requests", &[Ext("material"), I32("count")]),
946                    List(
947                        "merchants",
948                        &[
949                            Str("name"),
950                            I32("count"),
951                            I32sBy("materials", "count"),
952                            I32("value_18"),
953                        ],
954                    ),
955                    List("orders", &[U32("value_0"), I32("value_8"), I32("value_4")]),
956                    I32("value_28"),
957                    Time("time_78"),
958                    Bool("flag_71"),
959                    Ptr("merchant"),
960                    Bool("flag_70"),
961                ],
962            ),
963        ],
964    ),
965    // `FUN_140124520`.
966    (
967        "FishingComponent",
968        &[
969            RESOURCE_PRODUCE,
970            Block("fishing", 0, &[Bool("flag_50"), F32("value_54")]),
971        ],
972    ),
973    // `FUN_140124e30`: whether to plant and whether to cut.
974    (
975        "ForesterComponent",
976        &[
977            RESOURCE_PRODUCE,
978            Block("forester", 0, &[Bool("plant"), Bool("cut")]),
979        ],
980    ),
981    // `FUN_14004bb60`.
982    (
983        "HerbalistComponent",
984        &[RESOURCE_PRODUCE, Block("herbalist", 0, &[])],
985    ),
986    // Work (`Work` subclasses; serializers named by their class block).
987    // `FUN_1400f0e40`.
988    (
989        "ForesterWork",
990        &[
991            AUTO_PICKUP_WORK,
992            Block("forester", 0, &[CITIZENS, I32("value_58")]),
993        ],
994    ),
995    // `FUN_1400f4080`.
996    (
997        "HerbalistWork",
998        &[
999            AUTO_PICKUP_WORK,
1000            Block("herbalist", 0, &[CITIZENS, I32("value_58")]),
1001        ],
1002    ),
1003    // `FUN_1400f4ad0`.
1004    (
1005        "HuntWork",
1006        &[AUTO_PICKUP_WORK, Block("hunt", 0, &[CITIZENS])],
1007    ),
1008    // `FUN_1400f8130`.
1009    (
1010        "PastureWork",
1011        &[AUTO_PICKUP_WORK, Block("pasture", 0, &[CITIZENS])],
1012    ),
1013    // `FUN_1400e9e50`.
1014    (
1015        "BuildRoad",
1016        &[
1017            WORK,
1018            Block(
1019                "road",
1020                1,
1021                &[
1022                    U32("type"),
1023                    I32("cell"),
1024                    Ptr("builder"),
1025                    Ext("material"),
1026                    If(
1027                        VersionAtLeast(1),
1028                        &[I32("old_kind"), I32("old_built"), I32("old_type")],
1029                    ),
1030                ],
1031            ),
1032        ],
1033    ),
1034    // `FUN_1400ead20`.
1035    (
1036        "BuildStructure",
1037        &[
1038            WORK,
1039            Block(
1040                "build",
1041                0,
1042                &[
1043                    Bool("destroy"),
1044                    CITIZENS,
1045                    List("occupied", &[Bool("done")]),
1046                    I32("cursor"),
1047                ],
1048            ),
1049        ],
1050    ),
1051    // `FUN_1400ebc10`; `FUN_14002a570` pairs entities with citizens.
1052    (
1053        "ClearArea",
1054        &[
1055            WORK,
1056            Block(
1057                "clear",
1058                1,
1059                &[
1060                    List("assigned", &[Ptr("entity"), Ptr("citizen")]),
1061                    Ext("filter"),
1062                    Bool("clear_resources"),
1063                    If(
1064                        VersionAtLeast(1),
1065                        &[KEYED_CITIZENS, Bool("clear_cells"), Bool("clear_piles")],
1066                    ),
1067                ],
1068            ),
1069        ],
1070    ),
1071    // `FUN_1400ec820`.
1072    (
1073        "ClearStorage",
1074        &[WORK, Block("clear_storage", 0, &[CITIZENS])],
1075    ),
1076    // `FUN_1400ee4f0`.
1077    (
1078        "ConsumeProduce",
1079        &[
1080            WORK,
1081            Block(
1082                "consume",
1083                0,
1084                &[
1085                    CITIZENS,
1086                    List("points", &[I32("state")]),
1087                    Bool("full"),
1088                    I32("cycles"),
1089                    U32("type_50"),
1090                    I32("value_54"),
1091                ],
1092            ),
1093        ],
1094    ),
1095    // `FUN_1400eef20`.
1096    (
1097        "FireWork",
1098        &[WORK, Block("fire", 0, &[CITIZENS, I32("value_48")])],
1099    ),
1100    // `FUN_1400efde0`.
1101    (
1102        "FishingWork",
1103        &[
1104            WORK,
1105            Block(
1106                "fishing",
1107                0,
1108                &[CITIZENS, List("values", &[I32("value")]), I32("value_68")],
1109            ),
1110        ],
1111    ),
1112    // `FUN_1400f1a30`.
1113    (
1114        "GatherRawMaterials",
1115        &[
1116            WORK,
1117            Block(
1118                "gather",
1119                0,
1120                &[
1121                    List("stored", &[I32("value")]),
1122                    List("transit", &[I32("value")]),
1123                    CITIZENS,
1124                ],
1125            ),
1126        ],
1127    ),
1128    // `FUN_1400f5070`.
1129    (
1130        "LocationWork",
1131        &[WORK, Block("location", 0, &[Ptr("citizen")])],
1132    ),
1133    // `FUN_1400f7240`; from version 1 `FUN_14002bbf0`: citizen → (description → count).
1134    (
1135        "MarketWork",
1136        &[
1137            WORK,
1138            Block(
1139                "market",
1140                1,
1141                &[
1142                    If(VersionBelow(1), &[CITIZENS]),
1143                    If(
1144                        VersionAtLeast(1),
1145                        &[List(
1146                            "orders",
1147                            &[
1148                                Ptr("citizen"),
1149                                List("items", &[Ext("description"), I32("count")]),
1150                            ],
1151                        )],
1152                    ),
1153                ],
1154            ),
1155        ],
1156    ),
1157    // `FUN_1400f8f70`.
1158    (
1159        "Pickup",
1160        &[
1161            WORK,
1162            Block(
1163                "pickup",
1164                2,
1165                &[
1166                    I32("state"),
1167                    Ptr("claimer"),
1168                    If(VersionAtLeast(1), &[Ext("profession")]),
1169                ],
1170            ),
1171        ],
1172    ),
1173    // `FUN_1400f9a10`.
1174    (
1175        "PlantArea",
1176        &[
1177            WORK,
1178            Block(
1179                "plant",
1180                0,
1181                &[
1182                    KEYED_CITIZENS,
1183                    List("cells", &[Bool("done")]),
1184                    Ext("description"),
1185                ],
1186            ),
1187        ],
1188    ),
1189    // `FUN_1400fa420`.
1190    (
1191        "PlantOrchard",
1192        &[
1193            WORK,
1194            Block(
1195                "plant",
1196                0,
1197                &[
1198                    KEYED_CITIZENS,
1199                    List("cells", &[Bool("done")]),
1200                    Ext("description"),
1201                ],
1202            ),
1203        ],
1204    ),
1205    // `FUN_1400fadd0`.
1206    (
1207        "TendArea",
1208        &[WORK, Block("tend", 0, &[CITIZENS, I32("value_58")])],
1209    ),
1210    // `FUN_1400fc180`; `FUN_14002d200`: citizen → count and description.
1211    (
1212        "TradeWork",
1213        &[
1214            WORK,
1215            Block(
1216                "trade",
1217                0,
1218                &[List(
1219                    "orders",
1220                    &[Ptr("citizen"), I32("count"), Ext("description")],
1221                )],
1222            ),
1223        ],
1224    ),
1225    // `FUN_14004f900`.
1226    (
1227        "MarketLocationComponent",
1228        &[BASE, CONTROLLER, Block("market_location", 0, &[])],
1229    ),
1230    // `FUN_14004f430`.
1231    (
1232        "MarketComponent",
1233        &[RESOURCE_PRODUCE, Block("market", 0, &[])],
1234    ),
1235    // `FUN_14004ee10`: the town hall's nomads.
1236    (
1237        "ImmigrationComponent",
1238        &[
1239            BASE,
1240            CONTROLLER,
1241            Block(
1242                "immigration",
1243                0,
1244                &[
1245                    I32("value_50"),
1246                    List("nomads", &[Ptr("entity")]),
1247                    Time("time_48"),
1248                ],
1249            ),
1250        ],
1251    ),
1252    // `FUN_1400f2820`: crop harvest.
1253    (
1254        "HarvestArea",
1255        &[
1256            AUTO_PICKUP_WORK,
1257            Block(
1258                "harvest",
1259                0,
1260                &[KEYED_CITIZENS, List("cells", &[Bool("done")])],
1261            ),
1262        ],
1263    ),
1264    // `FUN_1400f3370`: fruit picking.
1265    (
1266        "HarvestOrchard",
1267        &[
1268            AUTO_PICKUP_WORK,
1269            Block(
1270                "harvest",
1271                0,
1272                &[KEYED_CITIZENS, List("cells", &[Bool("done")])],
1273            ),
1274        ],
1275    ),
1276    // `FUN_1400cd730`: the hunting lodge, the hunter, done (`+0x38`).
1277    (
1278        "HuntAnimal",
1279        &[
1280            ACTION,
1281            Block("hunt", 0, &[Ptr("lodge"), Ptr("hunter"), Bool("done")]),
1282        ],
1283    ),
1284    // `FUN_1400cfd40`.
1285    (
1286        "RemoveAnimal",
1287        &[ACTION, Block("remove", 0, &[Ptr("entity")])],
1288    ),
1289    // `FUN_1400505a0`: the town hall it walks to, its way, `+0x50`, its clock.
1290    (
1291        "NomadComponent",
1292        &[
1293            BASE,
1294            CONTROLLER,
1295            Block(
1296                "nomad",
1297                0,
1298                &[
1299                    Ptr("town_hall"),
1300                    F32s("from", 3),
1301                    F32s("to", 3),
1302                    I32("value_50"),
1303                    Time("time_48"),
1304                ],
1305            ),
1306        ],
1307    ),
1308    // `FUN_140057e60`: the heights kept, as points.
1309    (
1310        "TerraformComponent",
1311        &[
1312            BASE,
1313            Block("terraform", 0, &[List("points", &[F32s("point", 3)])]),
1314        ],
1315    ),
1316    // `FUN_140058240`: the time left (halved when read from version 1).
1317    (
1318        "TombstoneComponent",
1319        &[BASE, Block("tombstone", 2, &[Time("left")])],
1320    ),
1321    // `FUN_1400594e0`.
1322    (
1323        "TornadoComponent",
1324        &[
1325            BASE,
1326            Block(
1327                "tornado",
1328                0,
1329                &[
1330                    F32s("position", 3),
1331                    I32("value_50"),
1332                    List("path", &[F32s("point", 3)]),
1333                    Time("time_28"),
1334                    F32s("value_58", 3),
1335                ],
1336            ),
1337        ],
1338    ),
1339    // `FUN_1400e69f0`: the tutorial and its step.
1340    (
1341        "TutorialInstance",
1342        &[Block("tutorial", 0, &[Ext("tutorial"), I32("step")])],
1343    ),
1344    ("WellComponent", &[BASE, CONTROLLER, Block("well", 0, &[])]),
1345    // `FUN_14004b810`.
1346    (
1347        "HealerComponent",
1348        &[BASE, CONTROLLER, Block("healer", 0, &[])],
1349    ),
1350    // `FUN_14011dab0`.
1351    (
1352        "ClearResourceComponent",
1353        &[BASE, CONTROLLER, Block("clear", 0, &[])],
1354    ),
1355    // `FUN_140044d70`: the students, patients or worshippers.
1356    (
1357        "AttendanceComponent",
1358        &[
1359            BASE,
1360            CONTROLLER,
1361            Block("attendance", 0, &[List("visitors", &[Ptr("citizen")])]),
1362        ],
1363    ),
1364    // `FUN_140045330`: on (`+0x28`), its pile cells.
1365    (
1366        "AutoPickupComponent",
1367        &[
1368            BASE,
1369            Block(
1370                "pickup",
1371                0,
1372                &[Bool("on"), I32("cell_count"), I32sBy("cells", "cell_count")],
1373            ),
1374        ],
1375    ),
1376    // `FUN_140124a30`.
1377    (
1378        "FloorComponent",
1379        &[BASE, Block("floor", 0, &[Bool("active")])],
1380    ),
1381    // `FUN_14004a9d0`.
1382    (
1383        "FruitComponent",
1384        &[
1385            BASE,
1386            Block(
1387                "fruit",
1388                0,
1389                &[Ptr("orchard"), F32("value_30"), Bool("flag_34")],
1390            ),
1391        ],
1392    ),
1393    // `FUN_14004fbc0`.
1394    ("MineComponent", &[BASE, Block("mine", 0, &[])]),
1395    // `FUN_14012ad50`: the parts and the stages (`+0x58`).
1396    (
1397        "MultiObjectComponent",
1398        &[
1399            BASE,
1400            CONTROLLER,
1401            Block("multi", 0, &[List("parts", &[Ptr("part")]), I32("stages")]),
1402        ],
1403    ),
1404    // `FUN_140055b90`.
1405    ("QuarryComponent", &[BASE, Block("quarry", 0, &[])]),
1406    // `FUN_14012e800` (version 2).
1407    (
1408        "RawMaterialComponent",
1409        &[
1410            BASE,
1411            Block(
1412                "raw",
1413                2,
1414                &[
1415                    I32("count"),
1416                    Time("age"),
1417                    If(VersionAtLeast(2), &[Ext("profession")]),
1418                ],
1419            ),
1420        ],
1421    ),
1422    // `FUN_14011e2d0`: the selector (`+0x50`), the recipes' weights, productions (`+0x68`).
1423    (
1424        "ConsumeProduceComponent",
1425        &[
1426            RESOURCE_PRODUCE,
1427            Block(
1428                "consume_produce",
1429                1,
1430                &[
1431                    I32("selector"),
1432                    I32("weight_count"),
1433                    F32sBy("weights", "weight_count"),
1434                    I32("produced"),
1435                ],
1436            ),
1437        ],
1438    ),
1439    // `FUN_1400cab30`.
1440    (
1441        "AnimateNaturalResource",
1442        &[
1443            ACTION,
1444            Block("animate", 0, &[Ptr("entity"), I32("clip"), Bool("flag_2c")]),
1445        ],
1446    ),
1447    // `FUN_1400cbd60`.
1448    (
1449        "CreateNaturalResource",
1450        &[
1451            ACTION,
1452            Block(
1453                "create_natural",
1454                1,
1455                &[
1456                    Ext("template"),
1457                    I32("cell"),
1458                    If(VersionAtLeast(1), &[Ptr("occupant")]),
1459                ],
1460            ),
1461        ],
1462    ),
1463    // `FUN_1400cc400` (version 3).
1464    (
1465        "CreateRawMaterial",
1466        &[
1467            ACTION,
1468            Block(
1469                "create_raw",
1470                3,
1471                &[
1472                    Ext("template"),
1473                    Ptr("citizen"),
1474                    I32("cell"),
1475                    F32("factor"),
1476                    Bool("flag_50"),
1477                    I32("batch"),
1478                    Bool("flag_51"),
1479                    If(VersionAtLeast(1), &[Ptr("workplace")]),
1480                    If(VersionAtLeast(2), &[U32("value_48")]),
1481                    If(VersionAtLeast(3), &[Ptr("value_38")]),
1482                ],
1483            ),
1484        ],
1485    ),
1486    // `FUN_1400ccfe0`.
1487    (
1488        "DropOffRawMaterial",
1489        &[
1490            ACTION,
1491            Block(
1492                "drop_off",
1493                0,
1494                &[
1495                    Ptr("citizen"),
1496                    Ptr("storage"),
1497                    Ext("material"),
1498                    Bool("redirect"),
1499                    I32("count"),
1500                ],
1501            ),
1502        ],
1503    ),
1504    // `FUN_1400ceeb0`.
1505    (
1506        "PickupRawMaterial",
1507        &[ACTION, Block("pickup", 0, &[Ptr("citizen"), Ptr("entity")])],
1508    ),
1509    // `FUN_1400cf5a0`.
1510    (
1511        "PickupStoredRawMaterial",
1512        &[
1513            ACTION,
1514            Block(
1515                "pickup_stored",
1516                0,
1517                &[
1518                    Ptr("citizen"),
1519                    Ptr("storage"),
1520                    List("items", &[Ext("material"), I32("count")]),
1521                    Bool("flag_40"),
1522                ],
1523            ),
1524        ],
1525    ),
1526    // `FUN_1400d0120`.
1527    (
1528        "RemoveNaturalResource",
1529        &[
1530            ACTION,
1531            Block(
1532                "remove",
1533                0,
1534                &[Ptr("citizen"), Ptr("entity"), Bool("flag_30")],
1535            ),
1536        ],
1537    ),
1538];
1539
1540/// The schema for a type id: its name and steps.
1541/// An entity list (`FUN_140008150` and the inline copies): a count, then the pointers.
1542const fn ptr_list(name: &'static str) -> Op {
1543    List(name, &[Ptr("entity")])
1544}
1545
1546// +0x88
1547/// The game's achievement instances.
1548///
1549/// Original: `Achievement::AchievementManager::Persist`, Runtime `0x18009f4a0`.
1550pub const ACHIEVEMENTS: Op = Block("achievements", 0, &[List("instances", &[Ptr("instance")])]);
1551
1552// Game +0xf0
1553/// The town manager (version 6), `Game`: the registries of buildings and people by kind, the
1554/// professions' workers, the stock targets and the statistics.
1555///
1556/// Original: `FUN_140094c30`.
1557pub const TOWN: Op = Block(
1558    "town",
1559    6,
1560    &[
1561        ptr_list("storages"),
1562        ptr_list("markets"),
1563        ptr_list("trading_posts"),
1564        ptr_list("residences"),
1565        ptr_list("healers"),
1566        ptr_list("citizens"),
1567        // `FUN_140018450`: profession → workplaces.
1568        List("workplaces", &[Ext("profession"), ptr_list("entities")]),
1569        ptr_list("pastures"),
1570        ptr_list("cemeteries"),
1571        ptr_list("happiness"),
1572        ptr_list("attendance"),
1573        ptr_list("livestock"),
1574        ptr_list("herds"),
1575        If(VersionAtLeast(1), &[ptr_list("diseases")]),
1576        ptr_list("populations"),
1577        ptr_list("sites"),
1578        I32("known_material_count"),
1579        I32sBy("known_materials", "known_material_count"),
1580        I32("known_count"),
1581        I32sBy("known", "known_count"),
1582        Time("commute_time"),
1583        Time("time_198"),
1584        List("history", &[I32("count"), I32sBy("values", "count")]),
1585        If(
1586            VersionBelow(2),
1587            &[List("legacy_counts", &[Ext("description"), I32("count")])],
1588        ),
1589        // `FUN_1400186e0`: description → entities.
1590        If(
1591            VersionAtLeast(2),
1592            &[List("zones", &[Ext("description"), ptr_list("entities")])],
1593        ),
1594        ptr_list("wells"),
1595        List("water_sources", &[I32("a"), I32("b")]),
1596        If(
1597            VersionAtLeast(5),
1598            &[List(
1599                "values_140",
1600                &[I32("count"), I32sBy("values", "count")],
1601            )],
1602        ),
1603        If(
1604            VersionBelow(4),
1605            &[
1606                U32s("legacy_a", 13),
1607                U32s("legacy_b", 13),
1608                U32s("legacy_c", 13),
1609                Times("legacy_times", 13),
1610                U32s("legacy_d", 9),
1611                U32s("legacy_e", 9),
1612            ],
1613        ),
1614        If(
1615            VersionAtLeast(4),
1616            &[
1617                List(
1618                    "stock",
1619                    &[
1620                        I32("count"),
1621                        I32("previous"),
1622                        I32("limit"),
1623                        I32("used"),
1624                        I32("made"),
1625                        Time("cooldown"),
1626                    ],
1627                ),
1628                List(
1629                    "capacity_records",
1630                    &[I32("used"), I32("capacity"), Time("cooldown")],
1631                ),
1632            ],
1633        ),
1634        F32("visitor_risk"),
1635        Time("visitor_time"),
1636        If(VersionAtLeast(2), &[I32("trade_value")]),
1637        If(VersionAtLeast(3), &[I32("nomads_admitted")]),
1638    ],
1639);
1640
1641// +0x30; +0x40..; +0x120; +0x00..+0x08; +0x10; Game +0x118
1642/// The job manager (version 3): the table from id to work, the 14 category sets (13 before version
1643/// 3), the work by profession, then the last id, the next priority and the pass. The priority
1644/// index is rebuilt on load from each work's priority.
1645///
1646/// Original: `FUN_1400fe710`, `FUN_14002df50`; the category sets by `FUN_14002d9c0`.
1647pub const JOBS: Op = Block(
1648    "jobs",
1649    3,
1650    &[
1651        List("works", &[I32("key"), Ptr("work")]),
1652        List("category_1", &[I32("key"), Ptr("work")]),
1653        List("category_2", &[I32("key"), Ptr("work")]),
1654        List("category_3", &[I32("key"), Ptr("work")]),
1655        List("category_4", &[I32("key"), Ptr("work")]),
1656        List("category_5", &[I32("key"), Ptr("work")]),
1657        List("category_6", &[I32("key"), Ptr("work")]),
1658        List("category_7", &[I32("key"), Ptr("work")]),
1659        List("category_8", &[I32("key"), Ptr("work")]),
1660        List("category_9", &[I32("key"), Ptr("work")]),
1661        List("category_10", &[I32("key"), Ptr("work")]),
1662        List("category_11", &[I32("key"), Ptr("work")]),
1663        List("category_12", &[I32("key"), Ptr("work")]),
1664        List("category_13", &[I32("key"), Ptr("work")]),
1665        If(
1666            VersionAtLeast(3),
1667            &[List("category_14", &[I32("key"), Ptr("work")])],
1668        ),
1669        List(
1670            "by_profession",
1671            &[Ext("profession"), List("work", &[I32("key"), Ptr("work")])],
1672        ),
1673        I32("next_id"),
1674        I32("next_priority"),
1675        U32("pass"),
1676    ],
1677);
1678
1679/// The tool manager (version 2).
1680///
1681/// Original: `FUN_140107df0`.
1682pub const TOOLS: Op = Block(
1683    "tools",
1684    2,
1685    &[
1686        Bool("flag_30"),
1687        I32("value_34"),
1688        F32("value_3c"),
1689        F32("value_40"),
1690        F32("value_44"),
1691        // `FUN_14002ec40`.
1692        List(
1693            "descriptions",
1694            &[
1695                Ext("description"),
1696                I32("value_8"),
1697                I32("value_c"),
1698                Bool("flag_10"),
1699            ],
1700        ),
1701        // `FUN_14002edd0`.
1702        If(
1703            VersionAtLeast(1),
1704            &[List(
1705                "entities",
1706                &[
1707                    Ptr("entity"),
1708                    I32("value_8"),
1709                    I32("value_c"),
1710                    Bool("flag_10"),
1711                ],
1712            )],
1713        ),
1714        If(
1715            VersionAtLeast(2),
1716            &[List(
1717                "points",
1718                &[
1719                    F32s("position", 3),
1720                    F32("value_c"),
1721                    F32("value_10"),
1722                    F32("value_14"),
1723                    Bool("flag_18"),
1724                ],
1725            )],
1726        ),
1727    ],
1728);
1729
1730/// The calendar block of the game state: a value and the time.
1731///
1732/// Original: written by `FUN_14009af60`.
1733pub const CALENDAR: Op = Block("calendar", 0, &[I32("value"), Time("time")]);
1734
1735// Game +0x138
1736/// The event log (version 1), `Game`.
1737///
1738/// Original: `FUN_14009a790`.
1739pub const EVENT_LOG: Op = Block(
1740    "event_log",
1741    1,
1742    &[
1743        Bool("notify_0"),
1744        Bool("notify_1"),
1745        Bool("notify_2"),
1746        Bool("notify_3"),
1747        Bool("notify_4"),
1748        Bool("notify_5"),
1749        List(
1750            "entries",
1751            &[
1752                U32("color"),
1753                Str("text"),
1754                I32("kind"),
1755                I32("severity"),
1756                I32("cell"),
1757                I32("category"),
1758                I32("icon"),
1759            ],
1760        ),
1761    ],
1762);
1763
1764/// The environment: climate and month descriptions, weather state, then one entry per weather
1765/// effect of the climate (the count is not written).
1766///
1767/// Original: `FUN_140099160`.
1768pub const ENVIRONMENT: Op = Block(
1769    "environment",
1770    0,
1771    &[
1772        Ext("climate"),
1773        Ext("month"),
1774        I32("state"),
1775        I32("level"),
1776        F32("temperature"),
1777        F32s("season", 4),
1778        F32("baseline"),
1779        F32("baseline_target"),
1780        F32("bias"),
1781        F32("bias_target"),
1782        I32("slice"),
1783        Rest(
1784            "effects",
1785            &[
1786                Bool("active"),
1787                If(Flag("active"), &[F32("direction"), F32("intensity")]),
1788            ],
1789        ),
1790    ],
1791);
1792
1793/// The minimap: the two images' texels, then the image size.
1794///
1795/// Original: `FUN_1400aacc0`.
1796pub const MINIMAP: Op = Block(
1797    "minimap",
1798    0,
1799    &[
1800        I32("count_28"),
1801        I32sBy("image_28", "count_28"),
1802        I32("count_18"),
1803        I32sBy("image_18", "count_18"),
1804        I32("size"),
1805    ],
1806);
1807
1808/// The class name and the schema of an object type, by type id.
1809pub fn schema_for(ty: u32) -> Option<(&'static str, &'static [Op])> {
1810    static BY_ID: OnceLock<HashMap<u32, (&'static str, &'static [Op])>> = OnceLock::new();
1811    BY_ID
1812        .get_or_init(|| {
1813            SCHEMAS
1814                .iter()
1815                .map(|&(name, ops)| (type_id(name), (name, ops)))
1816                .collect()
1817        })
1818        .get(&ty)
1819        .copied()
1820}
1821
1822/// A new object's fields for type `name`: every step's default (zero, false, none, empty), in
1823/// the schema's order, each block at its version. The writer fills them in.
1824pub fn default_fields(name: &str) -> Option<crate::savegame::Fields> {
1825    let (_, ops) = schema_for(type_id(name))?;
1826    let mut out = crate::savegame::Fields::default();
1827    defaults_into(ops, 0, &mut out);
1828    Some(out)
1829}
1830
1831/// A block's fields at their defaults (the town, the managers), as [`default_fields`] makes
1832/// an object's.
1833pub fn default_block(ops: &[Op], version: u32) -> crate::savegame::Fields {
1834    let mut out = crate::savegame::Fields::default();
1835    defaults_into(ops, version, &mut out);
1836    out
1837}
1838
1839fn defaults_into(ops: &[Op], version: u32, out: &mut crate::savegame::Fields) {
1840    use crate::savegame::{Fields, Value};
1841    for op in ops {
1842        let (name, value) = match *op {
1843            Bool(n) => (n, Value::Bool(false)),
1844            I8(n) => (n, Value::I8(0)),
1845            I16(n) => (n, Value::I16(0)),
1846            U16(n) => (n, Value::U16(0)),
1847            I32(n) => (n, Value::I32(0)),
1848            U32(n) => (n, Value::U32(0)),
1849            Time(n) => (n, Value::I64(0)),
1850            F32(n) => (n, Value::F32(0.0)),
1851            Times(n, k) => (n, Value::I64s(vec![0; k])),
1852            F32s(n, k) => (n, Value::F32s(vec![0.0; k])),
1853            U32s(n, k) => (n, Value::U32s(vec![0; k])),
1854            I32sBy(n, _) => (n, Value::U32s(Vec::new())),
1855            F32sBy(n, _) => (n, Value::F32s(Vec::new())),
1856            Str(n) | WStr(n) => (n, Value::Str(String::new())),
1857            Ext(n) => (n, Value::Ext(u32::MAX)),
1858            Ptr(n) => (n, Value::Ptr(None)),
1859            List(n, _) | Rest(n, _) => (n, Value::List(Vec::new())),
1860            Block(n, v, inner) => {
1861                let mut f = Fields::default();
1862                defaults_into(inner, v, &mut f);
1863                (n, Value::Block(v, f))
1864            }
1865            Sub(inner) => {
1866                defaults_into(inner, version, out);
1867                continue;
1868            }
1869            If(cond, inner) => {
1870                let take = match cond {
1871                    VersionAtLeast(v) => version >= v,
1872                    VersionBelow(v) => version < v,
1873                    Flag(_) => false,
1874                };
1875                if take {
1876                    defaults_into(inner, version, out);
1877                }
1878                continue;
1879            }
1880        };
1881        out.0.push((name, value));
1882    }
1883}
1884
1885/// Every object type the schemas cover.
1886pub fn type_names() -> impl Iterator<Item = &'static str> {
1887    SCHEMAS.iter().map(|&(name, _)| name)
1888}