find_unused/
find_unused.rs

1use std::str::FromStr;
2
3use common_assets::AssetExt;
4use hashbrown::HashSet;
5use strum::IntoEnumIterator;
6use veloren_common::{
7    cmd,
8    comp::{
9        self,
10        inventory::loadout_builder::{
11            self, Hands, ItemSpec, LoadoutSpec, default_chest, default_main_tool,
12        },
13        item::{ItemDef, ItemDesc, ItemKind, Quality, all_item_defs_expect},
14    },
15    generation::{BodyBuilder, EntityConfig, LoadoutKind, try_all_entity_configs},
16    lottery::{LootSpec, Lottery},
17    recipe::{RecipeBookManifest, RecipeInput},
18    terrain::SpriteKind,
19};
20
21#[derive(Default)]
22struct Used {
23    loot_tables: HashSet<String>,
24    loadouts: HashSet<String>,
25    items: HashSet<String>,
26}
27
28impl Used {
29    fn use_item_spec(&mut self, i: &ItemSpec) {
30        match i {
31            ItemSpec::Item(i) => self.use_item(i),
32            ItemSpec::ModularWeapon { .. } => {},
33            ItemSpec::Choice(items) => {
34                for (_, i) in items {
35                    if let Some(i) = i.as_ref() {
36                        self.use_item_spec(i)
37                    }
38                }
39            },
40            ItemSpec::Seasonal(items) => {
41                for (_, i) in items {
42                    self.use_item_spec(i);
43                }
44            },
45        }
46    }
47
48    fn use_body(&mut self, body: &comp::Body) {
49        if let Some(i) = default_main_tool(body) {
50            self.use_item(i);
51        }
52        if let Some(i) = default_chest(body) {
53            self.use_item(i);
54        }
55    }
56
57    fn use_item(&mut self, s: impl Into<String>) { self.items.insert(s.into()); }
58
59    fn use_loot_table(&mut self, loot_table: &Lottery<LootSpec<impl AsRef<str>>>) {
60        for (_, l) in loot_table.iter() {
61            self.use_loot_spec(l);
62        }
63    }
64
65    fn use_loot_spec(&mut self, loot_spec: &LootSpec<impl AsRef<str>>) {
66        match loot_spec {
67            LootSpec::Item(i) => {
68                self.use_item(i.as_ref());
69            },
70            LootSpec::LootTable(loot_table) => {
71                if self.loot_tables.insert(loot_table.as_ref().to_string()) {
72                    // Only need to recurse again if it hasn't already been added
73                    let handle = Lottery::<LootSpec<String>>::load_expect(loot_table.as_ref());
74                    self.use_loot_table(&handle.read());
75                }
76            },
77            LootSpec::Nothing => {},
78            LootSpec::ModularWeapon { .. } => {},
79            LootSpec::ModularWeaponPrimaryComponent { .. } => {},
80            LootSpec::MultiDrop(loot_spec, _, _) => self.use_loot_spec(loot_spec),
81            LootSpec::All(loot_specs) => {
82                for l in loot_specs {
83                    self.use_loot_spec(l);
84                }
85            },
86            LootSpec::Lottery(l) => {
87                for (_, spec) in l {
88                    self.use_loot_spec(spec);
89                }
90            },
91        }
92    }
93
94    fn use_loadout_base(&mut self, base: &loadout_builder::Base) {
95        match base {
96            loadout_builder::Base::Asset(a) => self.use_loadout_asset(a),
97            loadout_builder::Base::Combine(bases) => {
98                for base in bases {
99                    self.use_loadout_base(base);
100                }
101            },
102            loadout_builder::Base::Choice(bases) => {
103                for (_, base) in bases {
104                    self.use_loadout_base(base);
105                }
106            },
107        }
108    }
109
110    fn use_loadout_asset(&mut self, asset: &str) {
111        if self.loadouts.insert(asset.to_string()) {
112            let loadout = LoadoutSpec::load_expect(asset);
113            self.use_loadout_spec(&loadout.read());
114        }
115    }
116
117    fn use_loadout_hands(&mut self, hands: &Hands) {
118        match hands {
119            Hands::InHands((a, b)) => {
120                if let Some(i) = a.as_ref() {
121                    self.use_item_spec(i)
122                }
123                if let Some(i) = b.as_ref() {
124                    self.use_item_spec(i)
125                }
126            },
127            Hands::Choice(items) => {
128                for (_, hands) in items {
129                    self.use_loadout_hands(hands);
130                }
131            },
132        }
133    }
134
135    fn use_loadout_spec(&mut self, spec: &LoadoutSpec) {
136        let LoadoutSpec {
137            inherit,
138            head,
139            neck,
140            shoulders,
141            chest,
142            gloves,
143            ring1,
144            ring2,
145            back,
146            belt,
147            legs,
148            feet,
149            tabard,
150            bag1,
151            bag2,
152            bag3,
153            bag4,
154            lantern,
155            glider,
156            active_hands,
157            inactive_hands,
158        } = spec;
159
160        if let Some(b) = inherit.as_ref() {
161            self.use_loadout_base(b)
162        }
163        if let Some(i) = head.as_ref() {
164            self.use_item_spec(i)
165        }
166        if let Some(i) = neck.as_ref() {
167            self.use_item_spec(i)
168        }
169        if let Some(i) = shoulders.as_ref() {
170            self.use_item_spec(i)
171        }
172        if let Some(i) = chest.as_ref() {
173            self.use_item_spec(i)
174        }
175        if let Some(i) = gloves.as_ref() {
176            self.use_item_spec(i)
177        }
178        if let Some(i) = ring1.as_ref() {
179            self.use_item_spec(i)
180        }
181        if let Some(i) = ring2.as_ref() {
182            self.use_item_spec(i)
183        }
184        if let Some(i) = back.as_ref() {
185            self.use_item_spec(i)
186        }
187        if let Some(i) = belt.as_ref() {
188            self.use_item_spec(i)
189        }
190        if let Some(i) = legs.as_ref() {
191            self.use_item_spec(i)
192        }
193        if let Some(i) = feet.as_ref() {
194            self.use_item_spec(i)
195        }
196        if let Some(i) = tabard.as_ref() {
197            self.use_item_spec(i)
198        }
199        if let Some(i) = bag1.as_ref() {
200            self.use_item_spec(i)
201        }
202        if let Some(i) = bag2.as_ref() {
203            self.use_item_spec(i)
204        }
205        if let Some(i) = bag3.as_ref() {
206            self.use_item_spec(i)
207        }
208        if let Some(i) = bag4.as_ref() {
209            self.use_item_spec(i)
210        }
211        if let Some(i) = lantern.as_ref() {
212            self.use_item_spec(i)
213        }
214        if let Some(i) = glider.as_ref() {
215            self.use_item_spec(i)
216        }
217        if let Some(i) = bag3.as_ref() {
218            self.use_item_spec(i)
219        }
220        if let Some(i) = bag4.as_ref() {
221            self.use_item_spec(i)
222        }
223        if let Some(i) = lantern.as_ref() {
224            self.use_item_spec(i)
225        }
226        if let Some(i) = glider.as_ref() {
227            self.use_item_spec(i)
228        }
229
230        if let Some(h) = active_hands.as_ref() {
231            self.use_loadout_hands(h)
232        }
233        if let Some(h) = inactive_hands.as_ref() {
234            self.use_loadout_hands(h)
235        }
236    }
237}
238
239fn main() {
240    let mut used = Used::default();
241
242    // Assumes all defined NPCs can spawn.
243    for npc in try_all_entity_configs().expect("Couldn't load npcs").iter() {
244        let config = EntityConfig::from_asset_expect_owned(npc);
245        used.use_loot_spec(&config.loot);
246        for (_, item) in config.inventory.items {
247            used.use_item(item);
248        }
249        match config.inventory.loadout {
250            LoadoutKind::FromBody => match config.body {
251                BodyBuilder::RandomWith(body) => {
252                    if let Ok(mut b) = veloren_common::npc::NpcBody::from_str(&body) {
253                        let body = (b.1)();
254                        used.use_body(&body);
255                    }
256                },
257                BodyBuilder::Exact(body) => used.use_body(&body),
258                BodyBuilder::Uninit => {},
259            },
260            LoadoutKind::Asset(asset) => used.use_loadout_asset(&asset),
261            LoadoutKind::Inline(loadout_spec) => used.use_loadout_spec(&loadout_spec),
262        }
263    }
264    // Assume all bodies can spawn.
265    for body in cmd::ENTITIES
266        .iter()
267        .filter_map(|e| veloren_common::npc::NpcBody::from_str(e).ok())
268        .map(|mut b| (b.1)())
269        .chain(
270            comp::object::ALL_OBJECTS
271                .into_iter()
272                .map(comp::Body::Object),
273        )
274    {
275        used.use_body(&body);
276    }
277
278    // Assumes all sprites can spawn.
279    for sprite in SpriteKind::iter() {
280        if let Some(Some(item)) = sprite.collectible_id() {
281            used.use_loot_spec(&item);
282        }
283    }
284
285    let recipes = RecipeBookManifest::load().read();
286
287    let mut recipes_to_check = recipes.keys().collect::<Vec<_>>();
288
289    loop {
290        let check_len = recipes_to_check.len();
291
292        recipes_to_check.retain(|recipe_key| {
293            let recipe = recipes.get(recipe_key).unwrap();
294
295            let has = |item: &ItemDef| {
296                item.item_definition_id()
297                    .itemdef_id()
298                    .is_none_or(|item| used.items.contains(item))
299            };
300
301            if recipe.inputs.iter().all(|(item, ..)| match item {
302                RecipeInput::Item(item_def) => has(item_def),
303                // Assume all tags are used
304                RecipeInput::Tag(_) => true,
305                RecipeInput::TagSameItem(_) => true,
306                RecipeInput::ListSameItem(item_defs) => item_defs.iter().all(|item| has(item)),
307            }) {
308                if let Some(item) = recipe.output.0.item_definition_id().itemdef_id() {
309                    used.use_item(item)
310                }
311                false
312            } else {
313                true
314            }
315        });
316
317        if check_len == recipes_to_check.len() {
318            break;
319        }
320    }
321    println!("Unused loot tables:");
322    for loot_table in common_assets::load_rec_dir::<Lottery<LootSpec<String>>>("common.loot_tables")
323        .expect("Couldn't load loot tables")
324        .read()
325        .ids()
326        .filter(|id| !used.loot_tables.contains(id.as_str()))
327    {
328        println!("  {loot_table}");
329    }
330
331    println!("Unused loadouts:");
332    for loadout in common_assets::load_rec_dir::<LoadoutSpec>("common.loadout")
333        .expect("Couldn't load loot tables")
334        .read()
335        .ids()
336        .filter(|id| !used.loadouts.contains(id.as_str()))
337    {
338        println!("  {loadout}");
339    }
340
341    println!("Unused items:");
342    for item in all_item_defs_expect()
343        .into_iter()
344        .filter(|id| !used.items.contains(id))
345        .filter(|id| {
346            let item = ItemDef::load_expect(id).read();
347            !matches!(
348                item.kind,
349                ItemKind::ModularComponent(_)
350                    | ItemKind::TagExamples { .. }
351                    | ItemKind::RecipeGroup { .. }
352            ) && !matches!(item.quality, Quality::Debug)
353        })
354    {
355        println!("  {item}");
356    }
357
358    println!("Impossible recipes:");
359    for recipe in recipes_to_check {
360        println!("  {recipe}");
361    }
362}