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},
|
chat::{KillSource, KillType},
|
||||||
controller::CraftEvent,
|
controller::CraftEvent,
|
||||||
group,
|
group,
|
||||||
|
inventory::item::{modular, ItemKind},
|
||||||
invite::{InviteKind, InviteResponse},
|
invite::{InviteKind, InviteResponse},
|
||||||
skills::Skill,
|
skills::Skill,
|
||||||
slot::{EquipSlot, InvSlotId, Slot},
|
slot::{EquipSlot, InvSlotId, Slot},
|
||||||
@ -1082,41 +1083,44 @@ impl Client {
|
|||||||
slot_b: InvSlotId,
|
slot_b: InvSlotId,
|
||||||
sprite_pos: Vec3<i32>,
|
sprite_pos: Vec3<i32>,
|
||||||
) -> bool {
|
) -> bool {
|
||||||
use comp::item::{
|
|
||||||
modular::ModularComponentKind::{Damage, Held},
|
|
||||||
ItemKind,
|
|
||||||
};
|
|
||||||
let inventories = self.inventories();
|
let inventories = self.inventories();
|
||||||
let inventory = inventories.get(self.entity());
|
let inventory = inventories.get(self.entity());
|
||||||
|
|
||||||
|
enum ModKind {
|
||||||
|
Primary,
|
||||||
|
Secondary,
|
||||||
|
}
|
||||||
|
|
||||||
// Closure to get inner modular component info from item in a given slot
|
// Closure to get inner modular component info from item in a given slot
|
||||||
let unwrap_modular = |slot| {
|
let mod_kind = |slot| match inventory
|
||||||
if let Some(ItemKind::ModularComponent(mod_comp)) = inventory
|
.and_then(|inv| inv.get(slot).map(|item| item.kind()))
|
||||||
.and_then(|inv| inv.get(slot).map(|item| item.kind()))
|
.as_deref()
|
||||||
.as_deref()
|
{
|
||||||
{
|
Some(ItemKind::ModularComponent(modular::ModularComponent::ToolPrimaryComponent {
|
||||||
Some(mod_comp.modkind)
|
..
|
||||||
} else {
|
})) => Some(ModKind::Primary),
|
||||||
None
|
Some(ItemKind::ModularComponent(
|
||||||
}
|
modular::ModularComponent::ToolSecondaryComponent { .. },
|
||||||
|
)) => Some(ModKind::Secondary),
|
||||||
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Gets slot order of damage and held components if two provided slots contains
|
// Gets slot order of damage and held components if two provided slots contains
|
||||||
// both a damage and held component
|
// both a primary and secondary component
|
||||||
let slot_order = match (unwrap_modular(slot_a), unwrap_modular(slot_b)) {
|
let slot_order = match (mod_kind(slot_a), mod_kind(slot_b)) {
|
||||||
(Some(Damage), Some(Held)) => Some((slot_a, slot_b)),
|
(Some(ModKind::Primary), Some(ModKind::Secondary)) => Some((slot_a, slot_b)),
|
||||||
(Some(Held), Some(Damage)) => Some((slot_b, slot_a)),
|
(Some(ModKind::Secondary), Some(ModKind::Primary)) => Some((slot_b, slot_a)),
|
||||||
_ => None,
|
_ => None,
|
||||||
};
|
};
|
||||||
|
|
||||||
drop(inventories);
|
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(
|
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
|
||||||
InventoryEvent::CraftRecipe {
|
InventoryEvent::CraftRecipe {
|
||||||
craft_event: CraftEvent::ModularWeapon {
|
craft_event: CraftEvent::ModularWeapon {
|
||||||
damage_component,
|
primary_component,
|
||||||
held_component,
|
secondary_component,
|
||||||
},
|
},
|
||||||
craft_sprite: Some(sprite_pos),
|
craft_sprite: Some(sprite_pos),
|
||||||
},
|
},
|
||||||
|
@ -100,8 +100,8 @@ pub enum CraftEvent {
|
|||||||
Salvage(InvSlotId),
|
Salvage(InvSlotId),
|
||||||
// TODO: Maybe look at making this more general when there are more modular recipes?
|
// TODO: Maybe look at making this more general when there are more modular recipes?
|
||||||
ModularWeapon {
|
ModularWeapon {
|
||||||
damage_component: InvSlotId,
|
primary_component: InvSlotId,
|
||||||
held_component: InvSlotId,
|
secondary_component: InvSlotId,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ pub mod modular;
|
|||||||
pub mod tool;
|
pub mod tool;
|
||||||
|
|
||||||
// Reexports
|
// Reexports
|
||||||
pub use modular::{ModularBase, ModularComponent, ModularComponentKind};
|
pub use modular::{ModularBase, ModularComponent};
|
||||||
pub use tool::{AbilitySet, AbilitySpec, Hands, MaterialStatManifest, Tool, ToolKind};
|
pub use tool::{AbilitySet, AbilitySpec, Hands, MaterialStatManifest, Tool, ToolKind};
|
||||||
|
|
||||||
use crate::{
|
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
|
// currently needed by trade_pricing
|
||||||
pub fn id(&self) -> &str { &self.item_definition_id }
|
pub fn id(&self) -> &str { &self.item_definition_id }
|
||||||
|
|
||||||
@ -896,11 +888,14 @@ impl Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn kind(&self) -> Cow<ItemKind> {
|
pub fn kind(&self) -> Cow<ItemKind> {
|
||||||
// TODO: Try to move further upward
|
|
||||||
let msm = MaterialStatManifest::default();
|
|
||||||
match &self.item_base {
|
match &self.item_base {
|
||||||
ItemBase::Raw(item_def) => Cow::Borrowed(&item_def.kind),
|
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::{
|
use super::{
|
||||||
tool::{self, AbilitySpec, Hands, MaterialStatManifest, Stats},
|
tool::{self, AbilitySpec, Hands, MaterialStatManifest},
|
||||||
Item, ItemBase, ItemDesc, ItemKind, Quality, ToolKind,
|
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 serde::{Deserialize, Serialize};
|
||||||
use std::borrow::Cow;
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub enum ModularBase {
|
pub enum ModularBase {
|
||||||
@ -22,45 +25,32 @@ impl ModularBase {
|
|||||||
fn resolve_hands(components: &[Item]) -> Hands {
|
fn resolve_hands(components: &[Item]) -> Hands {
|
||||||
// Checks if weapon has components that restrict hands to two. Restrictions to
|
// Checks if weapon has components that restrict hands to two. Restrictions to
|
||||||
// one hand or no restrictions default to one-handed weapon.
|
// 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))));
|
let hand_restriction = components.iter().find_map(|comp| match &*comp.kind() {
|
||||||
// If weapon is two handed, make it two handed
|
ItemKind::ModularComponent(mc) => match mc {
|
||||||
if is_two_handed {
|
ModularComponent::ToolPrimaryComponent {
|
||||||
Hands::Two
|
hand_restriction, ..
|
||||||
} else {
|
}
|
||||||
Hands::One
|
| 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 {
|
pub fn resolve_stats(components: &[Item], msm: &MaterialStatManifest) -> tool::Stats {
|
||||||
let mut stats = Stats::one();
|
components
|
||||||
let mut material_multipliers: Vec<Stats> = Vec::new();
|
.iter()
|
||||||
for item in components.iter() {
|
.filter_map(|comp| {
|
||||||
match &*item.kind() {
|
if let ItemKind::ModularComponent(mod_comp) = &*comp.kind() {
|
||||||
// Modular components directly multiply against the base stats
|
mod_comp.tool_stats(comp.components(), msm)
|
||||||
ItemKind::ModularComponent(mc) => {
|
} else {
|
||||||
let inner_stats = mc.stats * resolve_stats(item.components(), msm);
|
None
|
||||||
stats *= inner_stats;
|
}
|
||||||
},
|
})
|
||||||
// Ingredients push multiplier to vec as the ingredient multipliers are averaged
|
.fold(tool::Stats::one(), |a, b| a * b)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
@ -77,42 +67,30 @@ impl ModularBase {
|
|||||||
/// the damage component is created from.
|
/// the damage component is created from.
|
||||||
pub fn generate_name(&self, components: &[Item]) -> Cow<str> {
|
pub fn generate_name(&self, components: &[Item]) -> Cow<str> {
|
||||||
match self {
|
match self {
|
||||||
ModularBase::Tool(toolkind) => {
|
ModularBase::Tool(_toolkind) => {
|
||||||
// Closure to get material name from an item
|
let name = components
|
||||||
fn material_name(component: &Item) -> String {
|
.iter()
|
||||||
component
|
.find_map(|comp| match &*comp.kind() {
|
||||||
.components()
|
ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent {
|
||||||
.iter()
|
weapon_name,
|
||||||
.filter_map(|comp| match &*comp.kind() {
|
..
|
||||||
ItemKind::Ingredient { descriptor, .. } => Some(descriptor.to_owned()),
|
}) => {
|
||||||
_ => None,
|
let material_name = comp
|
||||||
})
|
.components()
|
||||||
.next()
|
.iter()
|
||||||
.unwrap_or_else(|| "Modular".to_owned())
|
.find_map(|mat| match &*mat.kind() {
|
||||||
}
|
ItemKind::Ingredient { descriptor, .. } => {
|
||||||
|
Some(descriptor.to_owned())
|
||||||
let main_component = components.iter().find(|comp| {
|
},
|
||||||
matches!(&*comp.kind(), ItemKind::ModularComponent(ModularComponent { modkind, .. })
|
_ => None,
|
||||||
if *modkind == ModularComponentKind::main_component(*toolkind)
|
})
|
||||||
)
|
.unwrap_or_else(|| "Modular".to_owned());
|
||||||
});
|
Some(format!("{} {}", material_name, weapon_name))
|
||||||
let (material_name, weapon_name) = if let Some(component) = main_component {
|
},
|
||||||
let material_name = material_name(component);
|
_ => None,
|
||||||
let weapon_name = if let ItemKind::ModularComponent(ModularComponent {
|
})
|
||||||
weapon_name,
|
.unwrap_or_else(|| "Modular Weapon".to_owned());
|
||||||
..
|
Cow::Owned(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))
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -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)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
pub struct ModularComponent {
|
pub enum ModularComponent {
|
||||||
pub toolkind: ToolKind,
|
ToolPrimaryComponent {
|
||||||
pub modkind: ModularComponentKind,
|
toolkind: ToolKind,
|
||||||
pub stats: tool::Stats,
|
stats: tool::Stats,
|
||||||
pub hand_restriction: Option<Hands>,
|
hand_restriction: Option<Hands>,
|
||||||
pub weapon_name: String,
|
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] = [
|
const SUPPORTED_TOOLKINDS: [ToolKind; 6] = [
|
||||||
@ -180,124 +172,171 @@ fn make_weapon_id(toolkind: ToolKind) -> String {
|
|||||||
format!("{}.{}", WEAPON_PREFIX, toolkind.identifier_name())
|
format!("{}.{}", WEAPON_PREFIX, toolkind.identifier_name())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns directory that contains components for a particular combination of
|
lazy_static! {
|
||||||
/// toolkind and modular component kind
|
static ref PRIMARY_COMPONENT_POOL: HashMap<(ToolKind, String), Vec<(Arc<ItemDef>, Option<Hands>)>> = {
|
||||||
fn make_mod_comp_dir_spec(tool: ToolKind, mod_kind: ModularComponentKind) -> String {
|
let mut component_pool = HashMap::new();
|
||||||
const MOD_COMP_DIR_PREFIX: &str = "common.items.crafting_ing.modular";
|
|
||||||
format!(
|
// Load recipe book (done to check that material is valid for a particular component)
|
||||||
"{}.{}.{}",
|
let recipe::RawRecipeBook(recipes) =
|
||||||
MOD_COMP_DIR_PREFIX,
|
recipe::RawRecipeBook::load_expect_cloned("common.recipe_book");
|
||||||
mod_kind.identifier_name(),
|
|
||||||
tool.identifier_name()
|
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
|
/// Creates a random modular weapon when provided with a toolkind, material, and
|
||||||
/// optionally the handedness
|
/// optionally the handedness
|
||||||
pub fn random_weapon(tool: ToolKind, material: super::Material, hands: Option<Hands>) -> Item {
|
pub fn random_weapon(
|
||||||
// Returns inner modular component of an item if it has one
|
tool: ToolKind,
|
||||||
fn unwrap_modular_component(item: &Item) -> Option<ModularComponent> {
|
material: Material,
|
||||||
if let ItemKind::ModularComponent(mod_comp) = &*item.kind() {
|
hand_restriction: Option<Hands>,
|
||||||
// TODO: Maybe get rid of clone?
|
) -> Result<Item, ModularWeaponCreationError> {
|
||||||
Some(mod_comp.clone())
|
if let Some(material_id) = material.asset_identifier() {
|
||||||
} else {
|
// Loads default ability map and material stat manifest for later use
|
||||||
None
|
let ability_map = Default::default();
|
||||||
}
|
let msm = Default::default();
|
||||||
}
|
|
||||||
|
|
||||||
// Loads default ability map and material stat manifest for later use
|
let mut rng = thread_rng();
|
||||||
let ability_map = Default::default();
|
|
||||||
let msm = Default::default();
|
|
||||||
|
|
||||||
// Load recipe book (done to check that material is valid for a particular
|
let material = Item::new_from_asset_expect(material_id);
|
||||||
// component)
|
let primary_components = PRIMARY_COMPONENT_POOL
|
||||||
let recipe::RawRecipeBook(recipes) =
|
.get(&(tool, material_id.to_owned()))
|
||||||
recipe::RawRecipeBook::load_expect_cloned("common.recipe_book");
|
.map_or(Vec::new(), |components| {
|
||||||
|
components
|
||||||
// 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
|
|
||||||
.iter()
|
.iter()
|
||||||
.any(|input| {
|
.filter(|(_def, hand)| match (hand_restriction, hand) {
|
||||||
match &input.0 {
|
(Some(restriction), Some(hand)) => restriction == *hand,
|
||||||
recipe::RawRecipeInput::ListSameItem(items) => {
|
(None, _) | (_, None) => true,
|
||||||
let assets = recipe::ItemList::load_expect_cloned(items).0;
|
|
||||||
assets.iter().any(|asset| Some(asset.as_str()) == material.asset_identifier())
|
|
||||||
},
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
})
|
.map(|entry| (1.0, entry))
|
||||||
};
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
|
||||||
// Finds which component has a material as a subcomponent
|
let (primary_component, hand_restriction) = {
|
||||||
let material_comp = ModularComponentKind::main_component(tool);
|
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
|
let secondary_components =
|
||||||
// modular weapon
|
SECONDARY_COMPONENT_POOL
|
||||||
let create_component = |directory, hands| {
|
.get(&tool)
|
||||||
// Load directory of components
|
.map_or(Vec::new(), |components| {
|
||||||
let components = Item::new_from_asset_glob(directory)
|
components
|
||||||
.expect("Asset directory did not properly load")
|
.iter()
|
||||||
.into_iter()
|
.filter(|(_def, hand)| match (hand_restriction, hand) {
|
||||||
// Filter by handedness requirement
|
(Some(restriction), Some(hand)) => restriction == *hand,
|
||||||
.filter(|item| {
|
(None, _) | (_, None) => true,
|
||||||
matches!(unwrap_modular_component(item), Some(ModularComponent { hand_restriction, .. }) if hand_restriction.zip(hands).map_or(true, |(hr1, hr2)| hr1 == hr2))
|
})
|
||||||
})
|
.map(|entry| (1.0, entry))
|
||||||
// Filter by if component does not have a material, or if material can be used in the modular component
|
.collect::<Vec<_>>()
|
||||||
.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<_>>();
|
|
||||||
|
|
||||||
// Create lottery and choose item
|
let secondary_component = {
|
||||||
Lottery::<Item>::from(components).choose_owned()
|
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
|
// Create modular weapon
|
||||||
let damage_comp_dir = make_mod_comp_dir_spec(tool, ModularComponentKind::Damage);
|
let components = vec![primary_component, secondary_component];
|
||||||
let mut damage_component = create_component(&damage_comp_dir, hands);
|
Ok(Item::new_from_item_base(
|
||||||
// Takes whichever is more restrictive of hand restriction passed in and hand
|
ItemBase::Modular(ModularBase::Tool(tool)),
|
||||||
// restriction from damage component e.g. if None is passed to function, and
|
&components,
|
||||||
// damage component chooses piece with two handed restriction, then makes held
|
&ability_map,
|
||||||
// component have two handed restriction as well
|
&msm,
|
||||||
let damage_hands = unwrap_modular_component(&damage_component)
|
))
|
||||||
.and_then(|mc| mc.hand_restriction)
|
} else {
|
||||||
.or(hands);
|
Err(ModularWeaponCreationError::MaterialNotFound)
|
||||||
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![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
|
/// 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
|
Hands::One
|
||||||
};
|
};
|
||||||
|
|
||||||
let (main_comp, material) = if let Some(main_comp) = mod_weap.components().iter().find(|comp| {
|
match mod_weap
|
||||||
matches!(&*comp.kind(), ItemKind::ModularComponent(mod_comp) if ModularComponentKind::main_component(mod_comp.toolkind) == mod_comp.modkind)
|
.components()
|
||||||
}) {
|
.iter()
|
||||||
let material = if let Some(material) = main_comp.components().iter().filter_map(|mat| {
|
.find_map(|comp| match &*comp.kind() {
|
||||||
if let Some(super::ItemTag::Material(material)) = mat.tags().iter().find(|tag| matches!(tag, super::ItemTag::Material(_))) {
|
ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent { .. }) => {
|
||||||
Some(material)
|
let component_id = comp.item_definition_id().to_owned();
|
||||||
} else {
|
let material_id = comp.components().iter().find_map(|mat| match &*mat.kind() {
|
||||||
None
|
ItemKind::Ingredient { .. } => Some(mat.item_definition_id().to_owned()),
|
||||||
}
|
_ => None,
|
||||||
}).next() {
|
});
|
||||||
material.into()
|
Some((component_id, material_id))
|
||||||
} else {
|
},
|
||||||
""
|
_ => None,
|
||||||
};
|
}) {
|
||||||
|
Some((component_id, Some(material_id))) => (component_id, material_id, hands),
|
||||||
(main_comp.item_definition_id(), material)
|
Some((component_id, None)) => (component_id, String::new(), hands),
|
||||||
} else {
|
None => (String::new(), String::new(), hands),
|
||||||
("", "")
|
}
|
||||||
};
|
|
||||||
|
|
||||||
(main_comp.to_owned(), material.to_owned(), hands)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This is used as a key to uniquely identify the modular weapon in asset
|
/// 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 type ModularWeaponComponentKey = (String, String);
|
||||||
|
|
||||||
pub enum ModularWeaponComponentKeyError {
|
pub enum ModularWeaponComponentKeyError {
|
||||||
NotModularComponent,
|
MaterialNotFound,
|
||||||
NotMainComponent,
|
NotMainComponent,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn weapon_component_to_key(
|
pub fn weapon_component_to_key(
|
||||||
mod_weap_comp: &dyn ItemDesc,
|
mod_weap_comp: &dyn ItemDesc,
|
||||||
) -> Result<ModularWeaponComponentKey, ModularWeaponComponentKeyError> {
|
) -> Result<ModularWeaponComponentKey, ModularWeaponComponentKeyError> {
|
||||||
if let ItemKind::ModularComponent(mod_comp) = &*mod_weap_comp.kind() {
|
match if let ItemKind::ModularComponent(ModularComponent::ToolPrimaryComponent { .. }) =
|
||||||
if ModularComponentKind::main_component(mod_comp.toolkind) == mod_comp.modkind {
|
&*mod_weap_comp.kind()
|
||||||
let material = if let Some(material) = mod_weap_comp
|
{
|
||||||
.components()
|
let component_id = mod_weap_comp.item_definition_id().to_owned();
|
||||||
.iter()
|
let material_id = mod_weap_comp
|
||||||
.filter_map(|mat| {
|
.components()
|
||||||
if let Some(super::ItemTag::Material(material)) = mat
|
.iter()
|
||||||
.tags()
|
.find_map(|mat| match &*mat.kind() {
|
||||||
.iter()
|
ItemKind::Ingredient { .. } => Some(mat.item_definition_id().to_owned()),
|
||||||
.find(|tag| matches!(tag, super::ItemTag::Material(_)))
|
_ => None,
|
||||||
{
|
});
|
||||||
Some(material)
|
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 {
|
} 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),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ impl Hands {
|
|||||||
tool,
|
tool,
|
||||||
material,
|
material,
|
||||||
hands,
|
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
|
.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(&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 iter(&self) -> impl Iterator<Item = &(f32, T)> { self.items.iter() }
|
||||||
|
|
||||||
pub fn total(&self) -> f32 { self.total }
|
pub fn total(&self) -> f32 { self.total }
|
||||||
@ -143,7 +130,7 @@ impl<T: AsRef<str>> LootSpec<T> {
|
|||||||
tool,
|
tool,
|
||||||
material,
|
material,
|
||||||
hands,
|
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(
|
pub fn modular_weapon(
|
||||||
inv: &mut Inventory,
|
inv: &mut Inventory,
|
||||||
damage_component: InvSlotId,
|
primary_component: InvSlotId,
|
||||||
held_component: InvSlotId,
|
secondary_component: InvSlotId,
|
||||||
ability_map: &AbilityMap,
|
ability_map: &AbilityMap,
|
||||||
msm: &MaterialStatManifest,
|
msm: &MaterialStatManifest,
|
||||||
) -> Result<Item, ModularWeaponError> {
|
) -> Result<Item, ModularWeaponError> {
|
||||||
use modular::{ModularComponent, ModularComponentKind};
|
use modular::ModularComponent;
|
||||||
// Closure to get inner modular component info from item in a given slot
|
// Closure to get inner modular component info from item in a given slot
|
||||||
fn unwrap_modular(inv: &Inventory, slot: InvSlotId) -> Option<ModularComponent> {
|
fn unwrap_modular(inv: &Inventory, slot: InvSlotId) -> Option<ModularComponent> {
|
||||||
if let Some(ItemKind::ModularComponent(mod_comp)) =
|
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
|
// Checks if both components are comptabile, and if so returns the toolkind to
|
||||||
// make weapon of
|
// make weapon of
|
||||||
let compatiblity = if let (Some(damage_component), Some(held_component)) = (
|
let compatiblity = if let (Some(primary_component), Some(secondary_component)) = (
|
||||||
unwrap_modular(inv, damage_component),
|
unwrap_modular(inv, primary_component),
|
||||||
unwrap_modular(inv, held_component),
|
unwrap_modular(inv, secondary_component),
|
||||||
) {
|
) {
|
||||||
// Checks that damage and held component slots each contain a damage and held
|
// Checks that damage and held component slots each contain a damage and held
|
||||||
// modular component respectively
|
// modular component respectively
|
||||||
if matches!(damage_component.modkind, ModularComponentKind::Damage)
|
if let (
|
||||||
&& matches!(held_component.modkind, ModularComponentKind::Held)
|
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
|
// 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
|
// Checks that if both components have a hand restriction, they are the same
|
||||||
let hands_check = damage_component.hand_restriction.map_or(true, |hands| {
|
let hands_check =
|
||||||
held_component
|
hands_a.map_or(true, |hands| hands_b.map_or(true, |hands2| hands == hands2));
|
||||||
.hand_restriction
|
|
||||||
.map_or(true, |hands2| hands == hands2)
|
|
||||||
});
|
|
||||||
if hands_check {
|
if hands_check {
|
||||||
Ok(damage_component.toolkind)
|
Ok(tool_a)
|
||||||
} else {
|
} else {
|
||||||
Err(ModularWeaponError::DifferentHands)
|
Err(ModularWeaponError::DifferentHands)
|
||||||
}
|
}
|
||||||
@ -298,15 +305,15 @@ pub fn modular_weapon(
|
|||||||
match compatiblity {
|
match compatiblity {
|
||||||
Ok(tool_kind) => {
|
Ok(tool_kind) => {
|
||||||
// Remove components from inventory
|
// Remove components from inventory
|
||||||
let damage_component = inv
|
let primary_component = inv
|
||||||
.take(damage_component, ability_map, msm)
|
.take(primary_component, ability_map, msm)
|
||||||
.expect("Expected component to exist");
|
.expect("Expected component to exist");
|
||||||
let held_component = inv
|
let secondary_component = inv
|
||||||
.take(held_component, ability_map, msm)
|
.take(secondary_component, ability_map, msm)
|
||||||
.expect("Expected component to exist");
|
.expect("Expected component to exist");
|
||||||
|
|
||||||
// Create modular weapon
|
// Create modular weapon
|
||||||
let components = vec![damage_component, held_component];
|
let components = vec![primary_component, secondary_component];
|
||||||
Ok(Item::new_from_item_base(
|
Ok(Item::new_from_item_base(
|
||||||
ItemBase::Modular(modular::ModularBase::Tool(tool_kind)),
|
ItemBase::Modular(modular::ModularBase::Tool(tool_kind)),
|
||||||
&components,
|
&components,
|
||||||
|
@ -1946,6 +1946,7 @@ where
|
|||||||
.map_err(|_| format!("Unknown item: {:#?}", item_id))?,
|
.map_err(|_| format!("Unknown item: {:#?}", item_id))?,
|
||||||
KitSpec::ModularWeapon { tool, material } => {
|
KitSpec::ModularWeapon { tool, material } => {
|
||||||
comp::item::modular::random_weapon(*tool, *material, None)
|
comp::item::modular::random_weapon(*tool, *material, None)
|
||||||
|
.map_err(|err| format!("{:#?}", err))?
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
let mut res = Ok(());
|
let mut res = Ok(());
|
||||||
|
@ -668,8 +668,8 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
CraftEvent::ModularWeapon {
|
CraftEvent::ModularWeapon {
|
||||||
damage_component,
|
primary_component,
|
||||||
held_component,
|
secondary_component,
|
||||||
} => {
|
} => {
|
||||||
let sprite = craft_sprite
|
let sprite = craft_sprite
|
||||||
.filter(|pos| {
|
.filter(|pos| {
|
||||||
@ -696,8 +696,8 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
|||||||
if matches!(sprite, Some(SpriteKind::CraftingBench)) {
|
if matches!(sprite, Some(SpriteKind::CraftingBench)) {
|
||||||
recipe::modular_weapon(
|
recipe::modular_weapon(
|
||||||
&mut inventory,
|
&mut inventory,
|
||||||
damage_component,
|
primary_component,
|
||||||
held_component,
|
secondary_component,
|
||||||
ability_map,
|
ability_map,
|
||||||
&msm,
|
&msm,
|
||||||
)
|
)
|
||||||
|
@ -106,9 +106,12 @@ pub fn modular_component_desc(
|
|||||||
msm: &MaterialStatManifest,
|
msm: &MaterialStatManifest,
|
||||||
description: &str,
|
description: &str,
|
||||||
) -> String {
|
) -> String {
|
||||||
let stats = mc.stats;
|
let mut result = format!("Modular Component\n\n{}", description);
|
||||||
let statblock = statblock_desc(&stats);
|
if let Some(tool_stats) = mc.tool_stats(components, msm) {
|
||||||
let mut result = format!("Modular Component\n\n{}\n\n{}", statblock, description);
|
let statblock = statblock_desc(&tool_stats);
|
||||||
|
result += "\n\n";
|
||||||
|
result += &statblock;
|
||||||
|
}
|
||||||
if !components.is_empty() {
|
if !components.is_empty() {
|
||||||
result += "\n\nMade from:\n";
|
result += "\n\nMade from:\n";
|
||||||
for component in components {
|
for component in components {
|
||||||
|
Loading…
Reference in New Issue
Block a user