Loot tables

This commit is contained in:
Samuel Keiffer 2021-03-22 09:39:35 +00:00
parent 1374900928
commit f65cd1e771
3 changed files with 148 additions and 10 deletions

View File

@ -1,18 +1,29 @@
#![deny(clippy::clone_on_ref_ptr)]
use std::error::Error;
use std::{
error::Error,
io::Write,
ops::{Div, Mul},
};
use structopt::StructOpt;
use comp::item::{
armor::{ArmorKind, Protection},
tool::{Hands, MaterialStatManifest, Tool, ToolKind},
ItemKind,
use veloren_common::{
assets::AssetExt,
comp::{
self,
item::{
armor::{ArmorKind, Protection},
tool::{Hands, MaterialStatManifest, Tool, ToolKind},
ItemKind,
},
},
lottery::Lottery,
};
use veloren_common::comp;
#[derive(StructOpt)]
struct Cli {
/// Available arguments: "armor_stats", "weapon_stats", "all_items"
/// Available arguments: "armor-stats", "weapon-stats", "all-items",
/// "loot-table"
function: String,
}
@ -205,6 +216,32 @@ fn all_items() -> Result<(), Box<dyn Error>> {
Ok(())
}
fn loot_table(loot_table: &str) -> Result<(), Box<dyn Error>> {
let mut wtr = csv::Writer::from_path("loot_table.csv")?;
wtr.write_record(&["Item", "Relative Chance"])?;
let loot_table = "common.loot_tables.".to_owned() + loot_table;
let loot_table = Lottery::<String>::load_expect(&loot_table).read();
for (i, (chance, item)) in loot_table.iter().enumerate() {
let chance = if let Some((next_chance, _)) = loot_table.iter().nth(i + 1) {
next_chance - chance
} else {
loot_table.total() - chance
}
.mul(10_f32.powi(5))
.round()
.div(10_f32.powi(5))
.to_string();
wtr.write_record(&[item, &chance])?;
}
wtr.flush()?;
Ok(())
}
fn main() {
let args = Cli::from_args();
if args.function.eq_ignore_ascii_case("armor-stats") {
@ -219,10 +256,30 @@ fn main() {
if let Err(e) = all_items() {
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 export to csv. Assumes loot table is in \
directory: assets.common.loot_tables.\n",
);
if let Err(e) = loot_table(&loot_table_name) {
println!("Error: {}\n", e)
}
} else {
println!(
"Invalid argument, available \
arguments:\n\"armor-stats\"\n\"weapon-stats\"\n\"all-items\""
arguments:\n\"armor-stats\"\n\"weapon-stats\"\n\"all-items\"\n\"loot-table [table]\""
)
}
}
pub fn get_input(prompt: &str) -> String {
// Function to get input from the user with a prompt
let mut input = String::new();
print!("{}", prompt);
std::io::stdout().flush().unwrap();
std::io::stdin()
.read_line(&mut input)
.expect("Error reading input");
String::from(input.trim())
}

View File

@ -14,7 +14,7 @@ use veloren_common::{assets::ASSETS_PATH, comp};
#[derive(StructOpt)]
struct Cli {
/// Available arguments: "armor_stats", "weapon_stats"
/// Available arguments: "armor-stats", "weapon-stats", "loot-table"
function: String,
}
@ -361,6 +361,48 @@ 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, String)>::new();
for record in rdr.records() {
if let Ok(ref record) = record {
let item = record.get(headers["Item"]).expect("No item");
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.to_string()));
}
}
let pretty_config = PrettyConfig::new()
.with_depth_limit(4)
.with_decimal_floats(true);
let mut path = ASSETS_PATH.clone();
path.push("common");
path.push("loot_tables");
path.push(loot_table);
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") {
@ -423,8 +465,45 @@ 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".to_string()
{
if let Err(e) = loot_table(&loot_table_name) {
println!("Error: {}\n", e)
}
}
} else {
println!("Invalid argument, available arguments:\n\"armor-stats\"\n\"weapon-stats\"\n\"")
println!(
"Invalid argument, available \
arguments:\n\"armor-stats\"\n\"weapon-stats\"\n\"loot-table\"\n\""
)
}
}

View File

@ -68,6 +68,8 @@ impl<T> Lottery<T> {
pub fn choose(&self) -> &T { self.choose_seeded(thread_rng().gen()) }
pub fn iter(&self) -> impl Iterator<Item = &(f32, T)> { self.items.iter() }
pub fn total(&self) -> f32 { self.total }
}
#[cfg(test)]