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}