From e794758dadd419581228bdf5362cdbd0b3ea133f Mon Sep 17 00:00:00 2001
From: Sam <samuelkeiffer@gmail.com>
Date: Thu, 7 Oct 2021 20:28:18 -0400
Subject: [PATCH 1/2] Csv export entity drops tool now has ability to export
 all entities at once.

---
 common/src/bin/csv_export/main.rs | 196 ++++++++++++++++--------------
 1 file changed, 108 insertions(+), 88 deletions(-)

diff --git a/common/src/bin/csv_export/main.rs b/common/src/bin/csv_export/main.rs
index ed485fa80f..21bf6eec6c 100644
--- a/common/src/bin/csv_export/main.rs
+++ b/common/src/bin/csv_export/main.rs
@@ -8,7 +8,7 @@ use std::{
 use structopt::StructOpt;
 
 use veloren_common::{
-    assets::AssetExt,
+    assets::{self, AssetExt},
     comp::{
         self,
         item::{
@@ -278,102 +278,121 @@ fn loot_table(loot_table: &str) -> Result<(), Box<dyn Error>> {
 
 fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
     let mut wtr = csv::Writer::from_path("drop_table.csv")?;
-    wtr.write_record(&["Percent Chance", "Item Path", "Quantity"])?;
 
-    let entity_config = "common.entity.".to_owned() + entity_config;
+    fn write_entity_loot<W: std::io::Write>(
+        wtr: &mut csv::Writer<W>,
+        asset_path: &str,
+    ) -> Result<(), Box<dyn Error>> {
+        wtr.write_record(&["Percent Chance", "Item Path", "Quantity"])?;
 
-    let entity_config = EntityConfig::load_expect(&entity_config).read();
+        let entity_config = EntityConfig::load_expect(&asset_path).read();
 
-    // Create initial entry in drop table
-    let entry: (f32, LootSpec<String>) = (1.0, entity_config.loot.clone());
+        // Create initial entry in drop table
+        let entry: (f32, LootSpec<String>) = (1.0, entity_config.loot.clone());
 
-    let mut table = vec![entry];
+        let mut table = vec![entry];
 
-    // Keep converting loot table lootspecs into non-loot table lootspecs until no
-    // more loot tables
-    while table
-        .iter()
-        .any(|(_, loot_spec)| matches!(loot_spec, LootSpec::LootTable(_)))
-    {
-        // Partition table of loot specs into a table of items and nothings, and another
-        // table of loot tables
-        let (sub_tables, main_table): (Vec<_>, Vec<_>) = table
-            .into_iter()
-            .partition(|(_, loot_spec)| matches!(loot_spec, LootSpec::LootTable(_)));
-        table = main_table;
+        // Keep converting loot table lootspecs into non-loot table lootspecs until no
+        // more loot tables
+        while table
+            .iter()
+            .any(|(_, loot_spec)| matches!(loot_spec, LootSpec::LootTable(_)))
+        {
+            // Partition table of loot specs into a table of items and nothings, and another
+            // table of loot tables
+            let (sub_tables, main_table): (Vec<_>, Vec<_>) = table
+                .into_iter()
+                .partition(|(_, loot_spec)| matches!(loot_spec, LootSpec::LootTable(_)));
+            table = main_table;
 
-        // Change table of loot tables to only contain the string that loads the loot
-        // table
-        let sub_tables = sub_tables.iter().filter_map(|(chance, loot_spec)| {
-            if let LootSpec::LootTable(loot_table) = loot_spec {
-                Some((chance, loot_table))
-            } else {
-                None
-            }
-        });
-        for (chance, loot_table) in sub_tables {
-            let loot_table = Lottery::<LootSpec<String>>::load_expect(loot_table).read();
-            // Converts from lottery's weight addition for each consecutive entry to keep
-            // the weights as they are in the ron file
-            let loot_table: Vec<_> = loot_table
-                .iter()
-                .enumerate()
-                .map(|(i, (chance, item))| {
-                    let chance = if let Some((next_chance, _)) = loot_table.iter().nth(i + 1) {
-                        next_chance - chance
-                    } else {
-                        loot_table.total() - chance
-                    };
-                    (chance, item)
-                })
-                .collect();
-            // Gets sum of all weights to use in normalization of entries
-            let weights_sum: f32 = loot_table.iter().map(|(chance, _)| chance).sum();
-            // Normalizes each entry in sub-loot table
-            let loot_table = loot_table
-                .iter()
-                .map(|(chance, item)| (chance / weights_sum, item));
-            for (sub_chance, &item) in loot_table {
-                // Multiplies normalized entry within each loot table by the chance for the loot
-                // table to drop in the above table
-                let entry = (chance * sub_chance, item.clone());
-                table.push(entry);
+            // Change table of loot tables to only contain the string that loads the loot
+            // table
+            let sub_tables = sub_tables.iter().filter_map(|(chance, loot_spec)| {
+                if let LootSpec::LootTable(loot_table) = loot_spec {
+                    Some((chance, loot_table))
+                } else {
+                    None
+                }
+            });
+            for (chance, loot_table) in sub_tables {
+                let loot_table = Lottery::<LootSpec<String>>::load_expect(loot_table).read();
+                // Converts from lottery's weight addition for each consecutive entry to keep
+                // the weights as they are in the ron file
+                let loot_table: Vec<_> = loot_table
+                    .iter()
+                    .enumerate()
+                    .map(|(i, (chance, item))| {
+                        let chance = if let Some((next_chance, _)) = loot_table.iter().nth(i + 1) {
+                            next_chance - chance
+                        } else {
+                            loot_table.total() - chance
+                        };
+                        (chance, item)
+                    })
+                    .collect();
+                // Gets sum of all weights to use in normalization of entries
+                let weights_sum: f32 = loot_table.iter().map(|(chance, _)| chance).sum();
+                // Normalizes each entry in sub-loot table
+                let loot_table = loot_table
+                    .iter()
+                    .map(|(chance, item)| (chance / weights_sum, item));
+                for (sub_chance, &item) in loot_table {
+                    // Multiplies normalized entry within each loot table by the chance for the loot
+                    // table to drop in the above table
+                    let entry = (chance * sub_chance, item.clone());
+                    table.push(entry);
+                }
             }
         }
+
+        // Normalizes each item drop entry so that everything adds to 1
+        let table_weight_sum: f32 = table.iter().map(|(chance, _)| chance).sum();
+        let table = table
+            .iter()
+            .map(|(chance, item)| (chance / table_weight_sum, item));
+
+        for (chance, item) in table {
+            // Changes normalized weight to add to 100, and rounds at 2nd decimal
+            let percent_chance = chance
+                .mul(10_f32.powi(4))
+                .round()
+                .div(10_f32.powi(2))
+                .to_string();
+
+            let (item_asset, quantity) = match item {
+                LootSpec::Item(item) => (Some(item), "1".to_string()),
+                LootSpec::ItemQuantity(item, lower, upper) => {
+                    (Some(item), format!("{}-{}", lower, upper))
+                },
+                LootSpec::LootTable(_) => panic!("Shouldn't exist"),
+                LootSpec::Nothing => (None, "-".to_string()),
+            };
+
+            let item = item_asset.map(|asset| Item::new_from_asset_expect(asset));
+
+            let item_name = if let Some(item) = &item {
+                item.name()
+            } else {
+                "Nothing"
+            };
+
+            wtr.write_record(&[&percent_chance, item_name, &quantity])?
+        }
+
+        Ok(())
     }
 
-    // Normalizes each item drop entry so that everything adds to 1
-    let table_weight_sum: f32 = table.iter().map(|(chance, _)| chance).sum();
-    let table = table
-        .iter()
-        .map(|(chance, item)| (chance / table_weight_sum, item));
-
-    for (chance, item) in table {
-        // Changes normalized weight to add to 100, and rounds at 2nd decimal
-        let percent_chance = chance
-            .mul(10_f32.powi(4))
-            .round()
-            .div(10_f32.powi(2))
-            .to_string();
-
-        let (item_asset, quantity) = match item {
-            LootSpec::Item(item) => (Some(item), "1".to_string()),
-            LootSpec::ItemQuantity(item, lower, upper) => {
-                (Some(item), format!("{}-{}", lower, upper))
-            },
-            LootSpec::LootTable(_) => panic!("Shouldn't exist"),
-            LootSpec::Nothing => (None, "-".to_string()),
-        };
-
-        let item = item_asset.map(|asset| Item::new_from_asset_expect(asset));
-
-        let item_name = if let Some(item) = &item {
-            item.name()
-        } else {
-            "Nothing"
-        };
-
-        wtr.write_record(&[&percent_chance, item_name, &quantity])?
+    if entity_config.eq_ignore_ascii_case("all") {
+        let configs = assets::load_dir::<EntityConfig>("common.entity", true)
+            .expect("Entity files moved somewhere else maybe?")
+            .ids();
+        for config in configs {
+            wtr.write_record(&[config, "", ""])?;
+            write_entity_loot(&mut wtr, config)?;
+        }
+    } else {
+        let entity_config = "common.entity.".to_owned() + entity_config;
+        write_entity_loot(&mut wtr, &entity_config)?;
     }
 
     wtr.flush()?;
@@ -405,7 +424,8 @@ fn main() {
     } else if args.function.eq_ignore_ascii_case("entity-drops") {
         let entity_config = get_input(
             "Specify the name of the entity to export loot drops to csv. Assumes entity config is \
-             in directory: assets.common.entity.\n",
+             in directory: assets.common.entity.\nCan also use \"all\" to export loot from all \
+             entity configs.\n",
         );
         if let Err(e) = entity_drops(&entity_config) {
             println!("Error: {}\n", e)

From 8791f994dc402496d1bc14c81e04dbb989c531ae Mon Sep 17 00:00:00 2001
From: Sam <samuelkeiffer@gmail.com>
Date: Sun, 10 Oct 2021 12:37:16 -0400
Subject: [PATCH 2/2] Now stores a column for entity path to more easily allow
 searching for what drops a particular item in the future Fixed excel
 perceiving certain csv entries as a date

---
 common/src/bin/csv_export/main.rs | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/common/src/bin/csv_export/main.rs b/common/src/bin/csv_export/main.rs
index 21bf6eec6c..29fc94815d 100644
--- a/common/src/bin/csv_export/main.rs
+++ b/common/src/bin/csv_export/main.rs
@@ -278,14 +278,13 @@ fn loot_table(loot_table: &str) -> Result<(), Box<dyn Error>> {
 
 fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
     let mut wtr = csv::Writer::from_path("drop_table.csv")?;
+    wtr.write_record(&["Entity Path", "Percent Chance", "Item Path", "Quantity"])?;
 
     fn write_entity_loot<W: std::io::Write>(
         wtr: &mut csv::Writer<W>,
         asset_path: &str,
     ) -> Result<(), Box<dyn Error>> {
-        wtr.write_record(&["Percent Chance", "Item Path", "Quantity"])?;
-
-        let entity_config = EntityConfig::load_expect(&asset_path).read();
+        let entity_config = EntityConfig::load_expect(asset_path).read();
 
         // Create initial entry in drop table
         let entry: (f32, LootSpec<String>) = (1.0, entity_config.loot.clone());
@@ -362,7 +361,8 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
             let (item_asset, quantity) = match item {
                 LootSpec::Item(item) => (Some(item), "1".to_string()),
                 LootSpec::ItemQuantity(item, lower, upper) => {
-                    (Some(item), format!("{}-{}", lower, upper))
+                    // Tab needed so excel doesn't think it is a date...
+                    (Some(item), format!("{}-{}\t", lower, upper))
                 },
                 LootSpec::LootTable(_) => panic!("Shouldn't exist"),
                 LootSpec::Nothing => (None, "-".to_string()),
@@ -376,7 +376,7 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
                 "Nothing"
             };
 
-            wtr.write_record(&[&percent_chance, item_name, &quantity])?
+            wtr.write_record(&[asset_path, &percent_chance, item_name, &quantity])?
         }
 
         Ok(())
@@ -387,7 +387,6 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
             .expect("Entity files moved somewhere else maybe?")
             .ids();
         for config in configs {
-            wtr.write_record(&[config, "", ""])?;
             write_entity_loot(&mut wtr, config)?;
         }
     } else {