1use rakata_core::ResRef;
4use rakata_formats::{
5 gff_schema::{AbsentDefault, FieldLife, FieldSchema, GffType},
6 GffLocalizedString, GffStruct, GffValue,
7};
8
9use super::item::SavedItem;
10use super::object_id_or_invalid;
11use crate::gff_helpers::{
12 get_f32, get_i32, get_locstring, get_resref, get_string, get_u8, upsert_field,
13};
14use crate::shared::ObjectId;
15
16#[derive(Debug, Clone, PartialEq, Default)]
20pub struct GitStore {
21 pub resref: ResRef,
23 pub x_orientation: f32,
25 pub y_orientation: f32,
27 pub z_orientation: f32,
29 pub x_position: f32,
31 pub y_position: f32,
33 pub z_position: f32,
35 pub object_id: ObjectId,
37}
38
39impl GitStore {
40 pub(crate) fn from_gff_struct(s: &GffStruct) -> Self {
41 Self {
42 resref: get_resref(s, "ResRef").unwrap_or_default(),
43 x_orientation: get_f32(s, "XOrientation").unwrap_or(0.0),
44 y_orientation: get_f32(s, "YOrientation").unwrap_or(0.0),
45 z_orientation: get_f32(s, "ZOrientation").unwrap_or(0.0),
46 x_position: get_f32(s, "XPosition").unwrap_or(0.0),
47 y_position: get_f32(s, "YPosition").unwrap_or(0.0),
48 z_position: get_f32(s, "ZPosition").unwrap_or(0.0),
49 object_id: object_id_or_invalid(s),
50 }
51 }
52
53 pub(crate) fn to_gff_struct(&self) -> GffStruct {
54 let mut s = GffStruct::new(11);
55 upsert_field(&mut s, "ResRef", GffValue::ResRef(self.resref));
56 upsert_field(&mut s, "XOrientation", GffValue::Single(self.x_orientation));
57 upsert_field(&mut s, "YOrientation", GffValue::Single(self.y_orientation));
58 upsert_field(&mut s, "ZOrientation", GffValue::Single(self.z_orientation));
59 upsert_field(&mut s, "XPosition", GffValue::Single(self.x_position));
60 upsert_field(&mut s, "YPosition", GffValue::Single(self.y_position));
61 upsert_field(&mut s, "ZPosition", GffValue::Single(self.z_position));
62 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id.get()));
63 s
64 }
65}
66
67pub(crate) static STORE_LIST_CHILDREN: &[FieldSchema] = &[
69 FieldSchema {
70 label: "ObjectId",
71 expected_type: GffType::UInt32,
72 life: FieldLife::Live,
73 required: false,
74 absent: AbsentDefault::Unverified,
75 children: None,
76 constraint: None,
77 },
78 FieldSchema {
79 label: "ResRef",
80 expected_type: GffType::ResRef,
81 life: FieldLife::Live,
82 required: false,
83 absent: AbsentDefault::Unverified,
84 children: None,
85 constraint: None,
86 },
87 FieldSchema {
88 label: "XOrientation",
89 expected_type: GffType::Single,
90 life: FieldLife::Live,
91 required: false,
92 absent: AbsentDefault::Unverified,
93 children: None,
94 constraint: None,
95 },
96 FieldSchema {
97 label: "YOrientation",
98 expected_type: GffType::Single,
99 life: FieldLife::Live,
100 required: false,
101 absent: AbsentDefault::Unverified,
102 children: None,
103 constraint: None,
104 },
105 FieldSchema {
106 label: "ZOrientation",
107 expected_type: GffType::Single,
108 life: FieldLife::Live,
109 required: false,
110 absent: AbsentDefault::Unverified,
111 children: None,
112 constraint: None,
113 },
114 FieldSchema {
115 label: "XPosition",
116 expected_type: GffType::Single,
117 life: FieldLife::Live,
118 required: false,
119 absent: AbsentDefault::Unverified,
120 children: None,
121 constraint: None,
122 },
123 FieldSchema {
124 label: "YPosition",
125 expected_type: GffType::Single,
126 life: FieldLife::Live,
127 required: false,
128 absent: AbsentDefault::Unverified,
129 children: None,
130 constraint: None,
131 },
132 FieldSchema {
133 label: "ZPosition",
134 expected_type: GffType::Single,
135 life: FieldLife::Live,
136 required: false,
137 absent: AbsentDefault::Unverified,
138 children: None,
139 constraint: None,
140 },
141];
142
143#[derive(Debug, Clone, PartialEq, Default)]
167pub struct SavedStore {
168 pub tag: String,
170 pub object_id: ObjectId,
172 pub loc_name: GffLocalizedString,
174
175 pub buy_sell_flag: u8,
177 pub mark_up: i32,
179 pub mark_down: i32,
181 pub items: Vec<SavedItem>,
183
184 pub commandable: bool,
186 pub on_open_store: ResRef,
188
189 pub x_position: f32,
191 pub y_position: f32,
193 pub z_position: f32,
195 pub x_orientation: f32,
197 pub y_orientation: f32,
199 pub z_orientation: f32,
201}
202
203impl SavedStore {
204 pub fn new() -> Self {
206 Self::default()
207 }
208
209 pub fn from_struct(structure: &GffStruct) -> Self {
211 Self {
212 tag: get_string(structure, "Tag").unwrap_or_default(),
213 object_id: object_id_or_invalid(structure),
214 loc_name: get_locstring(structure, "LocName")
215 .cloned()
216 .unwrap_or_default(),
217
218 buy_sell_flag: get_u8(structure, "BuySellFlag").unwrap_or(0),
219 mark_up: get_i32(structure, "MarkUp").unwrap_or(0),
220 mark_down: get_i32(structure, "MarkDown").unwrap_or(0),
221 items: match structure.field("ItemList") {
222 Some(GffValue::List(entries)) => {
223 entries.iter().map(SavedItem::from_struct).collect()
224 }
225 _ => Vec::new(),
226 },
227
228 commandable: crate::gff_helpers::get_bool(structure, "Commandable").unwrap_or(false),
229 on_open_store: get_resref(structure, "OnOpenStore").unwrap_or_default(),
230
231 x_position: get_f32(structure, "XPosition").unwrap_or(0.0),
232 y_position: get_f32(structure, "YPosition").unwrap_or(0.0),
233 z_position: get_f32(structure, "ZPosition").unwrap_or(0.0),
234 x_orientation: get_f32(structure, "XOrientation").unwrap_or(0.0),
235 y_orientation: get_f32(structure, "YOrientation").unwrap_or(0.0),
236 z_orientation: get_f32(structure, "ZOrientation").unwrap_or(0.0),
237 }
238 }
239
240 pub fn to_struct(&self, index: usize) -> GffStruct {
242 let mut s = GffStruct::new(i32::try_from(index).expect("store index fits i32"));
243
244 upsert_field(&mut s, "Tag", GffValue::String(self.tag.clone()));
245 upsert_field(&mut s, "ObjectId", GffValue::UInt32(self.object_id.get()));
246 upsert_field(
247 &mut s,
248 "LocName",
249 GffValue::LocalizedString(self.loc_name.clone()),
250 );
251
252 upsert_field(&mut s, "BuySellFlag", GffValue::UInt8(self.buy_sell_flag));
253 upsert_field(&mut s, "MarkUp", GffValue::Int32(self.mark_up));
254 upsert_field(&mut s, "MarkDown", GffValue::Int32(self.mark_down));
255 upsert_field(
256 &mut s,
257 "ItemList",
258 GffValue::List(
259 self.items
260 .iter()
261 .enumerate()
262 .map(|(index, item)| item.to_struct(index))
263 .collect(),
264 ),
265 );
266
267 upsert_field(
268 &mut s,
269 "Commandable",
270 GffValue::UInt8(self.commandable.into()),
271 );
272 upsert_field(&mut s, "OnOpenStore", GffValue::ResRef(self.on_open_store));
273
274 upsert_field(&mut s, "XPosition", GffValue::Single(self.x_position));
275 upsert_field(&mut s, "YPosition", GffValue::Single(self.y_position));
276 upsert_field(&mut s, "ZPosition", GffValue::Single(self.z_position));
277 upsert_field(&mut s, "XOrientation", GffValue::Single(self.x_orientation));
278 upsert_field(&mut s, "YOrientation", GffValue::Single(self.y_orientation));
279 upsert_field(&mut s, "ZOrientation", GffValue::Single(self.z_orientation));
280
281 s
282 }
283}
284
285#[cfg(test)]
286mod tests {
287 use super::*;
288
289 #[test]
290 fn round_trips_through_a_list_element() {
291 let store = SavedStore {
292 tag: "dan_merchant".to_string(),
293 object_id: ObjectId::new(0x8000_0009),
294 buy_sell_flag: 3,
295 mark_up: 100,
296 mark_down: 50,
297 items: vec![SavedItem {
298 tag: "g_w_blstrpstl001".to_string(),
299 stack_size: 1,
300 infinite: Some(true),
301 ..SavedItem::default()
302 }],
303 x_position: 3.0,
304 on_open_store: ResRef::new("k_store_open").expect("valid resref"),
305 ..SavedStore::default()
306 };
307
308 let parsed = SavedStore::from_struct(&store.to_struct(0));
309
310 assert_eq!(parsed, store);
311 }
312
313 #[test]
314 fn stock_keeps_its_infinite_flags() {
315 let store = SavedStore {
316 items: vec![SavedItem {
317 infinite: Some(true),
318 ..SavedItem::default()
319 }],
320 ..SavedStore::default()
321 };
322
323 let parsed = SavedStore::from_struct(&store.to_struct(0));
324
325 assert_eq!(parsed.items[0].infinite, Some(true));
326 }
327}