mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
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:
parent
b048179c0a
commit
a9e9a70687
@ -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
|
||||
let mod_kind = |slot| match inventory
|
||||
.and_then(|inv| inv.get(slot).map(|item| item.kind()))
|
||||
.as_deref()
|
||||
{
|
||||
Some(mod_comp.modkind)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
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),
|
||||
},
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
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) -> tool::Stats {
|
||||
components
|
||||
.iter()
|
||||
.filter_map(|comp| {
|
||||
if let ItemKind::ModularComponent(mod_comp) = &*comp.kind() {
|
||||
mod_comp.tool_stats(comp.components(), msm)
|
||||
} else {
|
||||
Hands::One
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
.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()
|
||||
ModularBase::Tool(_toolkind) => {
|
||||
let name = 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 {
|
||||
.find_map(|comp| match &*comp.kind() {
|
||||
ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent {
|
||||
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))
|
||||
}) => {
|
||||
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();
|
||||
|
||||
/// 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
|
||||
}
|
||||
}
|
||||
|
||||
// Loads default ability map and material stat manifest for later use
|
||||
let ability_map = Default::default();
|
||||
let msm = Default::default();
|
||||
|
||||
// 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::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 is_composed_of = |item: &str| {
|
||||
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
|
||||
.any(|recipe| {
|
||||
.find_map(|recipe| {
|
||||
recipe
|
||||
.inputs
|
||||
.iter()
|
||||
.any(|input| {
|
||||
.find_map(|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())
|
||||
Some(recipe::ItemList::load_expect_cloned(items).0)
|
||||
},
|
||||
_ => false,
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
})
|
||||
};
|
||||
|
||||
// Finds which component has a material as a subcomponent
|
||||
let material_comp = ModularComponentKind::main_component(tool);
|
||||
|
||||
// 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")
|
||||
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()
|
||||
// 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<_>>();
|
||||
.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Create lottery and choose item
|
||||
Lottery::<Item>::from(components).choose_owned()
|
||||
component_pool
|
||||
};
|
||||
|
||||
// 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",
|
||||
));
|
||||
static ref SECONDARY_COMPONENT_POOL: HashMap<ToolKind, Vec<(Arc<ItemDef>, Option<Hands>)>> = {
|
||||
let mut component_pool = HashMap::new();
|
||||
|
||||
// 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);
|
||||
},
|
||||
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: 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();
|
||||
|
||||
let mut rng = thread_rng();
|
||||
|
||||
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()
|
||||
.filter(|(_def, hand)| match (hand_restriction, hand) {
|
||||
(Some(restriction), Some(hand)) => restriction == *hand,
|
||||
(None, _) | (_, None) => true,
|
||||
})
|
||||
.map(|entry| (1.0, entry))
|
||||
.collect::<Vec<_>>()
|
||||
});
|
||||
|
||||
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))
|
||||
};
|
||||
|
||||
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<_>>()
|
||||
});
|
||||
|
||||
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)
|
||||
};
|
||||
|
||||
// Create modular weapon
|
||||
let components = vec![damage_component, held_component];
|
||||
Item::new_from_item_base(
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/// 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)
|
||||
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,
|
||||
}) {
|
||||
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
|
||||
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),
|
||||
}
|
||||
}).next() {
|
||||
material.into()
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
(main_comp.item_definition_id(), material)
|
||||
} else {
|
||||
("", "")
|
||||
};
|
||||
|
||||
(main_comp.to_owned(), material.to_owned(), 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
|
||||
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()
|
||||
.filter_map(|mat| {
|
||||
if let Some(super::ItemTag::Material(material)) = mat
|
||||
.tags()
|
||||
.iter()
|
||||
.find(|tag| matches!(tag, super::ItemTag::Material(_)))
|
||||
{
|
||||
Some(material)
|
||||
.find_map(|mat| match &*mat.kind() {
|
||||
ItemKind::Ingredient { .. } => Some(mat.item_definition_id().to_owned()),
|
||||
_ => None,
|
||||
});
|
||||
Some((component_id, material_id))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.next()
|
||||
{
|
||||
material.into()
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
Ok((
|
||||
mod_weap_comp.item_definition_id().to_owned(),
|
||||
material.to_owned(),
|
||||
))
|
||||
} else {
|
||||
Err(ModularWeaponComponentKeyError::NotMainComponent)
|
||||
}
|
||||
} else {
|
||||
Err(ModularWeaponComponentKeyError::NotModularComponent)
|
||||
} {
|
||||
Some((component_id, Some(material_id))) => Ok((component_id, material_id)),
|
||||
Some((_component_id, None)) => Err(ModularWeaponComponentKeyError::MaterialNotFound),
|
||||
None => Err(ModularWeaponComponentKeyError::NotMainComponent),
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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(());
|
||||
|
@ -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,
|
||||
)
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user