Skip to main content

rakata_generics/git/
area_properties.rs

1//! Area-level properties and the map block, both single structs on the GIT root.
2
3use rakata_formats::GffModel;
4
5/// Area-level properties (music, ambient sound, stealth XP, fog).
6///
7/// This is a single `Struct` in the GIT root (field label `AreaProperties`).
8#[derive(Debug, Clone, PartialEq, GffModel)]
9#[gff_entry(EnvAudio, wire = i32, read_only_dead = "the only EnvAudio the engine reads is the ARE per-room field, a different field sharing the label", unexamined)]
10pub struct GitAreaProperties {
11    /// Unescapable flag (`Unescapable`).
12    #[gff(Unescapable, unexamined)]
13    pub unescapable: bool,
14    /// Stealth XP maximum (`StealthXPMax`).
15    #[gff(
16        StealthXPMax,
17        read_only_dead = "the save writer nests it here but the reader pulls it from the GIT's top level instead, so it always resolves to whatever the object already held",
18        not_a_constant
19    )]
20    pub stealth_xp_max: u32,
21    /// Current stealth XP count (`StealthXPCurrent`). Save-game state. Natively clamped by the engine to not exceed `StealthXPMax`.
22    #[gff(
23        StealthXPCurrent,
24        read_only_dead = "the save writer nests it here but the reader pulls it from the GIT's top level instead, so it always resolves to whatever the object already held",
25        not_a_constant
26    )]
27    pub stealth_xp_current: u32,
28    /// Stealth XP loss per detection (`StealthXPLoss`).
29    #[gff(
30        StealthXPLoss,
31        read_only_dead = "the save writer nests it here but the reader pulls it from the GIT's top level instead, so it always resolves to whatever the object already held",
32        not_a_constant
33    )]
34    pub stealth_xp_loss: u32,
35    /// Stealth XP enabled flag (`StealthXPEnabled`).
36    #[gff(
37        StealthXPEnabled,
38        read_only_dead = "the save writer nests it here but the reader pulls it from the GIT's top level instead, so it always resolves to whatever the object already held",
39        not_a_constant
40    )]
41    pub stealth_xp_enabled: bool,
42    /// Transition pending flag (`TransPending`). Save-game state.
43    #[gff(
44        TransPending,
45        read_only_dead = "written both here and at the GIT's top level, and only the top-level copy is ever consulted",
46        not_a_constant
47    )]
48    pub trans_pending: bool,
49    /// Pending transition next ID (`TransPendNextID`). Save-game state.
50    #[gff(
51        TransPendNextID,
52        read_only_dead = "written both here and at the GIT's top level, and only the top-level copy is ever consulted",
53        not_a_constant
54    )]
55    pub trans_pend_next_id: u8,
56    /// Pending transition current ID (`TransPendCurrID`). Save-game state.
57    #[gff(
58        TransPendCurrID,
59        read_only_dead = "written both here and at the GIT's top level, and only the top-level copy is ever consulted",
60        not_a_constant
61    )]
62    pub trans_pend_curr_id: u8,
63    /// Sun/fog color packed as RGBA (`SunFogColor`).
64    #[gff(
65        SunFogColor,
66        read_only_dead = "the save writer nests it here but the reader pulls it from the GIT's top level instead, so it always resolves to whatever the object already held",
67        not_a_constant
68    )]
69    pub sun_fog_color: u32,
70    /// Music delay in ms (`MusicDelay`).
71    #[gff(MusicDelay, constructed = 5000)]
72    pub music_delay: i32,
73    /// Daytime music track ID (`MusicDay`). References ambientmusic.2da.
74    #[gff(MusicDay, constructed = 2)]
75    pub music_day: i32,
76    /// Night-time music track ID (`MusicNight`). References ambientmusic.2da.
77    #[gff(MusicNight, constructed = 3)]
78    pub music_night: i32,
79    /// Battle music track ID (`MusicBattle`). References ambientmusic.2da.
80    #[gff(MusicBattle, constructed = 1)]
81    pub music_battle: i32,
82    /// Daytime ambient sound ID (`AmbientSndDay`). References ambientsound.2da.
83    #[gff(AmbientSndDay, constructed = 1)]
84    pub ambient_snd_day: i32,
85    /// Night-time ambient sound ID (`AmbientSndNight`). References ambientsound.2da.
86    #[gff(AmbientSndNight, constructed = 2)]
87    pub ambient_snd_night: i32,
88    /// Daytime ambient sound volume (`AmbientSndDayVol`). Values outside 0-255 are truncated and wrapped to an 8-bit byte.
89    #[gff(AmbientSndDayVol, constructed)]
90    pub ambient_snd_day_vol: i32,
91    /// Night-time ambient sound volume (`AmbientSndNitVol`). Values outside 0-255 are truncated and wrapped to an 8-bit byte.
92    #[gff(AmbientSndNitVol, constructed)]
93    pub ambient_snd_nit_vol: i32,
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99    use rakata_formats::schema::{Absent, Resolution};
100    use rakata_formats::GffStruct;
101
102    /// `Default` against the constructor values the schema records.
103    ///
104    /// This table lives under `Git`'s `area_properties`, which is an `Option`
105    /// defaulting to `None`, so the shared guard walking `Git::default()`
106    /// finds nothing written at any of these paths and skips them all. Six
107    /// `Constructed` entries sat here against a derived `Default` of zeroes
108    /// and every run stayed green.
109    #[test]
110    fn default_matches_the_constructed_values_the_schema_records() {
111        let mut written = GffStruct::new(0);
112        GitAreaProperties::default().write_declared(&mut written);
113
114        let mut checked = 0;
115        for entry in GitAreaProperties::SCHEMA {
116            let Absent::Resolves(Resolution::Constructed, declared, _) = entry.absent else {
117                continue;
118            };
119            let field = written
120                .field(entry.label.as_str())
121                .unwrap_or_else(|| panic!("{} is declared and must be written", entry.label));
122            assert_eq!(
123                &declared.get(),
124                field,
125                "{}: the constructor holds one value and Default gives another",
126                entry.label
127            );
128            checked += 1;
129        }
130        assert!(checked > 0, "no constructed entries reached");
131    }
132}