diff --git a/.gitlab/scripts/code-quality.sh b/.gitlab/scripts/code-quality.sh index 88b49d27d7..5e63fdffb0 100755 --- a/.gitlab/scripts/code-quality.sh +++ b/.gitlab/scripts/code-quality.sh @@ -1,5 +1,5 @@ #!/bin/bash -time cargo clippy --all-targets --locked --features="bin_cmd_doc_gen,bin_compression,bin_csv,bin_graphviz,bin_bot,bin_entity_migrate,asset_tweak" -- -D warnings && +time cargo clippy --all-targets --locked --features="bin_cmd_doc_gen,bin_compression,bin_csv,bin_graphviz,bin_bot,bin_asset_migrate,asset_tweak" -- -D warnings && # Ensure that the veloren-voxygen default-publish feature builds as it excludes some default features time cargo clippy -p veloren-voxygen --locked --no-default-features --features="default-publish" -- -D warnings && time cargo fmt --all -- --check diff --git a/common/Cargo.toml b/common/Cargo.toml index b5a0e79888..057a84c2ee 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -11,7 +11,7 @@ simd = ["vek/platform_intrinsics"] bin_csv = ["ron", "csv", "structopt"] bin_graphviz = ["petgraph"] bin_cmd_doc_gen = [] -bin_entity_migrate = ["ron"] +bin_asset_migrate = ["ron"] rrt_pathfinding = ["kiddo"] calendar_events = [] @@ -108,8 +108,8 @@ name = "csv_import" required-features = ["bin_csv"] [[bin]] -name = "entity_migrate" -required-features = ["bin_entity_migrate"] +name = "asset_migrate" +required-features = ["bin_asset_migrate"] [[bin]] name = "recipe_graphviz" diff --git a/common/src/bin/asset_migrate.rs b/common/src/bin/asset_migrate.rs new file mode 100644 index 0000000000..83afb9b6b6 --- /dev/null +++ b/common/src/bin/asset_migrate.rs @@ -0,0 +1,146 @@ +use serde::{de::DeserializeOwned, Deserialize, Serialize}; +use std::{ + fs, io, + io::Write, + path::{Path, PathBuf}, +}; + +/// Old version. +mod v1 { + use super::*; + + #[derive(Serialize, Deserialize)] + pub struct Example { + pub field: u8, + } +} + +/// New version. +mod v2 { + use super::*; + + #[derive(Serialize, Deserialize)] + pub struct Example { + pub field: f64, + } + + impl From for Example { + fn from(old: super::v1::Example) -> Self { + Self { + field: f64::from(old.field), + } + } + } +} + +#[derive(Debug)] +enum Walk { + File(PathBuf), + Dir { path: PathBuf, content: Vec }, +} + +fn walk_tree(dir: &Path, root: &Path) -> io::Result> { + let mut buff = Vec::new(); + for entry in fs::read_dir(dir)? { + let entry = entry?; + let path = entry.path(); + if path.is_dir() { + buff.push(Walk::Dir { + path: path + .strip_prefix(root) + .expect("strip can't fail, this path is created from root") + .to_owned(), + content: walk_tree(&path, root)?, + }); + } else { + let filename = path + .strip_prefix(root) + .expect("strip can't fail, this file is created from root") + .to_owned(); + buff.push(Walk::File(filename)); + } + } + + Ok(buff) +} + +fn walk_with_migrate(tree: Walk, from: &Path, to: &Path) -> std::io::Result<()> +where + NewV: From, + OldV: DeserializeOwned, + NewV: Serialize, +{ + match tree { + Walk::Dir { path, content } => { + let target_dir = to.join(path); + fs::create_dir_all(target_dir)?; + for entry in content { + walk_with_migrate::(entry, from, to)?; + } + }, + Walk::File(path) => { + let source = fs::File::open(from.join(&path))?; + let old: OldV = ron::de::from_reader(source).unwrap(); + let new: NewV = old.into(); + let target = fs::File::create(to.join(&path))?; + let pretty_config = ron::ser::PrettyConfig::new(); + ron::ser::to_writer_pretty(target, &new, pretty_config).unwrap(); + println!("{path:?} done"); + }, + } + Ok(()) +} + +fn convert_loop(from: &str, to: &str) { + let root = Path::new(from); + let files = Walk::Dir { + path: Path::new("").to_owned(), + content: walk_tree(root, root).unwrap(), + }; + walk_with_migrate::(files, Path::new(from), Path::new(to)).unwrap(); +} + +fn input_string(prompt: &str) -> String { input_validated_string(prompt, &|_| true) } + +fn input_validated_string(prompt: &str, check: &dyn Fn(&str) -> bool) -> String { + println!("{}", prompt); + + print!("> "); + io::stdout().flush().unwrap(); + + let mut buff = String::new(); + io::stdin().read_line(&mut buff).unwrap(); + + while !check(buff.trim()) { + buff.clear(); + print!("> "); + io::stdout().flush().unwrap(); + io::stdin().read_line(&mut buff).unwrap(); + } + + buff.trim().to_owned() +} + +fn main() { + let prompt = r#" + Stub implementation. + If you want to migrate new assets, edit `v1` and `v2` modules. + If you want to migrate old assets, check commit history. + "#; + println!("{prompt}"); + + let old_dir = input_validated_string( + "Please input directory path with old entity configs:", + &|path| { + if !Path::new(path).exists() { + eprintln!("Source directory '{path}' does not exists."); + false + } else { + true + } + }, + ); + let new_dir = input_string("Please input directory path to place new entity configs:"); + + convert_loop(&old_dir, &new_dir) +} diff --git a/common/src/bin/entity_migrate.rs b/common/src/bin/entity_migrate.rs deleted file mode 100644 index 3d3113449c..0000000000 --- a/common/src/bin/entity_migrate.rs +++ /dev/null @@ -1,331 +0,0 @@ -use serde::{de::DeserializeOwned, Deserialize, Serialize}; -use veloren_common::{ - comp::{inventory::loadout_builder::ItemSpec, Alignment, Body}, - lottery::LootSpec, -}; - -use std::{ - fs, io, - io::Write, - path::{Path, PathBuf}, -}; - -/// First "stable" version. -mod v1 { - pub(super) use super::*; - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub enum NameKind { - Name(String), - Automatic, - Uninit, - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub enum BodyBuilder { - RandomWith(String), - Exact(Body), - Uninit, - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub enum AlignmentMark { - Alignment(Alignment), - Uninit, - } - - impl Default for AlignmentMark { - fn default() -> Self { Self::Alignment(Alignment::Wild) } - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub enum Hands { - TwoHanded(ItemSpec), - Paired(ItemSpec), - Mix { - mainhand: ItemSpec, - offhand: ItemSpec, - }, - Uninit, - } - - impl Default for Hands { - fn default() -> Self { Self::Uninit } - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub enum Meta { - LoadoutAsset(String), - SkillSetAsset(String), - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub struct EntityConfig { - pub name: NameKind, - pub body: BodyBuilder, - pub alignment: AlignmentMark, - pub loot: LootSpec, - pub hands: Hands, - #[serde(default)] - pub meta: Vec, - } -} - -/// Loadout update. -/// 1) Added ability to randomize loadout for entity. -/// 2) Simplified logic by squashing hands, loadout and inventory into one pack. -mod v2 { - pub(super) use super::*; - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub enum LoadoutAsset { - Loadout(String), - Choice(Vec<(f32, String)>), - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub enum Hands { - TwoHanded(ItemSpec), - Paired(ItemSpec), - Mix { - mainhand: ItemSpec, - offhand: ItemSpec, - }, - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub enum LoadoutKind { - FromBody, - Asset(LoadoutAsset), - Hands(Hands), - Extended { - hands: Hands, - base_asset: LoadoutAsset, - inventory: Vec<(u32, String)>, - }, - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub enum Meta { - SkillSetAsset(String), - } - - #[derive(Debug, Serialize, Deserialize, Clone)] - pub struct EntityConfig { - pub name: super::v1::NameKind, - pub body: super::v1::BodyBuilder, - pub alignment: super::v1::AlignmentMark, - pub loadout: LoadoutKind, - pub loot: super::v1::LootSpec, - #[serde(default)] - pub meta: Vec, - } - - impl From for EntityConfig { - fn from(old_config: super::v1::EntityConfig) -> Self { - let mut loadout_asset = None; - let mut meta = Vec::new(); - - for item in old_config.meta { - match item { - super::v1::Meta::SkillSetAsset(asset) => { - meta.push(Meta::SkillSetAsset(asset)); - }, - super::v1::Meta::LoadoutAsset(asset) => { - if loadout_asset == None { - loadout_asset = Some(asset); - } else { - tracing::error!("multiple loadout assets in meta[], bad"); - } - }, - } - } - - let loadout_kind = match loadout_asset { - Some(asset) => match old_config.hands { - super::v1::Hands::TwoHanded(spec) => LoadoutKind::Extended { - hands: Hands::TwoHanded(spec), - base_asset: LoadoutAsset::Loadout(asset), - inventory: vec![], - }, - super::v1::Hands::Paired(spec) => LoadoutKind::Extended { - hands: Hands::Paired(spec), - base_asset: LoadoutAsset::Loadout(asset), - inventory: vec![], - }, - super::v1::Hands::Mix { mainhand, offhand } => LoadoutKind::Extended { - hands: Hands::Mix { mainhand, offhand }, - base_asset: LoadoutAsset::Loadout(asset), - inventory: vec![], - }, - super::v1::Hands::Uninit => LoadoutKind::Asset(LoadoutAsset::Loadout(asset)), - }, - None => match old_config.hands { - super::v1::Hands::TwoHanded(spec) => LoadoutKind::Hands(Hands::TwoHanded(spec)), - super::v1::Hands::Paired(spec) => LoadoutKind::Hands(Hands::Paired(spec)), - super::v1::Hands::Mix { mainhand, offhand } => { - LoadoutKind::Hands(Hands::Mix { mainhand, offhand }) - }, - super::v1::Hands::Uninit => LoadoutKind::FromBody, - }, - }; - - Self { - name: old_config.name, - body: old_config.body, - alignment: old_config.alignment, - loadout: loadout_kind, - loot: old_config.loot, - meta, - } - } - } -} - -fn input_string(prompt: &str) -> String { input_validated_string(prompt, &|_| true) } - -fn input_validated_string(prompt: &str, check: &dyn Fn(&str) -> bool) -> String { - println!("{}", prompt); - - print!("> "); - io::stdout().flush().unwrap(); - - let mut buff = String::new(); - io::stdin().read_line(&mut buff).unwrap(); - - while !check(buff.trim()) { - buff.clear(); - print!("> "); - io::stdout().flush().unwrap(); - io::stdin().read_line(&mut buff).unwrap(); - } - - buff.trim().to_owned() -} - -#[derive(Debug)] -enum Walk { - File(PathBuf), - Dir { path: PathBuf, content: Vec }, -} - -fn walk_tree(dir: &Path, root: &Path) -> io::Result> { - let mut buff = Vec::new(); - for entry in fs::read_dir(dir)? { - let entry = entry?; - let path = entry.path(); - if path.is_dir() { - buff.push(Walk::Dir { - path: path - .strip_prefix(root) - .expect("strip can't fail, this path is created from root") - .to_owned(), - content: walk_tree(&path, root)?, - }); - } else { - let filename = path - .strip_prefix(root) - .expect("strip can't fail, this file is created from root") - .to_owned(); - buff.push(Walk::File(filename)); - } - } - - Ok(buff) -} - -fn walk_with_migrate(tree: Walk, from: &Path, to: &Path) -> std::io::Result<()> -where - NewV: From, - OldV: DeserializeOwned, - NewV: Serialize, -{ - match tree { - Walk::Dir { path, content } => { - let target_dir = to.join(path); - fs::create_dir_all(target_dir)?; - for entry in content { - walk_with_migrate::(entry, from, to)?; - } - }, - Walk::File(path) => { - let source = fs::File::open(from.join(&path))?; - let old: OldV = ron::de::from_reader(source).unwrap(); - let new: NewV = old.into(); - let target = fs::File::create(to.join(&path))?; - let pretty_config = ron::ser::PrettyConfig::new(); - ron::ser::to_writer_pretty(target, &new, pretty_config).unwrap(); - println!("{path:?} done"); - }, - } - Ok(()) -} - -fn convert_loop(from: &str, to: &str, old_ver: &str, new_ver: &str) { - #[rustfmt::skip] - println!( - "\nRequest info:\n\ - {old_ver} -> {new_ver}.\n\ - Get data from {from} and store in {to}." - ); - - let root = Path::new(from); - let files = Walk::Dir { - path: Path::new("").to_owned(), - content: walk_tree(root, root).unwrap(), - }; - if old_ver == "v1" && new_ver == "v2" { - walk_with_migrate::( - files, - Path::new(from), - Path::new(to), - ) - .unwrap(); - } else { - eprintln!("Unexpected versions") - } -} - -fn main() { - println!( - r#" -Hello, this tool can convert all your entity configs to newer version. -Currently it supports converting from "v1" to "v2". - "# - ); - - let old_dir = input_validated_string( - "Please input directory path with old entity configs:", - &|path| { - if !Path::new(path).exists() { - eprintln!("Source directory '{path}' does not exists."); - false - } else { - true - } - }, - ); - let new_dir = input_string("Please input directory path to place new entity configs:"); - - let old_version = - input_validated_string("Please input old version to migrate from:", &|version| { - let olds = ["v1"]; - if !olds.contains(&version) { - eprintln!("Unexpected version {version}. Available: {olds:?}"); - false - } else { - true - } - }); - let new_version = input_validated_string("Please input new version:", &|version| { - let news = ["v2"]; - if !news.contains(&version) { - eprintln!("Unexpected version {version}. Available: {news:?}"); - false - } else { - true - } - }); - - convert_loop(&old_dir, &new_dir, &old_version, &new_version) -}