Changed Item to have ItemBase instead of ItemDef. NO ASSETS.

This commit is contained in:
Sam 2021-11-18 10:41:08 -05:00
parent 5bacf526ad
commit 08b7bb781f
28 changed files with 680 additions and 716 deletions

View File

@ -1091,8 +1091,9 @@ impl Client {
// 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))
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 {

View File

@ -3,11 +3,7 @@ use crate::comp::buff::{Buff, BuffChange, BuffData, BuffKind, BuffSource};
use crate::{
comp::{
inventory::{
item::{
armor::Protection,
tool::{self, Tool, ToolKind},
Item, ItemDesc, ItemKind, MaterialStatManifest,
},
item::{armor::Protection, tool::ToolKind, ItemDesc, ItemKind, MaterialStatManifest},
slot::EquipSlot,
},
skillset::SkillGroupKind,
@ -949,26 +945,28 @@ impl CombatBuff {
}
#[cfg(not(target_arch = "wasm32"))]
fn equipped_item_and_tool(inv: &Inventory, slot: EquipSlot) -> Option<(&Item, &Tool)> {
inv.equipped(slot).and_then(|i| {
if let ItemKind::Tool(tool) = &i.kind() {
Some((i, tool))
pub fn get_weapon_kinds(inv: &Inventory) -> (Option<ToolKind>, Option<ToolKind>) {
(
inv.equipped(EquipSlot::ActiveMainhand).and_then(|i| {
if let ItemKind::Tool(tool) = &*i.kind() {
Some(tool.kind)
} else {
None
}
})
}),
inv.equipped(EquipSlot::ActiveOffhand).and_then(|i| {
if let ItemKind::Tool(tool) = &*i.kind() {
Some(tool.kind)
} else {
None
}
#[cfg(not(target_arch = "wasm32"))]
pub fn get_weapons(inv: &Inventory) -> (Option<ToolKind>, Option<ToolKind>) {
(
equipped_item_and_tool(inv, EquipSlot::ActiveMainhand).map(|(_, tool)| tool.kind),
equipped_item_and_tool(inv, EquipSlot::ActiveOffhand).map(|(_, tool)| tool.kind),
}),
)
}
#[cfg(not(target_arch = "wasm32"))]
pub fn weapon_rating<T: ItemDesc>(item: &T, msm: &MaterialStatManifest) -> f32 {
// TODO: Either remove msm or use it as argument in fn kind
pub fn weapon_rating<T: ItemDesc>(item: &T, _msm: &MaterialStatManifest) -> f32 {
const DAMAGE_WEIGHT: f32 = 2.0;
const SPEED_WEIGHT: f32 = 3.0;
const CRIT_CHANCE_WEIGHT: f32 = 1.25;
@ -978,8 +976,8 @@ pub fn weapon_rating<T: ItemDesc>(item: &T, msm: &MaterialStatManifest) -> f32 {
const ENERGY_EFFICIENCY_WEIGHT: f32 = 0.0;
const BUFF_STRENGTH_WEIGHT: f32 = 0.0;
if let ItemKind::Tool(tool) = item.kind() {
let stats = tool::Stats::from((msm, item.components(), tool));
if let ItemKind::Tool(tool) = &*item.kind() {
let stats = tool.stats;
// TODO: Look into changing the 0.5 to reflect armor later maybe?
// Since it is only for weapon though, it probably makes sense to leave
@ -1018,7 +1016,7 @@ pub fn weapon_rating<T: ItemDesc>(item: &T, msm: &MaterialStatManifest) -> f32 {
#[cfg(not(target_arch = "wasm32"))]
fn weapon_skills(inventory: &Inventory, skill_set: &SkillSet) -> f32 {
let (mainhand, offhand) = get_weapons(inventory);
let (mainhand, offhand) = get_weapon_kinds(inventory);
let mainhand_skills = if let Some(tool) = mainhand {
skill_set.earned_sp(SkillGroupKind::Weapon(tool)) as f32
} else {
@ -1034,15 +1032,13 @@ fn weapon_skills(inventory: &Inventory, skill_set: &SkillSet) -> f32 {
#[cfg(not(target_arch = "wasm32"))]
fn get_weapon_rating(inventory: &Inventory, msm: &MaterialStatManifest) -> f32 {
let mainhand_rating =
if let Some((item, _)) = equipped_item_and_tool(inventory, EquipSlot::ActiveMainhand) {
let mainhand_rating = if let Some(item) = inventory.equipped(EquipSlot::ActiveMainhand) {
weapon_rating(item, msm)
} else {
0.0
};
let offhand_rating =
if let Some((item, _)) = equipped_item_and_tool(inventory, EquipSlot::ActiveOffhand) {
let offhand_rating = if let Some(item) = inventory.equipped(EquipSlot::ActiveOffhand) {
weapon_rating(item, msm)
} else {
0.0
@ -1118,7 +1114,7 @@ pub fn compute_crit_mult(inventory: Option<&Inventory>) -> f32 {
inventory.map_or(1.25, |inv| {
inv.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &item.kind() {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.crit_power()
} else {
None
@ -1136,7 +1132,7 @@ pub fn compute_energy_reward_mod(inventory: Option<&Inventory>) -> f32 {
inventory.map_or(1.0, |inv| {
inv.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &item.kind() {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.energy_reward()
} else {
None
@ -1154,7 +1150,7 @@ pub fn compute_max_energy_mod(inventory: Option<&Inventory>) -> f32 {
inventory.map_or(0.0, |inv| {
inv.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &item.kind() {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.energy_max()
} else {
None
@ -1186,7 +1182,7 @@ pub fn stealth_multiplier_from_items(inventory: Option<&Inventory>) -> f32 {
let stealth_sum = inventory.map_or(0.0, |inv| {
inv.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &item.kind() {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.stealth()
} else {
None
@ -1206,7 +1202,7 @@ pub fn compute_protection(inventory: Option<&Inventory>) -> Option<f32> {
inventory.map_or(Some(0.0), |inv| {
inv.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &item.kind() {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.protection()
} else {
None

View File

@ -94,7 +94,7 @@ impl ActiveAbilities {
) -> [AuxiliaryAbility; MAX_ABILITIES] {
let tool_kind = |slot| {
inv.and_then(|inv| inv.equipped(slot))
.and_then(|item| match item.kind() {
.and_then(|item| match &*item.kind() {
ItemKind::Tool(tool) => Some(tool.kind),
_ => None,
})
@ -148,9 +148,9 @@ impl ActiveAbilities {
};
let scale_ability = |ability: CharacterAbility, equip_slot| {
let tool_kind =
inv.and_then(|inv| inv.equipped(equip_slot))
.and_then(|item| match &item.kind {
let tool_kind = inv
.and_then(|inv| inv.equipped(equip_slot))
.and_then(|item| match &*item.kind() {
ItemKind::Tool(tool) => Some(tool.kind),
_ => None,
});

View File

@ -62,7 +62,7 @@ impl From<Body> for super::Body {
impl From<&Item> for Body {
fn from(item: &Item) -> Self {
match item.kind() {
match &*item.kind() {
ItemKind::Tool(Tool { kind, .. }) => Body::Tool(*kind),
ItemKind::ModularComponent(_) => Body::ModularComponent,
ItemKind::Lantern(_) => Body::Lantern,

View File

@ -2,7 +2,7 @@ use crate::{
assets::AssetExt,
comp::inventory::item::{
armor::{Armor, ArmorKind},
modular, tool, Glider, ItemDef, ItemDesc, ItemKind, Lantern, Throwable, Utility,
modular, Glider, ItemDef, ItemDesc, ItemKind, Lantern, Throwable, Utility,
},
};
use serde::{Deserialize, Serialize};
@ -26,15 +26,14 @@ pub enum ItemKey {
impl<T: ItemDesc> From<&T> for ItemKey {
fn from(item_desc: &T) -> Self {
let item_kind = item_desc.kind();
let item_definition_id = item_desc.item_definition_id();
match item_kind {
ItemKind::Tool(tool) => {
use tool::StatKind;
match tool.stats {
StatKind::Direct(_) => ItemKey::Tool(item_definition_id.to_owned()),
StatKind::Modular => ItemKey::ModularWeapon(modular::weapon_to_key(item_desc)),
match &*item_desc.kind() {
ItemKind::Tool(_) => {
if item_desc.is_modular() {
ItemKey::ModularWeapon(modular::weapon_to_key(item_desc))
} else {
ItemKey::Tool(item_definition_id.to_owned())
}
},
ItemKind::ModularComponent(_) => {

View File

@ -4,7 +4,7 @@ pub mod modular;
pub mod tool;
// Reexports
pub use modular::{ModularComponent, ModularComponentKind};
pub use modular::{ModularBase, ModularComponent, ModularComponentKind};
pub use tool::{AbilitySet, AbilitySpec, Hands, MaterialStatManifest, Tool, ToolKind};
use crate::{
@ -18,7 +18,6 @@ use core::{
convert::TryFrom,
mem,
num::{NonZeroU32, NonZeroU64},
ops::Deref,
};
use crossbeam_utils::atomic::AtomicCell;
use serde::{de, Deserialize, Serialize, Serializer};
@ -71,12 +70,6 @@ pub struct Glider {
pub kind: String,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub enum QualityKind {
Direct(Quality),
Modular,
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Copy, PartialOrd, Ord)]
pub enum Quality {
Low, // Grey
@ -90,10 +83,10 @@ pub enum Quality {
}
pub trait TagExampleInfo {
fn name(&self) -> Cow<'static, str>;
fn name(&self) -> &str;
/// What item to show in the crafting hud if the player has nothing with the
/// tag
fn exemplar_identifier(&self) -> Cow<'static, str>;
fn exemplar_identifier(&self) -> &str;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, IntoStaticStr)]
@ -215,11 +208,9 @@ impl Material {
}
impl TagExampleInfo for Material {
fn name(&self) -> Cow<'static, str> { Cow::Borrowed(self.into()) }
fn name(&self) -> &str { self.into() }
fn exemplar_identifier(&self) -> Cow<'static, str> {
Cow::Borrowed(self.asset_identifier().unwrap_or(""))
}
fn exemplar_identifier(&self) -> &str { self.asset_identifier().unwrap_or("") }
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
@ -238,36 +229,36 @@ pub enum ItemTag {
}
impl TagExampleInfo for ItemTag {
fn name(&self) -> Cow<'static, str> {
fn name(&self) -> &str {
match self {
ItemTag::Material(material) => material.name(),
ItemTag::MaterialKind(material_kind) => Cow::Borrowed(material_kind.into()),
ItemTag::Leather => Cow::Borrowed("leather"),
ItemTag::Cultist => Cow::Borrowed("cultist"),
ItemTag::Potion => Cow::Borrowed("potion"),
ItemTag::Food => Cow::Borrowed("food"),
ItemTag::BaseMaterial => Cow::Borrowed("basemat"),
ItemTag::CraftingTool => Cow::Borrowed("tool"),
ItemTag::Utility => Cow::Borrowed("utility"),
ItemTag::Bag => Cow::Borrowed("bag"),
ItemTag::SalvageInto(_) => Cow::Borrowed("salvage"),
ItemTag::MaterialKind(material_kind) => material_kind.into(),
ItemTag::Leather => "leather",
ItemTag::Cultist => "cultist",
ItemTag::Potion => "potion",
ItemTag::Food => "food",
ItemTag::BaseMaterial => "basemat",
ItemTag::CraftingTool => "tool",
ItemTag::Utility => "utility",
ItemTag::Bag => "bag",
ItemTag::SalvageInto(_) => "salvage",
}
}
// TODO: Autogenerate these?
fn exemplar_identifier(&self) -> Cow<'static, str> {
fn exemplar_identifier(&self) -> &str {
match self {
ItemTag::Material(_) => Cow::Borrowed("common.items.tag_examples.placeholder"),
ItemTag::MaterialKind(_) => Cow::Borrowed("common.items.tag_examples.placeholder"),
ItemTag::Leather => Cow::Borrowed("common.items.tag_examples.leather"),
ItemTag::Cultist => Cow::Borrowed("common.items.tag_examples.cultist"),
ItemTag::Potion => Cow::Borrowed("common.items.tag_examples.placeholder"),
ItemTag::Food => Cow::Borrowed("common.items.tag_examples.placeholder"),
ItemTag::BaseMaterial => Cow::Borrowed("common.items.tag_examples.placeholder"),
ItemTag::CraftingTool => Cow::Borrowed("common.items.tag_examples.placeholder"),
ItemTag::Utility => Cow::Borrowed("common.items.tag_examples.placeholder"),
ItemTag::Bag => Cow::Borrowed("common.items.tag_examples.placeholder"),
ItemTag::SalvageInto(_) => Cow::Borrowed("common.items.tag_examples.placeholder"),
ItemTag::Material(_) => "common.items.tag_examples.placeholder",
ItemTag::MaterialKind(_) => "common.items.tag_examples.placeholder",
ItemTag::Leather => "common.items.tag_examples.leather",
ItemTag::Cultist => "common.items.tag_examples.cultist",
ItemTag::Potion => "common.items.tag_examples.placeholder",
ItemTag::Food => "common.items.tag_examples.placeholder",
ItemTag::BaseMaterial => "common.items.tag_examples.placeholder",
ItemTag::CraftingTool => "common.items.tag_examples.placeholder",
ItemTag::Utility => "common.items.tag_examples.placeholder",
ItemTag::Bag => "common.items.tag_examples.placeholder",
ItemTag::SalvageInto(_) => "common.items.tag_examples.placeholder",
}
}
}
@ -346,10 +337,10 @@ pub struct Item {
/// could change invariants like whether it was stackable (invalidating
/// the amount).
#[serde(
serialize_with = "serialize_item_def",
deserialize_with = "deserialize_item_def"
serialize_with = "serialize_item_base",
deserialize_with = "deserialize_item_base"
)]
item_def: Arc<ItemDef>,
item_base: ItemBase,
/// components is hidden to maintain the following invariants:
/// - It should only contain modular components (and enhancements, once they
/// exist)
@ -374,7 +365,7 @@ use std::hash::{Hash, Hasher};
// Used to find inventory item corresponding to hotbar slot
impl Hash for Item {
fn hash<H: Hasher>(&self, state: &mut H) {
self.item_def.item_definition_id.hash(state);
self.item_definition_id().hash(state);
self.components.hash(state);
}
}
@ -382,33 +373,43 @@ impl Hash for Item {
// Custom serialization for ItemDef, we only want to send the item_definition_id
// over the network, the client will use deserialize_item_def to fetch the
// ItemDef from assets.
fn serialize_item_def<S: Serializer>(field: &Arc<ItemDef>, serializer: S) -> Result<S::Ok, S::Error>
fn serialize_item_base<S: Serializer>(field: &ItemBase, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&field.item_definition_id)
serializer.serialize_str(match field {
ItemBase::Raw(item_def) => &item_def.item_definition_id,
// TODO: Encode this data somehow
ItemBase::Modular(_) => "modular",
})
}
// Custom de-serialization for ItemDef to retrieve the ItemDef from assets using
// its asset specifier (item_definition_id)
fn deserialize_item_def<'de, D>(deserializer: D) -> Result<Arc<ItemDef>, D::Error>
fn deserialize_item_base<'de, D>(deserializer: D) -> Result<ItemBase, D::Error>
where
D: de::Deserializer<'de>,
{
struct ItemDefStringVisitor;
impl<'de> de::Visitor<'de> for ItemDefStringVisitor {
type Value = Arc<ItemDef>;
type Value = ItemBase;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("item def string")
}
fn visit_str<E>(self, item_definition_id: &str) -> Result<Self::Value, E>
fn visit_str<E>(self, serialized_item_base: &str) -> Result<Self::Value, E>
where
E: de::Error,
{
Ok(Arc::<ItemDef>::load_expect_cloned(item_definition_id))
Ok(match serialized_item_base {
// TODO: Make this work
"modular" => ItemBase::Modular(ModularBase::Tool(ToolKind::Empty)),
item_definition_id => {
ItemBase::Raw(Arc::<ItemDef>::load_expect_cloned(item_definition_id))
},
})
}
}
@ -422,14 +423,36 @@ pub enum ItemName {
Component(String),
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ItemBase {
Raw(Arc<ItemDef>),
Modular(modular::ModularBase),
}
impl ItemBase {
fn num_slots(&self) -> u16 {
match self {
ItemBase::Raw(item_def) => item_def.num_slots(),
ItemBase::Modular(_) => 0,
}
}
fn item_definition_id(&self) -> &str {
match &self {
ItemBase::Raw(item_def) => &item_def.item_definition_id,
ItemBase::Modular(mod_base) => mod_base.pseudo_item_id(),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct ItemDef {
#[serde(default)]
item_definition_id: String,
pub name: ItemName,
pub name: String,
pub description: String,
pub kind: ItemKind,
pub quality: QualityKind,
pub quality: Quality,
pub tags: Vec<ItemTag>,
#[serde(default)]
pub slots: u16,
@ -457,17 +480,18 @@ impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
type Error = ItemConfigError;
fn try_from(
(item, ability_map, msm): (&Item, &AbilityMap, &MaterialStatManifest),
// TODO: Either remove msm or use it as argument in fn kind
(item, ability_map, _msm): (&Item, &AbilityMap, &MaterialStatManifest),
) -> Result<Self, Self::Error> {
if let ItemKind::Tool(tool) = &item.kind {
if let ItemKind::Tool(tool) = &*item.kind() {
// If no custom ability set is specified, fall back to abilityset of tool kind.
let tool_default = |tool_kind| {
let key = &AbilitySpec::Tool(tool_kind);
ability_map.get_ability_set(key)
};
let abilities = if let Some(set_key) = item.ability_spec() {
if let Some(set) = ability_map.get_ability_set(set_key) {
set.clone().modified_by_tool(tool, msm, &item.components)
if let Some(set) = ability_map.get_ability_set(&*set_key) {
set.clone().modified_by_tool(tool)
} else {
error!(
"Custom ability set: {:?} references non-existent set, falling back to \
@ -477,7 +501,7 @@ impl TryFrom<(&Item, &AbilityMap, &MaterialStatManifest)> for ItemConfig {
tool_default(tool.kind).cloned().unwrap_or_default()
}
} else if let Some(set) = tool_default(tool.kind) {
set.clone().modified_by_tool(tool, msm, &item.components)
set.clone().modified_by_tool(tool)
} else {
error!(
"No ability set defined for tool: {:?}, falling back to default ability set.",
@ -504,16 +528,6 @@ impl ItemDef {
)
}
pub fn is_modular(&self) -> bool {
matches!(
&self.kind,
ItemKind::Tool(tool::Tool {
stats: tool::StatKind::Modular,
..
}) | ItemKind::ModularComponent(_)
)
}
pub fn is_component(&self, kind: ModularComponentKind) -> bool {
if let ItemKind::ModularComponent(ModularComponent { modkind, .. }) = self.kind {
kind == modkind
@ -568,14 +582,14 @@ impl ItemDef {
/// please don't rely on this for anything!
impl PartialEq for Item {
fn eq(&self, other: &Self) -> bool {
self.item_def.item_definition_id == other.item_def.item_definition_id
if let (ItemBase::Raw(self_def), ItemBase::Raw(other_def)) =
(&self.item_base, &other.item_base)
{
self_def.item_definition_id == other_def.item_definition_id
} else {
false
}
}
impl Deref for Item {
type Target = ItemDef;
fn deref(&self) -> &Self::Target { &self.item_def }
}
impl assets::Compound for ItemDef {
@ -583,13 +597,6 @@ impl assets::Compound for ItemDef {
cache: &assets::AssetCache<S>,
specifier: &str,
) -> Result<Self, BoxedError> {
// load from the filesystem first, but if the file doesn't exist, see if it's a
// programmatically-generated asset
let raw = match cache.load::<RawItemDef>(specifier) {
Ok(handle) => handle.cloned(),
Err(e) => modular::synthesize_modular_asset(specifier).ok_or(e)?,
};
let RawItemDef {
name,
description,
@ -598,7 +605,7 @@ impl assets::Compound for ItemDef {
tags,
slots,
ability_spec,
} = raw;
} = cache.load::<RawItemDef>(specifier)?.cloned();
// Some commands like /give_item provide the asset specifier separated with \
// instead of .
@ -622,10 +629,10 @@ impl assets::Compound for ItemDef {
#[derive(Clone, Debug, Serialize, Deserialize)]
#[serde(rename = "ItemDef")]
struct RawItemDef {
name: ItemName,
name: String,
description: String,
kind: ItemKind,
quality: QualityKind,
quality: Quality,
tags: Vec<ItemTag>,
#[serde(default)]
slots: u16,
@ -646,25 +653,22 @@ impl Item {
// loadout when no weapon is present
pub fn empty() -> Self { Item::new_from_asset_expect("common.items.weapons.empty.empty") }
pub fn new_from_item_def(
inner_item: Arc<ItemDef>,
pub fn new_from_item_base(
inner_item: ItemBase,
input_components: &[Item],
ability_map: &AbilityMap,
msm: &MaterialStatManifest,
) -> Self {
let mut components = Vec::new();
if inner_item.is_modular() {
// recipe ensures that types match (i.e. no axe heads on a sword hilt, or double
// sword blades)
components.extend(
input_components
.iter()
.map(|comp| comp.duplicate(ability_map, msm)),
);
}
let item_hash = {
let mut s = DefaultHasher::new();
inner_item.item_definition_id.hash(&mut s);
inner_item.item_definition_id().hash(&mut s);
components.hash(&mut s);
s.finish()
};
@ -673,8 +677,8 @@ impl Item {
item_id: Arc::new(AtomicCell::new(None)),
amount: NonZeroU32::new(1).unwrap(),
components,
slots: vec![None; inner_item.slots as usize],
item_def: inner_item,
slots: vec![None; inner_item.num_slots() as usize],
item_base: inner_item,
item_config: None,
hash: item_hash,
};
@ -685,11 +689,7 @@ impl Item {
/// Creates a new instance of an `Item` from the provided asset identifier
/// Panics if the asset does not exist.
pub fn new_from_asset_expect(asset_specifier: &str) -> Self {
let inner_item = Arc::<ItemDef>::load_expect_cloned(asset_specifier);
// TODO: Figure out better way to get msm and ability_map
let msm = MaterialStatManifest::default();
let ability_map = AbilityMap::default();
Item::new_from_item_def(inner_item, &[], &ability_map, &msm)
Item::new_from_asset(asset_specifier).expect("Expected asset to exist")
}
/// Creates a Vec containing one of each item that matches the provided
@ -703,18 +703,26 @@ impl Item {
/// Creates a new instance of an `Item from the provided asset identifier if
/// it exists
pub fn new_from_asset(asset: &str) -> Result<Self, Error> {
let inner_item = Arc::<ItemDef>::load_cloned(asset)?;
let inner_item = ItemBase::Raw(Arc::<ItemDef>::load_cloned(asset)?);
// TODO: Get msm and ability_map less hackily
let msm = MaterialStatManifest::default();
let ability_map = AbilityMap::default();
Ok(Item::new_from_item_def(inner_item, &[], &ability_map, &msm))
Ok(Item::new_from_item_base(
inner_item,
&[],
&ability_map,
&msm,
))
}
/// Duplicates an item, creating an exact copy but with a new item ID
#[must_use]
pub fn duplicate(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Self {
let mut new_item = Item::new_from_item_def(
Arc::clone(&self.item_def),
let mut new_item = Item::new_from_item_base(
match &self.item_base {
ItemBase::Raw(item_def) => ItemBase::Raw(Arc::clone(item_def)),
ItemBase::Modular(mod_base) => ItemBase::Modular(mod_base.duplicate()),
},
&self.components,
ability_map,
msm,
@ -827,18 +835,28 @@ impl Item {
self.slots.iter_mut().filter_map(mem::take)
}
pub fn item_definition_id(&self) -> &str { &self.item_def.item_definition_id }
pub fn item_definition_id(&self) -> &str {
match &self.item_base {
ItemBase::Raw(item_def) => &item_def.item_definition_id,
// TODO: Should this be handled better?
ItemBase::Modular(_) => "",
}
}
pub fn is_same_item_def(&self, item_def: &ItemDef) -> bool {
self.item_def.item_definition_id == item_def.item_definition_id
if let ItemBase::Raw(self_def) = &self.item_base {
self_def.item_definition_id == item_def.item_definition_id
} else {
false
}
}
pub fn matches_recipe_input(&self, recipe_input: &RecipeInput) -> bool {
match recipe_input {
RecipeInput::Item(item_def) => self.is_same_item_def(item_def),
RecipeInput::Tag(tag) => self.item_def.tags.contains(tag),
RecipeInput::Tag(tag) => self.tags().contains(tag),
RecipeInput::TagSameItem(tag, amount) => {
self.item_def.tags.contains(tag) && u32::from(self.amount) >= *amount
self.tags().contains(tag) && u32::from(self.amount) >= *amount
},
RecipeInput::ListSameItem(item_defs, amount) => item_defs.iter().any(|item_def| {
self.is_same_item_def(item_def) && u32::from(self.amount) >= *amount
@ -847,48 +865,65 @@ impl Item {
}
pub fn is_salvageable(&self) -> bool {
self.item_def
.tags
self.tags()
.iter()
.any(|tag| matches!(tag, ItemTag::SalvageInto(_)))
}
pub fn salvage_output(&self) -> impl Iterator<Item = &str> {
self.item_def
.tags
.iter()
.filter_map(|tag| {
self.tags().iter().filter_map(|tag| {
if let ItemTag::SalvageInto(material) = tag {
Some(material)
material.asset_identifier()
} else {
None
}
})
.filter_map(|material| material.asset_identifier())
}
pub fn name(&self) -> Cow<'_, str> {
match &self.item_def.name {
ItemName::Direct(name) => Cow::Borrowed(name),
ItemName::Modular => modular::modular_name(self, ""),
ItemName::Component(name) => modular::modular_name(self, name),
pub fn name(&self) -> Cow<str> {
match &self.item_base {
ItemBase::Raw(item_def) => Cow::Borrowed(&item_def.name),
ItemBase::Modular(mod_base) => mod_base.generate_name(self.components()),
}
}
pub fn description(&self) -> &str { &self.item_def.description }
pub fn description(&self) -> &str {
match &self.item_base {
ItemBase::Raw(item_def) => &item_def.description,
// TODO: See if James wanted to make description, else leave with none
ItemBase::Modular(_) => "",
}
}
pub fn kind(&self) -> &ItemKind { &self.item_def.kind }
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),
}
}
pub fn amount(&self) -> u32 { u32::from(self.amount) }
pub fn is_stackable(&self) -> bool {
match &self.item_base {
ItemBase::Raw(item_def) => item_def.is_stackable(),
// TODO: Let whoever implements stackable modular items deal with this
ItemBase::Modular(_) => false,
}
}
pub fn num_slots(&self) -> u16 { self.item_base.num_slots() }
/// NOTE: invariant that amount() ≤ max_amount(), 1 ≤ max_amount(),
/// and if !self.is_stackable(), self.max_amount() = 1.
pub fn max_amount(&self) -> u32 { if self.is_stackable() { u32::MAX } else { 1 } }
pub fn quality(&self) -> Quality {
match self.item_def.quality {
QualityKind::Direct(quality) => quality,
QualityKind::Modular => modular::resolve_quality(self),
match &self.item_base {
ItemBase::Raw(item_def) => item_def.quality,
ItemBase::Modular(mod_base) => mod_base.compute_quality(self.components()),
}
}
@ -916,7 +951,27 @@ impl Item {
block.get_sprite()?.collectible_id()?.to_item()
}
pub fn ability_spec(&self) -> Option<&AbilitySpec> { self.item_def.ability_spec.as_ref() }
pub fn ability_spec(&self) -> Option<Cow<AbilitySpec>> {
match &self.item_base {
ItemBase::Raw(item_def) => item_def.ability_spec.as_ref().map(Cow::Borrowed),
ItemBase::Modular(mod_base) => mod_base.ability_spec(self.components()),
}
}
pub fn tags(&self) -> &[ItemTag] {
match &self.item_base {
ItemBase::Raw(item_def) => &item_def.tags,
// TODO: Do this properly. It'll probably be important at some point.
ItemBase::Modular(_) => &[],
}
}
pub fn is_modular(&self) -> bool {
match &self.item_base {
ItemBase::Raw(_) => false,
ItemBase::Modular(_) => true,
}
}
pub fn item_hash(&self) -> u64 { self.hash }
@ -935,19 +990,21 @@ impl Item {
/// for either an `Item` containing the definition, or the actual `ItemDef`
pub trait ItemDesc {
fn description(&self) -> &str;
fn name(&self) -> Cow<'_, str>;
fn kind(&self) -> &ItemKind;
fn name(&self) -> Cow<str>;
fn kind(&self) -> Cow<ItemKind>;
fn quality(&self) -> Quality;
fn num_slots(&self) -> u16;
fn item_definition_id(&self) -> &str;
fn tags(&self) -> &[ItemTag];
fn concrete_item(&self) -> Option<&Item>;
fn is_modular(&self) -> bool;
fn components(&self) -> &[Item] { self.concrete_item().map_or(&[], |i| i.components()) }
fn tool(&self) -> Option<&Tool> {
if let ItemKind::Tool(tool) = self.kind() {
Some(tool)
fn tool_info(&self) -> Option<ToolKind> {
if let ItemKind::Tool(tool) = &*self.kind() {
Some(tool.kind)
} else {
None
}
@ -955,48 +1012,33 @@ pub trait ItemDesc {
}
impl ItemDesc for Item {
fn description(&self) -> &str { &self.item_def.description }
fn description(&self) -> &str { self.description() }
fn name(&self) -> Cow<'_, str> { self.name() }
fn name(&self) -> Cow<str> { self.name() }
fn kind(&self) -> &ItemKind { &self.item_def.kind }
fn kind(&self) -> Cow<ItemKind> { self.kind() }
fn quality(&self) -> Quality { self.quality() }
fn num_slots(&self) -> u16 { self.item_def.slots }
fn num_slots(&self) -> u16 { self.num_slots() }
fn item_definition_id(&self) -> &str { &self.item_def.item_definition_id }
fn item_definition_id(&self) -> &str { self.item_definition_id() }
fn tags(&self) -> &[ItemTag] { &self.item_def.tags }
fn tags(&self) -> &[ItemTag] { self.tags() }
fn concrete_item(&self) -> Option<&Item> { Some(self) }
fn is_modular(&self) -> bool { self.is_modular() }
}
impl ItemDesc for ItemDef {
fn description(&self) -> &str { &self.description }
fn name(&self) -> Cow<'_, str> {
match &self.name {
ItemName::Direct(name) | ItemName::Component(name) => Cow::Borrowed(name),
ItemName::Modular => {
let toolkind = if let ItemKind::Tool(tool) = &self.kind {
tool.kind.identifier_name()
} else {
"Weapon"
};
Cow::Owned(format!("Modular {}", toolkind))
},
}
}
fn name(&self) -> Cow<str> { Cow::Borrowed(&self.name) }
fn kind(&self) -> &ItemKind { &self.kind }
fn kind(&self) -> Cow<ItemKind> { Cow::Borrowed(&self.kind) }
fn quality(&self) -> Quality {
match &self.quality {
QualityKind::Direct(quality) => *quality,
QualityKind::Modular => Quality::Common,
}
}
fn quality(&self) -> Quality { self.quality }
fn num_slots(&self) -> u16 { self.slots }
@ -1005,6 +1047,8 @@ impl ItemDesc for ItemDef {
fn tags(&self) -> &[ItemTag] { &self.tags }
fn concrete_item(&self) -> Option<&Item> { None }
fn is_modular(&self) -> bool { false }
}
impl Component for Item {
@ -1021,9 +1065,9 @@ impl Component for ItemDrop {
impl<'a, T: ItemDesc + ?Sized> ItemDesc for &'a T {
fn description(&self) -> &str { (*self).description() }
fn name(&self) -> Cow<'_, str> { (*self).name() }
fn name(&self) -> Cow<str> { (*self).name() }
fn kind(&self) -> &ItemKind { (*self).kind() }
fn kind(&self) -> Cow<ItemKind> { (*self).kind() }
fn quality(&self) -> Quality { (*self).quality() }
@ -1036,6 +1080,8 @@ impl<'a, T: ItemDesc + ?Sized> ItemDesc for &'a T {
fn tags(&self) -> &[ItemTag] { (*self).tags() }
fn concrete_item(&self) -> Option<&Item> { None }
fn is_modular(&self) -> bool { (*self).is_modular() }
}
/// Returns all item asset specifiers

View File

@ -1,13 +1,136 @@
use super::{
tool::{self, Hands},
Item, ItemDesc, ItemKind, ItemName, RawItemDef, ToolKind,
tool::{self, AbilitySpec, Hands, MaterialStatManifest, Stats},
Item, ItemBase, ItemDesc, ItemKind, Quality, ToolKind,
};
use crate::{assets::AssetExt, lottery::Lottery, recipe};
use hashbrown::HashMap;
use lazy_static::lazy_static;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum ModularBase {
Tool(ToolKind),
}
impl ModularBase {
pub(super) fn duplicate(&self) -> Self {
match self {
ModularBase::Tool(toolkind) => ModularBase::Tool(*toolkind),
}
}
pub fn kind(&self, components: &[Item], msm: &MaterialStatManifest) -> Cow<ItemKind> {
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
}
}
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
}
match self {
ModularBase::Tool(toolkind) => Cow::Owned(ItemKind::Tool(tool::Tool {
kind: *toolkind,
hands: resolve_hands(components),
stats: resolve_stats(components, msm),
})),
}
}
/// Modular weapons are named as "{Material} {Weapon}" where {Weapon} is
/// from the damage component used and {Material} is from the material
/// 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))
},
}
}
pub fn compute_quality(&self, components: &[Item]) -> Quality {
components
.iter()
.fold(Quality::Low, |a, b| a.max(b.quality()))
}
pub fn ability_spec(&self, _components: &[Item]) -> Option<Cow<AbilitySpec>> {
match self {
ModularBase::Tool(toolkind) => Some(Cow::Owned(AbilitySpec::Tool(*toolkind))),
}
}
}
// TODO: Look into changing to: Primary, Secondary
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum ModularComponentKind {
Damage,
@ -53,106 +176,8 @@ const SUPPORTED_TOOLKINDS: [ToolKind; 6] = [
const WEAPON_PREFIX: &str = "common.items.weapons.modular";
fn make_weapon_def(toolkind: ToolKind) -> (String, RawItemDef) {
let identifier = format!("{}.{}", WEAPON_PREFIX, toolkind.identifier_name());
let name = ItemName::Modular;
let tool = tool::Tool {
kind: toolkind,
hands: tool::HandsKind::Modular,
stats: tool::StatKind::Modular,
};
let kind = ItemKind::Tool(tool);
let item = RawItemDef {
name,
description: "".to_string(),
kind,
quality: super::QualityKind::Modular,
tags: Vec::new(),
slots: 0,
ability_spec: None,
};
(identifier, item)
}
fn initialize_modular_assets() -> HashMap<String, RawItemDef> {
let mut itemdefs = HashMap::new();
for &toolkind in &SUPPORTED_TOOLKINDS {
let (identifier, item) = make_weapon_def(toolkind);
itemdefs.insert(identifier, item);
}
itemdefs
}
lazy_static! {
static ref ITEM_DEFS: HashMap<String, RawItemDef> = initialize_modular_assets();
}
/// Synthesize modular assets programmatically, to allow for the following:
/// - Allow the modular tag_examples to auto-update with the list of applicable
/// components
pub(super) fn synthesize_modular_asset(specifier: &str) -> Option<RawItemDef> {
let ret = ITEM_DEFS.get(specifier).cloned();
tracing::trace!("synthesize_modular_asset({:?}) -> {:?}", specifier, ret);
ret
}
/// Modular weapons are named as "{Material} {Weapon}" where {Weapon} is from
/// the damage component used and {Material} is from the material the damage
/// component is created from.
pub(super) fn modular_name<'a>(item: &'a Item, arg1: &'a str) -> Cow<'a, str> {
// Closure to get material name from an item
let material_name = |component: &Item| {
component
.components()
.iter()
.filter_map(|comp| match comp.kind() {
ItemKind::Ingredient { descriptor, .. } => Some(descriptor.to_owned()),
_ => None,
})
.last()
.unwrap_or_else(|| "Modular".to_owned())
};
match item.kind() {
ItemKind::Tool(tool) => {
let main_components = item.components().iter().filter(|comp| {
matches!(comp.kind(), ItemKind::ModularComponent(ModularComponent { modkind, .. })
if *modkind == ModularComponentKind::main_component(tool.kind)
)
});
// Last fine as there should only ever be one damage component on a weapon
let (material_name, weapon_name) = if let Some(component) = main_components.last() {
let material_name = material_name(component);
let weapon_name =
if let ItemKind::ModularComponent(ModularComponent { weapon_name, .. }) =
component.kind()
{
weapon_name
} else {
tool.kind.identifier_name()
};
(material_name, weapon_name)
} else {
("Modular".to_owned(), tool.kind.identifier_name())
};
Cow::Owned(format!("{} {}", material_name, weapon_name))
},
ItemKind::ModularComponent(comp) => match comp.modkind {
ModularComponentKind::Damage => {
let material_name = material_name(item);
Cow::Owned(format!("{} {}", material_name, arg1))
},
ModularComponentKind::Held => Cow::Borrowed(arg1),
},
_ => Cow::Borrowed("Modular Item"),
}
}
pub(super) fn resolve_quality(item: &Item) -> super::Quality {
item.components
.iter()
.fold(super::Quality::Low, |a, b| a.max(b.quality()))
fn make_weapon_id(toolkind: ToolKind) -> String {
format!("{}.{}", WEAPON_PREFIX, toolkind.identifier_name())
}
/// Returns directory that contains components for a particular combination of
@ -167,18 +192,14 @@ fn make_mod_comp_dir_spec(tool: ToolKind, mod_kind: ModularComponentKind) -> Str
)
}
/// Creates initial item for a modular weapon
pub fn initialize_modular_weapon(toolkind: ToolKind) -> Item {
Item::new_from_asset_expect(&make_weapon_def(toolkind).0)
}
/// 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() {
Some(mod_comp)
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
}
@ -188,9 +209,6 @@ pub fn random_weapon(tool: ToolKind, material: super::Material, hands: Option<Ha
let ability_map = Default::default();
let msm = Default::default();
// Initialize modular weapon
let mut modular_weapon = initialize_modular_weapon(tool);
// Load recipe book (done to check that material is valid for a particular
// component)
let recipe::RawRecipeBook(recipes) =
@ -236,7 +254,7 @@ pub fn random_weapon(tool: ToolKind, material: super::Material, hands: Option<Ha
})
// 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)
matches!(unwrap_modular_component(item), Some(ModularComponent { modkind, .. }) if modkind != material_comp)
|| is_composed_of(item.item_definition_id())
})
.map(|item| (1.0, item))
@ -272,12 +290,14 @@ pub fn random_weapon(tool: ToolKind, material: super::Material, hands: Option<Ha
},
}
// Insert components onto modular weapon
modular_weapon.add_component(damage_component, &ability_map, &msm);
modular_weapon.add_component(held_component, &ability_map, &msm);
// Returns fully created modular weapon
modular_weapon
// 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
@ -285,14 +305,14 @@ pub fn random_weapon(tool: ToolKind, material: super::Material, hands: Option<Ha
pub type ModularWeaponKey = (String, String, Hands);
pub fn weapon_to_key(mod_weap: &dyn ItemDesc) -> ModularWeaponKey {
let hands = if let ItemKind::Tool(tool) = mod_weap.kind() {
tool.hands.resolve_hands(mod_weap.components())
let hands = if let ItemKind::Tool(tool) = &*mod_weap.kind() {
tool.hands
} else {
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)
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(_))) {
@ -326,7 +346,7 @@ pub enum ModularWeaponComponentKeyError {
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 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()

View File

@ -3,12 +3,12 @@
use crate::{
assets::{self, Asset, AssetExt},
comp::{item::ItemKind, skills::Skill, CharacterAbility, Item},
comp::{skills::Skill, CharacterAbility},
};
use hashbrown::HashMap;
use serde::{Deserialize, Serialize};
use std::{
ops::{AddAssign, DivAssign, MulAssign, Sub},
ops::{AddAssign, DivAssign, Mul, MulAssign, Sub},
time::Duration,
};
@ -75,30 +75,16 @@ impl ToolKind {
| ToolKind::Shield
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum HandsKind {
Direct(Hands),
Modular,
}
impl HandsKind {
pub fn resolve_hands(&self, components: &[Item]) -> Hands {
match self {
HandsKind::Direct(hands) => *hands,
HandsKind::Modular => {
// 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
}
},
}
pub fn can_block(&self) -> bool {
matches!(
self,
ToolKind::Sword
| ToolKind::Axe
| ToolKind::Hammer
| ToolKind::Shield
| ToolKind::Dagger
)
}
}
@ -108,16 +94,6 @@ pub enum Hands {
Two,
}
impl Hands {
// Changing this will break persistence of modular weapons
pub fn identifier_name(&self) -> &'static str {
match self {
Hands::One => "one-handed",
Hands::Two => "two-handed",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub struct Stats {
pub equip_time_secs: f32,
@ -131,7 +107,7 @@ pub struct Stats {
}
impl Stats {
pub fn zeroed() -> Stats {
pub fn zero() -> Stats {
Stats {
equip_time_secs: 0.0,
power: 0.0,
@ -156,14 +132,6 @@ impl Stats {
buff_strength: 1.0,
}
}
#[must_use]
pub fn clamp_speed(mut self) -> Self {
// if a tool has 0.0 speed, that panics due to being infinite duration, so
// enforce speed >= 0.1 on the final product (but not the intermediates)
self.speed = self.speed.max(0.1);
self
}
}
impl Asset for Stats {
@ -180,6 +148,7 @@ impl AddAssign<Stats> for Stats {
self.speed += other.speed;
self.crit_chance += other.crit_chance;
self.range += other.range;
self.energy_efficiency += other.energy_efficiency;
self.buff_strength += other.buff_strength;
}
}
@ -191,20 +160,35 @@ impl MulAssign<Stats> for Stats {
self.speed *= other.speed;
self.crit_chance *= other.crit_chance;
self.range *= other.range;
self.energy_efficiency *= other.energy_efficiency;
self.buff_strength *= other.buff_strength;
}
}
impl Mul<Stats> for Stats {
type Output = Self;
fn mul(self, other: Self) -> Self {
Self {
equip_time_secs: self.equip_time_secs * other.equip_time_secs,
power: self.power * other.power,
effect_power: self.effect_power * other.effect_power,
speed: self.speed * other.speed,
crit_chance: self.crit_chance * other.crit_chance,
range: self.range * other.range,
energy_efficiency: self.energy_efficiency * other.energy_efficiency,
buff_strength: self.buff_strength * other.buff_strength,
}
}
}
impl DivAssign<usize> for Stats {
fn div_assign(&mut self, scalar: usize) {
self.equip_time_secs /= scalar as f32;
// since averaging occurs when the stats are used multiplicatively, don't permit
// multiplying an equip_time_secs by 0, since that would be overpowered
self.equip_time_secs = self.equip_time_secs.max(0.001);
self.power /= scalar as f32;
self.effect_power /= scalar as f32;
self.speed /= scalar as f32;
self.crit_chance /= scalar as f32;
self.range /= scalar as f32;
self.energy_efficiency /= scalar as f32;
self.buff_strength /= scalar as f32;
}
}
@ -244,80 +228,24 @@ impl Default for MaterialStatManifest {
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
pub enum StatKind {
Direct(Stats),
Modular,
}
impl StatKind {
pub fn resolve_stats(&self, msm: &MaterialStatManifest, components: &[Item]) -> Stats {
let mut stats = match self {
StatKind::Direct(stats) => *stats,
StatKind::Modular => Stats::one(),
};
let mut 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 =
StatKind::Direct(mc.stats).resolve_stats(msm, item.components());
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()) {
multipliers.push(*mult_stats);
}
},
// TODO: add stats from enhancement slots
_ => (),
}
}
// Take the average of the material multipliers
if !multipliers.is_empty() {
let mut average_mult = Stats::zeroed();
for stat in multipliers.iter() {
average_mult += *stat;
}
average_mult /= multipliers.len();
stats *= average_mult;
}
stats
}
}
impl From<(&MaterialStatManifest, &[Item], &Tool)> for Stats {
fn from((msm, components, tool): (&MaterialStatManifest, &[Item], &Tool)) -> Self {
tool.stats.resolve_stats(msm, components).clamp_speed()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Tool {
pub kind: ToolKind,
pub hands: HandsKind,
pub stats: StatKind,
pub hands: Hands,
pub stats: Stats,
// TODO: item specific abilities
}
impl Tool {
// DO NOT USE UNLESS YOU KNOW WHAT YOU ARE DOING
// Added for CSV import of stats
pub fn new(kind: ToolKind, hands: Hands, stats: Stats) -> Self {
Self {
kind,
hands: HandsKind::Direct(hands),
stats: StatKind::Direct(stats),
}
}
pub fn new(kind: ToolKind, hands: Hands, stats: Stats) -> Self { Self { kind, hands, stats } }
pub fn empty() -> Self {
Self {
kind: ToolKind::Empty,
hands: HandsKind::Direct(Hands::One),
stats: StatKind::Direct(Stats {
hands: Hands::One,
stats: Stats {
equip_time_secs: 0.0,
power: 1.00,
effect_power: 1.00,
@ -326,56 +254,30 @@ impl Tool {
range: 1.0,
energy_efficiency: 1.0,
buff_strength: 1.0,
}),
},
}
}
// Keep power between 0.5 and 2.00
pub fn base_power(&self, msm: &MaterialStatManifest, components: &[Item]) -> f32 {
self.stats.resolve_stats(msm, components).power
pub fn base_power(&self) -> f32 { self.stats.power }
pub fn base_effect_power(&self) -> f32 { self.stats.effect_power }
pub fn base_speed(&self) -> f32 {
// Has floor to prevent infinite durations being created later down due to a
// divide by zero
self.stats.speed.max(0.1)
}
pub fn base_effect_power(&self, msm: &MaterialStatManifest, components: &[Item]) -> f32 {
self.stats.resolve_stats(msm, components).effect_power
}
pub fn base_crit_chance(&self) -> f32 { self.stats.crit_chance }
pub fn base_speed(&self, msm: &MaterialStatManifest, components: &[Item]) -> f32 {
self.stats
.resolve_stats(msm, components)
.clamp_speed()
.speed
}
pub fn base_range(&self) -> f32 { self.stats.range }
pub fn base_crit_chance(&self, msm: &MaterialStatManifest, components: &[Item]) -> f32 {
self.stats.resolve_stats(msm, components).crit_chance
}
pub fn base_energy_efficiency(&self) -> f32 { self.stats.energy_efficiency }
pub fn base_range(&self, msm: &MaterialStatManifest, components: &[Item]) -> f32 {
self.stats.resolve_stats(msm, components).range
}
pub fn base_buff_strength(&self) -> f32 { self.stats.buff_strength }
pub fn base_energy_efficiency(&self, msm: &MaterialStatManifest, components: &[Item]) -> f32 {
self.stats.resolve_stats(msm, components).energy_efficiency
}
pub fn base_buff_strength(&self, msm: &MaterialStatManifest, components: &[Item]) -> f32 {
self.stats.resolve_stats(msm, components).buff_strength
}
pub fn equip_time(&self, msm: &MaterialStatManifest, components: &[Item]) -> Duration {
Duration::from_secs_f32(self.stats.resolve_stats(msm, components).equip_time_secs)
}
pub fn can_block(&self) -> bool {
matches!(
self.kind,
ToolKind::Sword
| ToolKind::Axe
| ToolKind::Hammer
| ToolKind::Shield
| ToolKind::Dagger
)
}
pub fn equip_time(&self) -> Duration { Duration::from_secs_f32(self.stats.equip_time_secs) }
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -387,16 +289,10 @@ pub struct AbilitySet<T> {
impl AbilitySet<AbilityItem> {
#[must_use]
pub fn modified_by_tool(
self,
tool: &Tool,
msm: &MaterialStatManifest,
components: &[Item],
) -> Self {
let stats = Stats::from((msm, components, tool));
pub fn modified_by_tool(self, tool: &Tool) -> Self {
self.map(|a| AbilityItem {
id: a.id,
ability: a.ability.adjusted_by_stats(stats),
ability: a.ability.adjusted_by_stats(tool.stats),
})
}
}

View File

@ -1,6 +1,6 @@
use crate::comp::{
inventory::{
item::{Hands, ItemKind},
item::{tool::Tool, Hands, ItemKind},
slot::{ArmorSlot, EquipSlot},
InvSlot,
},
@ -168,9 +168,13 @@ impl Loadout {
assert_eq!(self.swap(equip_slot_a, item_b), None);
// Check if items are valid in their new positions
if !self.slot_can_hold(equip_slot_a, self.equipped(equip_slot_a))
|| !self.slot_can_hold(equip_slot_b, self.equipped(equip_slot_b))
{
if !self.slot_can_hold(
equip_slot_a,
self.equipped(equip_slot_a).map(|x| x.kind()).as_deref(),
) || !self.slot_can_hold(
equip_slot_b,
self.equipped(equip_slot_b).map(|x| x.kind()).as_deref(),
) {
// If not, revert the swap
let item_a = self.swap(equip_slot_a, None);
let item_b = self.swap(equip_slot_b, item_a);
@ -183,11 +187,11 @@ impl Loadout {
/// returned, or if there are no free slots then the first occupied slot
/// will be returned. The bool part of the tuple indicates whether an item
/// is already equipped in the slot.
pub(super) fn get_slot_to_equip_into(&self, item: &Item) -> Option<EquipSlot> {
pub(super) fn get_slot_to_equip_into(&self, item_kind: &ItemKind) -> Option<EquipSlot> {
let mut suitable_slots = self
.slots
.iter()
.filter(|s| self.slot_can_hold(s.equip_slot, Some(item)));
.filter(|s| self.slot_can_hold(s.equip_slot, Some(item_kind)));
let first = suitable_slots.next();
@ -199,14 +203,15 @@ impl Loadout {
.or_else(|| first.map(|x| x.equip_slot))
}
/// Returns all items currently equipped that an item could replace
pub(super) fn equipped_items_replaceable_by<'a>(
/// Returns all items currently equipped that an item of the given ItemKind
/// could replace
pub(super) fn equipped_items_of_kind<'a>(
&'a self,
item: &'a Item,
item_kind: &'a ItemKind,
) -> impl Iterator<Item = &'a Item> {
self.slots
.iter()
.filter(move |s| self.slot_can_hold(s.equip_slot, Some(item)))
.filter(move |s| self.slot_can_hold(s.equip_slot, Some(item_kind)))
.filter_map(|s| s.slot.as_ref())
}
@ -288,7 +293,7 @@ impl Loadout {
let loadout_slot = self
.slots
.iter()
.find(|s| s.slot.is_none() && self.slot_can_hold(s.equip_slot, Some(&item)))
.find(|s| s.slot.is_none() && self.slot_can_hold(s.equip_slot, Some(&*item.kind())))
.map(|s| s.equip_slot);
if let Some(slot) = self
.slots
@ -307,55 +312,53 @@ impl Loadout {
}
/// Checks that a slot can hold a given item
pub(super) fn slot_can_hold(&self, equip_slot: EquipSlot, item: Option<&Item>) -> bool {
pub(super) fn slot_can_hold(
&self,
equip_slot: EquipSlot,
item_kind: Option<&ItemKind>,
) -> bool {
// Disallow equipping incompatible weapon pairs (i.e a two-handed weapon and a
// one-handed weapon)
if !(match equip_slot {
EquipSlot::ActiveMainhand => {
Loadout::is_valid_weapon_pair(item, self.equipped(EquipSlot::ActiveOffhand))
},
EquipSlot::ActiveOffhand => {
Loadout::is_valid_weapon_pair(self.equipped(EquipSlot::ActiveMainhand), item)
},
EquipSlot::InactiveMainhand => {
Loadout::is_valid_weapon_pair(item, self.equipped(EquipSlot::InactiveOffhand))
},
EquipSlot::InactiveOffhand => {
Loadout::is_valid_weapon_pair(self.equipped(EquipSlot::InactiveMainhand), item)
},
EquipSlot::ActiveMainhand => Loadout::is_valid_weapon_pair(
item_kind,
self.equipped(EquipSlot::ActiveOffhand)
.map(|x| x.kind())
.as_deref(),
),
EquipSlot::ActiveOffhand => Loadout::is_valid_weapon_pair(
self.equipped(EquipSlot::ActiveMainhand)
.map(|x| x.kind())
.as_deref(),
item_kind,
),
EquipSlot::InactiveMainhand => Loadout::is_valid_weapon_pair(
item_kind,
self.equipped(EquipSlot::InactiveOffhand)
.map(|x| x.kind())
.as_deref(),
),
EquipSlot::InactiveOffhand => Loadout::is_valid_weapon_pair(
self.equipped(EquipSlot::InactiveMainhand)
.map(|x| x.kind())
.as_deref(),
item_kind,
),
_ => true,
}) {
return false;
}
item.map_or(true, |item| equip_slot.can_hold(item))
item_kind.map_or(true, |item| equip_slot.can_hold(item))
}
fn is_valid_weapon_pair(main_hand: Option<&Item>, off_hand: Option<&Item>) -> bool {
// Checks that a valid weapon pair is equipped, returns true if...
match (
main_hand.map(|i| (i.kind(), i.components())),
off_hand.map(|i| (i.kind(), i.components())),
) {
// A weapon is being equipped in the mainhand, but not in the offhand
(Some((ItemKind::Tool(_), _)), None) => true,
// A weapon is being equipped in both slots, and both weapons are 1 handed
(
Some((ItemKind::Tool(tool_1), components_1)),
Some((ItemKind::Tool(tool_2), components_2)),
) => {
matches!(
(
tool_1.hands.resolve_hands(components_1),
tool_2.hands.resolve_hands(components_2)
),
(Hands::One, Hands::One)
)
},
// A weapon is being unequipped that will result in both slots being empty
(None, None) => true,
_ => false,
}
#[rustfmt::skip]
fn is_valid_weapon_pair(main_hand: Option<&ItemKind>, off_hand: Option<&ItemKind>) -> bool {
matches!((main_hand, off_hand),
(Some(ItemKind::Tool(Tool { hands: Hands::One, .. })), None) |
(Some(ItemKind::Tool(Tool { hands: Hands::Two, .. })), None) |
(Some(ItemKind::Tool(Tool { hands: Hands::One, .. })), Some(ItemKind::Tool(Tool { hands: Hands::One, .. }))) |
(None, None))
}
pub(super) fn swap_equipped_weapons(&mut self) {
@ -363,7 +366,7 @@ impl Loadout {
// nothing is equipped in slot
let valid_slot = |equip_slot| {
self.equipped(equip_slot)
.map_or(true, |i| self.slot_can_hold(equip_slot, Some(i)))
.map_or(true, |i| self.slot_can_hold(equip_slot, Some(&*i.kind())))
};
// If every weapon is currently in a valid slot, after this change they will
@ -456,12 +459,10 @@ mod tests {
loadout.swap(EquipSlot::Armor(ArmorSlot::Bag1), Some(get_test_bag(1)));
let result = loadout
.get_slot_to_equip_into(&Item::create_test_item_from_kind(ItemKind::Armor(
Armor::test_armor(
.get_slot_to_equip_into(&ItemKind::Armor(Armor::test_armor(
ArmorKind::Bag("test".to_string()),
Protection::Normal(0.0),
Protection::Normal(0.0),
),
)))
.unwrap();
@ -478,12 +479,10 @@ mod tests {
loadout.swap(EquipSlot::Armor(ArmorSlot::Bag4), Some(get_test_bag(1)));
let result = loadout
.get_slot_to_equip_into(&Item::create_test_item_from_kind(ItemKind::Armor(
Armor::test_armor(
.get_slot_to_equip_into(&ItemKind::Armor(Armor::test_armor(
ArmorKind::Bag("test".to_string()),
Protection::Normal(0.0),
Protection::Normal(0.0),
),
)))
.unwrap();

View File

@ -1073,7 +1073,10 @@ impl LoadoutBuilder {
#[must_use = "Method consumes builder and returns updated builder."]
fn with_equipment(mut self, equip_slot: EquipSlot, item: Option<Item>) -> Self {
// Panic if item doesn't correspond to slot
assert!(item.as_ref().map_or(true, |item| equip_slot.can_hold(item)));
assert!(
item.as_ref()
.map_or(true, |item| equip_slot.can_hold(&*item.kind()))
);
self.0.swap(equip_slot, item);
self

View File

@ -2,14 +2,14 @@ use core::ops::Not;
use serde::{Deserialize, Serialize};
use specs::{Component, DerefFlaggedStorage};
use specs_idvs::IdvStorage;
use std::{borrow::Cow, convert::TryFrom, mem, ops::Range};
use std::{convert::TryFrom, mem, ops::Range};
use tracing::{debug, trace, warn};
use vek::Vec3;
use crate::{
comp::{
inventory::{
item::{tool::AbilityMap, ItemDef, MaterialStatManifest, TagExampleInfo},
item::{tool::AbilityMap, ItemDef, ItemKind, MaterialStatManifest, TagExampleInfo},
loadout::Loadout,
slot::{EquipSlot, Slot, SlotError},
},
@ -128,8 +128,8 @@ impl Inventory {
// Quality is sorted in reverse since we want high quality items first
InventorySortOrder::Quality => Ord::cmp(&b.quality(), &a.quality()),
InventorySortOrder::Tag => Ord::cmp(
&a.tags.first().map_or(Cow::Borrowed(""), |tag| tag.name()),
&b.tags.first().map_or(Cow::Borrowed(""), |tag| tag.name()),
&a.tags().first().map_or("", |tag| tag.name()),
&b.tags().first().map_or("", |tag| tag.name()),
),
});
@ -540,7 +540,7 @@ impl Inventory {
#[must_use = "Returned items will be lost if not used"]
pub fn equip(&mut self, inv_slot: InvSlotId) -> Vec<Item> {
self.get(inv_slot)
.and_then(|item| self.loadout.get_slot_to_equip_into(item))
.and_then(|item| self.loadout.get_slot_to_equip_into(&*item.kind()))
.map(|equip_slot| self.swap_inventory_loadout(inv_slot, equip_slot))
.unwrap_or_else(Vec::new)
}
@ -551,7 +551,7 @@ impl Inventory {
pub fn free_after_equip(&self, inv_slot: InvSlotId) -> i32 {
let (inv_slot_for_equipped, slots_from_equipped) = self
.get(inv_slot)
.and_then(|item| self.loadout.get_slot_to_equip_into(item))
.and_then(|item| self.loadout.get_slot_to_equip_into(&*item.kind()))
.and_then(|equip_slot| self.equipped(equip_slot))
.map_or((1, 0), |item| (0, item.slots().len()));
@ -758,7 +758,7 @@ impl Inventory {
pub fn can_swap(&self, inv_slot_id: InvSlotId, equip_slot: EquipSlot) -> bool {
// Check if loadout slot can hold item
if !self.get(inv_slot_id).map_or(true, |item| {
self.loadout.slot_can_hold(equip_slot, Some(item))
self.loadout.slot_can_hold(equip_slot, Some(&*item.kind()))
}) {
trace!("can_swap = false, equip slot can't hold item");
return false;
@ -775,11 +775,11 @@ impl Inventory {
true
}
pub fn equipped_items_replaceable_by<'a>(
pub fn equipped_items_of_kind<'a>(
&'a self,
item: &'a Item,
item_kind: &'a ItemKind,
) -> impl Iterator<Item = &'a Item> {
self.loadout.equipped_items_replaceable_by(item)
self.loadout.equipped_items_of_kind(item_kind)
}
pub fn swap_equipped_weapons(&mut self) { self.loadout.swap_equipped_weapons() }

View File

@ -110,28 +110,22 @@ pub enum ArmorSlot {
}
impl Slot {
pub fn can_hold(self, item: &item::Item) -> bool {
match (self, item) {
pub fn can_hold(self, item_kind: &item::ItemKind) -> bool {
match (self, item_kind) {
(Self::Inventory(_), _) => true,
(Self::Equip(slot), item) => slot.can_hold(item),
(Self::Equip(slot), item_kind) => slot.can_hold(item_kind),
}
}
}
impl EquipSlot {
pub fn can_hold(self, item: &item::Item) -> bool {
match (self, item.kind()) {
pub fn can_hold(self, item_kind: &item::ItemKind) -> bool {
match (self, item_kind) {
(Self::Armor(slot), ItemKind::Armor(armor::Armor { kind, .. })) => slot.can_hold(kind),
(Self::ActiveMainhand, ItemKind::Tool(_)) => true,
(Self::ActiveOffhand, ItemKind::Tool(tool)) => matches!(
tool.hands.resolve_hands(item.components()),
tool::Hands::One
),
(Self::ActiveOffhand, ItemKind::Tool(tool)) => matches!(tool.hands, tool::Hands::One),
(Self::InactiveMainhand, ItemKind::Tool(_)) => true,
(Self::InactiveOffhand, ItemKind::Tool(tool)) => matches!(
tool.hands.resolve_hands(item.components()),
tool::Hands::One
),
(Self::InactiveOffhand, ItemKind::Tool(tool)) => matches!(tool.hands, tool::Hands::One),
(Self::Lantern, ItemKind::Lantern(_)) => true,
(Self::Glider, ItemKind::Glider(_)) => true,
_ => false,

View File

@ -230,7 +230,7 @@ impl Poise {
let protection = inventory
.equipped_items()
.filter_map(|item| {
if let ItemKind::Armor(armor) = &item.kind() {
if let ItemKind::Armor(armor) = &*item.kind() {
armor.poise_resilience()
} else {
None

View File

@ -2,7 +2,9 @@ use crate::{
assets::{self, AssetExt, AssetHandle},
comp::{
inventory::slot::InvSlotId,
item::{modular, tool::AbilityMap, ItemDef, ItemKind, ItemTag, MaterialStatManifest},
item::{
modular, tool::AbilityMap, ItemBase, ItemDef, ItemKind, ItemTag, MaterialStatManifest,
},
Inventory, Item,
},
terrain::SpriteKind,
@ -123,8 +125,12 @@ impl Recipe {
}
let (item_def, quantity) = &self.output;
let mut crafted_item =
Item::new_from_item_def(Arc::clone(item_def), &[], ability_map, msm);
let mut crafted_item = Item::new_from_item_base(
ItemBase::Raw(Arc::clone(item_def)),
&[],
ability_map,
msm,
);
for component in components {
crafted_item.add_component(component, ability_map, msm);
}
@ -244,19 +250,22 @@ pub fn modular_weapon(
) -> Result<Item, ModularWeaponError> {
use modular::{ModularComponent, ModularComponentKind};
// Closure to get inner modular component info from item in a given slot
let unwrap_modular = |slot| -> Option<&ModularComponent> {
if let Some(ItemKind::ModularComponent(mod_comp)) = inv.get(slot).map(|item| &item.kind) {
Some(mod_comp)
fn unwrap_modular(inv: &Inventory, slot: InvSlotId) -> Option<ModularComponent> {
if let Some(ItemKind::ModularComponent(mod_comp)) =
inv.get(slot).map(|item| item.kind()).as_deref()
{
// TODO: Remove
Some(mod_comp.clone())
} else {
None
}
};
}
// 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(damage_component),
unwrap_modular(held_component),
unwrap_modular(inv, damage_component),
unwrap_modular(inv, held_component),
) {
// Checks that damage and held component slots each contain a damage and held
// modular component respectively
@ -296,14 +305,14 @@ pub fn modular_weapon(
.take(held_component, ability_map, msm)
.expect("Expected component to exist");
// Initialize modular weapon
let mut modular_weapon = modular::initialize_modular_weapon(tool_kind);
// Insert components into modular weapon item
modular_weapon.add_component(damage_component, ability_map, msm);
modular_weapon.add_component(held_component, ability_map, msm);
Ok(modular_weapon)
// Create modular weapon
let components = vec![damage_component, held_component];
Ok(Item::new_from_item_base(
ItemBase::Modular(modular::ModularBase::Tool(tool_kind)),
&components,
ability_map,
msm,
))
},
Err(err) => Err(err),
}

View File

@ -5,7 +5,7 @@ use crate::{
arthropod, biped_large, biped_small,
character_state::OutputEvents,
inventory::slot::{EquipSlot, Slot},
item::{Hands, Item, ItemKind, Tool, ToolKind},
item::{Hands, ItemKind, ToolKind},
quadruped_low, quadruped_medium, quadruped_small,
skills::{Skill, SwimSkill, SKILL_MODIFIERS},
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind,
@ -581,11 +581,10 @@ pub fn attempt_wield(data: &JoinData<'_>, update: &mut StateUpdate) {
let equip_time = |equip_slot| {
data.inventory
.and_then(|inv| inv.equipped(equip_slot))
.and_then(|item| match item.kind() {
ItemKind::Tool(tool) => Some((item, tool)),
.and_then(|item| match &*item.kind() {
ItemKind::Tool(tool) => Some(tool.equip_time()),
_ => None,
})
.map(|(item, tool)| tool.equip_time(data.msm, item.components()))
};
// Calculates time required to equip weapons, if weapon in mainhand and offhand,
@ -704,7 +703,7 @@ pub fn handle_manipulate_loadout(
if let Some((item_kind, item)) = data
.inventory
.and_then(|inv| inv.get(inv_slot))
.and_then(|item| Option::<ItemUseKind>::from(item.kind()).zip(Some(item)))
.and_then(|item| Option::<ItemUseKind>::from(&*item.kind()).zip(Some(item)))
{
let (buildup_duration, use_duration, recover_duration) = item_kind.durations();
// If item returns a valid kind for item use, do into use item character state
@ -948,7 +947,7 @@ pub fn attempt_input(
/// Checks that player can block, then attempts to block
pub fn handle_block_input(data: &JoinData<'_>, update: &mut StateUpdate) {
let can_block = |equip_slot| matches!(unwrap_tool_data(data, equip_slot), Some((tool, _)) if tool.can_block());
let can_block = |equip_slot| matches!(unwrap_tool_data(data, equip_slot), Some((kind, _)) if kind.can_block());
let hands = get_hands(data);
if input_is_pressed(data, InputKind::Block)
&& (can_block(EquipSlot::ActiveMainhand)
@ -1000,16 +999,14 @@ pub fn is_strafing(data: &JoinData<'_>, update: &StateUpdate) -> bool {
}
/// Returns tool and components
pub fn unwrap_tool_data<'a>(
data: &'a JoinData,
equip_slot: EquipSlot,
) -> Option<(&'a Tool, &'a [Item])> {
if let Some((ItemKind::Tool(tool), components)) = data
pub fn unwrap_tool_data(data: &JoinData, equip_slot: EquipSlot) -> Option<(ToolKind, Hands)> {
if let Some(ItemKind::Tool(tool)) = data
.inventory
.and_then(|inv| inv.equipped(equip_slot))
.map(|i| (i.kind(), i.components()))
.map(|i| i.kind())
.as_deref()
{
Some((tool, components))
Some((tool.kind, tool.hands))
} else {
None
}
@ -1017,12 +1014,13 @@ pub fn unwrap_tool_data<'a>(
pub fn get_hands(data: &JoinData<'_>) -> (Option<Hands>, Option<Hands>) {
let hand = |slot| {
if let Some((ItemKind::Tool(tool), components)) = data
if let Some(ItemKind::Tool(tool)) = data
.inventory
.and_then(|inv| inv.equipped(slot))
.map(|i| (i.kind(), i.components()))
.map(|i| i.kind())
.as_deref()
{
Some(tool.hands.resolve_hands(components))
Some(tool.hands)
} else {
None
}
@ -1046,8 +1044,8 @@ pub fn get_crit_data(data: &JoinData<'_>, ai: AbilityInfo) -> (f32, f32) {
})
.and_then(|slot| data.inventory.and_then(|inv| inv.equipped(slot)))
.and_then(|item| {
if let ItemKind::Tool(tool) = item.kind() {
Some(tool.base_crit_chance(data.msm, item.components()))
if let ItemKind::Tool(tool) = &*item.kind() {
Some(tool.base_crit_chance())
} else {
None
}
@ -1068,8 +1066,8 @@ pub fn get_buff_strength(data: &JoinData<'_>, ai: AbilityInfo) -> f32 {
})
.and_then(|slot| data.inventory.and_then(|inv| inv.equipped(slot)))
.and_then(|item| {
if let ItemKind::Tool(tool) = item.kind() {
Some(tool.base_buff_strength(data.msm, item.components()))
if let ItemKind::Tool(tool) = &*item.kind() {
Some(tool.base_buff_strength())
} else {
None
}
@ -1178,10 +1176,12 @@ impl AbilityInfo {
} else {
unwrap_tool_data(data, EquipSlot::ActiveMainhand)
};
let (tool, hand) = (
tool_data.map(|(t, _)| t.kind),
tool_data.map(|(t, components)| HandInfo::from_main_tool(t, components, from_offhand)),
);
let (tool, hand) = tool_data.map_or((None, None), |(kind, hands)| {
(
Some(kind),
Some(HandInfo::from_main_tool(hands, from_offhand)),
)
});
Self {
tool,
@ -1200,8 +1200,8 @@ pub enum HandInfo {
}
impl HandInfo {
pub fn from_main_tool(tool: &Tool, components: &[Item], from_offhand: bool) -> Self {
match tool.hands.resolve_hands(components) {
pub fn from_main_tool(tool_hands: Hands, from_offhand: bool) -> Self {
match tool_hands {
Hands::Two => Self::TwoHanded,
Hands::One => {
if from_offhand {

View File

@ -1111,7 +1111,7 @@ fn handle_exp_gain(
let mut add_tool_from_slot = |equip_slot| {
let tool_kind = inventory
.equipped(equip_slot)
.and_then(|i| match &i.kind() {
.and_then(|i| match &*i.kind() {
ItemKind::Tool(tool) if tool.kind.gains_combat_xp() => Some(tool.kind),
_ => None,
});

View File

@ -51,22 +51,22 @@ pub fn handle_lantern(server: &mut Server, entity: EcsEntity, enable: bool) {
.map_or(true, |h| !h.is_dead)
{
let inventory_storage = ecs.read_storage::<Inventory>();
let lantern_opt = inventory_storage
let lantern_info = inventory_storage
.get(entity)
.and_then(|inventory| inventory.equipped(EquipSlot::Lantern))
.and_then(|item| {
if let comp::item::ItemKind::Lantern(l) = item.kind() {
Some(l)
if let comp::item::ItemKind::Lantern(l) = &*item.kind() {
Some((l.color(), l.strength()))
} else {
None
}
});
if let Some(lantern) = lantern_opt {
if let Some((col, strength)) = lantern_info {
let _ =
ecs.write_storage::<comp::LightEmitter>()
.insert(entity, comp::LightEmitter {
col: lantern.color(),
strength: lantern.strength(),
col,
strength,
flicker: 0.35,
animated: true,
});

View File

@ -32,11 +32,11 @@ use common_net::msg::ServerGeneral;
pub fn swap_lantern(
storage: &mut WriteStorage<comp::LightEmitter>,
entity: EcsEntity,
lantern: &item::Lantern,
(lantern_color, lantern_strength): (Rgb<f32>, f32),
) {
if let Some(mut light) = storage.get_mut(entity) {
light.strength = lantern.strength();
light.col = lantern.color();
light.strength = lantern_strength;
light.col = lantern_color;
}
}
@ -264,16 +264,19 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
Slot::Inventory(slot) => {
use item::ItemKind;
let (is_equippable, lantern_opt) =
inventory.get(slot).map_or((false, None), |i| {
(i.kind().is_equippable(), match i.kind() {
ItemKind::Lantern(lantern) => Some(lantern),
let is_equippable = inventory
.get(slot)
.map_or(false, |i| i.kind().is_equippable());
if is_equippable {
if let Some(lantern_info) =
inventory.get(slot).and_then(|i| match &*i.kind() {
ItemKind::Lantern(lantern) => {
Some((lantern.color(), lantern.strength()))
},
_ => None,
})
});
if is_equippable {
if let Some(lantern) = lantern_opt {
swap_lantern(&mut state.ecs().write_storage(), entity, lantern);
{
swap_lantern(&mut state.ecs().write_storage(), entity, lantern_info);
}
if let Some(pos) = state.ecs().read_storage::<comp::Pos>().get(entity) {
dropped_items.extend(inventory.equip(slot).into_iter().map(|x| {
@ -292,7 +295,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
&state.ecs().read_resource::<AbilityMap>(),
&state.ecs().read_resource::<item::MaterialStatManifest>(),
) {
match item.kind() {
match &*item.kind() {
ItemKind::Consumable { effects, .. } => {
maybe_effect = Some(effects.clone());
Some(comp::InventoryUpdateEvent::Consumed(

View File

@ -167,7 +167,7 @@ impl<'a> System<'a> for Sys {
.equipped(EquipSlot::Glider)
.as_ref()
.map_or(false, |item| {
matches!(item.kind(), comp::item::ItemKind::Glider(_))
matches!(&*item.kind(), comp::item::ItemKind::Glider(_))
});
let is_gliding = matches!(
@ -701,7 +701,7 @@ impl<'a> AgentData<'a> {
.equipped(EquipSlot::Lantern)
.as_ref()
.map_or(false, |item| {
matches!(item.kind(), comp::item::ItemKind::Lantern(_))
matches!(&*item.kind(), comp::item::ItemKind::Lantern(_))
});
let lantern_turned_on = self.light_emitter.is_some();
let day_period = DayPeriod::from(read_data.time_of_day.0);
@ -1496,7 +1496,7 @@ impl<'a> AgentData<'a> {
let healing_value = |item: &Item| {
let mut value = 0.0;
if let ItemKind::Consumable { kind, effects, .. } = &item.kind {
if let ItemKind::Consumable { kind, effects, .. } = &*item.kind() {
if matches!(kind, ConsumableKind::Drink)
|| (relaxed && matches!(kind, ConsumableKind::Food))
{
@ -1639,7 +1639,7 @@ impl<'a> AgentData<'a> {
.as_ref()
.map(|item| {
if let Some(ability_spec) = item.ability_spec() {
match ability_spec {
match &*ability_spec {
AbilitySpec::Custom(spec) => match spec.as_str() {
"Oni" | "Sword Simple" => Tactic::Sword,
"Staff Simple" => Tactic::Staff,
@ -1697,7 +1697,7 @@ impl<'a> AgentData<'a> {
},
AbilitySpec::Tool(tool_kind) => tool_tactic(*tool_kind),
}
} else if let ItemKind::Tool(tool) = &item.kind() {
} else if let ItemKind::Tool(tool) = &*item.kind() {
tool_tactic(tool.kind)
} else {
Tactic::SimpleMelee

View File

@ -146,7 +146,7 @@ impl CombatEventMapper {
inventory: &Inventory,
) -> SfxEvent {
if let Some(item) = inventory.equipped(EquipSlot::ActiveMainhand) {
if let ItemKind::Tool(data) = item.kind() {
if let ItemKind::Tool(data) = &*item.kind() {
if character_state.is_attack() {
return SfxEvent::Attack(
CharacterAbilityType::from(character_state),

View File

@ -302,7 +302,7 @@ impl From<&InventoryUpdateEvent> for SfxEvent {
InventoryUpdateEvent::Collected(item) => {
// Handle sound effects for types of collected items, falling
// back to the default Collected event
match &item.kind() {
match &*item.kind() {
ItemKind::Tool(tool) => {
SfxEvent::Inventory(SfxInventoryEvent::CollectedTool(tool.kind))
},

View File

@ -192,11 +192,11 @@ impl CraftingTab {
match self {
CraftingTab::All | CraftingTab::Dismantle => true,
CraftingTab::Food => item.tags().contains(&ItemTag::Food),
CraftingTab::Armor => match item.kind() {
CraftingTab::Armor => match &*item.kind() {
ItemKind::Armor(_) => !item.tags().contains(&ItemTag::Bag),
_ => false,
},
CraftingTab::Glider => matches!(item.kind(), ItemKind::Glider(_)),
CraftingTab::Glider => matches!(&*item.kind(), ItemKind::Glider(_)),
CraftingTab::Potion => item.tags().contains(&ItemTag::Potion),
CraftingTab::ProcessedMaterial => item.tags().iter().any(|tag| {
matches!(
@ -207,7 +207,7 @@ impl CraftingTab {
CraftingTab::Bag => item.tags().contains(&ItemTag::Bag),
CraftingTab::Tool => item.tags().contains(&ItemTag::CraftingTool),
CraftingTab::Utility => item.tags().contains(&ItemTag::Utility),
CraftingTab::Weapon => match item.kind() {
CraftingTab::Weapon => match &*item.kind() {
ItemKind::Tool(_) => !item.tags().contains(&ItemTag::CraftingTool),
_ => false,
},
@ -468,8 +468,8 @@ impl<'a> Widget for Crafting<'a> {
SearchFilter::Input => recipe.inputs().any(|(input, _, _)| {
let input_name = match input {
RecipeInput::Item(def) => def.name(),
RecipeInput::Tag(tag) => tag.name(),
RecipeInput::TagSameItem(tag, _) => tag.name(),
RecipeInput::Tag(tag) => Cow::Borrowed(tag.name()),
RecipeInput::TagSameItem(tag, _) => Cow::Borrowed(tag.name()),
// Works, but probably will have some...interesting false positives
// Code reviewers probably required to do magic to make not hacky
RecipeInput::ListSameItem(defs, _) => {
@ -916,8 +916,7 @@ impl<'a> Widget for Crafting<'a> {
RecipeInput::Item(item_def) => Arc::clone(item_def),
RecipeInput::Tag(tag) | RecipeInput::TagSameItem(tag, _) => {
Arc::<ItemDef>::load_expect_cloned(
self
.inventory
self.inventory
.slots()
.find_map(|slot| {
slot.as_ref().and_then(|item| {
@ -928,12 +927,11 @@ impl<'a> Widget for Crafting<'a> {
}
})
})
.unwrap_or(&tag.exemplar_identifier()),
.unwrap_or_else(|| tag.exemplar_identifier()),
)
},
RecipeInput::ListSameItem(item_defs, _) => Arc::<ItemDef>::load_expect_cloned(
self
.inventory
self.inventory
.slots()
.find_map(|slot| {
slot.as_ref().and_then(|item| {

View File

@ -4,7 +4,7 @@ use common::{
inventory::trade_pricing::TradePricing,
item::{
armor::{Armor, ArmorKind, Protection},
tool::{Hands, StatKind, Stats, Tool, ToolKind},
tool::{Hands, Stats, Tool, ToolKind},
Item, ItemDesc, ItemKind, MaterialKind, MaterialStatManifest, ModularComponent,
},
BuffKind,
@ -71,12 +71,12 @@ pub fn price_desc(
}
pub fn kind_text<'a>(item: &dyn ItemDesc, i18n: &'a Localization) -> Cow<'a, str> {
match item.kind() {
match &*item.kind() {
ItemKind::Armor(armor) => Cow::Borrowed(armor_kind(armor, i18n)),
ItemKind::Tool(tool) => Cow::Owned(format!(
"{} ({})",
tool_kind(tool, i18n),
tool_hands(tool, item.components(), i18n)
tool_hands(tool, i18n)
)),
ItemKind::ModularComponent(_mc) => Cow::Borrowed(i18n.get("common.bag.shoulders")),
ItemKind::Glider(_glider) => Cow::Borrowed(i18n.get("common.kind.glider")),
@ -106,7 +106,7 @@ pub fn modular_component_desc(
msm: &MaterialStatManifest,
description: &str,
) -> String {
let stats = StatKind::Direct(mc.stats).resolve_stats(msm, components);
let stats = mc.stats;
let statblock = statblock_desc(&stats);
let mut result = format!("Modular Component\n\n{}\n\n{}", statblock, description);
if !components.is_empty() {
@ -121,7 +121,7 @@ pub fn modular_component_desc(
}
pub fn stats_count(item: &dyn ItemDesc) -> usize {
let mut count = match item.kind() {
let mut count = match &*item.kind() {
ItemKind::Armor(armor) => {
if matches!(armor.kind, ArmorKind::Bag(_)) {
0
@ -139,7 +139,7 @@ pub fn stats_count(item: &dyn ItemDesc) -> usize {
_ => 0,
};
let is_bag = match item.kind() {
let is_bag = match &*item.kind() {
ItemKind::Armor(armor) => matches!(armor.kind, ArmorKind::Bag(_)),
_ => false,
};
@ -274,8 +274,8 @@ fn tool_kind<'a>(tool: &Tool, i18n: &'a Localization) -> &'a str {
}
/// Output the number of hands needed to hold a tool
pub fn tool_hands<'a>(tool: &Tool, components: &[Item], i18n: &'a Localization) -> &'a str {
let hands = match tool.hands.resolve_hands(components) {
pub fn tool_hands<'a>(tool: &Tool, i18n: &'a Localization) -> &'a str {
let hands = match tool.hands {
Hands::One => i18n.get("common.hands.one"),
Hands::Two => i18n.get("common.hands.two"),
};

View File

@ -152,6 +152,7 @@ impl CharacterCacheKey {
})) = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Head))
.map(|i| i.kind())
.as_deref()
{
Some(armor.clone())
} else {
@ -163,6 +164,7 @@ impl CharacterCacheKey {
})) = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Shoulders))
.map(|i| i.kind())
.as_deref()
{
Some(armor.clone())
} else {
@ -174,6 +176,7 @@ impl CharacterCacheKey {
})) = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Chest))
.map(|i| i.kind())
.as_deref()
{
Some(armor.clone())
} else {
@ -185,6 +188,7 @@ impl CharacterCacheKey {
})) = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Belt))
.map(|i| i.kind())
.as_deref()
{
Some(armor.clone())
} else {
@ -196,6 +200,7 @@ impl CharacterCacheKey {
})) = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Back))
.map(|i| i.kind())
.as_deref()
{
Some(armor.clone())
} else {
@ -207,6 +212,7 @@ impl CharacterCacheKey {
})) = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Legs))
.map(|i| i.kind())
.as_deref()
{
Some(armor.clone())
} else {
@ -233,15 +239,19 @@ impl CharacterCacheKey {
} else {
None
},
lantern: if let Some(ItemKind::Lantern(lantern)) =
inventory.equipped(EquipSlot::Lantern).map(|i| i.kind())
lantern: if let Some(ItemKind::Lantern(lantern)) = inventory
.equipped(EquipSlot::Lantern)
.map(|i| i.kind())
.as_deref()
{
Some(lantern.kind.clone())
} else {
None
},
glider: if let Some(ItemKind::Glider(glider)) =
inventory.equipped(EquipSlot::Glider).map(|i| i.kind())
glider: if let Some(ItemKind::Glider(glider)) = inventory
.equipped(EquipSlot::Glider)
.map(|i| i.kind())
.as_deref()
{
Some(glider.kind.clone())
} else {
@ -253,6 +263,7 @@ impl CharacterCacheKey {
})) = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Hands))
.map(|i| i.kind())
.as_deref()
{
Some(armor.clone())
} else {
@ -264,6 +275,7 @@ impl CharacterCacheKey {
})) = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Feet))
.map(|i| i.kind())
.as_deref()
{
Some(armor.clone())
} else {
@ -275,6 +287,7 @@ impl CharacterCacheKey {
})) = inventory
.equipped(EquipSlot::Armor(ArmorSlot::Head))
.map(|i| i.kind())
.as_deref()
{
Some(armor.clone())
} else {

View File

@ -873,12 +873,8 @@ impl FigureMgr {
inventory
.and_then(|i| i.equipped(equip_slot))
.map(|i| {
if let ItemKind::Tool(tool) = i.kind() {
(
Some(tool.kind),
Some(tool.hands.resolve_hands(i.components())),
i.ability_spec(),
)
if let ItemKind::Tool(tool) = &*i.kind() {
(Some(tool.kind), Some(tool.hands), i.ability_spec())
} else {
(None, None, None)
}
@ -888,8 +884,10 @@ impl FigureMgr {
let (active_tool_kind, active_tool_hand, active_tool_spec) =
tool_info(EquipSlot::ActiveMainhand);
let active_tool_spec = active_tool_spec.as_deref();
let (second_tool_kind, second_tool_hand, second_tool_spec) =
tool_info(EquipSlot::ActiveOffhand);
let second_tool_spec = second_tool_spec.as_deref();
let hands = (active_tool_hand, second_tool_hand);

View File

@ -283,11 +283,8 @@ impl Scene {
inventory
.and_then(|inv| inv.equipped(equip_slot))
.and_then(|i| {
if let ItemKind::Tool(tool) = i.kind() {
Some((
Some(tool.kind),
Some(tool.hands.resolve_hands(i.components())),
))
if let ItemKind::Tool(tool) = &*i.kind() {
Some((Some(tool.kind), Some(tool.hands)))
} else {
None
}

View File

@ -410,8 +410,8 @@ impl PlayState for SessionState {
.inventories()
.get(player_entity)
.and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
.and_then(|item| item.tool())
.map_or(false, |tool| tool.kind == ToolKind::Pick)
.and_then(|item| item.tool_info())
.map_or(false, |tool_kind| tool_kind == ToolKind::Pick)
&& client.is_wielding() == Some(true);
// Check to see whether we're aiming at anything

View File

@ -462,11 +462,9 @@ impl<'a> Widget for ItemTooltip<'a> {
let quality = get_quality_col(item);
let equip_slot = item
.concrete_item()
.map(|item| inventory.equipped_items_replaceable_by(item))
.into_iter()
.flatten();
let item_kind = &*item.kind();
let equip_slot = inventory.equipped_items_of_kind(item_kind);
let (title, desc) = (item.name().to_string(), item.description().to_string());
@ -582,12 +580,12 @@ impl<'a> Widget for ItemTooltip<'a> {
.set(state.ids.subtitle, ui);
// Stats
match item.kind() {
match &*item.kind() {
ItemKind::Tool(tool) => {
let power = tool.base_power(self.msm, item.components()) * 10.0;
let speed = tool.base_speed(self.msm, item.components());
let effect_power = tool.base_effect_power(self.msm, item.components()) * 10.0;
let crit_chance = tool.base_crit_chance(self.msm, item.components()) * 100.0;
let power = tool.base_power() * 10.0;
let speed = tool.base_speed();
let effect_power = tool.base_effect_power() * 10.0;
let crit_chance = tool.base_crit_chance() * 100.0;
let combat_rating = combat::weapon_rating(&item, self.msm) * 10.0;
// Combat Rating
@ -667,14 +665,8 @@ impl<'a> Widget for ItemTooltip<'a> {
if let Some(equipped_item) = first_equipped {
if let ItemKind::Tool(equipped_tool) = equipped_item.kind() {
let tool_stats = tool
.stats
.resolve_stats(self.msm, item.components())
.clamp_speed();
let equipped_tool_stats = equipped_tool
.stats
.resolve_stats(self.msm, equipped_item.components())
.clamp_speed();
let tool_stats = tool.stats;
let equipped_tool_stats = equipped_tool.stats;
let diff = tool_stats - equipped_tool_stats;
let power_diff =
util::comparison(tool_stats.power, equipped_tool_stats.power);
@ -970,7 +962,7 @@ impl<'a> Widget for ItemTooltip<'a> {
}
if let Some(equipped_item) = first_equipped {
if let ItemKind::Armor(equipped_armor) = equipped_item.kind() {
if let ItemKind::Armor(equipped_armor) = &*equipped_item.kind() {
let diff = armor.stats - equipped_armor.stats;
let protection_diff = util::option_comparison(
&armor.protection(),