use super::{
armor,
tool::{self, AbilityMap, AbilitySpec, Hands, Tool},
DurabilityMultiplier, Item, ItemBase, ItemDef, ItemDesc, ItemKind, ItemTag, Material, Quality,
ToolKind,
};
use crate::{
assets::{self, Asset, AssetExt, AssetHandle},
recipe,
};
use common_base::dev_panic;
use hashbrown::HashMap;
use lazy_static::lazy_static;
use rand::{prelude::SliceRandom, Rng};
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, sync::Arc};
#[macro_export]
macro_rules! modular_item_id_prefix {
() => {
"veloren.core.pseudo_items.modular."
};
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct MaterialStatManifest {
tool_stats: HashMap<String, tool::Stats>,
armor_stats: HashMap<String, armor::Stats>,
}
impl MaterialStatManifest {
pub fn load() -> AssetHandle<Self> { Self::load_expect("common.material_stats_manifest") }
pub fn armor_stats(&self, key: &str) -> Option<armor::Stats> {
self.armor_stats.get(key).copied()
}
#[doc(hidden)]
pub fn with_empty() -> Self {
Self {
tool_stats: HashMap::default(),
armor_stats: HashMap::default(),
}
}
}
impl Asset for MaterialStatManifest {
type Loader = assets::RonLoader;
const EXTENSION: &'static str = "ron";
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum ModularBase {
Tool,
}
impl ModularBase {
pub fn pseudo_item_id(&self) -> &str {
match self {
ModularBase::Tool => concat!(modular_item_id_prefix!(), "tool"),
}
}
pub fn load_from_pseudo_id(id: &str) -> Self {
match id {
concat!(modular_item_id_prefix!(), "tool") => ModularBase::Tool,
_ => panic!("Attempted to load a non existent pseudo item: {}", id),
}
}
fn resolve_hands(components: &[Item]) -> Hands {
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,
});
hand_restriction.unwrap_or(Hands::One)
}
#[inline(never)]
pub(super) fn kind(
&self,
components: &[Item],
msm: &MaterialStatManifest,
durability_multiplier: DurabilityMultiplier,
) -> Cow<ItemKind> {
let toolkind = components
.iter()
.find_map(|comp| match &*comp.kind() {
ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent {
toolkind,
..
}) => Some(*toolkind),
_ => None,
})
.unwrap_or(ToolKind::Empty);
let stats: 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)
* durability_multiplier;
match self {
ModularBase::Tool => Cow::Owned(ItemKind::Tool(Tool::new(
toolkind,
Self::resolve_hands(components),
stats,
))),
}
}
pub fn generate_name(&self, components: &[Item]) -> Cow<str> {
match self {
ModularBase::Tool => {
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() {
#[allow(deprecated)]
Cow::Owned(ItemKind::Ingredient { descriptor, .. }) => {
Some(Cow::Owned(descriptor))
},
#[allow(deprecated)]
Cow::Borrowed(ItemKind::Ingredient { descriptor, .. }) => {
Some(Cow::Borrowed(descriptor.as_str()))
},
_ => None,
})
.unwrap_or_else(|| "Modular".into());
Some(format!(
"{} {}",
material_name,
weapon_name.resolve_name(Self::resolve_hands(components))
))
},
_ => None,
})
.unwrap_or_else(|| "Modular Weapon".to_owned());
Cow::Owned(name)
},
}
}
pub fn compute_quality(&self, components: &[Item]) -> Quality {
components
.iter()
.fold(Quality::MIN, |a, b| a.max(b.quality()))
}
pub fn ability_spec(&self, components: &[Item]) -> Option<Cow<AbilitySpec>> {
match self {
ModularBase::Tool => components.iter().find_map(|comp| match &*comp.kind() {
ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent {
toolkind,
..
}) => Some(Cow::Owned(AbilitySpec::Tool(*toolkind))),
_ => None,
}),
}
}
pub fn generate_tags(&self, components: &[Item]) -> Vec<ItemTag> {
match self {
ModularBase::Tool => {
if let Some(comp) = components.iter().find(|comp| {
matches!(
&*comp.kind(),
ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent { .. })
)
}) {
if let Some(material) =
comp.components()
.iter()
.find_map(|comp| match &*comp.kind() {
ItemKind::Ingredient { .. } => {
comp.tags().into_iter().find_map(|tag| match tag {
ItemTag::Material(material) => Some(material),
_ => None,
})
},
_ => None,
})
{
vec![
ItemTag::Material(material),
ItemTag::SalvageInto(material, 1),
]
} else {
Vec::new()
}
} else {
Vec::new()
}
},
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(deny_unknown_fields)]
pub enum ModularComponent {
ToolPrimaryComponent {
toolkind: ToolKind,
stats: tool::Stats,
hand_restriction: Option<Hands>,
weapon_name: WeaponName,
},
ToolSecondaryComponent {
toolkind: ToolKind,
stats: tool::Stats,
hand_restriction: Option<Hands>,
},
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum WeaponName {
Universal(String),
HandednessDependent {
one_handed: String,
two_handed: String,
},
}
impl WeaponName {
fn resolve_name(&self, handedness: Hands) -> &str {
match self {
Self::Universal(name) => name,
Self::HandednessDependent {
one_handed: name1,
two_handed: name2,
} => match handedness {
Hands::One => name1,
Hands::Two => name2,
},
}
}
}
impl ModularComponent {
pub fn tool_stats(
&self,
components: &[Item],
msm: &MaterialStatManifest,
) -> Option<tool::Stats> {
match self {
Self::ToolPrimaryComponent { stats, .. } => {
let average_material_mult = components
.iter()
.filter_map(|comp| {
comp.item_definition_id()
.itemdef_id()
.and_then(|id| msm.tool_stats.get(id))
.copied()
.zip(Some(1))
})
.reduce(|(stats_a, count_a), (stats_b, count_b)| {
(stats_a + stats_b, count_a + count_b)
})
.map_or_else(tool::Stats::one, |(stats_sum, count)| {
stats_sum / (count as f32)
});
Some(*stats * average_material_mult)
},
Self::ToolSecondaryComponent { stats, .. } => Some(*stats),
}
}
pub fn toolkind(&self) -> Option<ToolKind> {
match self {
Self::ToolPrimaryComponent { toolkind, .. }
| Self::ToolSecondaryComponent { toolkind, .. } => Some(*toolkind),
}
}
}
const SUPPORTED_TOOLKINDS: [ToolKind; 6] = [
ToolKind::Sword,
ToolKind::Axe,
ToolKind::Hammer,
ToolKind::Bow,
ToolKind::Staff,
ToolKind::Sceptre,
];
type PrimaryComponentPool = HashMap<(ToolKind, String), Vec<(Item, Option<Hands>)>>;
type SecondaryComponentPool = HashMap<ToolKind, Vec<(Arc<ItemDef>, Option<Hands>)>>;
lazy_static! {
pub static ref PRIMARY_COMPONENT_POOL: PrimaryComponentPool = {
let mut component_pool = HashMap::new();
use crate::recipe::ComponentKey;
let recipes = recipe::default_component_recipe_book().read();
let ability_map = &AbilityMap::load().read();
let msm = &MaterialStatManifest::load().read();
recipes.iter().for_each(
|(
ComponentKey {
toolkind, material, ..
},
recipe,
)| {
let component = recipe.item_output(ability_map, msm);
let hand_restriction =
if let ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent {
hand_restriction,
..
}) = &*component.kind()
{
*hand_restriction
} else {
return;
};
let entry: &mut Vec<_> = component_pool
.entry((*toolkind, String::from(material)))
.or_default();
entry.push((component, hand_restriction));
},
);
component_pool
};
static ref SECONDARY_COMPONENT_POOL: SecondaryComponentPool = {
let mut component_pool = HashMap::new();
const ASSET_PREFIX: &str = "common.items.modular.weapon.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()
.filter_map(|comp| Some(comp.item_definition_id().itemdef_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: &mut Vec<_> = component_pool.entry(toolkind).or_default();
entry.push((Arc::clone(&comp_def), hand_restriction));
}
});
}
}
component_pool
};
}
#[derive(Debug)]
pub enum ModularWeaponCreationError {
MaterialNotFound,
PrimaryComponentNotFound,
SecondaryComponentNotFound,
}
pub fn compatible_handedness(a: Option<Hands>, b: Option<Hands>) -> bool {
match (a, b) {
(Some(a), Some(b)) => a == b,
_ => true,
}
}
pub fn generate_weapon_primary_components(
tool: ToolKind,
material: Material,
hand_restriction: Option<Hands>,
) -> Result<Vec<(Item, Option<Hands>)>, ModularWeaponCreationError> {
if let Some(material_id) = material.asset_identifier() {
let ability_map = &AbilityMap::load().read();
let msm = &MaterialStatManifest::load().read();
Ok(PRIMARY_COMPONENT_POOL
.get(&(tool, material_id.to_owned()))
.into_iter()
.flatten()
.filter(|(_comp, hand)| compatible_handedness(hand_restriction, *hand))
.map(|(c, h)| (c.duplicate(ability_map, msm), hand_restriction.or(*h)))
.collect())
} else {
Err(ModularWeaponCreationError::MaterialNotFound)
}
}
pub fn random_weapon_primary_component(
tool: ToolKind,
material: Material,
hand_restriction: Option<Hands>,
mut rng: &mut impl Rng,
) -> Result<(Item, Option<Hands>), ModularWeaponCreationError> {
let result = {
if let Some(material_id) = material.asset_identifier() {
let ability_map = &AbilityMap::load().read();
let msm = &MaterialStatManifest::load().read();
let primary_components = PRIMARY_COMPONENT_POOL
.get(&(tool, material_id.to_owned()))
.into_iter()
.flatten()
.filter(|(_comp, hand)| compatible_handedness(hand_restriction, *hand))
.collect::<Vec<_>>();
let (comp, hand) = primary_components
.choose(&mut rng)
.ok_or(ModularWeaponCreationError::PrimaryComponentNotFound)?;
let comp = comp.duplicate(ability_map, msm);
Ok((comp, hand_restriction.or(*hand)))
} else {
Err(ModularWeaponCreationError::MaterialNotFound)
}
};
if let Err(err) = &result {
let error_str = format!(
"Failed to synthesize a primary component for a modular {tool:?} made of {material:?} \
that had a hand restriction of {hand_restriction:?}. Error: {err:?}"
);
dev_panic!(error_str)
}
result
}
pub fn generate_weapons(
tool: ToolKind,
material: Material,
hand_restriction: Option<Hands>,
) -> Result<Vec<Item>, ModularWeaponCreationError> {
let ability_map = &AbilityMap::load().read();
let msm = &MaterialStatManifest::load().read();
let primaries = generate_weapon_primary_components(tool, material, hand_restriction)?;
let mut weapons = Vec::new();
for (comp, comp_hand) in primaries {
let secondaries = SECONDARY_COMPONENT_POOL
.get(&tool)
.into_iter()
.flatten()
.filter(|(_def, hand)| compatible_handedness(hand_restriction, *hand))
.filter(|(_def, hand)| compatible_handedness(comp_hand, *hand));
for (def, _hand) in secondaries {
let secondary = Item::new_from_item_base(
ItemBase::Simple(Arc::clone(def)),
Vec::new(),
ability_map,
msm,
);
let it = Item::new_from_item_base(
ItemBase::Modular(ModularBase::Tool),
vec![comp.duplicate(ability_map, msm), secondary],
ability_map,
msm,
);
weapons.push(it);
}
}
Ok(weapons)
}
pub fn random_weapon(
tool: ToolKind,
material: Material,
hand_restriction: Option<Hands>,
mut rng: &mut impl Rng,
) -> Result<Item, ModularWeaponCreationError> {
let result = {
let ability_map = &AbilityMap::load().read();
let msm = &MaterialStatManifest::load().read();
let (primary_component, hand_restriction) =
random_weapon_primary_component(tool, material, hand_restriction, rng)?;
let secondary_components = SECONDARY_COMPONENT_POOL
.get(&tool)
.into_iter()
.flatten()
.filter(|(_def, hand)| compatible_handedness(hand_restriction, *hand))
.collect::<Vec<_>>();
let secondary_component = {
let def = &secondary_components
.choose(&mut rng)
.ok_or(ModularWeaponCreationError::SecondaryComponentNotFound)?
.0;
Item::new_from_item_base(
ItemBase::Simple(Arc::clone(def)),
Vec::new(),
ability_map,
msm,
)
};
Ok(Item::new_from_item_base(
ItemBase::Modular(ModularBase::Tool),
vec![primary_component, secondary_component],
ability_map,
msm,
))
};
if let Err(err) = &result {
let error_str = format!(
"Failed to synthesize a modular {tool:?} made of {material:?} that had a hand \
restriction of {hand_restriction:?}. Error: {err:?}"
);
dev_panic!(error_str)
}
result
}
pub fn modify_name<'a>(item_name: &'a str, item: &'a Item) -> Cow<'a, str> {
if let ItemKind::ModularComponent(_) = &*item.kind() {
if let Some(material_name) = item
.components()
.iter()
.find_map(|comp| match &*comp.kind() {
#[allow(deprecated)]
ItemKind::Ingredient { descriptor, .. } => Some(descriptor.to_owned()),
_ => None,
})
{
Cow::Owned(format!("{} {}", material_name, item_name))
} else {
Cow::Borrowed(item_name)
}
} else {
Cow::Borrowed(item_name)
}
}
pub type ModularWeaponKey = (String, String, Hands);
pub fn weapon_to_key(mod_weap: impl ItemDesc) -> ModularWeaponKey {
let hands = if let ItemKind::Tool(tool) = &*mod_weap.kind() {
tool.hands
} else {
Hands::One
};
match mod_weap
.components()
.iter()
.find_map(|comp| match &*comp.kind() {
ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent { .. }) => {
let component_id = comp.item_definition_id().itemdef_id()?.to_owned();
let material_id = comp.components().iter().find_map(|mat| match &*mat.kind() {
ItemKind::Ingredient { .. } => {
Some(mat.item_definition_id().itemdef_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),
}
}
pub type ModularWeaponComponentKey = (String, String);
pub enum ModularWeaponComponentKeyError {
MaterialNotFound,
}
pub fn weapon_component_to_key(
item_def_id: &str,
components: &[Item],
) -> Result<ModularWeaponComponentKey, ModularWeaponComponentKeyError> {
match components.iter().find_map(|mat| match &*mat.kind() {
ItemKind::Ingredient { .. } => Some(mat.item_definition_id().itemdef_id()?.to_owned()),
_ => None,
}) {
Some(material_id) => Ok((item_def_id.to_owned(), material_id)),
None => Err(ModularWeaponComponentKeyError::MaterialNotFound),
}
}