Skip to main content

warpack_formats/
objectstore.rs

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    /// Returns the 'prototype' for this object
167    /// which is the parent if its a custom object,
168    /// or the original if its a stock modified object
169    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}