mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
commit
5d8a708d6b
@ -13,6 +13,7 @@ use veloren_common::{
|
|||||||
self,
|
self,
|
||||||
item::{
|
item::{
|
||||||
armor::{ArmorKind, Protection},
|
armor::{ArmorKind, Protection},
|
||||||
|
modular::{generate_weapon_primary_components, generate_weapons},
|
||||||
tool::{Hands, Tool, ToolKind},
|
tool::{Hands, Tool, ToolKind},
|
||||||
Item, MaterialStatManifest,
|
Item, MaterialStatManifest,
|
||||||
},
|
},
|
||||||
@ -320,21 +321,21 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
|
|||||||
|
|
||||||
let mut table = vec![entry];
|
let mut table = vec![entry];
|
||||||
|
|
||||||
// Keep converting loot table lootspecs into non-loot table lootspecs until no
|
// Keep converting loot table lootspecs into non-loot table lootspecs
|
||||||
// more loot tables
|
// until no more loot tables
|
||||||
while table
|
while table
|
||||||
.iter()
|
.iter()
|
||||||
.any(|(_, loot_spec)| matches!(loot_spec, LootSpec::LootTable(_)))
|
.any(|(_, loot_spec)| matches!(loot_spec, LootSpec::LootTable(_)))
|
||||||
{
|
{
|
||||||
// Partition table of loot specs into a table of items and nothings, and another
|
// Partition table of loot specs into a table of items and
|
||||||
// table of loot tables
|
// nothings, and another table of loot tables
|
||||||
let (sub_tables, main_table): (Vec<_>, Vec<_>) = table
|
let (sub_tables, main_table): (Vec<_>, Vec<_>) = table
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.partition(|(_, loot_spec)| matches!(loot_spec, LootSpec::LootTable(_)));
|
.partition(|(_, loot_spec)| matches!(loot_spec, LootSpec::LootTable(_)));
|
||||||
table = main_table;
|
table = main_table;
|
||||||
|
|
||||||
// Change table of loot tables to only contain the string that loads the loot
|
// Change table of loot tables to only contain the string that
|
||||||
// table
|
// loads the loot table
|
||||||
let sub_tables = sub_tables.iter().filter_map(|(chance, loot_spec)| {
|
let sub_tables = sub_tables.iter().filter_map(|(chance, loot_spec)| {
|
||||||
if let LootSpec::LootTable(loot_table) = loot_spec {
|
if let LootSpec::LootTable(loot_table) = loot_spec {
|
||||||
Some((chance, loot_table))
|
Some((chance, loot_table))
|
||||||
@ -342,10 +343,11 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
|
|||||||
None
|
None
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
for (chance, loot_table) in sub_tables {
|
for (chance, loot_table) in sub_tables {
|
||||||
let loot_table = Lottery::<LootSpec<String>>::load_expect(loot_table).read();
|
let loot_table = Lottery::<LootSpec<String>>::load_expect(loot_table).read();
|
||||||
// Converts from lottery's weight addition for each consecutive entry to keep
|
// Converts from lottery's weight addition for each consecutive
|
||||||
// the weights as they are in the ron file
|
// entry to keep the weights as they are in the ron file
|
||||||
let loot_table: Vec<_> = loot_table
|
let loot_table: Vec<_> = loot_table
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@ -365,8 +367,8 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|(chance, item)| (chance / weights_sum, item));
|
.map(|(chance, item)| (chance / weights_sum, item));
|
||||||
for (sub_chance, &item) in loot_table {
|
for (sub_chance, &item) in loot_table {
|
||||||
// Multiplies normalized entry within each loot table by the chance for the loot
|
// Multiplies normalized entry within each loot table by
|
||||||
// table to drop in the above table
|
// the chance for the loot table to drop in the above table
|
||||||
let entry = (chance * sub_chance, item.clone());
|
let entry = (chance * sub_chance, item.clone());
|
||||||
table.push(entry);
|
table.push(entry);
|
||||||
}
|
}
|
||||||
@ -387,39 +389,90 @@ fn entity_drops(entity_config: &str) -> Result<(), Box<dyn Error>> {
|
|||||||
.div(10_f32.powi(2))
|
.div(10_f32.powi(2))
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let (item_asset, quantity) = match item {
|
let item_name = |asset| Item::new_from_asset_expect(asset).name().into_owned();
|
||||||
LootSpec::Item(item) => (Some(item), "1".to_string()),
|
|
||||||
|
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) => {
|
LootSpec::ItemQuantity(item, lower, upper) => {
|
||||||
// Tab needed so excel doesn't think it is a date...
|
wtr.write_record(&[
|
||||||
(Some(item), format!("{}-{}\t", lower, upper))
|
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 { .. } => {
|
LootSpec::Nothing => {
|
||||||
// TODO: Figure out how modular weapons should work here
|
wtr.write_record(&[
|
||||||
(None, String::from("1"))
|
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 { .. } => {
|
LootSpec::ModularWeapon {
|
||||||
// TODO: Figure out how modular weapon components should work here
|
tool,
|
||||||
(None, String::from("1"))
|
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::ModularWeaponPrimaryComponent {
|
||||||
LootSpec::Nothing => (None, "-".to_string()),
|
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 {
|
for (comp, _hands) in comps {
|
||||||
item.name().into_owned()
|
wtr.write_record(&[
|
||||||
} else {
|
name.clone(),
|
||||||
String::from("Nothing")
|
asset_path.to_owned(),
|
||||||
};
|
percent_chance.clone(),
|
||||||
|
comp.name().into_owned(),
|
||||||
wtr.write_record(&[
|
"1".to_owned(),
|
||||||
name.clone(),
|
])?;
|
||||||
asset_path.to_string(),
|
}
|
||||||
percent_chance,
|
},
|
||||||
item_name.to_string(),
|
LootSpec::LootTable(_) => unreachable!(),
|
||||||
quantity,
|
}
|
||||||
])?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
use hashbrown::HashMap;
|
use hashbrown::HashMap;
|
||||||
use ron::ser::{to_string_pretty, PrettyConfig};
|
use ron::ser::{to_string_pretty, PrettyConfig};
|
||||||
use serde::Serialize;
|
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 structopt::StructOpt;
|
||||||
|
|
||||||
use veloren_common::{
|
use veloren_common::{
|
||||||
@ -13,16 +13,15 @@ use veloren_common::{
|
|||||||
self,
|
self,
|
||||||
item::{
|
item::{
|
||||||
armor::{ArmorKind, Protection, StatsSource},
|
armor::{ArmorKind, Protection, StatsSource},
|
||||||
tool::{AbilitySpec, Hands, Stats, ToolKind},
|
tool::{AbilitySpec, Hands, Stats},
|
||||||
ItemDefinitionId, ItemKind, ItemTag, Material, Quality,
|
ItemDefinitionId, ItemKind, ItemTag, Quality,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
lottery::LootSpec,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(StructOpt)]
|
#[derive(StructOpt)]
|
||||||
struct Cli {
|
struct Cli {
|
||||||
/// Available arguments: "armor-stats", "weapon-stats", "loot-table"
|
/// Available arguments: "armor-stats", "weapon-stats"
|
||||||
function: String,
|
function: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,132 +462,6 @@ fn weapon_stats() -> Result<(), Box<dyn Error>> {
|
|||||||
Ok(())
|
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() {
|
fn main() {
|
||||||
let args = Cli::from_args();
|
let args = Cli::from_args();
|
||||||
if args.function.eq_ignore_ascii_case("armor-stats") {
|
if args.function.eq_ignore_ascii_case("armor-stats") {
|
||||||
@ -651,40 +524,6 @@ Would you like to continue? (y/n)
|
|||||||
println!("Error: {}\n", e)
|
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 {
|
} else {
|
||||||
println!(
|
println!(
|
||||||
"Invalid argument, available \
|
"Invalid argument, available \
|
||||||
|
Loading…
Reference in New Issue
Block a user