Merge branch 'juliancoffee/loot_export_fix' into 'master'

Make loot export display all modulars

See merge request veloren/veloren!3598
This commit is contained in:
Christof Petig 2022-09-16 21:55:59 +00:00
commit 5d8a708d6b
2 changed files with 94 additions and 202 deletions

View File

@ -13,6 +13,7 @@ use veloren_common::{
self,
item::{
armor::{ArmorKind, Protection},
modular::{generate_weapon_primary_components, generate_weapons},
tool::{Hands, Tool, ToolKind},
Item, MaterialStatManifest,
},
@ -320,21 +321,21 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
let mut table = vec![entry];
// Keep converting loot table lootspecs into non-loot table lootspecs until no
// more loot tables
// 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
// 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
// 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))
@ -342,10 +343,11 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
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
// 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()
@ -365,8 +367,8 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
.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
// 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);
}
@ -387,39 +389,90 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
.div(10_f32.powi(2))
.to_string();
let (item_asset, quantity) = match item {
LootSpec::Item(item) => (Some(item), "1".to_string()),
let item_name = |asset| Item::new_from_asset_expect(asset).name().into_owned();
match item {
LootSpec::Item(item) => {
wtr.write_record(&[
name.clone(),
asset_path.to_owned(),
percent_chance,
item_name(item),
"1".to_owned(),
])?;
},
LootSpec::ItemQuantity(item, lower, upper) => {
// Tab needed so excel doesn't think it is a date...
(Some(item), format!("{}-{}\t", lower, upper))
wtr.write_record(&[
name.clone(),
asset_path.to_owned(),
percent_chance,
item_name(item),
// Tab needed so excel doesn't think it is a date...
format!("{lower}-{upper}\t"),
])?;
},
LootSpec::ModularWeapon { .. } => {
// TODO: Figure out how modular weapons should work here
(None, String::from("1"))
LootSpec::Nothing => {
wtr.write_record(&[
name.clone(),
asset_path.to_owned(),
percent_chance,
"Nothing".to_owned(),
// Tab needed so excel doesn't think it is a date...
"-".to_owned(),
])?;
},
LootSpec::ModularWeaponPrimaryComponent { .. } => {
// TODO: Figure out how modular weapon components should work here
(None, String::from("1"))
LootSpec::ModularWeapon {
tool,
material,
hands,
} => {
let weapons = generate_weapons(*tool, *material, *hands)
.expect("failed to generate modular weapons");
let chance = chance / weapons.len() as f32;
let percent_chance = chance
.mul(10_f32.powi(4))
.round()
.div(10_f32.powi(2))
.to_string();
for weapon in weapons {
wtr.write_record(&[
name.clone(),
asset_path.to_owned(),
percent_chance.clone(),
weapon.name().into_owned(),
"1".to_owned(),
])?;
}
},
LootSpec::LootTable(_) => panic!("Shouldn't exist"),
LootSpec::Nothing => (None, "-".to_string()),
};
LootSpec::ModularWeaponPrimaryComponent {
tool,
material,
hands,
} => {
let comps = generate_weapon_primary_components(*tool, *material, *hands)
.expect("failed to generate modular weapons");
let item = item_asset.map(|asset| Item::new_from_asset_expect(asset));
let chance = chance / comps.len() as f32;
let percent_chance = chance
.mul(10_f32.powi(4))
.round()
.div(10_f32.powi(2))
.to_string();
let item_name = if let Some(item) = &item {
item.name().into_owned()
} else {
String::from("Nothing")
};
wtr.write_record(&[
name.clone(),
asset_path.to_string(),
percent_chance,
item_name.to_string(),
quantity,
])?
for (comp, _hands) in comps {
wtr.write_record(&[
name.clone(),
asset_path.to_owned(),
percent_chance.clone(),
comp.name().into_owned(),
"1".to_owned(),
])?;
}
},
LootSpec::LootTable(_) => unreachable!(),
}
}
Ok(())

View File

@ -4,7 +4,7 @@
use hashbrown::HashMap;
use ron::ser::{to_string_pretty, PrettyConfig};
use serde::Serialize;
use std::{error::Error, fs::File, io::Write, str::FromStr};
use std::{error::Error, fs::File, io::Write};
use structopt::StructOpt;
use veloren_common::{
@ -13,16 +13,15 @@ use veloren_common::{
self,
item::{
armor::{ArmorKind, Protection, StatsSource},
tool::{AbilitySpec, Hands, Stats, ToolKind},
ItemDefinitionId, ItemKind, ItemTag, Material, Quality,
tool::{AbilitySpec, Hands, Stats},
ItemDefinitionId, ItemKind, ItemTag, Quality,
},
},
lottery::LootSpec,
};
#[derive(StructOpt)]
struct Cli {
/// Available arguments: "armor-stats", "weapon-stats", "loot-table"
/// Available arguments: "armor-stats", "weapon-stats"
function: String,
}
@ -463,132 +462,6 @@ fn weapon_stats() -> Result<(), Box<dyn Error>> {
Ok(())
}
fn loot_table(loot_table: &str) -> Result<(), Box<dyn Error>> {
let mut rdr = csv::Reader::from_path("loot_table.csv")?;
let headers: HashMap<String, usize> = rdr
.headers()
.expect("Failed to read CSV headers")
.iter()
.enumerate()
.map(|(i, x)| (x.to_string(), i))
.collect();
let mut items = Vec::<(f32, LootSpec<String>)>::new();
let get_tool_kind = |tool: String| match tool.as_str() {
"Sword" => Some(ToolKind::Sword),
"Axe" => Some(ToolKind::Axe),
"Hammer" => Some(ToolKind::Hammer),
"Bow" => Some(ToolKind::Bow),
"Staff" => Some(ToolKind::Staff),
"Sceptre" => Some(ToolKind::Sceptre),
"Dagger" => Some(ToolKind::Dagger),
"Shield" => Some(ToolKind::Shield),
"Spear" => Some(ToolKind::Spear),
"Debug" => Some(ToolKind::Debug),
"Farming" => Some(ToolKind::Farming),
"Pick" => Some(ToolKind::Pick),
"Natural" => Some(ToolKind::Natural),
"Empty" => Some(ToolKind::Empty),
_ => None,
};
let get_tool_hands = |hands: String| match hands.as_str() {
"One" | "one" | "1" => Some(Hands::One),
"Two" | "two" | "2" => Some(Hands::Two),
_ => None,
};
for ref record in rdr.records().flatten() {
let item = match record.get(headers["Kind"]).expect("No loot specifier") {
"Item" => {
if let (Some(Ok(lower)), Some(Ok(upper))) = (
record
.get(headers["Lower Amount or Material"])
.map(|a| a.parse()),
record
.get(headers["Upper Amount or Hands"])
.map(|a| a.parse()),
) {
LootSpec::ItemQuantity(
record.get(headers["Item"]).expect("No item").to_string(),
lower,
upper,
)
} else {
LootSpec::Item(record.get(headers["Item"]).expect("No item").to_string())
}
},
"LootTable" => LootSpec::LootTable(
record
.get(headers["Item"])
.expect("No loot table")
.to_string(),
),
"Nothing" => LootSpec::Nothing,
"Modular Weapon" => LootSpec::ModularWeapon {
tool: get_tool_kind(record.get(headers["Item"]).expect("No tool").to_string())
.expect("Invalid tool kind"),
material: Material::from_str(
record
.get(headers["Lower Amount or Material"])
.expect("No material"),
)
.expect("Invalid material type"),
hands: get_tool_hands(
record
.get(headers["Upper Amount or Hands"])
.expect("No hands")
.to_string(),
),
},
"Modular Weapon Primary Component" => LootSpec::ModularWeaponPrimaryComponent {
tool: get_tool_kind(record.get(headers["Item"]).expect("No tool").to_string())
.expect("Invalid tool kind"),
material: Material::from_str(
record
.get(headers["Lower Amount or Material"])
.expect("No material"),
)
.expect("Invalid material type"),
hands: get_tool_hands(
record
.get(headers["Upper Amount or Hands"])
.expect("No hands")
.to_string(),
),
},
a => panic!(
"Loot specifier kind must be either \"Item\", \"LootTable\", or \"Nothing\"\n{}",
a
),
};
let chance: f32 = record
.get(headers["Relative Chance"])
.expect("No chance for item in entry")
.parse()
.expect("Not an f32 for chance in entry");
items.push((chance, item));
}
let pretty_config = PrettyConfig::new().depth_limit(4).decimal_floats(true);
let mut path = ASSETS_PATH.clone();
path.push("common");
path.push("loot_tables");
for part in loot_table.split('.') {
path.push(part);
}
path.set_extension("ron");
let path_str = path.to_str().expect("File path not unicode?!");
let mut writer = File::create(path_str)?;
write!(writer, "{}", to_string_pretty(&items, pretty_config)?)?;
Ok(())
}
fn main() {
let args = Cli::from_args();
if args.function.eq_ignore_ascii_case("armor-stats") {
@ -651,40 +524,6 @@ Would you like to continue? (y/n)
println!("Error: {}\n", e)
}
}
} else if args.function.eq_ignore_ascii_case("loot-table") {
let loot_table_name = get_input(
"Specify the name of the loot table to import from csv. Adds loot table to the \
directory: assets.common.loot_tables.\n",
);
if get_input(
"
-------------------------------------------------------------------------------
| DISCLAIMER |
-------------------------------------------------------------------------------
| |
| This script will wreck the RON file for a loot table if it messes up. |
| You might want to save a back up of the loot table or be prepared to |
| use `git checkout HEAD -- ../assets/common/loot_tables/*` if needed. |
| If this script does mess up your files, please fix it. Otherwise your |
| files will be yeeted away and you will get a bonk on the head. |
| |
-------------------------------------------------------------------------------
In order for this script to work, you need to have first run the csv exporter.
Once you have loot_table.csv you can make changes to item drops and their drop
chance in your preferred editor. Save the csv file and then run this script
to import your changes back to RON.
Would you like to continue? (y/n)
> ",
)
.to_lowercase()
== *"y"
{
if let Err(e) = loot_table(&loot_table_name) {
println!("Error: {}\n", e)
}
}
} else {
println!(
"Invalid argument, available \