Skip to main content

rakata_generics/git/
store.rs

1//! Store placements in a GIT.
2
3use rakata_core::ResRef;
4use rakata_formats::{GffLocalizedString, GffModel, GffStruct};
5
6use super::item::SavedItem;
7use super::GitObjectPlacement;
8/// A store instance placed in the area (struct type 0xb).
9///
10/// Stores use `ResRef` (not `TemplateResRef`) for the template reference.
11#[derive(Debug, Clone, PartialEq, GffModel)]
12#[gff_manual_element]
13pub struct GitStore {
14    /// The block both forms carry, which the list declares as its element.
15    ///
16    /// No `#[gff]`: the arm contributes its parts to the split, and the
17    /// element is this block, so declaring it here too would make one label
18    /// two declarations. The arm's own codec reads and writes it.
19    pub common: GitObjectPlacement,
20    /// Template resref (`ResRef`). Note the non-standard field name.
21    #[gff(ResRef, unexamined)]
22    pub resref: ResRef,
23}
24
25// =========================================================================
26// The saved form
27// =========================================================================
28
29// Merchant stores as stored inside a save game.
30//
31// A static `.git` store is a placement referencing a `.utm`. A savegame
32// store carries the whole object inline, stock included, which is what makes
33// a merchant's current inventory survive a reload after you have bought half
34// of it.
35//
36// Unlike a placeable's container, a store always writes its `ItemList`:
37// every store in the fixture saves has the field, and none of them is empty.
38// Store items also carry an `Infinite` flag that no other list writes; see
39// [`SavedItem`](crate::saved_item::SavedItem).
40//
41// ## What is not modelled
42//
43// `ActionList`, `VarTable` and `SWVarTable` are live runtime state whose
44// layouts are only partly audited, and they are skipped for the same reason
45// [`SavedCreature`](crate::git::creature::SavedCreature) skips them.
46
47/// A merchant store as stored inside a save game's module `GIT`.
48#[derive(Debug, Clone, PartialEq, GffModel)]
49#[gff_manual_element]
50pub struct SavedStore {
51    /// The block both forms carry, which the list declares as its element.
52    ///
53    /// No `#[gff]`: the arm contributes its parts to the split, and the
54    /// element is this block, so declaring it here too would make one label
55    /// two declarations. The arm's own codec reads and writes it.
56    pub common: GitObjectPlacement,
57    /// Whether the store buys, sells, or both (`BuySellFlag`).
58    #[gff(BuySellFlag, constructed = 3)]
59    pub buy_sell_flag: u8,
60    /// Accepts scripted commands (`Commandable`).
61    #[gff(Commandable, unexamined)]
62    pub commandable: bool,
63    /// Current stock (`ItemList`).
64    #[gff(ItemList, not_a_constant, list = SavedItem, element_extra = GitObjectPlacement, element_id = positional)]
65    pub items: Vec<SavedItem>,
66    /// Displayed name (`LocName`).
67    #[gff(LocName, stamped)]
68    pub loc_name: GffLocalizedString,
69    /// Percentage taken when buying from the player (`MarkDown`).
70    #[gff(MarkDown, stamped)]
71    pub mark_down: i32,
72    /// Percentage added when selling to the player (`MarkUp`).
73    #[gff(MarkUp, stamped)]
74    pub mark_up: i32,
75    /// `OnOpenStore`.
76    #[gff(OnOpenStore, stamped)]
77    pub on_open_store: ResRef,
78    /// Object tag (`Tag`).
79    #[gff(Tag, stamped)]
80    pub tag: String,
81}
82
83impl GitStore {
84    /// Reads one element, with the block the list declares as its element.
85    pub fn read_element(structure: &GffStruct) -> Self {
86        Self {
87            common: GitObjectPlacement::read_declared(structure),
88            ..Self::read_declared(structure)
89        }
90    }
91
92    /// Writes one element, the common block included.
93    pub fn write_element(&self, structure: &mut GffStruct) {
94        self.write_declared(structure);
95        self.common.write_declared(structure);
96    }
97}
98
99impl SavedStore {
100    /// Reads one element, with the block the list declares as its element.
101    pub fn read_element(structure: &GffStruct) -> Self {
102        Self {
103            common: GitObjectPlacement::read_declared(structure),
104            ..Self::read_declared(structure)
105        }
106    }
107
108    /// Writes one element, the common block included.
109    pub fn write_element(&self, structure: &mut GffStruct) {
110        self.write_declared(structure);
111        self.common.write_declared(structure);
112    }
113}
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    /// One value written into an element struct carrying the list's own id.
120    macro_rules! written {
121        ($value:expr, $id:expr) => {{
122            let mut element = GffStruct::new($id);
123            $value.write_element(&mut element);
124            element
125        }};
126    }
127
128    #[test]
129    fn round_trips_through_a_list_element() {
130        let store = SavedStore {
131            tag: "dan_merchant".to_string(),
132            common: GitObjectPlacement {
133                x_position: 3.0,
134                object_id: Some(crate::shared::ObjectId::new(0x8000_0009)),
135                ..GitObjectPlacement::default()
136            },
137            buy_sell_flag: 3,
138            mark_up: 100,
139            mark_down: 50,
140            items: vec![SavedItem {
141                tag: "g_w_blstrpstl001".to_string(),
142                stack_size: 1,
143                infinite: Some(true),
144                ..SavedItem::default()
145            }],
146            on_open_store: ResRef::new("k_store_open").expect("valid resref"),
147            ..SavedStore::default()
148        };
149
150        let parsed = SavedStore::read_element(&written!(store, 0));
151
152        assert_eq!(parsed, store);
153    }
154
155    #[test]
156    fn stock_keeps_its_infinite_flags() {
157        let store = SavedStore {
158            items: vec![SavedItem {
159                infinite: Some(true),
160                ..SavedItem::default()
161            }],
162            ..SavedStore::default()
163        };
164
165        let parsed = SavedStore::read_element(&written!(store, 0));
166
167        assert_eq!(parsed.items[0].infinite, Some(true));
168    }
169}