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 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 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 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 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 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}