Fixed random generation of modular weapons.

This commit is contained in:
Sam 2022-02-06 19:20:02 -05:00
parent e86d2ab46a
commit 6fa7852a8c
2 changed files with 87 additions and 107 deletions

View File

@ -3,6 +3,7 @@ use super::{
Item, ItemBase, ItemDef, ItemDesc, ItemKind, ItemTag, Material, Quality, ToolKind, Item, ItemBase, ItemDef, ItemDesc, ItemKind, ItemTag, Material, Quality, ToolKind,
}; };
use crate::{assets::AssetExt, recipe}; use crate::{assets::AssetExt, recipe};
use common_base::dev_panic;
use hashbrown::HashMap; use hashbrown::HashMap;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use rand::{prelude::SliceRandom, thread_rng}; use rand::{prelude::SliceRandom, thread_rng};
@ -247,7 +248,7 @@ const SUPPORTED_TOOLKINDS: [ToolKind; 6] = [
ToolKind::Sceptre, ToolKind::Sceptre,
]; ];
type PrimaryComponentPool = HashMap<(ToolKind, String), Vec<(Arc<ItemDef>, Option<Hands>)>>; type PrimaryComponentPool = HashMap<(ToolKind, String), Vec<(Item, Option<Hands>)>>;
type SecondaryComponentPool = HashMap<ToolKind, Vec<(Arc<ItemDef>, Option<Hands>)>>; type SecondaryComponentPool = HashMap<ToolKind, Vec<(Arc<ItemDef>, Option<Hands>)>>;
// TODO: Fix this. It broke when changes were made to component recipes // TODO: Fix this. It broke when changes were made to component recipes
@ -256,54 +257,23 @@ lazy_static! {
let mut component_pool = HashMap::new(); let mut component_pool = HashMap::new();
// Load recipe book (done to check that material is valid for a particular component) // Load recipe book (done to check that material is valid for a particular component)
let recipe_book = recipe::RawRecipeBook::load_expect("common.recipe_book"); use crate::recipe::ComponentKey;
let recipes = &recipe_book.read().0; let recipes = recipe::default_component_recipe_book().read();
let ability_map = &AbilityMap::load().read();
let msm = &MaterialStatManifest::load().read();
const ASSET_PREFIX: &str = "common.items.crafting_ing.modular.primary"; recipes
.iter()
// Closure to check that an Item has a recipe that uses the provided material .for_each(|(ComponentKey { toolkind, material, .. }, recipe)| {
let valid_materials = |item: &str| { let component = recipe.item_output(ability_map, msm);
// Iterate over all recipes in the raw recipe book let hand_restriction = if let ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent { hand_restriction, .. }) = &*component.kind() {
recipes *hand_restriction
.values() } else {
// Filter by recipes that have an output of the item of interest return;
.filter(|recipe| recipe.output.0.eq(item)) };
// Check that item is composed of material, uses heuristic that assumes all modular components use the ListSameItem recipe input let entry = component_pool.entry((*toolkind, String::from(material))).or_insert(Vec::new());
.find_map(|recipe| { entry.push((component, hand_restriction));
recipe });
.inputs
.iter()
.find_map(|input| {
match &input.0 {
recipe::RawRecipeInput::ListSameItem(items) => {
Some(recipe::ItemList::load_expect_cloned(items).0)
},
_ => None,
}
})
})
};
for toolkind in SUPPORTED_TOOLKINDS {
let directory = format!("{}.{}", ASSET_PREFIX, toolkind.identifier_name());
if let Ok(items) = Item::new_from_asset_glob(&directory) {
items
.into_iter()
.map(|comp| comp.item_definition_id().to_owned())
.filter_map(|id| Arc::<ItemDef>::load_cloned(&id).ok())
.for_each(|comp_def| {
if let ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent { hand_restriction, .. }) = comp_def.kind {
if let Some(material_ids) = valid_materials(comp_def.id()) {
for material in material_ids {
let entry = component_pool.entry((toolkind, material)).or_insert(Vec::new());
entry.push((Arc::clone(&comp_def), hand_restriction));
}
}
}
}
);
}
}
component_pool component_pool
}; };
@ -347,65 +317,74 @@ pub fn random_weapon(
material: Material, material: Material,
hand_restriction: Option<Hands>, hand_restriction: Option<Hands>,
) -> Result<Item, ModularWeaponCreationError> { ) -> Result<Item, ModularWeaponCreationError> {
if let Some(material_id) = material.asset_identifier() { let result = (|| {
// Loads default ability map and material stat manifest for later use if let Some(material_id) = material.asset_identifier() {
let ability_map = &AbilityMap::load().read(); // Loads default ability map and material stat manifest for later use
let msm = &MaterialStatManifest::load().read(); let ability_map = &AbilityMap::load().read();
let msm = &MaterialStatManifest::load().read();
let mut rng = thread_rng(); let mut rng = thread_rng();
let material = Item::new_from_asset_expect(material_id); let primary_components = PRIMARY_COMPONENT_POOL
let primary_components = PRIMARY_COMPONENT_POOL .get(&(tool, material_id.to_owned()))
.get(&(tool, material_id.to_owned())) .into_iter()
.into_iter() .flatten()
.flatten() .filter(|(_comp, hand)| match (hand_restriction, hand) {
.filter(|(_def, hand)| match (hand_restriction, hand) { (Some(restriction), Some(hand)) => restriction == *hand,
(Some(restriction), Some(hand)) => restriction == *hand, (None, _) | (_, None) => true,
(None, _) | (_, None) => true, })
}) .collect::<Vec<_>>();
.collect::<Vec<_>>();
let (primary_component, hand_restriction) = { let (primary_component, hand_restriction) = {
let (def, hand) = primary_components let (comp, hand) = primary_components
.choose(&mut rng) .choose(&mut rng)
.ok_or(ModularWeaponCreationError::PrimaryComponentNotFound)?; .ok_or(ModularWeaponCreationError::PrimaryComponentNotFound)?;
let comp = Item::new_from_item_base( let comp = comp.duplicate(ability_map, msm);
ItemBase::Raw(Arc::clone(def)), (comp, hand_restriction.or(*hand))
vec![material], };
let secondary_components = SECONDARY_COMPONENT_POOL
.get(&tool)
.into_iter()
.flatten()
.filter(|(_def, hand)| match (hand_restriction, hand) {
(Some(restriction), Some(hand)) => restriction == *hand,
(None, _) | (_, None) => true,
})
.collect::<Vec<_>>();
let secondary_component = {
let def = &secondary_components
.choose(&mut rng)
.ok_or(ModularWeaponCreationError::SecondaryComponentNotFound)?
.0;
Item::new_from_item_base(
ItemBase::Raw(Arc::clone(def)),
Vec::new(),
ability_map,
msm,
)
};
// Create modular weapon
Ok(Item::new_from_item_base(
ItemBase::Modular(ModularBase::Tool),
vec![primary_component, secondary_component],
ability_map, ability_map,
msm, msm,
); ))
(comp, hand_restriction.or(*hand)) } else {
}; Err(ModularWeaponCreationError::MaterialNotFound)
}
let secondary_components = SECONDARY_COMPONENT_POOL })();
.get(&tool) if result.is_err() {
.into_iter() let error_str = format!(
.flatten() "Failed to synthesize a modular {tool:?} made of {material:?} that had a hand \
.filter(|(_def, hand)| match (hand_restriction, hand) { restriction of {hand_restriction:?}."
(Some(restriction), Some(hand)) => restriction == *hand, );
(None, _) | (_, None) => true, dev_panic!(error_str)
})
.collect::<Vec<_>>();
let secondary_component = {
let def = &secondary_components
.choose(&mut rng)
.ok_or(ModularWeaponCreationError::SecondaryComponentNotFound)?
.0;
Item::new_from_item_base(ItemBase::Raw(Arc::clone(def)), Vec::new(), ability_map, msm)
};
// Create modular weapon
Ok(Item::new_from_item_base(
ItemBase::Modular(ModularBase::Tool),
vec![primary_component, secondary_component],
ability_map,
msm,
))
} else {
Err(ModularWeaponCreationError::MaterialNotFound)
} }
result
} }
pub fn modify_name<'a>(item_name: &'a str, item: &'a Item) -> Cow<'a, str> { pub fn modify_name<'a>(item_name: &'a str, item: &'a Item) -> Cow<'a, str> {

View File

@ -527,11 +527,10 @@ impl ComponentRecipe {
input_slots: I, input_slots: I,
) { ) {
let mut required = amount; let mut required = amount;
// Check used for recipes that have an input that is not consumed, e.g. // contains_any check used for recipes that have an input that is not consumed,
// craftsman hammer // e.g. craftsman hammer
let mut contains_any = false;
// Goes through each slot and marks some amount from each slot as claimed // Goes through each slot and marks some amount from each slot as claimed
for slot in input_slots { let contains_any = input_slots.into_iter().all(|slot| {
// Checks that the item in the slot can be used for the input // Checks that the item in the slot can be used for the input
if let Some(item) = inv if let Some(item) = inv
.get(slot) .get(slot)
@ -544,9 +543,11 @@ impl ComponentRecipe {
let provided = available.min(required); let provided = available.min(required);
required -= provided; required -= provided;
*claimed += provided; *claimed += provided;
contains_any = true; true
} else {
false
} }
} });
// If there were not sufficient items to cover requirement between all provided // If there were not sufficient items to cover requirement between all provided
// slots, or if non-consumed item was not present, mark input as not satisfied // slots, or if non-consumed item was not present, mark input as not satisfied
if required > 0 || !contains_any { if required > 0 || !contains_any {