1use std::collections::BTreeMap;
2use std::fs;
3use std::path::Path;
4use std::sync::{Arc, RwLock};
5
6use serde::{Deserialize, Serialize};
7
8use crate::metadata::MetadataStore;
9use crate::object::*;
10use crate::ObjectId;
11use crate::ObjectKind;
12use crate::parser::profile;
13use crate::parser::slk;
14
15#[derive(Debug, Serialize, Deserialize)]
16pub struct ObjectStore {
17 objects: BTreeMap<ObjectId, Arc<RwLock<Object>>>,
18}
19
20#[derive(Debug, Serialize, Deserialize)]
21pub struct ObjectStoreStock {
22 objects: BTreeMap<ObjectId, Object>,
23}
24
25impl Default for ObjectStore {
26 fn default() -> ObjectStore {
27 ObjectStore {
28 objects: Default::default(),
29 }
30 }
31}
32
33impl Default for ObjectStoreStock {
34 fn default() -> ObjectStoreStock {
35 ObjectStoreStock {
36 objects: Default::default(),
37 }
38 }
39}
40
41impl ObjectStore {
42 pub fn is_dirty(&self) -> bool {
43 for object in self.objects.values() {
44 let object = object.read().unwrap();
45 if object.is_dirty() {
46 return true;
47 }
48 }
49
50 false
51 }
52
53 pub fn reset_dirty(&self) {
54 for object in self.objects.values() {
55 let mut object = object.write().unwrap();
56 object.set_dirty(false);
57 }
58 }
59
60 pub fn objects(&self) -> impl Iterator<Item = &Arc<RwLock<Object>>> {
61 self.objects.values()
62 }
63
64 pub fn object(&self, id: ObjectId) -> Option<&Arc<RwLock<Object>>> {
65 self.objects.get(&id)
66 }
67
68 pub fn insert_object(&mut self, object: Object) {
69 self.objects
70 .insert(object.id(), Arc::new(RwLock::new(object)));
71 }
72
73 pub fn remove_object(&mut self, id: ObjectId) {
74 self.objects.remove(&id);
75 }
76
77 pub fn add_from(&mut self, other: &ObjectStore) {
78 for (id, other_object) in &other.objects {
79 if let Some(object) = self.objects.get_mut(&id) {
80 object.write().unwrap().add_from(&other_object.read().unwrap());
81 } else {
82 let cloned = other_object.read().unwrap().clone();
83 self.objects.insert(*id, Arc::new(RwLock::new(cloned)));
84 }
85 }
86 }
87
88 fn insert_slk_row<'src>(
89 &mut self,
90 kind: ObjectKind,
91 row: slk::Row<'src>,
92 legend: &slk::Legend<'src>,
93 metadata: &MetadataStore,
94 ) -> Option<()> {
95 let first_cell = row.cells.get(0);
96 let second_cell = row.cells.get(1);
97
98 let id = first_cell
99 .and_then(|c| c.value().as_str())
100 .and_then(|id| ObjectId::from_bytes(id.as_bytes()))?;
101
102 let object = if kind == ObjectKind::empty() {
103 self.objects.get_mut(&id)?
104 } else {
105 self.objects
106 .entry(id)
107 .or_insert_with(|| Arc::new(RwLock::new(Object::new(id, kind))))
108 };
109
110 let has_aliased_id = first_cell.and_then(|c| legend.name_by_cell(c)).map(|name| name == "alias").unwrap_or(false)
111 && second_cell.and_then(|c| legend.name_by_cell(c)).map(|name| name == "code").unwrap_or(false);
112
113 if has_aliased_id {
114 let aliased_id = row.cells.get(1)
115 .and_then(|c| c.value().as_str())
116 .and_then(|id| ObjectId::from_bytes(id.as_bytes()))?;
117 object.write().unwrap().set_aliased_id(Some(aliased_id));
118 }
119
120 for (value, name) in row
121 .cells
122 .iter()
123 .filter_map(|cell| legend.name_by_cell(&cell).map(|name| (cell.value(), name)))
124 {
125 object.write().unwrap().process_slk_field(value, name, metadata);
126 }
127
128 Some(())
129 }
130
131 fn insert_func_entry(&mut self, entry: profile::Entry, metadata: &MetadataStore) -> Option<()> {
132 let id = ObjectId::from_bytes(entry.id.as_bytes())?;
133 let object = self.objects.get_mut(&id)?;
134
135 for (key, values) in entry.values {
136 for (index, value) in values.split(',').enumerate() {
137 object
138 .write().unwrap()
139 .process_func_field(key, value, index as i8, metadata);
140 }
141 }
142
143 Some(())
144 }
145}
146
147impl ObjectStoreStock {
148 pub fn new(data: &ObjectStore) -> ObjectStoreStock {
149 let mut data_static = Self::default();
150 data_static.merge_from(data);
151 data_static
152 }
153
154 fn merge_from(&mut self, data: &ObjectStore) {
155 for object in data.objects() {
156 let object = object.read().unwrap().clone();
157
158 self.objects.insert(object.id(), object);
159 }
160 }
161
162 pub fn object(&self, id: ObjectId) -> Option<&Object> {
163 self.objects.get(&id)
164 }
165
166 pub fn object_prototype(&self, object: &Object) -> Option<&Object> {
170 self.objects
171 .get(&object.id())
172 .or_else(|| object.parent_id().and_then(|pid| self.objects.get(&pid)))
173 }
174
175 pub fn objects(&self) -> impl Iterator<Item = &Object> {
176 self.objects.values()
177 }
178}
179
180fn read_func_file<P: AsRef<Path>>(path: P, metadata: &MetadataStore, data: &mut ObjectStore) {
181 dbg!(path.as_ref());
182
183 let src = fs::read(path).unwrap();
184 let entries = profile::Entries::new(&src);
185
186 for entry in entries {
187 data.insert_func_entry(entry, metadata);
188 }
189}
190
191fn read_slk_file<P: AsRef<Path>>(
192 path: P,
193 kind: ObjectKind,
194 metadata: &MetadataStore,
195 data: &mut ObjectStore,
196) {
197 let src = fs::read(path).unwrap();
198 let mut table = slk::Table::new(&src).unwrap();
199 let legend = table.legend();
200
201 while table.has_next() {
202 if let Some(row) = table.next_row() {
203 data.insert_slk_row(kind, row, &legend, metadata);
204 }
205 }
206}
207
208pub fn read_data_dir<P: AsRef<Path>>(path: P, metadata: &MetadataStore) -> ObjectStore {
209 let path = path.as_ref();
210 let mut data = ObjectStore::default();
211
212 const SLKS: &[(ObjectKind, &str)] = &[
213 (ObjectKind::UNIT, "units/unitdata.slk"),
214 (ObjectKind::ABILITY, "units/abilitydata.slk"),
215 (ObjectKind::ITEM, "units/itemdata.slk"),
216 (ObjectKind::BUFF, "units/abilitybuffdata.slk"),
217 (ObjectKind::DESTRUCTABLE, "units/destructabledata.slk"),
218 (ObjectKind::UPGRADE, "units/upgradedata.slk"),
219 (ObjectKind::DOODAD, "doodads/doodads.slk"),
220 (ObjectKind::LIGHTNING, "splats/lightningdata.slk"),
221 (ObjectKind::empty(), "units/unitbalance.slk"),
222 (ObjectKind::empty(), "units/unitabilities.slk"),
223 (ObjectKind::empty(), "units/unitweapons.slk"),
224 (ObjectKind::empty(), "units/unitui.slk"),
225 ];
226
227 for (kind, file_path) in SLKS {
228 read_slk_file(path.join(file_path), *kind, &metadata, &mut data);
229 }
230
231 const PROFILES: &[&str] = &[
232 "doodads/doodadskins.txt",
233 "units/abilityskin.txt",
234 "units/campaignabilityfunc.txt",
235 "units/campaignunitfunc.txt",
236 "units/campaignupgradefunc.txt",
237 "units/commandfunc.txt",
238 "units/commonabilityfunc.txt",
239 "units/destructableskin.txt",
240 "units/humanabilityfunc.txt",
241 "units/humanunitfunc.txt",
242 "units/humanupgradefunc.txt",
243 "units/itemabilityfunc.txt",
244 "units/itemfunc.txt",
245 "units/itemskin.txt",
246 "units/miscdata.txt",
247 "units/miscgame.txt",
248 "units/neutralabilityfunc.txt",
249 "units/neutralunitfunc.txt",
250 "units/neutralupgradefunc.txt",
251 "units/nightelfabilityfunc.txt",
252 "units/nightelfunitfunc.txt",
253 "units/nightelfupgradefunc.txt",
254 "units/orcabilityfunc.txt",
255 "units/orcunitfunc.txt",
256 "units/orcupgradefunc.txt",
257 "units/undeadabilityfunc.txt",
258 "units/undeadunitfunc.txt",
259 "units/undeadupgradefunc.txt",
260 "units/unitskin.txt",
261 "units/unitweaponsfunc.txt",
262 "units/unitweaponsskin.txt",
263 "units/upgradeskin.txt",
264 "units_en/campaignabilitystrings.txt",
265 "units_en/campaignunitstrings.txt",
266 "units_en/campaignupgradestrings.txt",
267 "units_en/commandstrings.txt",
268 "units_en/commonabilitystrings.txt",
269 "units_en/humanabilitystrings.txt",
270 "units_en/humanunitstrings.txt",
271 "units_en/humanupgradestrings.txt",
272 "units_en/itemabilitystrings.txt",
273 "units_en/itemstrings.txt",
274 "units_en/neutralabilitystrings.txt",
275 "units_en/neutralunitstrings.txt",
276 "units_en/neutralupgradestrings.txt",
277 "units_en/nightelfabilitystrings.txt",
278 "units_en/nightelfunitstrings.txt",
279 "units_en/nightelfupgradestrings.txt",
280 "units_en/orcabilitystrings.txt",
281 "units_en/orcunitstrings.txt",
282 "units_en/orcupgradestrings.txt",
283 "units_en/undeadabilitystrings.txt",
284 "units_en/undeadunitstrings.txt",
285 "units_en/undeadupgradestrings.txt",
286 "units_en/unitglobalstrings.txt",
287 ];
288
289 for file_path in PROFILES {
290 read_func_file(path.join(file_path), &metadata, &mut data);
291 }
292
293 data
294}