From fbe07f016a54f857a759f500e6c5669cee702d4c Mon Sep 17 00:00:00 2001 From: juliancoffee Date: Sun, 23 Jan 2022 15:52:42 +0200 Subject: [PATCH] Loadout Update. EntityInfo part EntityConfig changes: + Add LoadoutKind instead of Hands + Meta::LoadoutAsset. + Allows for random loadout asset and inventory. ``` pub enum LoadoutAsset { Loadout(String), Choice(Vec<(f32, String)>), } pub enum LoadoutKind { FromBody, Asset(LoadoutAsset), Hands(Hands), Extended { hands: Hands, base_asset: LoadoutAsset, inventory: Vec<(u32, String)>, }, } ``` EntityInfo: Remove - pub main_tool: Option, - pub second_tool: Option, - pub loadout_asset: Option, Add + pub inventory: Vec<(u32, Item)>, + pub loadout: Option, --- common/src/generation.rs | 332 ++++++++++++++++++++++++++++----------- 1 file changed, 237 insertions(+), 95 deletions(-) diff --git a/common/src/generation.rs b/common/src/generation.rs index 757eaf720f..8637ee5050 100644 --- a/common/src/generation.rs +++ b/common/src/generation.rs @@ -1,3 +1,7 @@ +// TODO: +// Reviewers, bonk me if I forgot to remove every thread_rng() here. +// Rng should be passed outside. + use crate::{ assets::{self, AssetExt, Error}, comp::{ @@ -10,17 +14,18 @@ use crate::{ trade, trade::SiteInformation, }; +use rand::prelude::SliceRandom; use serde::Deserialize; use vek::*; -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Clone, PartialEq)] pub enum NameKind { Name(String), Automatic, Uninit, } -#[derive(Debug, Deserialize, Clone)] +#[derive(Debug, Deserialize, Clone, PartialEq)] pub enum BodyBuilder { RandomWith(String), Exact(Body), @@ -45,16 +50,28 @@ pub enum Hands { mainhand: ItemSpec, offhand: ItemSpec, }, - Uninit, } -impl Default for Hands { - fn default() -> Self { Self::Uninit } +#[derive(Debug, Deserialize, Clone)] +pub enum LoadoutAsset { + Loadout(String), + Choice(Vec<(f32, String)>), +} + +#[derive(Debug, Deserialize, Clone)] +pub enum LoadoutKind { + FromBody, + Asset(LoadoutAsset), + Hands(Hands), + Extended { + hands: Hands, + base_asset: LoadoutAsset, + inventory: Vec<(u32, String)>, + }, } #[derive(Debug, Deserialize, Clone)] pub enum Meta { - LoadoutAsset(String), SkillSetAsset(String), } @@ -74,6 +91,8 @@ pub enum Meta { /// /// Intended to use with .ron files as base definion or /// in rare cases as extension manifest. +/// Pure data struct, doesn't do anything until evaluated with EntityInfo. +/// /// Check assets/common/entity/template.ron or other examples. /// /// # Example @@ -108,21 +127,17 @@ pub struct EntityConfig { /// See LootSpec in lottery pub loot: LootSpec, - /// Hands: - /// - TwoHanded(ItemSpec) for one 2h or 1h weapon, - /// - Paired(ItemSpec) for two 1h weapons aka berserker mode, - /// - Mix { mainhand: ItemSpec, offhand: ItemSpec, } - /// for two different 1h weapons, - /// - Uninit which means that tool should be specified somewhere in code, - /// Where ItemSpec is taken from loadout_builder module - // TODO: better name for this? - // wielding? equipped? what do you think Tigers are wielding? - // should we use this field for animals without visible weapons at all? - pub hands: Hands, + /// Loadout & Inventory + /// Variants: + /// `FromBody` - will get default equipement for entity body. + /// `Asset` - will get loadout from loadout asset. + /// `Hands` - naked character with weapons. + /// `Extended` - combination of Asset and Hands with ability to specify + /// inventory of entity. + pub loadout: LoadoutKind, /// Meta Info for optional fields /// Possible fields: - /// LoadoutAsset(String) with asset_specifier for loadout /// SkillSetAsset(String) with asset_specifier for skillset #[serde(default)] pub meta: Vec, @@ -158,19 +173,27 @@ pub fn try_all_entity_configs() -> Result, Error> { pub struct EntityInfo { pub pos: Vec3, pub is_waypoint: bool, // Edge case, overrides everything else + // Agent pub has_agency: bool, pub alignment: Alignment, pub agent_mark: Option, + // Stats pub body: Body, pub name: Option, - pub main_tool: Option, - pub second_tool: Option, pub scale: f32, + // Loot pub loot: LootSpec, - pub loadout_asset: Option, + // Loadout + pub inventory: Vec<(u32, Item)>, + pub loadout: Option, pub make_loadout: Option) -> LoadoutBuilder>, + // Skills pub skillset_asset: Option, + + // Not implemented pub pet: Option>, + + // Economy // we can't use DHashMap, do we want to move that into common? pub trading_information: Option, //Option>, /* price and available amount */ @@ -186,11 +209,10 @@ impl EntityInfo { agent_mark: None, body: Body::Humanoid(humanoid::Body::random()), name: None, - main_tool: None, - second_tool: None, scale: 1.0, loot: LootSpec::Nothing, - loadout_asset: None, + inventory: vec![], + loadout: None, make_loadout: None, skillset_asset: None, pet: None, @@ -213,8 +235,8 @@ impl EntityInfo { name, body, alignment, + loadout, loot, - hands, meta, } = config; @@ -250,12 +272,100 @@ impl EntityInfo { self = self.with_loot_drop(loot); + // NOTE: set loadout after body, as it's used with default equipement + self = self.with_loadout(loadout, config_asset); + + for field in meta { + match field { + Meta::SkillSetAsset(asset) => { + self = self.with_skillset_asset(asset); + }, + } + } + + self + } + + #[must_use] + /// Return EntityInfo with LoadoutBuilder overwritten + // NOTE: helpder function, think twice before exposing it + fn with_loadout(mut self, loadout: LoadoutKind, config_asset: Option<&str>) -> Self { + match loadout { + LoadoutKind::FromBody => { + self = self.with_default_equip(); + }, + LoadoutKind::Asset(loadout) => { + self = self.with_loadout_asset(loadout); + }, + LoadoutKind::Hands(hands) => { + self = self.with_hands(hands, config_asset); + }, + LoadoutKind::Extended { + hands, + base_asset, + inventory, + } => { + self = self.with_loadout_asset(base_asset); + self = self.with_hands(hands, config_asset); + self.inventory = inventory + .into_iter() + .map(|(num, i)| (num, Item::new_from_asset_expect(&i))) + .collect(); + }, + } + + self + } + + #[must_use] + /// Return EntityInfo with LoadoutBuilder overwritten + // NOTE: helpder function, think twice before exposing it + fn with_default_equip(mut self) -> Self { + let loadout_builder = LoadoutBuilder::from_default(&self.body); + self.loadout = Some(loadout_builder); + + self + } + + #[must_use] + /// Return EntityInfo with LoadoutBuilder overwritten + // NOTE: helpder function, think twice before exposing it + fn with_loadout_asset(mut self, loadout: LoadoutAsset) -> Self { + match loadout { + LoadoutAsset::Loadout(asset) => { + let mut rng = rand::thread_rng(); + let loadout = LoadoutBuilder::from_asset_expect(&asset, Some(&mut rng)); + self.loadout = Some(loadout); + }, + LoadoutAsset::Choice(assets) => { + let mut rng = rand::thread_rng(); + let (_p, asset) = assets + .choose_weighted(&mut rng, |(p, _asset)| *p) + .expect("rng error"); + + let loadout = LoadoutBuilder::from_asset_expect(&asset, Some(&mut rng)); + self.loadout = Some(loadout); + }, + } + + self + } + + #[must_use] + /// Return EntityInfo, create new loadout if needed + // NOTE: helpder function, think twice before exposing it + fn with_hands(mut self, hands: Hands, config_asset: Option<&str>) -> Self { let rng = &mut rand::thread_rng(); + + if self.loadout.is_none() { + self.loadout = Some(LoadoutBuilder::empty()); + } + match hands { Hands::TwoHanded(main_tool) => { let tool = main_tool.try_to_item(config_asset.unwrap_or("??"), rng); if let Some(tool) = tool { - self = self.with_main_tool(tool); + self.loadout = self.loadout.map(|l| l.active_mainhand(Some(tool))); } }, Hands::Paired(tool) => { @@ -263,35 +373,24 @@ impl EntityInfo { //figure out reasonable way to clone item let main_tool = tool.try_to_item(config_asset.unwrap_or("??"), rng); let second_tool = tool.try_to_item(config_asset.unwrap_or("??"), rng); + if let Some(main_tool) = main_tool { - self = self.with_main_tool(main_tool); + self.loadout = self.loadout.map(|l| l.active_mainhand(Some(main_tool))); } if let Some(second_tool) = second_tool { - self = self.with_second_tool(second_tool); + self.loadout = self.loadout.map(|l| l.active_offhand(Some(second_tool))); } }, Hands::Mix { mainhand, offhand } => { let main_tool = mainhand.try_to_item(config_asset.unwrap_or("??"), rng); let second_tool = offhand.try_to_item(config_asset.unwrap_or("??"), rng); if let Some(main_tool) = main_tool { - self = self.with_main_tool(main_tool); + self.loadout = self.loadout.map(|l| l.active_mainhand(Some(main_tool))); } if let Some(second_tool) = second_tool { - self = self.with_second_tool(second_tool); + self.loadout = self.loadout.map(|l| l.active_offhand(Some(second_tool))); } }, - Hands::Uninit => {}, - } - - for field in meta { - match field { - Meta::LoadoutAsset(asset) => { - self = self.with_loadout_asset(asset); - }, - Meta::SkillSetAsset(asset) => { - self = self.with_skillset_asset(asset); - }, - } } self @@ -341,18 +440,6 @@ impl EntityInfo { self } - #[must_use] - pub fn with_main_tool(mut self, main_tool: Item) -> Self { - self.main_tool = Some(main_tool); - self - } - - #[must_use] - pub fn with_second_tool(mut self, second_tool: Item) -> Self { - self.second_tool = Some(second_tool); - self - } - #[must_use] pub fn with_loot_drop(mut self, loot_drop: LootSpec) -> Self { self.loot = loot_drop; @@ -365,12 +452,6 @@ impl EntityInfo { self } - #[must_use] - pub fn with_loadout_asset(mut self, asset: String) -> Self { - self.loadout_asset = Some(asset); - self - } - #[must_use] pub fn with_lazy_loadout( mut self, @@ -449,19 +530,99 @@ mod tests { #[derive(Debug, Eq, Hash, PartialEq)] enum MetaId { - LoadoutAsset, SkillSetAsset, } impl Meta { fn id(&self) -> MetaId { match self { - Meta::LoadoutAsset(_) => MetaId::LoadoutAsset, Meta::SkillSetAsset(_) => MetaId::SkillSetAsset, } } } + #[cfg(test)] + fn validate_body(body: &BodyBuilder, config_asset: &str) { + match body { + BodyBuilder::RandomWith(string) => { + let npc::NpcBody(_body_kind, mut body_creator) = + string.parse::().unwrap_or_else(|err| { + panic!( + "failed to parse body {:?} in {}. Err: {:?}", + &string, config_asset, err + ) + }); + let _ = body_creator(); + }, + BodyBuilder::Uninit | BodyBuilder::Exact { .. } => {}, + } + } + + #[cfg(test)] + fn validate_loadout(loadout: LoadoutKind, body: &BodyBuilder, config_asset: &str) { + match loadout { + LoadoutKind::FromBody => { + if body.clone() == BodyBuilder::Uninit { + // there is a big chance to call automatic name + // when body is yet undefined + // + // use .with_automatic_name() in code explicitly + panic!("Used FromBody loadout with Uninit body in {}", config_asset); + } + }, + LoadoutKind::Asset(loadout) => { + validate_loadout_asset(loadout, config_asset); + }, + LoadoutKind::Hands(hands) => { + validate_hands(hands, config_asset); + }, + LoadoutKind::Extended { + hands, + base_asset, + inventory, + } => { + validate_hands(hands, config_asset); + validate_loadout_asset(base_asset, config_asset); + for (num, item_str) in inventory { + let item = Item::new_from_asset(&item_str); + let mut item = item.unwrap_or_else(|err| { + panic!("can't load {} in {}: {:?}", item_str, config_asset, err); + }); + item.set_amount(num).unwrap_or_else(|err| { + panic!( + "can't set amount {} for {} in {}: {:?}", + num, item_str, config_asset, err + ); + }); + } + }, + } + } + + #[cfg(test)] + fn validate_loadout_asset(loadout: LoadoutAsset, config_asset: &str) { + match loadout { + LoadoutAsset::Loadout(asset) => { + let rng = &mut rand::thread_rng(); + // loadout is tested in loadout_builder + // we just try to load it and check that it exists + std::mem::drop(LoadoutBuilder::from_asset_expect(&asset, Some(rng))); + }, + LoadoutAsset::Choice(assets) => { + for (p, asset) in assets { + if p <= 0.0 { + #[rustfmt::skip] + panic!( + "Weight of asset is less or equal to 0.0 in {}", + config_asset + ); + } + validate_loadout_asset(LoadoutAsset::Loadout(asset), config_asset); + } + }, + } + } + #[cfg(test)] fn validate_hands(hands: Hands, _config_asset: &str) { match hands { @@ -476,33 +637,17 @@ mod tests { mainhand.validate(EquipSlot::ActiveMainhand); offhand.validate(EquipSlot::ActiveOffhand); }, - Hands::Uninit => {}, } } #[cfg(test)] - fn validate_body_and_name(body: BodyBuilder, name: NameKind, config_asset: &str) { - match body { - BodyBuilder::RandomWith(string) => { - let npc::NpcBody(_body_kind, mut body_creator) = - string.parse::().unwrap_or_else(|err| { - panic!( - "failed to parse body {:?} in {}. Err: {:?}", - &string, config_asset, err - ) - }); - let _ = body_creator(); - }, - BodyBuilder::Uninit => { - if let NameKind::Automatic = name { - // there is a big chance to call automatic name - // when body is yet undefined - // - // use .with_automatic_name() in code explicitly - panic!("Used Automatic name with Uninit body in {}", config_asset); - } - }, - BodyBuilder::Exact { .. } => {}, + fn validate_name(name: NameKind, body: BodyBuilder, config_asset: &str) { + if name == NameKind::Automatic && body == BodyBuilder::Uninit { + // there is a big chance to call automatic name + // when body is yet undefined + // + // use .with_automatic_name() in code explicitly + panic!("Used Automatic name with Uninit body in {}", config_asset); } } @@ -520,14 +665,8 @@ mod tests { .entry(field.id()) .and_modify(|c| *c += 1) .or_insert(1); + match field { - Meta::LoadoutAsset(asset) => { - let rng = &mut rand::thread_rng(); - let builder = LoadoutBuilder::empty(); - // we need to just load it check if it exists, - // because all loadouts are tested in LoadoutBuilder module - std::mem::drop(builder.with_asset_expect(&asset, rng)); - }, Meta::SkillSetAsset(asset) => { std::mem::drop(SkillSetBuilder::from_asset_expect(&asset)); }, @@ -559,16 +698,19 @@ mod tests { println!("{}:", &config_asset); let EntityConfig { - hands, body, + loadout, name, loot, meta, alignment: _alignment, // can't fail if serialized, it's a boring enum } = EntityConfig::from_asset_expect(&config_asset); - validate_hands(hands, &config_asset); - validate_body_and_name(body, name, &config_asset); + validate_body(&body, &config_asset); + // body dependent stuff + validate_loadout(loadout, &body, &config_asset); + validate_name(name, body, &config_asset); + // misc validate_loot(loot, &config_asset); validate_meta(meta, &config_asset); }