Changed ModularComponent to allow it to be more extensible. Overhauled random modular weapon function so that there is less runtime cost to generate one. NO ASSETS

This commit is contained in:
Sam 2021-11-20 11:52:20 -05:00
parent b048179c0a
commit a9e9a70687
10 changed files with 362 additions and 343 deletions

View File

@ -25,6 +25,7 @@ use common::{
chat::{KillSource, KillType},
controller::CraftEvent,
group,
inventory::item::{modular, ItemKind},
invite::{InviteKind, InviteResponse},
skills::Skill,
slot::{EquipSlot, InvSlotId, Slot},
@ -1082,41 +1083,44 @@ impl Client {
slot_b: InvSlotId,
sprite_pos: Vec3<i32>,
) -> bool {
use comp::item::{
modular::ModularComponentKind::{Damage, Held},
ItemKind,
};
let inventories = self.inventories();
let inventory = inventories.get(self.entity());
enum ModKind {
Primary,
Secondary,
}
// Closure to get inner modular component info from item in a given slot
let unwrap_modular = |slot| {
if let Some(ItemKind::ModularComponent(mod_comp)) = inventory
.and_then(|inv| inv.get(slot).map(|item| item.kind()))
.as_deref()
{
Some(mod_comp.modkind)
} else {
None
}
let mod_kind = |slot| match inventory
.and_then(|inv| inv.get(slot).map(|item| item.kind()))
.as_deref()
{
Some(ItemKind::ModularComponent(modular::ModularComponent::ToolPrimaryComponent {
..
})) => Some(ModKind::Primary),
Some(ItemKind::ModularComponent(
modular::ModularComponent::ToolSecondaryComponent { .. },
)) => Some(ModKind::Secondary),
_ => None,
};
// Gets slot order of damage and held components if two provided slots contains
// both a damage and held component
let slot_order = match (unwrap_modular(slot_a), unwrap_modular(slot_b)) {
(Some(Damage), Some(Held)) => Some((slot_a, slot_b)),
(Some(Held), Some(Damage)) => Some((slot_b, slot_a)),
// both a primary and secondary component
let slot_order = match (mod_kind(slot_a), mod_kind(slot_b)) {
(Some(ModKind::Primary), Some(ModKind::Secondary)) => Some((slot_a, slot_b)),
(Some(ModKind::Secondary), Some(ModKind::Primary)) => Some((slot_b, slot_a)),
_ => None,
};
drop(inventories);
if let Some((damage_component, held_component)) = slot_order {
if let Some((primary_component, secondary_component)) = slot_order {
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
InventoryEvent::CraftRecipe {
craft_event: CraftEvent::ModularWeapon {
damage_component,
held_component,
primary_component,
secondary_component,
},
craft_sprite: Some(sprite_pos),
},

View File

@ -100,8 +100,8 @@ pub enum CraftEvent {
Salvage(InvSlotId),
// TODO: Maybe look at making this more general when there are more modular recipes?
ModularWeapon {
damage_component: InvSlotId,
held_component: InvSlotId,
primary_component: InvSlotId,
secondary_component: InvSlotId,
},
}

View File

@ -4,7 +4,7 @@ pub mod modular;
pub mod tool;
// Reexports
pub use modular::{ModularBase, ModularComponent, ModularComponentKind};
pub use modular::{ModularBase, ModularComponent};
pub use tool::{AbilitySet, AbilitySpec, Hands, MaterialStatManifest, Tool, ToolKind};
use crate::{
@ -528,14 +528,6 @@ impl ItemDef {
)
}
pub fn is_component(&self, kind: ModularComponentKind) -> bool {
if let ItemKind::ModularComponent(ModularComponent { modkind, .. }) = self.kind {
kind == modkind
} else {
false
}
}
// currently needed by trade_pricing
pub fn id(&self) -> &str { &self.item_definition_id }
@ -896,11 +888,14 @@ impl Item {
}
pub fn kind(&self) -> Cow<ItemKind> {
// TODO: Try to move further upward
let msm = MaterialStatManifest::default();
match &self.item_base {
ItemBase::Raw(item_def) => Cow::Borrowed(&item_def.kind),
ItemBase::Modular(mod_base) => mod_base.kind(self.components(), &msm),
ItemBase::Modular(mod_base) => {
// TODO: Try to move further upward
let msm = MaterialStatManifest::default();
mod_base.kind(self.components(), &msm)
},
}
}

View File

@ -1,10 +1,13 @@
use super::{
tool::{self, AbilitySpec, Hands, MaterialStatManifest, Stats},
Item, ItemBase, ItemDesc, ItemKind, Quality, ToolKind,
tool::{self, AbilitySpec, Hands, MaterialStatManifest},
Item, ItemBase, ItemDef, ItemDesc, ItemKind, Material, Quality, ToolKind,
};
use crate::{assets::AssetExt, lottery::Lottery, recipe};
use crate::{assets::AssetExt, recipe};
use hashbrown::HashMap;
use lazy_static::lazy_static;
use rand::{prelude::SliceRandom, thread_rng};
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::{borrow::Cow, sync::Arc};
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ModularBase {
@ -22,45 +25,32 @@ impl ModularBase {
fn resolve_hands(components: &[Item]) -> Hands {
// Checks if weapon has components that restrict hands to two. Restrictions to
// one hand or no restrictions default to one-handed weapon.
let is_two_handed = components.iter().any(|item| matches!(&*item.kind(), ItemKind::ModularComponent(mc) if matches!(mc.hand_restriction, Some(Hands::Two))));
// If weapon is two handed, make it two handed
if is_two_handed {
Hands::Two
} else {
Hands::One
}
let hand_restriction = components.iter().find_map(|comp| match &*comp.kind() {
ItemKind::ModularComponent(mc) => match mc {
ModularComponent::ToolPrimaryComponent {
hand_restriction, ..
}
| ModularComponent::ToolSecondaryComponent {
hand_restriction, ..
} => *hand_restriction,
},
_ => None,
});
// In the event of no hand restrictions on the components, default to one handed
hand_restriction.unwrap_or(Hands::One)
}
pub fn resolve_stats(components: &[Item], msm: &MaterialStatManifest) -> Stats {
let mut stats = Stats::one();
let mut material_multipliers: Vec<Stats> = Vec::new();
for item in components.iter() {
match &*item.kind() {
// Modular components directly multiply against the base stats
ItemKind::ModularComponent(mc) => {
let inner_stats = mc.stats * resolve_stats(item.components(), msm);
stats *= inner_stats;
},
// Ingredients push multiplier to vec as the ingredient multipliers are averaged
ItemKind::Ingredient { .. } => {
if let Some(mult_stats) = msm.0.get(item.item_definition_id()) {
material_multipliers.push(*mult_stats);
}
},
_ => (),
}
}
// Take the average of the material multipliers
if !material_multipliers.is_empty() {
let mut average_mult = Stats::zero();
for stat in material_multipliers.iter() {
average_mult += *stat;
}
average_mult /= material_multipliers.len();
stats *= average_mult;
}
stats
pub fn resolve_stats(components: &[Item], msm: &MaterialStatManifest) -> tool::Stats {
components
.iter()
.filter_map(|comp| {
if let ItemKind::ModularComponent(mod_comp) = &*comp.kind() {
mod_comp.tool_stats(comp.components(), msm)
} else {
None
}
})
.fold(tool::Stats::one(), |a, b| a * b)
}
match self {
@ -77,42 +67,30 @@ impl ModularBase {
/// the damage component is created from.
pub fn generate_name(&self, components: &[Item]) -> Cow<str> {
match self {
ModularBase::Tool(toolkind) => {
// Closure to get material name from an item
fn material_name(component: &Item) -> String {
component
.components()
.iter()
.filter_map(|comp| match &*comp.kind() {
ItemKind::Ingredient { descriptor, .. } => Some(descriptor.to_owned()),
_ => None,
})
.next()
.unwrap_or_else(|| "Modular".to_owned())
}
let main_component = components.iter().find(|comp| {
matches!(&*comp.kind(), ItemKind::ModularComponent(ModularComponent { modkind, .. })
if *modkind == ModularComponentKind::main_component(*toolkind)
)
});
let (material_name, weapon_name) = if let Some(component) = main_component {
let material_name = material_name(component);
let weapon_name = if let ItemKind::ModularComponent(ModularComponent {
weapon_name,
..
}) = &*component.kind()
{
weapon_name.to_owned()
} else {
toolkind.identifier_name().to_owned()
};
(material_name, weapon_name)
} else {
("Modular".to_owned(), toolkind.identifier_name().to_owned())
};
Cow::Owned(format!("{} {}", material_name, weapon_name))
ModularBase::Tool(_toolkind) => {
let name = components
.iter()
.find_map(|comp| match &*comp.kind() {
ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent {
weapon_name,
..
}) => {
let material_name = comp
.components()
.iter()
.find_map(|mat| match &*mat.kind() {
ItemKind::Ingredient { descriptor, .. } => {
Some(descriptor.to_owned())
},
_ => None,
})
.unwrap_or_else(|| "Modular".to_owned());
Some(format!("{} {}", material_name, weapon_name))
},
_ => None,
})
.unwrap_or_else(|| "Modular Weapon".to_owned());
Cow::Owned(name)
},
}
}
@ -130,39 +108,53 @@ impl ModularBase {
}
}
// TODO: Look into changing to: Primary, Secondary
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ModularComponentKind {
Damage,
Held,
}
impl ModularComponentKind {
fn identifier_name(&self) -> &'static str {
match self {
ModularComponentKind::Damage => "damage",
ModularComponentKind::Held => "held",
}
}
/// Returns the main component of a weapon, i.e. which component has a
/// material component
fn main_component(tool: ToolKind) -> Self {
match tool {
ToolKind::Sword | ToolKind::Axe | ToolKind::Hammer | ToolKind::Bow => Self::Damage,
ToolKind::Staff | ToolKind::Sceptre => Self::Held,
_ => unimplemented!(),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct ModularComponent {
pub toolkind: ToolKind,
pub modkind: ModularComponentKind,
pub stats: tool::Stats,
pub hand_restriction: Option<Hands>,
pub weapon_name: String,
pub enum ModularComponent {
ToolPrimaryComponent {
toolkind: ToolKind,
stats: tool::Stats,
hand_restriction: Option<Hands>,
weapon_name: String,
},
ToolSecondaryComponent {
toolkind: ToolKind,
stats: tool::Stats,
hand_restriction: Option<Hands>,
},
}
impl ModularComponent {
pub fn tool_stats(
&self,
components: &[Item],
msm: &MaterialStatManifest,
) -> Option<tool::Stats> {
match self {
Self::ToolPrimaryComponent { stats, .. } => {
let mut material_multipliers = Vec::new();
for component in components.iter() {
if let Some(tool_stats) = msm.0.get(component.item_definition_id()) {
material_multipliers.push(*tool_stats);
}
}
// Take the average of the material multipliers
let material_mult = if !material_multipliers.is_empty() {
let mut average_mult = tool::Stats::zero();
for stat in material_multipliers.iter() {
average_mult += *stat;
}
average_mult /= material_multipliers.len();
average_mult
} else {
tool::Stats::one()
};
Some(*stats * material_mult)
},
Self::ToolSecondaryComponent { stats, .. } => Some(*stats),
}
}
}
const SUPPORTED_TOOLKINDS: [ToolKind; 6] = [
@ -180,124 +172,171 @@ fn make_weapon_id(toolkind: ToolKind) -> String {
format!("{}.{}", WEAPON_PREFIX, toolkind.identifier_name())
}
/// Returns directory that contains components for a particular combination of
/// toolkind and modular component kind
fn make_mod_comp_dir_spec(tool: ToolKind, mod_kind: ModularComponentKind) -> String {
const MOD_COMP_DIR_PREFIX: &str = "common.items.crafting_ing.modular";
format!(
"{}.{}.{}",
MOD_COMP_DIR_PREFIX,
mod_kind.identifier_name(),
tool.identifier_name()
)
lazy_static! {
static ref PRIMARY_COMPONENT_POOL: HashMap<(ToolKind, String), Vec<(Arc<ItemDef>, Option<Hands>)>> = {
let mut component_pool = HashMap::new();
// Load recipe book (done to check that material is valid for a particular component)
let recipe::RawRecipeBook(recipes) =
recipe::RawRecipeBook::load_expect_cloned("common.recipe_book");
const ASSET_PREFIX: &str = "common.items.crafting_ing.modular.primary";
// Closure to check that an Item has a recipe that uses the provided material
let valid_materials = |item: &str| {
// Iterate over all recipes in the raw recipe book
recipes
.values()
// Filter by recipes that have an output of the item of interest
.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
.find_map(|recipe| {
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
};
static ref SECONDARY_COMPONENT_POOL: HashMap<ToolKind, Vec<(Arc<ItemDef>, Option<Hands>)>> = {
let mut component_pool = HashMap::new();
const ASSET_PREFIX: &str = "common.items.crafting_ing.modular.secondary";
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::ToolSecondaryComponent { hand_restriction, .. }) = comp_def.kind {
let entry = component_pool.entry(toolkind).or_insert(Vec::new());
entry.push((Arc::clone(&comp_def), hand_restriction));
}
});
}
}
component_pool
};
}
#[derive(Debug)]
pub enum ModularWeaponCreationError {
MaterialNotFound,
PrimaryComponentNotFound,
SecondaryComponentNotFound,
}
/// Creates a random modular weapon when provided with a toolkind, material, and
/// optionally the handedness
pub fn random_weapon(tool: ToolKind, material: super::Material, hands: Option<Hands>) -> Item {
// Returns inner modular component of an item if it has one
fn unwrap_modular_component(item: &Item) -> Option<ModularComponent> {
if let ItemKind::ModularComponent(mod_comp) = &*item.kind() {
// TODO: Maybe get rid of clone?
Some(mod_comp.clone())
} else {
None
}
}
pub fn random_weapon(
tool: ToolKind,
material: Material,
hand_restriction: Option<Hands>,
) -> Result<Item, ModularWeaponCreationError> {
if let Some(material_id) = material.asset_identifier() {
// Loads default ability map and material stat manifest for later use
let ability_map = Default::default();
let msm = Default::default();
// Loads default ability map and material stat manifest for later use
let ability_map = Default::default();
let msm = Default::default();
let mut rng = thread_rng();
// Load recipe book (done to check that material is valid for a particular
// component)
let recipe::RawRecipeBook(recipes) =
recipe::RawRecipeBook::load_expect_cloned("common.recipe_book");
// Closure to check that an Item has a recipe that uses the provided material
let is_composed_of = |item: &str| {
// Iterate over all recipes in the raw recipe book
recipes
.values()
// Filter by recipes that have an output of the item of interest
.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
.any(|recipe| {
recipe
.inputs
let material = Item::new_from_asset_expect(material_id);
let primary_components = PRIMARY_COMPONENT_POOL
.get(&(tool, material_id.to_owned()))
.map_or(Vec::new(), |components| {
components
.iter()
.any(|input| {
match &input.0 {
recipe::RawRecipeInput::ListSameItem(items) => {
let assets = recipe::ItemList::load_expect_cloned(items).0;
assets.iter().any(|asset| Some(asset.as_str()) == material.asset_identifier())
},
_ => false,
}
.filter(|(_def, hand)| match (hand_restriction, hand) {
(Some(restriction), Some(hand)) => restriction == *hand,
(None, _) | (_, None) => true,
})
})
};
.map(|entry| (1.0, entry))
.collect::<Vec<_>>()
});
// Finds which component has a material as a subcomponent
let material_comp = ModularComponentKind::main_component(tool);
let (primary_component, hand_restriction) = {
let (def, hand) = primary_components
.choose(&mut rng)
.ok_or(ModularWeaponCreationError::PrimaryComponentNotFound)?
.1;
let comp = Item::new_from_item_base(
ItemBase::Raw(Arc::clone(def)),
&[material],
&ability_map,
&msm,
);
(comp, hand_restriction.or(*hand))
};
// Closure to return vec of components that are eligible to be used in the
// modular weapon
let create_component = |directory, hands| {
// Load directory of components
let components = Item::new_from_asset_glob(directory)
.expect("Asset directory did not properly load")
.into_iter()
// Filter by handedness requirement
.filter(|item| {
matches!(unwrap_modular_component(item), Some(ModularComponent { hand_restriction, .. }) if hand_restriction.zip(hands).map_or(true, |(hr1, hr2)| hr1 == hr2))
})
// Filter by if component does not have a material, or if material can be used in the modular component
.filter(|item| {
matches!(unwrap_modular_component(item), Some(ModularComponent { modkind, .. }) if modkind != material_comp)
|| is_composed_of(item.item_definition_id())
})
.map(|item| (1.0, item))
.collect::<Vec<_>>();
let secondary_components =
SECONDARY_COMPONENT_POOL
.get(&tool)
.map_or(Vec::new(), |components| {
components
.iter()
.filter(|(_def, hand)| match (hand_restriction, hand) {
(Some(restriction), Some(hand)) => restriction == *hand,
(None, _) | (_, None) => true,
})
.map(|entry| (1.0, entry))
.collect::<Vec<_>>()
});
// Create lottery and choose item
Lottery::<Item>::from(components).choose_owned()
};
let secondary_component = {
let def = &secondary_components
.choose(&mut rng)
.ok_or(ModularWeaponCreationError::SecondaryComponentNotFound)?
.1
.0;
Item::new_from_item_base(ItemBase::Raw(Arc::clone(def)), &[], &ability_map, &msm)
};
// Creates components of modular weapon
let damage_comp_dir = make_mod_comp_dir_spec(tool, ModularComponentKind::Damage);
let mut damage_component = create_component(&damage_comp_dir, hands);
// Takes whichever is more restrictive of hand restriction passed in and hand
// restriction from damage component e.g. if None is passed to function, and
// damage component chooses piece with two handed restriction, then makes held
// component have two handed restriction as well
let damage_hands = unwrap_modular_component(&damage_component)
.and_then(|mc| mc.hand_restriction)
.or(hands);
let held_comp_dir = make_mod_comp_dir_spec(tool, ModularComponentKind::Held);
let mut held_component = create_component(&held_comp_dir, damage_hands);
let material_component = Item::new_from_asset_expect(material.asset_identifier().expect(
"Code reviewers: open comment here if I forget about this, I got lazy during a rebase",
));
// Insert material item into modular component of appropriate kind
match material_comp {
ModularComponentKind::Damage => {
damage_component.add_component(material_component, &ability_map, &msm);
},
ModularComponentKind::Held => {
held_component.add_component(material_component, &ability_map, &msm);
},
// Create modular weapon
let components = vec![primary_component, secondary_component];
Ok(Item::new_from_item_base(
ItemBase::Modular(ModularBase::Tool(tool)),
&components,
&ability_map,
&msm,
))
} else {
Err(ModularWeaponCreationError::MaterialNotFound)
}
// Create modular weapon
let components = vec![damage_component, held_component];
Item::new_from_item_base(
ItemBase::Modular(ModularBase::Tool(tool)),
&components,
&ability_map,
&msm,
)
}
/// This is used as a key to uniquely identify the modular weapon in asset
@ -311,27 +350,24 @@ pub fn weapon_to_key(mod_weap: &dyn ItemDesc) -> ModularWeaponKey {
Hands::One
};
let (main_comp, material) = if let Some(main_comp) = mod_weap.components().iter().find(|comp| {
matches!(&*comp.kind(), ItemKind::ModularComponent(mod_comp) if ModularComponentKind::main_component(mod_comp.toolkind) == mod_comp.modkind)
}) {
let material = if let Some(material) = main_comp.components().iter().filter_map(|mat| {
if let Some(super::ItemTag::Material(material)) = mat.tags().iter().find(|tag| matches!(tag, super::ItemTag::Material(_))) {
Some(material)
} else {
None
}
}).next() {
material.into()
} else {
""
};
(main_comp.item_definition_id(), material)
} else {
("", "")
};
(main_comp.to_owned(), material.to_owned(), hands)
match mod_weap
.components()
.iter()
.find_map(|comp| match &*comp.kind() {
ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent { .. }) => {
let component_id = comp.item_definition_id().to_owned();
let material_id = comp.components().iter().find_map(|mat| match &*mat.kind() {
ItemKind::Ingredient { .. } => Some(mat.item_definition_id().to_owned()),
_ => None,
});
Some((component_id, material_id))
},
_ => None,
}) {
Some((component_id, Some(material_id))) => (component_id, material_id, hands),
Some((component_id, None)) => (component_id, String::new(), hands),
None => (String::new(), String::new(), hands),
}
}
/// This is used as a key to uniquely identify the modular weapon in asset
@ -339,44 +375,30 @@ pub fn weapon_to_key(mod_weap: &dyn ItemDesc) -> ModularWeaponKey {
pub type ModularWeaponComponentKey = (String, String);
pub enum ModularWeaponComponentKeyError {
NotModularComponent,
MaterialNotFound,
NotMainComponent,
}
pub fn weapon_component_to_key(
mod_weap_comp: &dyn ItemDesc,
) -> Result<ModularWeaponComponentKey, ModularWeaponComponentKeyError> {
if let ItemKind::ModularComponent(mod_comp) = &*mod_weap_comp.kind() {
if ModularComponentKind::main_component(mod_comp.toolkind) == mod_comp.modkind {
let material = if let Some(material) = mod_weap_comp
.components()
.iter()
.filter_map(|mat| {
if let Some(super::ItemTag::Material(material)) = mat
.tags()
.iter()
.find(|tag| matches!(tag, super::ItemTag::Material(_)))
{
Some(material)
} else {
None
}
})
.next()
{
material.into()
} else {
""
};
Ok((
mod_weap_comp.item_definition_id().to_owned(),
material.to_owned(),
))
} else {
Err(ModularWeaponComponentKeyError::NotMainComponent)
}
match if let ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent { .. }) =
&*mod_weap_comp.kind()
{
let component_id = mod_weap_comp.item_definition_id().to_owned();
let material_id = mod_weap_comp
.components()
.iter()
.find_map(|mat| match &*mat.kind() {
ItemKind::Ingredient { .. } => Some(mat.item_definition_id().to_owned()),
_ => None,
});
Some((component_id, material_id))
} else {
Err(ModularWeaponComponentKeyError::NotModularComponent)
None
} {
Some((component_id, Some(material_id))) => Ok((component_id, material_id)),
Some((_component_id, None)) => Err(ModularWeaponComponentKeyError::MaterialNotFound),
None => Err(ModularWeaponComponentKeyError::NotMainComponent),
}
}

View File

@ -133,7 +133,7 @@ impl Hands {
tool,
material,
hands,
} => Some(item::modular::random_weapon(*tool, *material, *hands)),
} => item::modular::random_weapon(*tool, *material, *hands).ok(),
}
}

View File

@ -69,21 +69,8 @@ impl<T> Lottery<T> {
.1
}
pub fn choose_seeded_owned(mut self, seed: u32) -> T {
let x = ((seed % 65536) as f32 / 65536.0) * self.total;
self.items
.remove(
self.items
.binary_search_by(|(y, _)| y.partial_cmp(&x).unwrap())
.unwrap_or_else(|i| i.saturating_sub(1)),
)
.1
}
pub fn choose(&self) -> &T { self.choose_seeded(thread_rng().gen()) }
pub fn choose_owned(self) -> T { self.choose_seeded_owned(thread_rng().gen()) }
pub fn iter(&self) -> impl Iterator<Item = &(f32, T)> { self.items.iter() }
pub fn total(&self) -> f32 { self.total }
@ -143,7 +130,7 @@ impl<T: AsRef<str>> LootSpec<T> {
tool,
material,
hands,
} => Some(item::modular::random_weapon(*tool, *material, *hands)),
} => item::modular::random_weapon(*tool, *material, *hands).ok(),
}
}
}

View File

@ -243,12 +243,12 @@ pub enum ModularWeaponError {
pub fn modular_weapon(
inv: &mut Inventory,
damage_component: InvSlotId,
held_component: InvSlotId,
primary_component: InvSlotId,
secondary_component: InvSlotId,
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Result<Item, ModularWeaponError> {
use modular::{ModularComponent, ModularComponentKind};
use modular::ModularComponent;
// Closure to get inner modular component info from item in a given slot
fn unwrap_modular(inv: &Inventory, slot: InvSlotId) -> Option<ModularComponent> {
if let Some(ItemKind::ModularComponent(mod_comp)) =
@ -263,25 +263,32 @@ pub fn modular_weapon(
// Checks if both components are comptabile, and if so returns the toolkind to
// make weapon of
let compatiblity = if let (Some(damage_component), Some(held_component)) = (
unwrap_modular(inv, damage_component),
unwrap_modular(inv, held_component),
let compatiblity = if let (Some(primary_component), Some(secondary_component)) = (
unwrap_modular(inv, primary_component),
unwrap_modular(inv, secondary_component),
) {
// Checks that damage and held component slots each contain a damage and held
// modular component respectively
if matches!(damage_component.modkind, ModularComponentKind::Damage)
&& matches!(held_component.modkind, ModularComponentKind::Held)
if let (
ModularComponent::ToolPrimaryComponent {
toolkind: tool_a,
hand_restriction: hands_a,
..
},
ModularComponent::ToolSecondaryComponent {
toolkind: tool_b,
hand_restriction: hands_b,
..
},
) = (primary_component, secondary_component)
{
// Checks that both components are of the same tool kind
if damage_component.toolkind == held_component.toolkind {
if tool_a == tool_b {
// Checks that if both components have a hand restriction, they are the same
let hands_check = damage_component.hand_restriction.map_or(true, |hands| {
held_component
.hand_restriction
.map_or(true, |hands2| hands == hands2)
});
let hands_check =
hands_a.map_or(true, |hands| hands_b.map_or(true, |hands2| hands == hands2));
if hands_check {
Ok(damage_component.toolkind)
Ok(tool_a)
} else {
Err(ModularWeaponError::DifferentHands)
}
@ -298,15 +305,15 @@ pub fn modular_weapon(
match compatiblity {
Ok(tool_kind) => {
// Remove components from inventory
let damage_component = inv
.take(damage_component, ability_map, msm)
let primary_component = inv
.take(primary_component, ability_map, msm)
.expect("Expected component to exist");
let held_component = inv
.take(held_component, ability_map, msm)
let secondary_component = inv
.take(secondary_component, ability_map, msm)
.expect("Expected component to exist");
// Create modular weapon
let components = vec![damage_component, held_component];
let components = vec![primary_component, secondary_component];
Ok(Item::new_from_item_base(
ItemBase::Modular(modular::ModularBase::Tool(tool_kind)),
&components,

View File

@ -1946,6 +1946,7 @@ where
.map_err(|_| format!("Unknown item: {:#?}", item_id))?,
KitSpec::ModularWeapon { tool, material } => {
comp::item::modular::random_weapon(*tool, *material, None)
.map_err(|err| format!("{:#?}", err))?
},
};
let mut res = Ok(());

View File

@ -668,8 +668,8 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
}
},
CraftEvent::ModularWeapon {
damage_component,
held_component,
primary_component,
secondary_component,
} => {
let sprite = craft_sprite
.filter(|pos| {
@ -696,8 +696,8 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
if matches!(sprite, Some(SpriteKind::CraftingBench)) {
recipe::modular_weapon(
&mut inventory,
damage_component,
held_component,
primary_component,
secondary_component,
ability_map,
&msm,
)

View File

@ -106,9 +106,12 @@ pub fn modular_component_desc(
msm: &MaterialStatManifest,
description: &str,
) -> String {
let stats = mc.stats;
let statblock = statblock_desc(&stats);
let mut result = format!("Modular Component\n\n{}\n\n{}", statblock, description);
let mut result = format!("Modular Component\n\n{}", description);
if let Some(tool_stats) = mc.tool_stats(components, msm) {
let statblock = statblock_desc(&tool_stats);
result += "\n\n";
result += &statblock;
}
if !components.is_empty() {
result += "\n\nMade from:\n";
for component in components {