Skip to main content

rakata_generics/git/
waypoint.rs

1//! Waypoint placements in a GIT, and the map note one can carry.
2
3use rakata_core::ResRef;
4use rakata_formats::gff::upsert_field;
5use rakata_formats::gff_label;
6use rakata_formats::{GffLocalizedString, GffModel, GffStruct, GffValue};
7
8use crate::shared::ObjectId;
9/// A waypoint placed in the area (struct type 5).
10///
11/// Waypoints are the one object list with a single form. `LoadWaypoints`
12/// ignores `UseTemplates` and always reads inline data, so there is no
13/// static / saved split here and [`Git::waypoints`](super::Git::waypoints) is a plain [`Vec`].
14///
15/// ## Dead fields this type does not model
16///
17/// `TemplateResRef` and `LinkedTo` are universal in a real install and read by
18/// nothing; `docs/src/formats/gff/utw.md` carries both the engine side and the
19/// corpus figures. `docs/src/architecture.md`'s projection rule uses this
20/// type's `TemplateResRef` as its worked example of prevalence not being a
21/// criterion for modelling.
22///
23/// `Tag`, in contrast, is live and modelled. A waypoint is always inline with
24/// no blueprint behind it, so unlike [`GitDoor`](super::GitDoor) there is no second copy for
25/// the placement's to lose to.
26///
27/// ## The map note is one value, not a flag plus two optionals
28///
29/// The engine gates the note behind `HasMapNote` and then discards the whole
30/// trio unless `MapNote` itself was there, so neither half alone produces a
31/// note and there is no state to model between them.
32///
33/// Read the flag, not the text. Keying the note off `MapNote` alone passes
34/// against the fixture saves, where the two never disagree, and a larger
35/// corpus does not support it. `docs/src/testing.md` carries that as a worked
36/// example of a corpus too small to tell a relationship from a constant.
37#[derive(Debug, Clone, PartialEq, GffModel)]
38#[gff_entry(Appearance, wire = u8, read_only_dead = "a waypoint has no rendered model to select, and no waypoint load path reads the field", unexamined)]
39#[gff_entry(Description, wire = GffLocalizedString, read_only_dead = "a waypoint resolves no template, so this value has no source anywhere rather than one that goes unread", unexamined)]
40#[gff_entry(HasMapNote, wire = u8, constructed)]
41#[gff_entry(LinkedTo, wire = String, read_only_dead = "a waypoint has no transition capability, so there is nothing for a destination tag to name", unexamined)]
42#[gff_entry(TemplateResRef, wire = ResRef, read_only_dead = "no waypoint load path reads it, including the script-spawn fallback, where the resref comes from the script call", unexamined)]
43pub struct GitWaypoint {
44    /// Object tag (`Tag`).
45    #[gff(Tag, stamped)]
46    pub tag: String,
47    /// Displayed name (`LocalizedName`).
48    #[gff(LocalizedName, stamped)]
49    pub localized_name: GffLocalizedString,
50    /// Accepts scripted commands (`Commandable`).
51    /// Whether the object takes scripted commands (`Commandable`).
52    ///
53    /// One of the base-object labels `CSWSObject::LoadObjectState` reads, and
54    /// that read runs only on the save-instance branch, so a static placement
55    /// never reaches it. `SaveObjectState` writes it whenever anything is
56    /// saved, which is why every saved element carries it and no install
57    /// element does. A waypoint is the one owner this reaches, because
58    /// `WaypointList` has no static arm to keep it off.
59    #[gff(Commandable, unexamined, optional = bool)]
60    pub commandable: Option<bool>,
61    /// Map note, when this waypoint carries one.
62    ///
63    /// The two labels sit at this level rather than under one of their own,
64    /// so the member is flattened. Whether it is there at all is a rule no
65    /// attribute states: `git.md` has an absent `MapNote` discarding the
66    /// whole `HasMapNote`/`MapNoteEnabled`/`MapNote` trio, and the flag is
67    /// derived from this member on write so the two cannot disagree.
68    #[gff(flatten = GitMapNote, manual_read, manual_write)]
69    pub map_note: Option<GitMapNote>,
70    /// X orientation (`XOrientation`).
71    #[gff(XOrientation, not_a_constant)]
72    pub x_orientation: f32,
73    /// Y orientation (`YOrientation`).
74    #[gff(YOrientation, not_a_constant)]
75    pub y_orientation: f32,
76    /// Z orientation (`ZOrientation`).
77    /// Z orientation (`ZOrientation`), which only a saved form carries.
78    #[gff(ZOrientation, not_a_constant, optional = f32)]
79    pub z_orientation: Option<f32>,
80    /// Runtime object ID (`ObjectId`).
81    /// Runtime object id (`ObjectId`), which only a saved form carries.
82    ///
83    /// The area-level dispatcher reads it once per element ahead of the
84    /// static-versus-saved branch, so the engine reads it on both forms and
85    /// the liveness below is right. What it does not do is author it: every
86    /// element the engine saves carries one and no element the toolset shipped
87    /// does. Held as the absence so writing a static placement back does not
88    /// invent a runtime id for it.
89    #[gff(ObjectId, stamped = ObjectId::INVALID, optional = ObjectId)]
90    pub object_id: Option<ObjectId>,
91    /// X position (`XPosition`).
92    #[gff(XPosition, stamped)]
93    pub x_position: f32,
94    /// Y position (`YPosition`).
95    #[gff(YPosition, stamped)]
96    pub y_position: f32,
97    /// Z position (`ZPosition`).
98    #[gff(ZPosition, stamped)]
99    pub z_position: f32,
100}
101
102/// The two labels a waypoint's map note carries, at the waypoint's own level.
103#[derive(Debug, Clone, PartialEq, GffModel)]
104pub struct GitMapNote {
105    /// Note text (`MapNote`).
106    #[gff(MapNote, constructed)]
107    pub text: GffLocalizedString,
108    /// Whether the note shows on the map right now (`MapNoteEnabled`).
109    #[gff(MapNoteEnabled, stamped)]
110    pub enabled: bool,
111}
112
113impl GitWaypoint {
114    /// Reads one waypoint, resolving the map-note group.
115    pub fn read_element(structure: &GffStruct) -> Self {
116        let carried = ["MapNote", "MapNoteEnabled"]
117            .iter()
118            .all(|label| structure.field(label).is_some());
119        Self {
120            map_note: carried.then(|| GitMapNote::read_declared(structure)),
121            ..Self::read_declared(structure)
122        }
123    }
124
125    /// Writes one waypoint, deriving the flag from whether the note is there.
126    pub fn write_element(&self, structure: &mut GffStruct) {
127        self.write_declared(structure);
128        upsert_field(
129            structure,
130            gff_label!("HasMapNote"),
131            GffValue::UInt8(u8::from(self.map_note.is_some())),
132        );
133        if let Some(note) = &self.map_note {
134            note.write_declared(structure);
135        }
136    }
137}