Skip to main content

rakata_generics/git/
encounter.rs

1//! Encounter placements in a GIT, with their geometry and spawn points.
2
3use rakata_core::ResRef;
4use rakata_formats::{GffLocalizedString, GffModel};
5
6use crate::shared::ObjectId;
7
8/// A vertex in an encounter geometry polygon (`X`/`Y`/`Z`).
9#[derive(Debug, Clone, PartialEq, GffModel)]
10pub struct GitEncounterPoint {
11    /// X coordinate (`X`).
12    #[gff(X, unexamined)]
13    pub x: f32,
14    /// Y coordinate (`Y`).
15    #[gff(Y, unexamined)]
16    pub y: f32,
17    /// Z coordinate (`Z`).
18    #[gff(Z, unexamined)]
19    pub z: f32,
20}
21
22/// One entry in an encounter's spawn table (`SpawnList[]`).
23///
24/// Declared and not modelled, like the rest of an encounter's own state. The
25/// element labels are named here because the engine's read names them; a list
26/// declared with no element schema would leave them reading as unrecognised.
27#[derive(Debug, Clone, PartialEq, GffModel)]
28#[gff_entry(SpawnResRef, wire = ResRef, live)]
29#[gff_entry(SpawnCR, wire = f32, live)]
30pub struct GitEncounterSpawn {}
31
32/// A spawn point within an encounter (`X`/`Y`/`Z`/`Orientation`).
33#[derive(Debug, Clone, PartialEq, GffModel)]
34pub struct GitSpawnPoint {
35    /// X coordinate (`X`).
36    #[gff(X, unexamined)]
37    pub x: f32,
38    /// Y coordinate (`Y`).
39    #[gff(Y, unexamined)]
40    pub y: f32,
41    /// Z coordinate (`Z`).
42    #[gff(Z, unexamined)]
43    pub z: f32,
44    /// Facing angle (`Orientation`).
45    #[gff(Orientation, unexamined)]
46    pub orientation: f32,
47}
48
49/// An encounter instance placed in the area (struct type 7).
50#[derive(Debug, Clone, PartialEq, GffModel)]
51// A saved encounter's own field set, declared and not modelled.
52//
53// `ReadEncounterFromGff` reads all of these unconditionally, from both its
54// callers, and there is no branch that skips session state for a templated
55// GIT. A static module GIT simply never has the labels written, so they take
56// their defaults like any other absent field. That is why they are `Live`
57// here rather than being filed under the placement's own flag.
58//
59// Five are not: `ActionList`, `AreaListSize`, `Commandable`, `SWVarTable` and
60// `VarTable`. The read list does not mention them, and a list of what is read
61// is not a claim about what is not. They keep the state meaning nobody has
62// looked.
63//
64// The label set was enumerated from the local save backup, which is modded:
65// 645 encounter elements spread over the 260 save GITs that carry any. The
66// committed fixtures carry none. The liveness is not from that corpus.
67#[gff_entry(Active, wire = u8, live)]
68#[gff_entry(AreaListMaxSize, wire = i32, live)]
69#[gff_entry(AreaListSize, wire = i32, unexamined)]
70#[gff_entry(AreaPoints, wire = f32, live)]
71#[gff_entry(Commandable, wire = u8, unexamined)]
72#[gff_entry(CurrentSpawns, wire = i32, live)]
73#[gff_entry(CustomScriptId, wire = i32, live)]
74#[gff_entry(Difficulty, wire = i32, live)]
75#[gff_entry(DifficultyIndex, wire = i32, live)]
76#[gff_entry(Exhausted, wire = u8, live)]
77#[gff_entry(Faction, wire = u32, live)]
78#[gff_entry(HeartbeatDay, wire = u32, live)]
79#[gff_entry(HeartbeatTime, wire = u32, live)]
80#[gff_entry(LastEntered, wire = u32, live)]
81#[gff_entry(LastLeft, wire = u32, live)]
82#[gff_entry(LastSpawnDay, wire = u32, live)]
83#[gff_entry(LastSpawnTime, wire = u32, live)]
84#[gff_entry(LocalizedName, wire = GffLocalizedString, live)]
85#[gff_entry(MaxCreatures, wire = i32, live)]
86#[gff_entry(NumberSpawned, wire = i32, live)]
87#[gff_entry(OnEntered, wire = ResRef, live)]
88#[gff_entry(OnExhausted, wire = ResRef, live)]
89#[gff_entry(OnExit, wire = ResRef, live)]
90#[gff_entry(OnHeartbeat, wire = ResRef, live)]
91#[gff_entry(OnUserDefined, wire = ResRef, live)]
92#[gff_entry(PlayerOnly, wire = u8, live)]
93#[gff_entry(RecCreatures, wire = i32, live)]
94#[gff_entry(Reset, wire = u8, live)]
95#[gff_entry(ResetTime, wire = i32, live)]
96#[gff_entry(Respawns, wire = i32, live)]
97#[gff_entry(SpawnOption, wire = i32, live)]
98#[gff_entry(SpawnPoolActive, wire = f32, live)]
99#[gff_entry(Started, wire = u8, live)]
100#[gff_entry(Tag, wire = String, live)]
101#[gff_entry(ActionList, container = list, unexamined)]
102#[gff_entry(AreaList, container = list, live)]
103#[gff_entry(CreatureList, container = list, live)]
104#[gff_entry(VarTable, container = list, unexamined)]
105#[gff_entry(SWVarTable, container = nested, unexamined)]
106#[gff_entry(SpawnList, list = GitEncounterSpawn, element_id = positional, live)]
107pub struct GitEncounter {
108    /// The encounter's boundary (`Geometry`).
109    #[gff(
110        Geometry,
111        not_a_constant,
112        list = GitEncounterPoint,
113        element_id = unenforced(
114            1,
115            "neither read route asks an element for its type: LoadEncounterGeometry \
116             walks the list by position and takes every field by label, and it is \
117             reached the same way through ReadEncounterFromGff and through \
118             LoadEncounters' override branch. A module's static .git writes 1 here \
119             and a savegame writes each element's position, and neither is wrong"
120        )
121    )]
122    pub geometry: Vec<GitEncounterPoint>,
123    /// Where its creatures appear (`SpawnPointList`).
124    #[gff(
125        SpawnPointList,
126        not_a_constant,
127        list = GitSpawnPoint,
128        element_id = unenforced(
129            2,
130            "LoadEncounterSpawnPoints walks by position and takes every field by \
131             label, on both routes into it, so nothing tests an element's id. The \
132             two writers spend it differently for the same reason its sibling \
133             Geometry does"
134        )
135    )]
136    pub spawn_points: Vec<GitSpawnPoint>,
137    /// Runtime object ID (`ObjectId`). Save-game only.
138    ///
139    /// The label belongs to the area-level list dispatcher on both sides, not
140    /// to the per-type encounter functions. `SaveEncounters` writes it off the
141    /// live object's id before handing the rest of the element to
142    /// `SaveEncounter`, which never touches it, mirroring the read. That is
143    /// why it is here despite the per-type save function not writing it, and
144    /// why every encounter element in the local save backup carries it with a
145    /// non-zero value.
146    /// Runtime object id (`ObjectId`), which only a saved form carries.
147    ///
148    /// The area-level dispatcher reads it once per element ahead of the
149    /// static-versus-saved branch, so the engine reads it on both forms and
150    /// the liveness below is right. What it does not do is author it: every
151    /// element the engine saves carries one and no element the toolset shipped
152    /// does. Held as the absence so writing a static placement back does not
153    /// invent a runtime id for it.
154    #[gff(ObjectId, stamped = ObjectId::INVALID, optional = ObjectId)]
155    pub object_id: Option<ObjectId>,
156    /// Template resref (`TemplateResRef`), which only a static placement has.
157    ///
158    /// The mirror image of `ObjectId` above, on the one list with no arms to
159    /// keep the two forms apart: every one of the 51 install elements carries
160    /// it and none of the 645 saved ones do. A saved encounter is the whole
161    /// object rather than a reference to a blueprint, so there is nothing for
162    /// it to name.
163    #[gff(TemplateResRef, unexamined, optional = ResRef)]
164    pub template_resref: Option<ResRef>,
165    /// X position (`XPosition`).
166    #[gff(XPosition, constructed)]
167    pub x_position: f32,
168    /// Y position (`YPosition`).
169    #[gff(YPosition, constructed)]
170    pub y_position: f32,
171    /// Z position (`ZPosition`).
172    #[gff(ZPosition, constructed)]
173    pub z_position: f32,
174}