mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Addressed most review comments (no assets).
This commit is contained in:
parent
5ab9c0f1ad
commit
afd2c8730f
@ -1084,10 +1084,12 @@ impl Client {
|
||||
/// Crafts modular weapon from components in the provided slots.
|
||||
/// `sprite_pos` should be the location of the necessary crafting station in
|
||||
/// range of the player.
|
||||
/// Returns whether or not the networking event was sent (which is based on
|
||||
/// whether the player has two modular components in the provided slots)
|
||||
pub fn craft_modular_weapon(
|
||||
&mut self,
|
||||
slot_a: InvSlotId,
|
||||
slot_b: InvSlotId,
|
||||
primary_component: InvSlotId,
|
||||
secondary_component: InvSlotId,
|
||||
sprite_pos: Option<Vec3<i32>>,
|
||||
) -> bool {
|
||||
let inventories = self.inventories();
|
||||
@ -1112,17 +1114,10 @@ impl Client {
|
||||
_ => None,
|
||||
};
|
||||
|
||||
// Gets slot order of damage and held components if two provided slots contains
|
||||
// both a primary and secondary component
|
||||
let slot_order = match (mod_kind(slot_a), mod_kind(slot_b)) {
|
||||
(Some(ModKind::Primary), Some(ModKind::Secondary)) => Some((slot_a, slot_b)),
|
||||
(Some(ModKind::Secondary), Some(ModKind::Primary)) => Some((slot_b, slot_a)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
drop(inventories);
|
||||
|
||||
if let Some((primary_component, secondary_component)) = slot_order {
|
||||
if let (Some(ModKind::Primary), Some(ModKind::Secondary)) =
|
||||
(mod_kind(primary_component), mod_kind(secondary_component))
|
||||
{
|
||||
drop(inventories);
|
||||
self.send_msg(ClientGeneral::ControlEvent(ControlEvent::InventoryEvent(
|
||||
InventoryEvent::CraftRecipe {
|
||||
craft_event: CraftEvent::ModularWeapon {
|
||||
|
@ -967,12 +967,20 @@ mod tests {
|
||||
#[test]
|
||||
fn test_load_kits() {
|
||||
let kits = KitManifest::load_expect(KIT_MANIFEST_PATH).read();
|
||||
let mut rng = rand::thread_rng();
|
||||
for kit in kits.0.values() {
|
||||
for (item_id, _) in kit.iter() {
|
||||
match item_id {
|
||||
KitSpec::Item(item_id) => std::mem::drop(Item::new_from_asset_expect(item_id)),
|
||||
KitSpec::Item(item_id) => {
|
||||
Item::new_from_asset_expect(item_id);
|
||||
},
|
||||
KitSpec::ModularWeapon { tool, material } => {
|
||||
std::mem::drop(comp::item::modular::random_weapon(*tool, *material, None))
|
||||
comp::item::modular::random_weapon(*tool, *material, None, &mut rng)
|
||||
.unwrap_or_else(|_| {
|
||||
panic!(
|
||||
"Failed to synthesize a modular {tool:?} made of {material:?}."
|
||||
)
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -90,7 +90,7 @@ pub trait TagExampleInfo {
|
||||
fn name(&self) -> &str;
|
||||
/// What item to show in the crafting hud if the player has nothing with the
|
||||
/// tag
|
||||
fn exemplar_identifier(&self) -> &str;
|
||||
fn exemplar_identifier(&self) -> Option<&str>;
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, IntoStaticStr)]
|
||||
@ -214,7 +214,7 @@ impl Material {
|
||||
impl TagExampleInfo for Material {
|
||||
fn name(&self) -> &str { self.into() }
|
||||
|
||||
fn exemplar_identifier(&self) -> &str { self.asset_identifier().unwrap_or("") }
|
||||
fn exemplar_identifier(&self) -> Option<&str> { self.asset_identifier() }
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
||||
@ -250,18 +250,18 @@ impl TagExampleInfo for ItemTag {
|
||||
}
|
||||
|
||||
// TODO: Autogenerate these?
|
||||
fn exemplar_identifier(&self) -> &str {
|
||||
fn exemplar_identifier(&self) -> Option<&str> {
|
||||
match self {
|
||||
ItemTag::Material(_) => "common.items.tag_examples.placeholder",
|
||||
ItemTag::MaterialKind(_) => "common.items.tag_examples.placeholder",
|
||||
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",
|
||||
ItemTag::Material(material) => material.exemplar_identifier(),
|
||||
ItemTag::MaterialKind(_) => None,
|
||||
ItemTag::Cultist => Some("common.items.tag_examples.cultist"),
|
||||
ItemTag::Potion => None,
|
||||
ItemTag::Food => None,
|
||||
ItemTag::BaseMaterial => None,
|
||||
ItemTag::CraftingTool => None,
|
||||
ItemTag::Utility => None,
|
||||
ItemTag::Bag => None,
|
||||
ItemTag::SalvageInto(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -286,6 +286,7 @@ pub enum ItemKind {
|
||||
},
|
||||
Ingredient {
|
||||
kind: String,
|
||||
/// Used to generate names for modular items composed of this ingredient
|
||||
descriptor: String,
|
||||
},
|
||||
TagExamples {
|
||||
@ -339,10 +340,6 @@ pub struct Item {
|
||||
/// item_def is hidden because changing the item definition for an item
|
||||
/// could change invariants like whether it was stackable (invalidating
|
||||
/// the amount).
|
||||
#[serde(
|
||||
serialize_with = "serialize_item_base",
|
||||
deserialize_with = "deserialize_item_base"
|
||||
)]
|
||||
item_base: ItemBase,
|
||||
/// components is hidden to maintain the following invariants:
|
||||
/// - It should only contain modular components (and enhancements, once they
|
||||
@ -373,51 +370,6 @@ 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_base<S: Serializer>(field: &ItemBase, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(match field {
|
||||
ItemBase::Raw(item_def) => &item_def.item_definition_id,
|
||||
ItemBase::Modular(mod_base) => mod_base.pseudo_item_id(),
|
||||
})
|
||||
}
|
||||
|
||||
// Custom de-serialization for ItemBase to retrieve the ItemBase from assets
|
||||
// using its asset specifier (item_definition_id)
|
||||
fn deserialize_item_base<'de, D>(deserializer: D) -> Result<ItemBase, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
struct ItemBaseStringVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for ItemBaseStringVisitor {
|
||||
type Value = ItemBase;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("item def string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, serialized_item_base: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(
|
||||
if serialized_item_base.starts_with("veloren.core.pseudo_items.modular.") {
|
||||
ItemBase::Modular(ModularBase::load_from_pseudo_id(serialized_item_base))
|
||||
} else {
|
||||
ItemBase::Raw(Arc::<ItemDef>::load_expect_cloned(serialized_item_base))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(ItemBaseStringVisitor)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ItemName {
|
||||
Direct(String),
|
||||
@ -425,12 +377,61 @@ pub enum ItemName {
|
||||
Component(String),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum ItemBase {
|
||||
Raw(Arc<ItemDef>),
|
||||
Modular(modular::ModularBase),
|
||||
}
|
||||
|
||||
impl Serialize for ItemBase {
|
||||
// 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<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
serializer.serialize_str(match self {
|
||||
ItemBase::Raw(item_def) => &item_def.item_definition_id,
|
||||
ItemBase::Modular(mod_base) => mod_base.pseudo_item_id(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for ItemBase {
|
||||
// Custom de-serialization for ItemBase to retrieve the ItemBase from assets
|
||||
// using its asset specifier (item_definition_id)
|
||||
fn deserialize<D>(deserializer: D) -> Result<ItemBase, D::Error>
|
||||
where
|
||||
D: de::Deserializer<'de>,
|
||||
{
|
||||
struct ItemBaseStringVisitor;
|
||||
|
||||
impl<'de> de::Visitor<'de> for ItemBaseStringVisitor {
|
||||
type Value = ItemBase;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||
formatter.write_str("item def string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, serialized_item_base: &str) -> Result<Self::Value, E>
|
||||
where
|
||||
E: de::Error,
|
||||
{
|
||||
Ok(
|
||||
if serialized_item_base.starts_with(crate::modular_item_id_prefix!()) {
|
||||
ItemBase::Modular(ModularBase::load_from_pseudo_id(serialized_item_base))
|
||||
} else {
|
||||
ItemBase::Raw(Arc::<ItemDef>::load_expect_cloned(serialized_item_base))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_str(ItemBaseStringVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl ItemBase {
|
||||
fn num_slots(&self) -> u16 {
|
||||
match self {
|
||||
@ -672,7 +673,7 @@ impl Item {
|
||||
components,
|
||||
slots: vec![None; inner_item.num_slots() as usize],
|
||||
item_base: inner_item,
|
||||
// Updated immediately below
|
||||
// These fields are updated immediately below
|
||||
item_config: None,
|
||||
hash: 0,
|
||||
};
|
||||
@ -963,19 +964,27 @@ impl Item {
|
||||
|
||||
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).or({
|
||||
// If no custom ability set is specified, fall back to abilityset of tool kind.
|
||||
if let ItemKind::Tool(tool) = &item_def.kind {
|
||||
Some(Cow::Owned(AbilitySpec::Tool(tool.kind)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}),
|
||||
ItemBase::Raw(item_def) => {
|
||||
item_def
|
||||
.ability_spec
|
||||
.as_ref()
|
||||
.map(Cow::Borrowed)
|
||||
.or_else(|| {
|
||||
// If no custom ability set is specified, fall back to abilityset of tool
|
||||
// kind.
|
||||
if let ItemKind::Tool(tool) = &item_def.kind {
|
||||
Some(Cow::Owned(AbilitySpec::Tool(tool.kind)))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
},
|
||||
ItemBase::Modular(mod_base) => mod_base.ability_spec(self.components()),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Maybe try to make slice again instead of vec?
|
||||
// TODO: Maybe try to make slice again instead of vec? Could also try to make an
|
||||
// iterator?
|
||||
pub fn tags(&self) -> Vec<ItemTag> {
|
||||
match &self.item_base {
|
||||
ItemBase::Raw(item_def) => item_def.tags.to_vec(),
|
||||
|
@ -6,10 +6,20 @@ use crate::{assets::AssetExt, recipe};
|
||||
use common_base::dev_panic;
|
||||
use hashbrown::HashMap;
|
||||
use lazy_static::lazy_static;
|
||||
use rand::{prelude::SliceRandom, thread_rng};
|
||||
use rand::{prelude::SliceRandom, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
// Macro instead of constant to work with concat! macro.
|
||||
// DO NOT CHANGE. THIS PREFIX AFFECTS PERSISTENCE AND IF CHANGED A MIGRATION
|
||||
// MUST BE PERFORMED.
|
||||
#[macro_export]
|
||||
macro_rules! modular_item_id_prefix {
|
||||
() => {
|
||||
"veloren.core.pseudo_items.modular."
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub enum ModularBase {
|
||||
Tool,
|
||||
@ -20,7 +30,7 @@ impl ModularBase {
|
||||
// FUNCTION BELOW.
|
||||
pub fn pseudo_item_id(&self) -> &str {
|
||||
match self {
|
||||
ModularBase::Tool => "veloren.core.pseudo_items.modular.tool",
|
||||
ModularBase::Tool => concat!(modular_item_id_prefix!(), "tool"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -28,7 +38,7 @@ impl ModularBase {
|
||||
// FUNCTION ABOVE.
|
||||
pub fn load_from_pseudo_id(id: &str) -> Self {
|
||||
match id {
|
||||
"veloren.core.pseudo_items.modular.tool" => ModularBase::Tool,
|
||||
concat!(modular_item_id_prefix!(), "tool") => ModularBase::Tool,
|
||||
_ => panic!("Attempted to load a non existent pseudo item: {}", id),
|
||||
}
|
||||
}
|
||||
@ -36,6 +46,8 @@ impl ModularBase {
|
||||
fn resolve_hands(components: &[Item]) -> Hands {
|
||||
// Checks if weapon has components that restrict hands to two. Restrictions to
|
||||
// one hand or no restrictions default to one-handed weapon.
|
||||
// Note: Hand restriction here will never conflict on components
|
||||
// TODO: Maybe look into adding an assert at some point?
|
||||
let hand_restriction = components.iter().find_map(|comp| match &*comp.kind() {
|
||||
ItemKind::ModularComponent(mc) => match mc {
|
||||
ModularComponent::ToolPrimaryComponent {
|
||||
@ -52,7 +64,7 @@ impl ModularBase {
|
||||
}
|
||||
|
||||
pub fn kind(&self, components: &[Item], msm: &MaterialStatManifest) -> Cow<ItemKind> {
|
||||
pub fn resolve_stats(components: &[Item], msm: &MaterialStatManifest) -> tool::Stats {
|
||||
fn resolve_stats(components: &[Item], msm: &MaterialStatManifest) -> tool::Stats {
|
||||
components
|
||||
.iter()
|
||||
.filter_map(|comp| {
|
||||
@ -230,7 +242,9 @@ impl ModularComponent {
|
||||
.reduce(|(stats_a, count_a), (stats_b, count_b)| {
|
||||
(stats_a + stats_b, count_a + count_b)
|
||||
})
|
||||
.map_or_else(tool::Stats::one, |(stats_sum, count)| stats_sum / count);
|
||||
.map_or_else(tool::Stats::one, |(stats_sum, count)| {
|
||||
stats_sum / (count as f32)
|
||||
});
|
||||
|
||||
Some(*stats * average_material_mult)
|
||||
},
|
||||
@ -258,7 +272,6 @@ const SUPPORTED_TOOLKINDS: [ToolKind; 6] = [
|
||||
type PrimaryComponentPool = HashMap<(ToolKind, String), Vec<(Item, Option<Hands>)>>;
|
||||
type SecondaryComponentPool = HashMap<ToolKind, Vec<(Arc<ItemDef>, Option<Hands>)>>;
|
||||
|
||||
// TODO: Fix this. It broke when changes were made to component recipes
|
||||
lazy_static! {
|
||||
static ref PRIMARY_COMPONENT_POOL: PrimaryComponentPool = {
|
||||
let mut component_pool = HashMap::new();
|
||||
@ -323,6 +336,7 @@ pub fn random_weapon(
|
||||
tool: ToolKind,
|
||||
material: Material,
|
||||
hand_restriction: Option<Hands>,
|
||||
mut rng: &mut impl Rng,
|
||||
) -> Result<Item, ModularWeaponCreationError> {
|
||||
let result = (|| {
|
||||
if let Some(material_id) = material.asset_identifier() {
|
||||
@ -330,8 +344,6 @@ pub fn random_weapon(
|
||||
let ability_map = &AbilityMap::load().read();
|
||||
let msm = &MaterialStatManifest::load().read();
|
||||
|
||||
let mut rng = thread_rng();
|
||||
|
||||
let primary_components = PRIMARY_COMPONENT_POOL
|
||||
.get(&(tool, material_id.to_owned()))
|
||||
.into_iter()
|
||||
@ -384,10 +396,10 @@ pub fn random_weapon(
|
||||
Err(ModularWeaponCreationError::MaterialNotFound)
|
||||
}
|
||||
})();
|
||||
if result.is_err() {
|
||||
if let Err(err) = &result {
|
||||
let error_str = format!(
|
||||
"Failed to synthesize a modular {tool:?} made of {material:?} that had a hand \
|
||||
restriction of {hand_restriction:?}."
|
||||
restriction of {hand_restriction:?}. Error: {err:?}"
|
||||
);
|
||||
dev_panic!(error_str)
|
||||
}
|
||||
|
@ -193,11 +193,10 @@ impl Mul<Stats> for Stats {
|
||||
impl MulAssign<Stats> for Stats {
|
||||
fn mul_assign(&mut self, other: Stats) { *self = *self * other; }
|
||||
}
|
||||
impl Div<usize> for Stats {
|
||||
impl Div<f32> for Stats {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, scalar: usize) -> Self {
|
||||
let scalar = scalar as f32;
|
||||
fn div(self, scalar: f32) -> Self {
|
||||
Self {
|
||||
equip_time_secs: self.equip_time_secs / scalar,
|
||||
power: self.power / scalar,
|
||||
@ -211,7 +210,7 @@ impl Div<usize> for Stats {
|
||||
}
|
||||
}
|
||||
impl DivAssign<usize> for Stats {
|
||||
fn div_assign(&mut self, scalar: usize) { *self = *self / scalar; }
|
||||
fn div_assign(&mut self, scalar: usize) { *self = *self / (scalar as f32); }
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
|
@ -403,6 +403,8 @@ impl Loadout {
|
||||
}
|
||||
}
|
||||
|
||||
/// Update internal computed state of all top level items in this loadout.
|
||||
/// Used only when loading in persistence code.
|
||||
pub fn persistence_update_all_item_states(
|
||||
&mut self,
|
||||
ability_map: &item::tool::AbilityMap,
|
||||
|
@ -40,7 +40,8 @@ enum ItemSpec {
|
||||
Item(String),
|
||||
Choice(Vec<(Weight, Option<ItemSpec>)>),
|
||||
/// Modular weapon
|
||||
/// Example:
|
||||
/// Parameters in this variant are used to randomly create a modular weapon
|
||||
/// that meets the provided parameters Example:
|
||||
/// ModularWeapon {
|
||||
/// tool: Sword,
|
||||
/// material: Iron,
|
||||
@ -76,9 +77,7 @@ impl ItemSpec {
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
} => {
|
||||
item::modular::random_weapon(*tool, *material, *hands)
|
||||
},
|
||||
} => item::modular::random_weapon(*tool, *material, *hands),
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,6 +97,13 @@ impl ItemSpec {
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
ItemSpec::ModularWeapon {
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
} => {
|
||||
item::modular::random_weapon(*tool, *material, *hands)
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -136,11 +142,6 @@ impl Hands {
|
||||
|
||||
pair_spec.try_to_pair(rng)
|
||||
},
|
||||
ItemSpec::ModularWeapon {
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
} => item::modular::random_weapon(*tool, *material, *hands).ok(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -784,13 +784,15 @@ impl Inventory {
|
||||
|
||||
pub fn swap_equipped_weapons(&mut self) { self.loadout.swap_equipped_weapons() }
|
||||
|
||||
/// Update internal computed state of all top level items in this loadout.
|
||||
/// Used only when loading in persistence code.
|
||||
pub fn persistence_update_all_item_states(
|
||||
&mut self,
|
||||
ability_map: &item::tool::AbilityMap,
|
||||
msm: &item::tool::MaterialStatManifest,
|
||||
) {
|
||||
self.slots_mut().for_each(|mut slot| {
|
||||
if let Some(item) = &mut slot {
|
||||
self.slots_mut().for_each(|slot| {
|
||||
if let Some(item) = slot {
|
||||
item.update_item_state(ability_map, msm);
|
||||
}
|
||||
});
|
||||
|
@ -812,8 +812,8 @@ mod tests {
|
||||
assert!((lootsum2 - 1.0).abs() < 1e-4);
|
||||
|
||||
// highly nested
|
||||
let loot3 = expand_loot_table("common.loot_tables.creature.biped_large.wendigo");
|
||||
let lootsum3 = loot3.iter().fold(0.0, |s, i| s + i.0);
|
||||
// let loot3 = expand_loot_table("common.loot_tables.creature.biped_large.wendigo");
|
||||
// let lootsum3 = loot3.iter().fold(0.0, |s, i| s + i.0);
|
||||
// TODO: Re-enable this. See note at top of test (though this specific
|
||||
// table can also be fixed by properly integrating modular weapons into
|
||||
// probability files)
|
||||
|
@ -96,6 +96,7 @@ pub enum LootSpec<T: AsRef<str>> {
|
||||
|
||||
impl<T: AsRef<str>> LootSpec<T> {
|
||||
pub fn to_item(&self) -> Option<Item> {
|
||||
let mut rng = thread_rng();
|
||||
match self {
|
||||
Self::Item(item) => Item::new_from_asset(item.as_ref()).map_or_else(
|
||||
|e| {
|
||||
@ -130,7 +131,7 @@ impl<T: AsRef<str>> LootSpec<T> {
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
} => item::modular::random_weapon(*tool, *material, *hands).map_or_else(
|
||||
} => item::modular::random_weapon(*tool, *material, *hands, &mut rng).map_or_else(
|
||||
|e| {
|
||||
warn!(
|
||||
?e,
|
||||
@ -138,7 +139,7 @@ impl<T: AsRef<str>> LootSpec<T> {
|
||||
Hands: {:?}",
|
||||
tool,
|
||||
material,
|
||||
hands
|
||||
hands,
|
||||
);
|
||||
None
|
||||
},
|
||||
@ -159,6 +160,7 @@ pub mod tests {
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn validate_loot_spec(item: &LootSpec<String>) {
|
||||
let mut rng = thread_rng();
|
||||
match item {
|
||||
LootSpec::Item(item) => {
|
||||
Item::new_from_asset_expect(item);
|
||||
@ -183,12 +185,20 @@ pub mod tests {
|
||||
validate_table_contents(loot_table);
|
||||
},
|
||||
LootSpec::Nothing => {},
|
||||
// TODO: Figure out later
|
||||
LootSpec::ModularWeapon {
|
||||
tool,
|
||||
material,
|
||||
hands,
|
||||
} => std::mem::drop(item::modular::random_weapon(*tool, *material, *hands)),
|
||||
} => {
|
||||
item::modular::random_weapon(*tool, *material, *hands, &mut rng).unwrap_or_else(
|
||||
|_| {
|
||||
panic!(
|
||||
"Failed to synthesize a modular {tool:?} made of {material:?} that \
|
||||
had a hand restriction of {hands:?}."
|
||||
)
|
||||
},
|
||||
);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -156,7 +156,11 @@ impl Recipe {
|
||||
.map(|(item_def, amount, is_mod_comp)| (item_def, *amount, *is_mod_comp))
|
||||
}
|
||||
|
||||
/// Determines if the inventory contains the ingredients for a given recipe
|
||||
/// Determine whether the inventory contains the ingredients for a recipe.
|
||||
/// If it does, return a vec of inventory slots that contain the
|
||||
/// ingredients needed, whose positions correspond to particular recipe
|
||||
/// inputs. If items are missing, return the missing items, and how many
|
||||
/// are missing.
|
||||
pub fn inventory_contains_ingredients(
|
||||
&self,
|
||||
inv: &Inventory,
|
||||
@ -170,14 +174,15 @@ impl Recipe {
|
||||
}
|
||||
|
||||
/// Determine whether the inventory contains the ingredients for a recipe.
|
||||
/// If it does, return a vec of inventory slots that contain the
|
||||
/// If it does, return a vec of inventory slots that contain the
|
||||
/// ingredients needed, whose positions correspond to particular recipe
|
||||
/// inputs. If items are missing, return the missing items, and how many
|
||||
/// are missing.
|
||||
// Note: Doc comment duplicated on two public functions that call this function
|
||||
#[allow(clippy::type_complexity)]
|
||||
fn inventory_contains_ingredients<'a, 'b, I: Iterator<Item = (&'a RecipeInput, u32)>>(
|
||||
fn inventory_contains_ingredients<'a, I: Iterator<Item = (&'a RecipeInput, u32)>>(
|
||||
ingredients: I,
|
||||
inv: &'b Inventory,
|
||||
inv: &Inventory,
|
||||
) -> Result<Vec<(u32, InvSlotId)>, Vec<(&'a RecipeInput, u32)>> {
|
||||
// Hashmap tracking the quantity that needs to be removed from each slot (so
|
||||
// that it doesn't think a slot can provide more items than it contains)
|
||||
@ -278,7 +283,7 @@ pub fn modular_weapon(
|
||||
})
|
||||
}
|
||||
|
||||
// Checks if both components are comptabile, and if so returns the toolkind to
|
||||
// Checks if both components are compatible, and if so returns the toolkind to
|
||||
// make weapon of
|
||||
let compatiblity = if let (Some(primary_component), Some(secondary_component)) = (
|
||||
unwrap_modular(inv, primary_component),
|
||||
@ -387,7 +392,7 @@ impl assets::Asset for RawRecipeBook {
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Clone)]
|
||||
pub struct ItemList(pub Vec<String>);
|
||||
pub struct ItemList(Vec<String>);
|
||||
|
||||
impl assets::Asset for ItemList {
|
||||
type Loader = assets::RonLoader;
|
||||
@ -484,12 +489,12 @@ impl assets::Asset for RawComponentRecipeBook {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Hash, Eq, PartialEq)]
|
||||
pub struct ComponentKey {
|
||||
// Can't use ItemDef here because hash needed, item definition id used instead
|
||||
// TODO: Figure out how to get back to ItemDef maybe?
|
||||
// Keeping under ComponentRecipe may be sufficient?
|
||||
// TODO: Make more general for other things that have component inputs that should be tracked
|
||||
// after item creation
|
||||
pub toolkind: ToolKind,
|
||||
/// Refers to the item definition id of the material
|
||||
pub material: String,
|
||||
/// Refers to the item definition id of the material
|
||||
pub modifier: Option<String>,
|
||||
}
|
||||
|
||||
@ -503,7 +508,7 @@ pub struct ComponentRecipe {
|
||||
}
|
||||
|
||||
impl ComponentRecipe {
|
||||
/// Craft an itme that has components, returning a list of missing items on
|
||||
/// Craft an item that has components, returning a list of missing items on
|
||||
/// failure
|
||||
pub fn craft_component(
|
||||
&self,
|
||||
@ -621,7 +626,10 @@ impl ComponentRecipe {
|
||||
}
|
||||
|
||||
#[allow(clippy::type_complexity)]
|
||||
/// Determines if the inventory contains the ignredients for a given recipe
|
||||
/// Determine whether the inventory contains the additional ingredients for
|
||||
/// a component recipe. If it does, return a vec of inventory slots that
|
||||
/// contain the ingredients needed, whose positions correspond to particular
|
||||
/// recipe are missing.
|
||||
pub fn inventory_contains_additional_ingredients<'a>(
|
||||
&self,
|
||||
inv: &'a Inventory,
|
||||
@ -636,12 +644,6 @@ impl ComponentRecipe {
|
||||
|
||||
pub fn item_output(&self, ability_map: &AbilityMap, msm: &MaterialStatManifest) -> Item {
|
||||
match &self.output {
|
||||
ComponentOutput::Item(item_def) => Item::new_from_item_base(
|
||||
ItemBase::Raw(Arc::clone(item_def)),
|
||||
Vec::new(),
|
||||
ability_map,
|
||||
msm,
|
||||
),
|
||||
ComponentOutput::ItemComponents {
|
||||
item: item_def,
|
||||
components,
|
||||
@ -669,31 +671,38 @@ impl ComponentRecipe {
|
||||
|
||||
pub fn inputs(&self) -> impl ExactSizeIterator<Item = (&RecipeInput, u32)> {
|
||||
pub struct ComponentRecipeInputsIterator<'a> {
|
||||
material: bool,
|
||||
modifier: bool,
|
||||
index: usize,
|
||||
recipe: &'a ComponentRecipe,
|
||||
// material: bool,
|
||||
// modifier: bool,
|
||||
// index: usize,
|
||||
// recipe: &'a ComponentRecipe,
|
||||
material: Option<&'a (RecipeInput, u32)>,
|
||||
modifier: Option<&'a (RecipeInput, u32)>,
|
||||
additional_inputs: std::slice::Iter<'a, (RecipeInput, u32)>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for ComponentRecipeInputsIterator<'a> {
|
||||
type Item = &'a (RecipeInput, u32);
|
||||
|
||||
fn next(&mut self) -> Option<&'a (RecipeInput, u32)> {
|
||||
if !self.material {
|
||||
self.material = true;
|
||||
Some(&self.recipe.material)
|
||||
} else if !self.modifier {
|
||||
self.modifier = true;
|
||||
if self.recipe.modifier.is_some() {
|
||||
self.recipe.modifier.as_ref()
|
||||
} else {
|
||||
self.index += 1;
|
||||
self.recipe.additional_inputs.get(self.index - 1)
|
||||
}
|
||||
} else {
|
||||
self.index += 1;
|
||||
self.recipe.additional_inputs.get(self.index - 1)
|
||||
}
|
||||
// if !self.material {
|
||||
// self.material = true;
|
||||
// Some(&self.recipe.material)
|
||||
// } else if !self.modifier {
|
||||
// self.modifier = true;
|
||||
// if self.recipe.modifier.is_some() {
|
||||
// self.recipe.modifier.as_ref()
|
||||
// } else {
|
||||
// self.index += 1;
|
||||
// self.recipe.additional_inputs.get(self.index - 1)
|
||||
// }
|
||||
// } else {
|
||||
// self.index += 1;
|
||||
// self.recipe.additional_inputs.get(self.index - 1)
|
||||
// }
|
||||
self.material
|
||||
.take()
|
||||
.or_else(|| self.modifier.take())
|
||||
.or_else(|| self.additional_inputs.next())
|
||||
}
|
||||
}
|
||||
|
||||
@ -703,17 +712,24 @@ impl ComponentRecipe {
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
ComponentRecipeInputsIterator {
|
||||
material: false,
|
||||
modifier: false,
|
||||
index: 0,
|
||||
recipe: self,
|
||||
// material: false,
|
||||
// modifier: false,
|
||||
// index: 0,
|
||||
// recipe: self,
|
||||
material: Some(&self.material),
|
||||
modifier: self.modifier.as_ref(),
|
||||
additional_inputs: self.additional_inputs.as_slice().iter(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ExactSizeIterator for ComponentRecipeInputsIterator<'a> {
|
||||
fn len(&self) -> usize {
|
||||
1 + self.recipe.modifier.is_some() as usize + self.recipe.additional_inputs.len()
|
||||
// 1 + self.recipe.modifier.is_some() as usize +
|
||||
// self.recipe.additional_inputs.len()
|
||||
self.material.is_some() as usize
|
||||
+ self.modifier.is_some() as usize
|
||||
+ self.additional_inputs.len()
|
||||
}
|
||||
}
|
||||
|
||||
@ -732,7 +748,6 @@ struct RawComponentRecipe {
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
enum ComponentOutput {
|
||||
Item(Arc<ItemDef>),
|
||||
ItemComponents {
|
||||
item: Arc<ItemDef>,
|
||||
components: Vec<Arc<ItemDef>>,
|
||||
@ -741,7 +756,6 @@ enum ComponentOutput {
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
enum RawComponentOutput {
|
||||
Item(String),
|
||||
ItemComponents {
|
||||
item: String,
|
||||
components: Vec<String>,
|
||||
@ -778,9 +792,6 @@ impl assets::Compound for ComponentRecipeBook {
|
||||
output: &RawComponentOutput,
|
||||
) -> Result<ComponentOutput, assets::Error> {
|
||||
let def = match &output {
|
||||
RawComponentOutput::Item(def) => {
|
||||
ComponentOutput::Item(Arc::<ItemDef>::load_cloned(def)?)
|
||||
},
|
||||
RawComponentOutput::ItemComponents {
|
||||
item: def,
|
||||
components: defs,
|
||||
|
@ -400,6 +400,12 @@ impl SpriteKind {
|
||||
SpriteKind::ChestBuried => table("common.loot_tables.sprite.chest-buried"),
|
||||
SpriteKind::Mud => table("common.loot_tables.sprite.mud"),
|
||||
SpriteKind::Crate => table("common.loot_tables.sprite.crate"),
|
||||
SpriteKind::Wood => item("common.items.log.wood"),
|
||||
SpriteKind::Bamboo => item("common.items.log.bamboo"),
|
||||
SpriteKind::Hardwood => item("common.items.log.hardwood"),
|
||||
SpriteKind::Ironwood => item("common.items.log.ironwood"),
|
||||
SpriteKind::Frostwood => item("common.items.log.frostwood"),
|
||||
SpriteKind::Eldwood => item("common.items.log.eldwood"),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ use common_state::{BuildAreaError, BuildAreas};
|
||||
use core::{cmp::Ordering, convert::TryFrom, time::Duration};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use humantime::Duration as HumanDuration;
|
||||
use rand::Rng;
|
||||
use rand::{thread_rng, Rng};
|
||||
use specs::{
|
||||
saveload::MarkerAllocator, storage::StorageEntry, Builder, Entity as EcsEntity, Join, WorldExt,
|
||||
};
|
||||
@ -1941,11 +1941,12 @@ where
|
||||
return Err("Inventory doesn't have enough slots".to_owned());
|
||||
}
|
||||
for (item_id, quantity) in kit {
|
||||
let mut rng = thread_rng();
|
||||
let mut item = match &item_id {
|
||||
KitSpec::Item(item_id) => comp::Item::new_from_asset(item_id)
|
||||
.map_err(|_| format!("Unknown item: {:#?}", item_id))?,
|
||||
KitSpec::ModularWeapon { tool, material } => {
|
||||
comp::item::modular::random_weapon(*tool, *material, None)
|
||||
comp::item::modular::random_weapon(*tool, *material, None, &mut rng)
|
||||
.map_err(|err| format!("{:#?}", err))?
|
||||
},
|
||||
};
|
||||
|
@ -264,18 +264,20 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
Slot::Inventory(slot) => {
|
||||
use item::ItemKind;
|
||||
|
||||
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() {
|
||||
let (is_equippable, lantern_info) =
|
||||
inventory.get(slot).map_or((false, None), |i| {
|
||||
let kind = i.kind();
|
||||
let is_equippable = kind.is_equippable();
|
||||
let lantern_info = match &*kind {
|
||||
ItemKind::Lantern(lantern) => {
|
||||
Some((lantern.color(), lantern.strength()))
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
};
|
||||
(is_equippable, lantern_info)
|
||||
});
|
||||
if is_equippable {
|
||||
if let Some(lantern_info) = lantern_info {
|
||||
swap_lantern(&mut state.ecs().write_storage(), entity, lantern_info);
|
||||
}
|
||||
if let Some(pos) = state.ecs().read_storage::<comp::Pos>().get(entity) {
|
||||
@ -595,37 +597,39 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
use comp::controller::CraftEvent;
|
||||
use recipe::ComponentKey;
|
||||
let recipe_book = default_recipe_book().read();
|
||||
let component_recipes = default_component_recipe_book().read();
|
||||
let ability_map = &state.ecs().read_resource::<AbilityMap>();
|
||||
let msm = state.ecs().read_resource::<MaterialStatManifest>();
|
||||
|
||||
let get_craft_sprite = |state, sprite_pos: Option<Vec3<i32>>| {
|
||||
sprite_pos
|
||||
.filter(|pos| {
|
||||
let entity_cylinder = get_cylinder(state, entity);
|
||||
let in_range = within_pickup_range(entity_cylinder, || {
|
||||
Some(find_dist::Cube {
|
||||
min: pos.as_(),
|
||||
side_length: 1.0,
|
||||
})
|
||||
});
|
||||
if !in_range {
|
||||
debug!(
|
||||
?entity_cylinder,
|
||||
"Failed to craft recipe as not within range of required sprite, \
|
||||
sprite pos: {}",
|
||||
pos
|
||||
);
|
||||
}
|
||||
in_range
|
||||
})
|
||||
.and_then(|pos| state.terrain().get(pos).ok().copied())
|
||||
.and_then(|block| block.get_sprite())
|
||||
};
|
||||
|
||||
let crafted_items = match craft_event {
|
||||
CraftEvent::Simple { recipe, slots } => recipe_book
|
||||
.get(&recipe)
|
||||
.filter(|r| {
|
||||
if let Some(needed_sprite) = r.craft_sprite {
|
||||
let sprite = craft_sprite
|
||||
.filter(|pos| {
|
||||
let entity_cylinder = get_cylinder(state, entity);
|
||||
if !within_pickup_range(entity_cylinder, || {
|
||||
Some(find_dist::Cube {
|
||||
min: pos.as_(),
|
||||
side_length: 1.0,
|
||||
})
|
||||
}) {
|
||||
debug!(
|
||||
?entity_cylinder,
|
||||
"Failed to craft recipe as not within range of \
|
||||
required sprite, sprite pos: {}",
|
||||
pos
|
||||
);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.and_then(|pos| state.terrain().get(pos).ok().copied())
|
||||
.and_then(|block| block.get_sprite());
|
||||
let sprite = get_craft_sprite(state, craft_sprite);
|
||||
Some(needed_sprite) == sprite
|
||||
} else {
|
||||
true
|
||||
@ -641,28 +645,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
.ok()
|
||||
}),
|
||||
CraftEvent::Salvage(slot) => {
|
||||
let sprite = craft_sprite
|
||||
.filter(|pos| {
|
||||
let entity_cylinder = get_cylinder(state, entity);
|
||||
if !within_pickup_range(entity_cylinder, || {
|
||||
Some(find_dist::Cube {
|
||||
min: pos.as_(),
|
||||
side_length: 1.0,
|
||||
})
|
||||
}) {
|
||||
debug!(
|
||||
?entity_cylinder,
|
||||
"Failed to craft recipe as not within range of required \
|
||||
sprite, sprite pos: {}",
|
||||
pos
|
||||
);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.and_then(|pos| state.terrain().get(pos).ok().copied())
|
||||
.and_then(|block| block.get_sprite());
|
||||
let sprite = get_craft_sprite(state, craft_sprite);
|
||||
if matches!(sprite, Some(SpriteKind::DismantlingBench)) {
|
||||
recipe::try_salvage(&mut inventory, slot, ability_map, &msm).ok()
|
||||
} else {
|
||||
@ -673,28 +656,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
primary_component,
|
||||
secondary_component,
|
||||
} => {
|
||||
let sprite = craft_sprite
|
||||
.filter(|pos| {
|
||||
let entity_cylinder = get_cylinder(state, entity);
|
||||
if !within_pickup_range(entity_cylinder, || {
|
||||
Some(find_dist::Cube {
|
||||
min: pos.as_(),
|
||||
side_length: 1.0,
|
||||
})
|
||||
}) {
|
||||
debug!(
|
||||
?entity_cylinder,
|
||||
"Failed to craft recipe as not within range of required \
|
||||
sprite, sprite pos: {}",
|
||||
pos
|
||||
);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.and_then(|pos| state.terrain().get(pos).ok().copied())
|
||||
.and_then(|block| block.get_sprite());
|
||||
let sprite = get_craft_sprite(state, craft_sprite);
|
||||
if matches!(sprite, Some(SpriteKind::CraftingBench)) {
|
||||
recipe::modular_weapon(
|
||||
&mut inventory,
|
||||
@ -715,6 +677,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
modifier,
|
||||
slots,
|
||||
} => {
|
||||
let component_recipes = default_component_recipe_book().read();
|
||||
let item_id = |slot| inventory.get(slot).map(|item| item.item_definition_id());
|
||||
if let Some(material_item_id) = item_id(material) {
|
||||
component_recipes
|
||||
@ -725,28 +688,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
|
||||
})
|
||||
.filter(|r| {
|
||||
if let Some(needed_sprite) = r.craft_sprite {
|
||||
let sprite = craft_sprite
|
||||
.filter(|pos| {
|
||||
let entity_cylinder = get_cylinder(state, entity);
|
||||
if !within_pickup_range(entity_cylinder, || {
|
||||
Some(find_dist::Cube {
|
||||
min: pos.as_(),
|
||||
side_length: 1.0,
|
||||
})
|
||||
}) {
|
||||
debug!(
|
||||
?entity_cylinder,
|
||||
"Failed to craft recipe as not within range \
|
||||
of required sprite, sprite pos: {}",
|
||||
pos
|
||||
);
|
||||
false
|
||||
} else {
|
||||
true
|
||||
}
|
||||
})
|
||||
.and_then(|pos| state.terrain().get(pos).ok().copied())
|
||||
.and_then(|block| block.get_sprite());
|
||||
let sprite = get_craft_sprite(state, craft_sprite);
|
||||
Some(needed_sprite) == sprite
|
||||
} else {
|
||||
true
|
||||
|
@ -268,7 +268,7 @@ fn get_mutable_item<'a, 'b, T>(
|
||||
inventory_items: &'a [Item],
|
||||
item_indices: &'a HashMap<i64, usize>,
|
||||
inventory: &'b mut T,
|
||||
get_mut_item: &'a dyn Fn(&'b mut T, &str) -> Option<&'b mut VelorenItem>,
|
||||
get_mut_item: &'a impl Fn(&'b mut T, &str) -> Option<&'b mut VelorenItem>,
|
||||
) -> Option<&'a mut VelorenItem>
|
||||
where
|
||||
'b: 'a,
|
||||
|
@ -498,30 +498,18 @@ impl<'a> Widget for Crafting<'a> {
|
||||
}
|
||||
};
|
||||
|
||||
let weapon_recipe = Recipe {
|
||||
let make_psuedo_recipe = |craft_sprite| Recipe {
|
||||
output: (
|
||||
Arc::<ItemDef>::load_expect_cloned("common.items.weapons.empty.empty"),
|
||||
0,
|
||||
),
|
||||
inputs: Vec::new(),
|
||||
craft_sprite: Some(SpriteKind::CraftingBench),
|
||||
};
|
||||
let metal_comp_recipe = Recipe {
|
||||
output: (
|
||||
Arc::<ItemDef>::load_expect_cloned("common.items.weapons.empty.empty"),
|
||||
0,
|
||||
),
|
||||
inputs: Vec::new(),
|
||||
craft_sprite: Some(SpriteKind::Anvil),
|
||||
};
|
||||
let wood_comp_recipe = Recipe {
|
||||
output: (
|
||||
Arc::<ItemDef>::load_expect_cloned("common.items.weapons.empty.empty"),
|
||||
0,
|
||||
),
|
||||
inputs: Vec::new(),
|
||||
craft_sprite: Some(SpriteKind::CraftingBench),
|
||||
craft_sprite: Some(craft_sprite),
|
||||
};
|
||||
|
||||
let weapon_recipe = make_psuedo_recipe(SpriteKind::CraftingBench);
|
||||
let metal_comp_recipe = make_psuedo_recipe(SpriteKind::Anvil);
|
||||
let wood_comp_recipe = make_psuedo_recipe(SpriteKind::CraftingBench);
|
||||
let modular_entries = {
|
||||
let mut modular_entries = BTreeMap::new();
|
||||
modular_entries.insert(
|
||||
@ -806,7 +794,6 @@ impl<'a> Widget for Crafting<'a> {
|
||||
},
|
||||
None => None,
|
||||
} {
|
||||
// TODO: Avoid reallocating string and appease borrow checker
|
||||
let recipe_name = String::from(recipe_name);
|
||||
let title = if let Some((_recipe, modular_name)) = modular_entries.get(&recipe_name) {
|
||||
*modular_name
|
||||
@ -852,10 +839,8 @@ impl<'a> Widget for Crafting<'a> {
|
||||
_ => RecipeKind::Simple,
|
||||
};
|
||||
|
||||
let mut modular_slot_check_hack = (None, None, false);
|
||||
|
||||
// Output slot, tags, and modular input slots
|
||||
match recipe_kind {
|
||||
let (modular_primary_slot, modular_secondary_slot, can_perform) = match recipe_kind {
|
||||
RecipeKind::ModularWeapon | RecipeKind::Component(_) => {
|
||||
let mut slot_maker = SlotMaker {
|
||||
empty_slot: self.imgs.inv_slot,
|
||||
@ -894,8 +879,8 @@ impl<'a> Widget for Crafting<'a> {
|
||||
index: 0,
|
||||
invslot: self.show.crafting_fields.recipe_inputs.get(&0).copied(),
|
||||
requirement: match recipe_kind {
|
||||
RecipeKind::ModularWeapon => |inv, slot| {
|
||||
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
|
||||
RecipeKind::ModularWeapon => |item| {
|
||||
item.map_or(false, |item| {
|
||||
matches!(
|
||||
&*item.kind(),
|
||||
ItemKind::ModularComponent(
|
||||
@ -914,8 +899,8 @@ impl<'a> Widget for Crafting<'a> {
|
||||
// },
|
||||
RecipeKind::Component(
|
||||
ToolKind::Sword | ToolKind::Axe | ToolKind::Hammer,
|
||||
) => |inv, slot| {
|
||||
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
|
||||
) => |item| {
|
||||
item.map_or(false, |item| {
|
||||
matches!(&*item.kind(), ItemKind::Ingredient { .. })
|
||||
&& item
|
||||
.tags()
|
||||
@ -928,8 +913,8 @@ impl<'a> Widget for Crafting<'a> {
|
||||
},
|
||||
RecipeKind::Component(
|
||||
ToolKind::Bow | ToolKind::Staff | ToolKind::Sceptre,
|
||||
) => |inv, slot| {
|
||||
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
|
||||
) => |item| {
|
||||
item.map_or(false, |item| {
|
||||
matches!(&*item.kind(), ItemKind::Ingredient { .. })
|
||||
&& item
|
||||
.tags()
|
||||
@ -940,7 +925,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.any(|tag| matches!(tag, ItemTag::Material(_)))
|
||||
})
|
||||
},
|
||||
RecipeKind::Component(_) | RecipeKind::Simple => |_, _| false,
|
||||
RecipeKind::Component(_) | RecipeKind::Simple => |_| unreachable!(),
|
||||
},
|
||||
};
|
||||
|
||||
@ -949,44 +934,28 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.top_left_with_margins_on(state.ids.modular_art, 4.0, 4.0)
|
||||
.parent(state.ids.align_ing);
|
||||
|
||||
if let Some(slot) = primary_slot.invslot {
|
||||
if let Some(item) = self.inventory.get(slot) {
|
||||
primary_slot_widget
|
||||
.with_item_tooltip(
|
||||
self.item_tooltip_manager,
|
||||
core::iter::once(item as &dyn ItemDesc),
|
||||
&None,
|
||||
&item_tooltip,
|
||||
)
|
||||
.set(state.ids.modular_inputs[0], ui);
|
||||
} else {
|
||||
primary_slot_widget.set(state.ids.modular_inputs[0], ui);
|
||||
}
|
||||
if let Some(item) = primary_slot
|
||||
.invslot
|
||||
.and_then(|slot| self.inventory.get(slot))
|
||||
{
|
||||
primary_slot_widget
|
||||
.with_item_tooltip(
|
||||
self.item_tooltip_manager,
|
||||
core::iter::once(item as &dyn ItemDesc),
|
||||
&None,
|
||||
&item_tooltip,
|
||||
)
|
||||
.set(state.ids.modular_inputs[0], ui);
|
||||
} else {
|
||||
primary_slot_widget.set(state.ids.modular_inputs[0], ui);
|
||||
}
|
||||
|
||||
// Not sure why it doesn't work, maybe revisit later?
|
||||
// if let Some(item) = primary_slot.invslot.and_then(|slot|
|
||||
// self.inventory.get(slot)) { primary_slot_widget
|
||||
// .with_item_tooltip(
|
||||
// self.item_tooltip_manager,
|
||||
// core::iter::once(item as &dyn ItemDesc),
|
||||
// &None,
|
||||
// &item_tooltip,
|
||||
// )
|
||||
// .set(state.ids.modular_inputs[0], ui);
|
||||
// } else {
|
||||
// primary_slot_widget
|
||||
// .set(state.ids.modular_inputs[0], ui);
|
||||
// }
|
||||
|
||||
let secondary_slot = CraftSlot {
|
||||
index: 1,
|
||||
invslot: self.show.crafting_fields.recipe_inputs.get(&1).copied(),
|
||||
requirement: match recipe_kind {
|
||||
RecipeKind::ModularWeapon => |inv, slot| {
|
||||
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
|
||||
RecipeKind::ModularWeapon => |item| {
|
||||
item.map_or(false, |item| {
|
||||
matches!(
|
||||
&*item.kind(),
|
||||
ItemKind::ModularComponent(
|
||||
@ -1003,13 +972,13 @@ impl<'a> Widget for Crafting<'a> {
|
||||
// _recipe)| key.toolkind == toolkind).any(|(key, _recipe)| key.modifier
|
||||
// == Some(item.item_definition_id()))
|
||||
// }) },
|
||||
RecipeKind::Component(_) => |inv, slot| {
|
||||
inv.and_then(|inv| inv.get(slot)).map_or(false, |item| {
|
||||
RecipeKind::Component(_) => |item| {
|
||||
item.map_or(false, |item| {
|
||||
item.item_definition_id()
|
||||
.starts_with("common.items.crafting_ing.animal_misc")
|
||||
})
|
||||
},
|
||||
RecipeKind::Simple => |_, _| false,
|
||||
RecipeKind::Simple => |_| unreachable!(),
|
||||
},
|
||||
};
|
||||
|
||||
@ -1018,42 +987,27 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.top_right_with_margins_on(state.ids.modular_art, 4.0, 4.0)
|
||||
.parent(state.ids.align_ing);
|
||||
|
||||
if let Some(slot) = secondary_slot.invslot {
|
||||
if let Some(item) = self.inventory.get(slot) {
|
||||
secondary_slot_widget
|
||||
.with_item_tooltip(
|
||||
self.item_tooltip_manager,
|
||||
core::iter::once(item as &dyn ItemDesc),
|
||||
&None,
|
||||
&item_tooltip,
|
||||
)
|
||||
.set(state.ids.modular_inputs[1], ui);
|
||||
} else {
|
||||
secondary_slot_widget.set(state.ids.modular_inputs[1], ui);
|
||||
}
|
||||
if let Some(item) = secondary_slot
|
||||
.invslot
|
||||
.and_then(|slot| self.inventory.get(slot))
|
||||
{
|
||||
secondary_slot_widget
|
||||
.with_item_tooltip(
|
||||
self.item_tooltip_manager,
|
||||
core::iter::once(item as &dyn ItemDesc),
|
||||
&None,
|
||||
&item_tooltip,
|
||||
)
|
||||
.set(state.ids.modular_inputs[1], ui);
|
||||
} else {
|
||||
secondary_slot_widget.set(state.ids.modular_inputs[1], ui);
|
||||
}
|
||||
|
||||
// if let Some(item) = secondary_slot.invslot.and_then(|slot|
|
||||
// self.inventory.get(slot)) { secondary_slot_widget
|
||||
// .with_item_tooltip(
|
||||
// self.item_tooltip_manager,
|
||||
// core::iter::once(item as &dyn ItemDesc),
|
||||
// &None,
|
||||
// &item_tooltip,
|
||||
// )
|
||||
// .set(state.ids.modular_inputs[1], ui);
|
||||
// } else {
|
||||
// secondary_slot_widget
|
||||
// .set(state.ids.modular_inputs[1], ui);
|
||||
// }
|
||||
|
||||
let prim_item_placed = primary_slot.invslot.is_some();
|
||||
let sec_item_placed = secondary_slot.invslot.is_some();
|
||||
|
||||
let prim_icon = match recipe_kind {
|
||||
RecipeKind::ModularWeapon => self.imgs.ing_ico_prim,
|
||||
RecipeKind::ModularWeapon => self.imgs.icon_primary_comp,
|
||||
RecipeKind::Component(ToolKind::Sword) => self.imgs.icon_ingot,
|
||||
RecipeKind::Component(ToolKind::Axe) => self.imgs.icon_ingot,
|
||||
RecipeKind::Component(ToolKind::Hammer) => self.imgs.icon_ingot,
|
||||
@ -1064,7 +1018,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
};
|
||||
|
||||
let sec_icon = match recipe_kind {
|
||||
RecipeKind::ModularWeapon => self.imgs.ing_ico_sec,
|
||||
RecipeKind::ModularWeapon => self.imgs.icon_secondary_comp,
|
||||
RecipeKind::Component(_) => self.imgs.icon_pelt,
|
||||
_ => self.imgs.not_found,
|
||||
};
|
||||
@ -1096,7 +1050,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
let ability_map = &AbilityMap::load().read();
|
||||
let msm = &MaterialStatManifest::load().read();
|
||||
|
||||
if let Some(output_item) = match recipe_kind {
|
||||
let output_item = match recipe_kind {
|
||||
RecipeKind::ModularWeapon => {
|
||||
if let Some((primary_comp, toolkind)) = primary_slot
|
||||
.invslot
|
||||
@ -1162,7 +1116,9 @@ impl<'a> Widget for Crafting<'a> {
|
||||
}
|
||||
},
|
||||
RecipeKind::Simple => None,
|
||||
} {
|
||||
};
|
||||
|
||||
if let Some(output_item) = output_item {
|
||||
Button::image(animate_by_pulse(
|
||||
&self
|
||||
.item_imgs
|
||||
@ -1184,8 +1140,12 @@ impl<'a> Widget for Crafting<'a> {
|
||||
&item_tooltip,
|
||||
)
|
||||
.set(state.ids.output_img, ui);
|
||||
modular_slot_check_hack =
|
||||
(primary_slot.invslot, secondary_slot.invslot, true);
|
||||
(
|
||||
primary_slot.invslot,
|
||||
secondary_slot.invslot,
|
||||
self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
|
||||
== recipe.craft_sprite,
|
||||
)
|
||||
} else {
|
||||
Text::new(self.localized_strings.get("hud.crafting.modular_desc"))
|
||||
.mid_top_with_margin_on(state.ids.modular_art, -18.0)
|
||||
@ -1193,14 +1153,13 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.font_size(self.fonts.cyri.scale(13))
|
||||
.color(TEXT_COLOR)
|
||||
.set(state.ids.title_main, ui);
|
||||
Image::new(self.imgs.wep_ico)
|
||||
Image::new(self.imgs.icon_mod_weap)
|
||||
.middle_of(state.ids.output_img_frame)
|
||||
.color(Some(bg_col))
|
||||
.w_h(70.0, 70.0)
|
||||
.graphics_for(state.ids.output_img)
|
||||
.set(state.ids.modular_wep_empty_bg, ui);
|
||||
modular_slot_check_hack =
|
||||
(primary_slot.invslot, secondary_slot.invslot, false);
|
||||
(primary_slot.invslot, secondary_slot.invslot, false)
|
||||
}
|
||||
},
|
||||
RecipeKind::Simple => {
|
||||
@ -1291,29 +1250,19 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.set(state.ids.tags_ing[i], ui);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
let can_perform = match recipe_kind {
|
||||
RecipeKind::ModularWeapon => {
|
||||
modular_slot_check_hack.2
|
||||
&& self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
|
||||
== recipe.craft_sprite
|
||||
},
|
||||
RecipeKind::Component(_) => {
|
||||
modular_slot_check_hack.2
|
||||
&& self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
|
||||
== recipe.craft_sprite
|
||||
},
|
||||
RecipeKind::Simple => {
|
||||
self.client
|
||||
.available_recipes()
|
||||
.get(&recipe_name)
|
||||
.map_or(false, |cs| {
|
||||
cs.map_or(true, |cs| {
|
||||
Some(cs) == self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
|
||||
})
|
||||
})
|
||||
(
|
||||
None,
|
||||
None,
|
||||
self.client
|
||||
.available_recipes()
|
||||
.get(&recipe_name)
|
||||
.map_or(false, |cs| {
|
||||
cs.map_or(true, |cs| {
|
||||
Some(cs)
|
||||
== self.show.crafting_fields.craft_sprite.map(|(_, s)| s)
|
||||
})
|
||||
}),
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
@ -1339,12 +1288,12 @@ impl<'a> Widget for Crafting<'a> {
|
||||
.mid_bottom_with_margin_on(state.ids.align_ing, -31.0)
|
||||
.parent(state.ids.window_frame)
|
||||
.set(state.ids.btn_craft, ui)
|
||||
.was_clicked()
|
||||
.was_clicked() && can_perform
|
||||
{
|
||||
match recipe_kind {
|
||||
RecipeKind::ModularWeapon => {
|
||||
if let (Some(primary_slot), Some(secondary_slot), true) =
|
||||
modular_slot_check_hack
|
||||
if let (Some(primary_slot), Some(secondary_slot)) =
|
||||
(modular_primary_slot, modular_secondary_slot)
|
||||
{
|
||||
events.push(Event::CraftModularWeapon {
|
||||
primary_slot,
|
||||
@ -1353,11 +1302,11 @@ impl<'a> Widget for Crafting<'a> {
|
||||
}
|
||||
},
|
||||
RecipeKind::Component(toolkind) => {
|
||||
if let Some(primary_slot) = modular_slot_check_hack.0 {
|
||||
if let Some(primary_slot) = modular_primary_slot {
|
||||
events.push(Event::CraftModularWeaponComponent {
|
||||
toolkind,
|
||||
material: primary_slot,
|
||||
modifier: modular_slot_check_hack.1,
|
||||
modifier: modular_secondary_slot,
|
||||
});
|
||||
}
|
||||
},
|
||||
@ -1450,16 +1399,14 @@ impl<'a> Widget for Crafting<'a> {
|
||||
&mut iter_b
|
||||
},
|
||||
RecipeKind::Component(toolkind) => {
|
||||
if let Some(material) = modular_slot_check_hack
|
||||
.0
|
||||
if let Some(material) = modular_primary_slot
|
||||
.and_then(|slot| self.inventory.get(slot))
|
||||
.map(|item| String::from(item.item_definition_id()))
|
||||
{
|
||||
let component_key = ComponentKey {
|
||||
toolkind,
|
||||
material,
|
||||
modifier: modular_slot_check_hack
|
||||
.1
|
||||
modifier: modular_secondary_slot
|
||||
.and_then(|slot| self.inventory.get(slot))
|
||||
.map(|item| String::from(item.item_definition_id())),
|
||||
};
|
||||
@ -1553,7 +1500,8 @@ impl<'a> Widget for Crafting<'a> {
|
||||
}
|
||||
})
|
||||
})
|
||||
.unwrap_or_else(|| tag.exemplar_identifier()),
|
||||
.or_else(|| tag.exemplar_identifier())
|
||||
.unwrap_or_else(|| "common.items.weapons.empty.empty"),
|
||||
)
|
||||
},
|
||||
RecipeInput::ListSameItem(item_defs) => Arc::<ItemDef>::load_expect_cloned(
|
||||
@ -1572,7 +1520,7 @@ impl<'a> Widget for Crafting<'a> {
|
||||
item_defs
|
||||
.first()
|
||||
.map(|i| i.item_definition_id())
|
||||
.unwrap_or("common.items.weapons.empty.empty")
|
||||
.unwrap_or_else(|| "common.items.weapons.empty.empty")
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
@ -128,9 +128,9 @@ image_ids! {
|
||||
icon_bag: "voxygen.element.items.item_bag_leather_large",
|
||||
icon_processed_material: "voxygen.element.ui.crafting.icons.processed_material",
|
||||
crafting_modular_art: "voxygen.element.ui.crafting.modular_weps_art",
|
||||
ing_ico_prim: "voxygen.element.ui.crafting.icons.blade",
|
||||
ing_ico_sec: "voxygen.element.ui.crafting.icons.handle",
|
||||
wep_ico: "voxygen.element.ui.bag.backgrounds.mainhand",
|
||||
icon_primary_comp: "voxygen.element.ui.crafting.icons.blade",
|
||||
icon_secondary_comp: "voxygen.element.ui.crafting.icons.handle",
|
||||
icon_mod_weap: "voxygen.element.ui.bag.backgrounds.mainhand",
|
||||
// Group Window
|
||||
member_frame: "voxygen.element.ui.groups.group_member_frame",
|
||||
member_bg: "voxygen.element.ui.groups.group_member_bg",
|
||||
|
@ -536,13 +536,13 @@ pub enum Event {
|
||||
CraftModularWeapon {
|
||||
primary_slot: InvSlotId,
|
||||
secondary_slot: InvSlotId,
|
||||
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
||||
craft_sprite: Option<Vec3<i32>>,
|
||||
},
|
||||
CraftModularWeaponComponent {
|
||||
toolkind: ToolKind,
|
||||
material: InvSlotId,
|
||||
modifier: Option<InvSlotId>,
|
||||
craft_sprite: Option<(Vec3<i32>, SpriteKind)>,
|
||||
craft_sprite: Option<Vec3<i32>>,
|
||||
},
|
||||
InviteMember(Uid),
|
||||
AcceptInvite,
|
||||
@ -2963,7 +2963,11 @@ impl Hud {
|
||||
events.push(Event::CraftModularWeapon {
|
||||
primary_slot,
|
||||
secondary_slot,
|
||||
craft_sprite: self.show.crafting_fields.craft_sprite,
|
||||
craft_sprite: self
|
||||
.show
|
||||
.crafting_fields
|
||||
.craft_sprite
|
||||
.map(|(pos, _sprite)| pos),
|
||||
});
|
||||
},
|
||||
crafting::Event::CraftModularWeaponComponent {
|
||||
@ -2975,7 +2979,11 @@ impl Hud {
|
||||
toolkind,
|
||||
material,
|
||||
modifier,
|
||||
craft_sprite: self.show.crafting_fields.craft_sprite,
|
||||
craft_sprite: self
|
||||
.show
|
||||
.crafting_fields
|
||||
.craft_sprite
|
||||
.map(|(pos, _sprite)| pos),
|
||||
});
|
||||
},
|
||||
crafting::Event::Close => {
|
||||
@ -3467,7 +3475,11 @@ impl Hud {
|
||||
}
|
||||
} else if let (Inventory(i), Crafting(c)) = (a, b) {
|
||||
// Add item to crafting input
|
||||
if (c.requirement)(inventories.get(client.entity()), i.slot) {
|
||||
if (c.requirement)(
|
||||
inventories
|
||||
.get(client.entity())
|
||||
.and_then(|inv| inv.get(i.slot)),
|
||||
) {
|
||||
self.show
|
||||
.crafting_fields
|
||||
.recipe_inputs
|
||||
|
@ -8,7 +8,7 @@ use crate::ui::slot::{self, SlotKey, SumSlot};
|
||||
use common::comp::{
|
||||
ability::{Ability, AbilityInput, AuxiliaryAbility},
|
||||
slot::InvSlotId,
|
||||
ActiveAbilities, Body, Energy, Inventory, ItemKey, SkillSet,
|
||||
ActiveAbilities, Body, Energy, Inventory, Item, ItemKey, SkillSet,
|
||||
};
|
||||
use conrod_core::{image, Color};
|
||||
use specs::Entity as EcsEntity;
|
||||
@ -239,7 +239,7 @@ impl<'a> SlotKey<AbilitiesSource<'a>, img_ids::Imgs> for AbilitySlot {
|
||||
pub struct CraftSlot {
|
||||
pub index: u32,
|
||||
pub invslot: Option<InvSlotId>,
|
||||
pub requirement: fn(Option<&Inventory>, InvSlotId) -> bool,
|
||||
pub requirement: fn(Option<&Item>) -> bool,
|
||||
}
|
||||
|
||||
impl PartialEq for CraftSlot {
|
||||
@ -249,7 +249,13 @@ impl PartialEq for CraftSlot {
|
||||
}
|
||||
|
||||
impl Debug for CraftSlot {
|
||||
fn fmt(&self, _: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { todo!() }
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
f.debug_struct("CraftSlot")
|
||||
.field("index", &self.index)
|
||||
.field("invslot", &self.invslot)
|
||||
.field("requirement", &"fn ptr")
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl SlotKey<Inventory, ItemImgs> for CraftSlot {
|
||||
|
@ -70,8 +70,8 @@ pub fn price_desc(
|
||||
}
|
||||
}
|
||||
|
||||
pub fn kind_text<'a, I: ItemDesc + ?Sized>(item: &I, i18n: &'a Localization) -> Cow<'a, str> {
|
||||
match &*item.kind() {
|
||||
pub fn kind_text<'a>(kind: &ItemKind, i18n: &'a Localization) -> Cow<'a, str> {
|
||||
match kind {
|
||||
ItemKind::Armor(armor) => Cow::Borrowed(armor_kind(armor, i18n)),
|
||||
ItemKind::Tool(tool) => Cow::Owned(format!(
|
||||
"{} ({})",
|
||||
|
@ -1429,7 +1429,7 @@ impl PlayState for SessionState {
|
||||
self.client.borrow_mut().craft_modular_weapon(
|
||||
primary_slot,
|
||||
secondary_slot,
|
||||
craft_sprite.map(|(pos, _sprite)| pos),
|
||||
craft_sprite,
|
||||
);
|
||||
},
|
||||
HudEvent::CraftModularWeaponComponent {
|
||||
@ -1470,7 +1470,7 @@ impl PlayState for SessionState {
|
||||
material,
|
||||
modifier,
|
||||
additional_slots,
|
||||
craft_sprite.map(|(pos, _sprite)| pos),
|
||||
craft_sprite,
|
||||
);
|
||||
}
|
||||
},
|
||||
|
@ -465,11 +465,11 @@ impl<'a> Widget for ItemTooltip<'a> {
|
||||
|
||||
let item_kind = &*item.kind();
|
||||
|
||||
let equip_slot = inventory.equipped_items_replaceable_by(item_kind);
|
||||
let equipped_item = inventory.equipped_items_replaceable_by(item_kind).next();
|
||||
|
||||
let (title, desc) = (item.name().to_string(), item.description().to_string());
|
||||
|
||||
let item_kind = util::kind_text(item, i18n).to_string();
|
||||
let item_kind = util::kind_text(item_kind, i18n).to_string();
|
||||
|
||||
let material = item.tags().into_iter().find_map(|t| match t {
|
||||
ItemTag::MaterialKind(material) => Some(material),
|
||||
@ -706,7 +706,7 @@ impl<'a> Widget for ItemTooltip<'a> {
|
||||
.down_from(state.ids.stats[5], V_PAD_STATS)
|
||||
.set(state.ids.stats[6], ui);
|
||||
|
||||
if let Some(equipped_item) = first_equipped {
|
||||
if let Some(equipped_item) = equipped_item {
|
||||
if let ItemKind::Tool(equipped_tool) = &*equipped_item.kind() {
|
||||
let tool_stats = tool.stats;
|
||||
let equipped_tool_stats = equipped_tool.stats;
|
||||
@ -1034,7 +1034,7 @@ impl<'a> Widget for ItemTooltip<'a> {
|
||||
},
|
||||
}
|
||||
|
||||
if let Some(equipped_item) = first_equipped {
|
||||
if let Some(equipped_item) = equipped_item {
|
||||
if let ItemKind::Armor(equipped_armor) = &*equipped_item.kind() {
|
||||
let diff = armor.stats - equipped_armor.stats;
|
||||
let protection_diff = util::option_comparison(
|
||||
|
Loading…
Reference in New Issue
Block a user