Addressed most review comments (no assets).

This commit is contained in:
Sam 2022-05-04 22:53:24 -04:00
parent 5ab9c0f1ad
commit afd2c8730f
22 changed files with 379 additions and 415 deletions

View File

@ -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 {

View File

@ -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:?}."
)
});
},
}
}

View File

@ -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(),

View File

@ -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)
}

View File

@ -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)]

View File

@ -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,

View File

@ -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(),
}
}

View File

@ -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);
}
});

View File

@ -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)

View File

@ -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:?}."
)
},
);
},
}
}

View File

@ -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,

View File

@ -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,
})
}

View File

@ -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))?
},
};

View File

@ -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

View File

@ -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,

View File

@ -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")
}),
),
};

View File

@ -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",

View File

@ -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

View File

@ -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 {

View File

@ -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!(
"{} ({})",

View File

@ -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,
);
}
},

View File

@ -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(